diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8854c2a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,1096 @@
+# slate-react
+
+## 0.117.3
+
+### Patch Changes
+
+- [#5901](https://github.com/ianstormtaylor/slate/pull/5901) [`5a20ea3a`](https://github.com/ianstormtaylor/slate/commit/5a20ea3ad8f725b6072b6512f68d48e440da8901) Thanks [@bibixx](https://github.com/bibixx)! - Fix Android cursor jumping to word start after autocorrect
+
+## 0.117.2
+
+### Patch Changes
+
+- [#5908](https://github.com/ianstormtaylor/slate/pull/5908) [`06b21fdc`](https://github.com/ianstormtaylor/slate/commit/06b21fdca39705c4686c2d3afb9649d63f9ddde5) Thanks [@nabbydude](https://github.com/nabbydude)! - Fixed issue on android where deleting forward at the end of a block would delete the first character in the next block instead of the linebreak
+
+## 0.117.1
+
+### Patch Changes
+
+- [#5902](https://github.com/ianstormtaylor/slate/pull/5902) [`47da9bf4`](https://github.com/ianstormtaylor/slate/commit/47da9bf45599c81359cb6bc86aa59d01c77bba52) Thanks [@zbeyens](https://github.com/zbeyens)! - Fixes #5900
+
+## 0.116.0
+
+### Minor Changes
+
+- [#5871](https://github.com/ianstormtaylor/slate/pull/5871) [`fb87646e`](https://github.com/ianstormtaylor/slate/commit/fb87646e8643e1d0547134cea9d1f57912f06a92) Thanks [@12joan](https://github.com/12joan)! - - Implement experimental chunking optimization (disabled by default, see https://docs.slatejs.org/walkthroughs/09-performance).
+ - Add `useElement` and `useElementIf` hooks to get the current element.
+ - **BREAKING CHANGE:** Decorations are no longer recomputed when a node's parent re-renders, only when the node itself re-renders or when the `decorate` function is changed.
+ - Ensure that `decorate` is a pure function of the node passed into it. Depending on the node's parent may result in decorations not being recomputed when you expect them to be.
+ - If this change impacts you, consider changing your `decorate` function to work on the node's parent instead.
+ - For example, if your `decorate` function decorates a `code-line` based on the parent `code-block`'s language, decorate the `code-block` instead.
+ - This is unlikely to result in any performance detriment, since in previous versions of `slate-react`, the decorations of all siblings were recomputed when one sibling was modified.
+ - **BREAKING CHANGE:** Elements no longer re-render due to selection changes.
+ - To re-render whenever an element becomes selected or deselected, subscribe to `useSelected`.
+ - To re-render whenever the selection changes anywhere in the editor, subscribe to `useSlateSelection`.
+ - To re-render whenever the intersection of the selection with an element changes (the previous behaviour), use `useSlateSelector` to compute this intersection using `Range.intersection`. Ensure you provide a suitable equality function using `Range.equals`.
+ - Increase minimum `slate-dom` version to `0.116.0`.
+ - Deprecate the `useSlateWithV` hook
+ - PERF: Use subscribable pattern for `useSlate`, `useSelected` and decorations to reduce re-renders.
+
+## 0.115.0
+
+### Patch Changes
+
+- [#5881](https://github.com/ianstormtaylor/slate/pull/5881) [`05263b54`](https://github.com/ianstormtaylor/slate/commit/05263b544c32b4c704d141dc5142190f18c056e1) Thanks [@12joan](https://github.com/12joan)! - Fix IME issues in Firefox caused by placeholder
+
+- [#5877](https://github.com/ianstormtaylor/slate/pull/5877) [`747ebfda`](https://github.com/ianstormtaylor/slate/commit/747ebfda0a06b29fc31720f9172c67222fbeae07) Thanks [@12joan](https://github.com/12joan)! - Fix a crash on iOS when composing text using an IME at the start of a block, at the cost of breaking capitalization on iOS in an empty editor.
+
+- [#5859](https://github.com/ianstormtaylor/slate/pull/5859) [`72532fd2`](https://github.com/ianstormtaylor/slate/commit/72532fd2d7be594251ea26fefb5c1ce8337b76ed) Thanks [@12joan](https://github.com/12joan)! - Optimize `isElement`, `isText`, `isNodeList` and `isEditor` by removing dependency on `is-plain-object` and by performing shallow checks by default. To perform a full check, including all descendants, pass the `{ deep: true }` option to `isElement`, `isNodeList` or `isEditor`.
+
+## 0.114.2
+
+### Patch Changes
+
+- [#5855](https://github.com/ianstormtaylor/slate/pull/5855) [`ec367fb0`](https://github.com/ianstormtaylor/slate/commit/ec367fb04f018c8fd0daf8b0d09f87c89a8c155b) Thanks [@zbeyens](https://github.com/zbeyens)! - Fix `renderText` prop not applied
+
+## 0.114.1
+
+### Patch Changes
+
+- [#5853](https://github.com/ianstormtaylor/slate/pull/5853) [`9fe6184c`](https://github.com/ianstormtaylor/slate/commit/9fe6184ca26c518a1114b66df319504f31a07cbb) Thanks [@12joan](https://github.com/12joan)! - Increase minimum `slate` version to 0.114.0 due to https://github.com/ianstormtaylor/slate/issues/5852
+
+## 0.114.0
+
+### Minor Changes
+
+- [#5850](https://github.com/ianstormtaylor/slate/pull/5850) [`22a3dda3`](https://github.com/ianstormtaylor/slate/commit/22a3dda36d4362d5dfdb9a75836297dae8cd7f9e) Thanks [@zbeyens](https://github.com/zbeyens)! - - Update `RenderLeafProps` interface to add `leafPosition` property containing `start`, `end`, `isFirst`, and `isLast` when a text node is split by decorations.
+
+ - Add optional `renderText` prop to `` component for customizing text node rendering.
+
+- [#5848](https://github.com/ianstormtaylor/slate/pull/5848) [`2c62e017`](https://github.com/ianstormtaylor/slate/commit/2c62e0179734871369ac23d38ebfc1378ec9ad68) Thanks [@dpolugic](https://github.com/dpolugic)! - Use equalityFn in useSlateSelector during render as well
+
+## 0.113.0
+
+### Patch Changes
+
+- [#5822](https://github.com/ianstormtaylor/slate/pull/5822) [`68915e8c`](https://github.com/ianstormtaylor/slate/commit/68915e8cfadd9d8dd545dbcd8c33b6b69827a287) Thanks [@RavenColEvol](https://github.com/RavenColEvol)! - add context menu undo support
+
+## 0.112.1
+
+### Patch Changes
+
+- [#5795](https://github.com/ianstormtaylor/slate/pull/5795) [`f456dfbf`](https://github.com/ianstormtaylor/slate/commit/f456dfbf133f93fabe683849adb7952b8b04fc60) Thanks [@12joan](https://github.com/12joan)! - Ignore selectionchange events originating from input and textarea elements (addresses Chrome bug https://issues.chromium.org/issues/389368412)
+
+## 0.112.0
+
+### Patch Changes
+
+- [#5763](https://github.com/ianstormtaylor/slate/pull/5763) [`644ebdc8`](https://github.com/ianstormtaylor/slate/commit/644ebdc8f5f30878fb87dc8685f62e0636c23491) Thanks [@TyMick](https://github.com/TyMick)! - Use extended `Editor` type in `useSlateWithV` return type
+
+- [#5741](https://github.com/ianstormtaylor/slate/pull/5741) [`90fbcdef`](https://github.com/ianstormtaylor/slate/commit/90fbcdeff58d9b1f5de13102a1198f1c7244ae0c) Thanks [@AdrienPoupa](https://github.com/AdrienPoupa)! - Fix ReactEditor.toDOMRange crash in setDomSelection
+
+## 0.111.0
+
+### Minor Changes
+
+- [#5734](https://github.com/ianstormtaylor/slate/pull/5734) [`9a212512`](https://github.com/ianstormtaylor/slate/commit/9a2125127064f35332d5c06df2dfa3768f745185) Thanks [@bmingles](https://github.com/bmingles)! - Split out slate-dom package
+
+## 0.110.3
+
+### Patch Changes
+
+- [#5746](https://github.com/ianstormtaylor/slate/pull/5746) [`e97a9f88`](https://github.com/ianstormtaylor/slate/commit/e97a9f8857b24d57c1386b2d01e9922360f98599) Thanks [@DustinMackintosh](https://github.com/DustinMackintosh)! - Invalidate node maps when nodes change until next react paint
+
+## 0.110.2
+
+### Patch Changes
+
+- [#5737](https://github.com/ianstormtaylor/slate/pull/5737) [`cd21bb1f`](https://github.com/ianstormtaylor/slate/commit/cd21bb1f80cfaa824253849b407781c9471dcd9a) Thanks [@WindRunnerMax](https://github.com/WindRunnerMax)! - fix: sync built-in state on undo when editor is unfocused
+
+- [#5727](https://github.com/ianstormtaylor/slate/pull/5727) [`335c5418`](https://github.com/ianstormtaylor/slate/commit/335c54188ff1e2985cc584dd9fa3117508208dd3) Thanks [@hernansartorio](https://github.com/hernansartorio)! - Call unref on pathRefs created for move_node to remove memory leak
+
+## 0.110.1
+
+### Patch Changes
+
+- [#5716](https://github.com/ianstormtaylor/slate/pull/5716) [`10abeff8`](https://github.com/ianstormtaylor/slate/commit/10abeff84fd856f9fd72240f8ee1631466f98d02) Thanks [@TyMick](https://github.com/TyMick)! - Fix selections with non-void non-editable focus
+
+## 0.110.0
+
+### Minor Changes
+
+- [#5706](https://github.com/ianstormtaylor/slate/pull/5706) [`f9e83b80`](https://github.com/ianstormtaylor/slate/commit/f9e83b807f4b2ae717814af5943f53f366a48fd2) Thanks [@yf-yang](https://github.com/yf-yang)! - Expose useComposing hook
+
+## 0.109.0
+
+### Minor Changes
+
+- [#5695](https://github.com/ianstormtaylor/slate/pull/5695) [`6cb38e37`](https://github.com/ianstormtaylor/slate/commit/6cb38e37a4bd3a43ee02652c514288df62da1c21) Thanks [@yf-yang](https://github.com/yf-yang)! - feat: Add useComposing hook"
+
+## 0.108.0
+
+### Minor Changes
+
+- [#5681](https://github.com/ianstormtaylor/slate/pull/5681) [`b8bf92dc`](https://github.com/ianstormtaylor/slate/commit/b8bf92dc7e42f7547128fcfe1c63e89dffb29032) Thanks [@yf-yang](https://github.com/yf-yang)! - Forward ref from Editable component
+
+## 0.107.1
+
+### Patch Changes
+
+- [#5677](https://github.com/ianstormtaylor/slate/pull/5677) [`a9a70405`](https://github.com/ianstormtaylor/slate/commit/a9a7040583ffe6a5ddf623acbaa91dee8fc76904) Thanks [@WindrunnerMax](https://github.com/WindrunnerMax)! - fix unexpected event triggered when using `ReactEditor.focus`
+
+## 0.107.0
+
+### Minor Changes
+
+- [#5676](https://github.com/ianstormtaylor/slate/pull/5676) [`ec9e5f0a`](https://github.com/ianstormtaylor/slate/commit/ec9e5f0a366dda80f826e90042ff8a49b1cf3933) Thanks [@ivan-sysoi](https://github.com/ivan-sysoi)! - Changed behaviour of ReactEditor.findDocumentOrShadowRoot. It returns shadow root or document without checking for the existence of the getSelection method.
+
+## 0.106.0
+
+### Minor Changes
+
+- [#5659](https://github.com/ianstormtaylor/slate/pull/5659) [`e6254f70`](https://github.com/ianstormtaylor/slate/commit/e6254f706a47d0e451d3d40485bf96f819eaa9ab) Thanks [@MahmoudElsayad](https://github.com/MahmoudElsayad)! - Enable Shadow DOM fix for all Safari versions.
+
+### Patch Changes
+
+- [#5664](https://github.com/ianstormtaylor/slate/pull/5664) [`0016f984`](https://github.com/ianstormtaylor/slate/commit/0016f9843f76fee1fe7d26330dd00cd1307915ec) Thanks [@12joan](https://github.com/12joan)! - Fix: `state.isDraggingInternally` is stale if a drop handler outside the editor causes the dragged DOM element to unmount
+
+## 0.105.0
+
+### Minor Changes
+
+- [#5654](https://github.com/ianstormtaylor/slate/pull/5654) [`2a8b4e95`](https://github.com/ianstormtaylor/slate/commit/2a8b4e958bd02f3b70da51c3880fd764270424ad) Thanks [@alex-starostin](https://github.com/alex-starostin)! - Make capitalizing work for iOS
+
+## 0.104.0
+
+### Minor Changes
+
+- [#5648](https://github.com/ianstormtaylor/slate/pull/5648) [`0bb7be54`](https://github.com/ianstormtaylor/slate/commit/0bb7be5496db4c31042667a17f9ff95c3f3d42b0) Thanks [@MahmoudElsayad](https://github.com/MahmoudElsayad)! - Fix Safari selection inside Shadow DOM.
+
+## 0.102.0
+
+### Patch Changes
+
+- [#5541](https://github.com/ianstormtaylor/slate/pull/5541) [`c2ae1eda`](https://github.com/ianstormtaylor/slate/commit/c2ae1eda91d0aae1cd63bd46af759c542c292a8a) Thanks [@12joan](https://github.com/12joan)! - Do not move selection outside inline node when composition starts
+
+## 0.101.6
+
+### Patch Changes
+
+- [#5593](https://github.com/ianstormtaylor/slate/pull/5593) [`54594d0f`](https://github.com/ianstormtaylor/slate/commit/54594d0f81627166d72c97256203c4b5642a82ff) Thanks [@12joan](https://github.com/12joan)! - Fix: Calling `ReactEditor.focus` doesn't update `useFocused` when running in @testing-library/react
+
+## 0.101.5
+
+### Patch Changes
+
+- [#5584](https://github.com/ianstormtaylor/slate/pull/5584) [`884ab424`](https://github.com/ianstormtaylor/slate/commit/884ab4249485e5930f1f79abb939bf375ffd47c0) Thanks [@Elvin7CF](https://github.com/Elvin7CF)! - Fix onCompositionEnd not updating isComposing
+
+## 0.101.3
+
+### Patch Changes
+
+- [#5576](https://github.com/ianstormtaylor/slate/pull/5576) [`8ce52fd4`](https://github.com/ianstormtaylor/slate/commit/8ce52fd494c5156c8f08841b972bc5eda2817c03) Thanks [@qirong77](https://github.com/qirong77)! - fix onCompositionEnd update error.
+
+## 0.101.2
+
+### Patch Changes
+
+- [#5567](https://github.com/ianstormtaylor/slate/pull/5567) [`07f59e36`](https://github.com/ianstormtaylor/slate/commit/07f59e36071bae2b9c09b787f1dd514c6bf859a4) Thanks [@timagixe](https://github.com/timagixe)! - Fix cursor position on selection collapse for RTL direction
+
+## 0.101.1
+
+### Patch Changes
+
+- [#5564](https://github.com/ianstormtaylor/slate/pull/5564) [`9aa573e9`](https://github.com/ianstormtaylor/slate/commit/9aa573e9b8b2aff0c702fc6efa622e71db7759f1) Thanks [@12joan](https://github.com/12joan)! - Apply 300ms placeholder delay only on Android devices
+
+## 0.101.0
+
+### Minor Changes
+
+- [#5527](https://github.com/ianstormtaylor/slate/pull/5527) [`fc081816`](https://github.com/ianstormtaylor/slate/commit/fc081816e08ade6838d05a96f84088de9f2734ce) Thanks [@skogsmaskin](https://github.com/skogsmaskin)! - Fixes a bug with `ReactEditor.focus` where it would throw an error if the editor was in the middle of applying pending operations.
+ With this change, setting focus will be retried until the editor no longer has any pending operations.
+ Calling `ReactEditor.focus` on a editor without a current selection, will now make a selection in the top of the document.
+
+### Patch Changes
+
+- [#5549](https://github.com/ianstormtaylor/slate/pull/5549) [`f9cca97f`](https://github.com/ianstormtaylor/slate/commit/f9cca97f00e4b7827f7056cd7f1644345a4be953) Thanks [@12joan](https://github.com/12joan)! - Firefox compat: Fix incorrect focus.offset when text node ends with \n
+
+- [#5556](https://github.com/ianstormtaylor/slate/pull/5556) [`22495e14`](https://github.com/ianstormtaylor/slate/commit/22495e143d81fd602ff3efa0b5f6339a4b05b6c0) Thanks [@dylans](https://github.com/dylans)! - Revert #5542
+
+## 0.100.1
+
+### Patch Changes
+
+- [#5542](https://github.com/ianstormtaylor/slate/pull/5542) [`8688ed5c`](https://github.com/ianstormtaylor/slate/commit/8688ed5c680069c4277d8b575b79fe525737935d) Thanks [@hellsan631](https://github.com/hellsan631)! - Fix Memory Leak when switching between focused editables
+
+## 0.100.0
+
+### Minor Changes
+
+- [#5526](https://github.com/ianstormtaylor/slate/pull/5526) [`623f4452`](https://github.com/ianstormtaylor/slate/commit/623f44521ee95be38c53b6def456ed8c5f16e14b) Thanks [@jkcs](https://github.com/jkcs)! - Add `onSelectionChange` and `onValueChange` in Slate React component
+
+- [#5528](https://github.com/ianstormtaylor/slate/pull/5528) [`c4c14882`](https://github.com/ianstormtaylor/slate/commit/c4c14882edf13828f6583a88e50754ce63583bd7) Thanks [@dylans](https://github.com/dylans)! - Update dependencies to React 18, Node 20, TS 5.2, etc.
+
+## 0.99.0
+
+### Minor Changes
+
+- [#5516](https://github.com/ianstormtaylor/slate/pull/5516) [`300dc57a`](https://github.com/ianstormtaylor/slate/commit/300dc57a00c6437519ae0044384811efec653758) Thanks [@josephmr](https://github.com/josephmr)! - Retain editor selection when using ReactEditor.focus()
+
+### Patch Changes
+
+- [#5514](https://github.com/ianstormtaylor/slate/pull/5514) [`ff7db221`](https://github.com/ianstormtaylor/slate/commit/ff7db221205014605464628d18e41f1310bcead9) Thanks [@YaoKaiLun](https://github.com/YaoKaiLun)! - Fix move_node triggers nodes re-render
+
+## 0.98.4
+
+### Patch Changes
+
+- [#5510](https://github.com/ianstormtaylor/slate/pull/5510) [`13c7d271`](https://github.com/ianstormtaylor/slate/commit/13c7d271e35406a2497e78ef114417ad17796c65) Thanks [@e1himself](https://github.com/e1himself)! - Remove an unused React ref
+
+## 0.98.3
+
+### Patch Changes
+
+- [#5503](https://github.com/ianstormtaylor/slate/pull/5503) [`e308cd66`](https://github.com/ianstormtaylor/slate/commit/e308cd664d381ba1cd37f423445f189b9b5e4d1d) Thanks [@janpaepke](https://github.com/janpaepke)! - bugfix: slate breaks on load on safari < 16.4
+
+## 0.98.2
+
+### Patch Changes
+
+- [#5497](https://github.com/ianstormtaylor/slate/pull/5497) [`76ba3759`](https://github.com/ianstormtaylor/slate/commit/76ba3759838fd538587fda2f4027f7c74ff09589) Thanks [@Dimitri-WEI-Lingfeng](https://github.com/Dimitri-WEI-Lingfeng)! - fix the bug that user cannot input chinese on mac wechat browser.
+
+## 0.98.1
+
+### Patch Changes
+
+- [#5491](https://github.com/ianstormtaylor/slate/pull/5491) [`a5576e56`](https://github.com/ianstormtaylor/slate/commit/a5576e56a73f061972775953f270b34081a5cad8) Thanks [@WcaleNieWolny](https://github.com/WcaleNieWolny)! - Fix firefox table selection if table is contentedtiable
+
+## 0.98.0
+
+### Minor Changes
+
+- [#5486](https://github.com/ianstormtaylor/slate/pull/5486) [`8b548fb5`](https://github.com/ianstormtaylor/slate/commit/8b548fb53af861e1f391f2d5c052e3279f0a0b6c) Thanks [@WcaleNieWolny](https://github.com/WcaleNieWolny)! - Fix invalid usage of the selection API in firefox
+
+## 0.97.2
+
+### Patch Changes
+
+- [#5462](https://github.com/ianstormtaylor/slate/pull/5462) [`a6b606d8`](https://github.com/ianstormtaylor/slate/commit/a6b606d804795d9b134784a35e3b00ac77f3ebbc) Thanks [@Ben-Wormald](https://github.com/Ben-Wormald)! - Update hotkeys util to use isHotkey for better support for non-latin keyboards
+
+* [#5470](https://github.com/ianstormtaylor/slate/pull/5470) [`4bd15ed3`](https://github.com/ianstormtaylor/slate/commit/4bd15ed3950e3a0871f5d0ecb391bb637c05e59d) Thanks [@josephmr](https://github.com/josephmr)! - Fix Android caret placement regression when inputting into empty editor
+
+## 0.97.1
+
+### Patch Changes
+
+- [#5460](https://github.com/ianstormtaylor/slate/pull/5460) [`53395449`](https://github.com/ianstormtaylor/slate/commit/53395449e5b03fde5c0521203ef044064f3c159e) Thanks [@12joan](https://github.com/12joan)! - Do not attempt to batch updates manually in React >= 18
+
+## 0.97.0
+
+### Minor Changes
+
+- [#5451](https://github.com/ianstormtaylor/slate/pull/5451) [`12ff246e`](https://github.com/ianstormtaylor/slate/commit/12ff246e101bb7ae51248066c07c378ee4be9220) Thanks [@gtluszcz](https://github.com/gtluszcz)! - Fixed occasional crashes when selecting void elements in Chrome
+
+### Patch Changes
+
+- [#5453](https://github.com/ianstormtaylor/slate/pull/5453) [`cde0a155`](https://github.com/ianstormtaylor/slate/commit/cde0a155e23d015d4ee72f9f10f63b67e878668e) Thanks [@Shiba-ligo](https://github.com/Shiba-ligo)! - fix regular expression for testing Webkit based browser.
+
+## 0.96.0
+
+### Minor Changes
+
+- [#5437](https://github.com/ianstormtaylor/slate/pull/5437) [`3ad13d60`](https://github.com/ianstormtaylor/slate/commit/3ad13d601550341688cc75466a75b616d8232154) Thanks [@josephmr](https://github.com/josephmr)! - Detect all WebKit based browsers for COMPAT behavior
+
+### Patch Changes
+
+- [#5443](https://github.com/ianstormtaylor/slate/pull/5443) [`eb7f5987`](https://github.com/ianstormtaylor/slate/commit/eb7f598707ab9a4f1bd62fd195719049e9536be0) Thanks [@OldDream](https://github.com/OldDream)! - fix wrong caret position during composition.
+
+## 0.95.0
+
+### Minor Changes
+
+- [#5422](https://github.com/ianstormtaylor/slate/pull/5422) [`0b179909`](https://github.com/ianstormtaylor/slate/commit/0b1799091a6800c7e868d5a6148b82648cbe8270) Thanks [@Chudesnov](https://github.com/Chudesnov)! - Prevents default focus styles from being removed in Editable
+
+* [#5421](https://github.com/ianstormtaylor/slate/pull/5421) [`91e388ec`](https://github.com/ianstormtaylor/slate/commit/91e388ecd9e6a540b4a651978436f196f38f667d) Thanks [@e1himself](https://github.com/e1himself)! - Rename `` component prop from `value` to `initialValue` to emphasize uncontrolled nature of it
+
+## 0.94.2
+
+### Patch Changes
+
+- [#5423](https://github.com/ianstormtaylor/slate/pull/5423) [`042bca16`](https://github.com/ianstormtaylor/slate/commit/042bca167ac810acccae229bc905a49098aee546) Thanks [@horacioh](https://github.com/horacioh)! - fix placeholder position in Safari 16.x
+
+## 0.94.0
+
+### Patch Changes
+
+- [#5307](https://github.com/ianstormtaylor/slate/pull/5307) [`3243c7e3`](https://github.com/ianstormtaylor/slate/commit/3243c7e34ac2602618c67c88b1b7df07fde1c2ec) Thanks [@zbeyens](https://github.com/zbeyens)! - Interface methods JSDoc should now work on IDEs.
+
+## 0.93.0
+
+### Patch Changes
+
+- [#5383](https://github.com/ianstormtaylor/slate/pull/5383) [`3c3ea29a`](https://github.com/ianstormtaylor/slate/commit/3c3ea29a2d7c70bab3629f0f78ea28dca4058b53) Thanks [@12joan](https://github.com/12joan)! - Fix issue when tabbing into editor in Safari (https://github.com/udecode/plate/issues/2315)
+
+* [#5368](https://github.com/ianstormtaylor/slate/pull/5368) [`5a0d3974`](https://github.com/ianstormtaylor/slate/commit/5a0d3974d6cb2c099dff4c0976e9390d24c345ad) Thanks [@edhager](https://github.com/edhager)! - Delay rendering of placeholder to avoid IME hiding
+
+## 0.92.0
+
+### Minor Changes
+
+- [#5363](https://github.com/ianstormtaylor/slate/pull/5363) [`d42cd005`](https://github.com/ianstormtaylor/slate/commit/d42cd005db862165f5ac63fba4d35f36d92864f6) Thanks [@aciccarello](https://github.com/aciccarello)! - update dependencies on react hooks to be more senstive to changes
+
+ The code should now meet eslint react hook standards
+
+ This could result in more renders
+
+ closes #3886
+
+### Patch Changes
+
+- [#5369](https://github.com/ianstormtaylor/slate/pull/5369) [`556a4565`](https://github.com/ianstormtaylor/slate/commit/556a4565d2bb4a611d34bb30ecd9bac324664066) Thanks [@alex-vladut](https://github.com/alex-vladut)! - Allow copying from editable void input
+
+## 0.91.11
+
+### Patch Changes
+
+- [#5362](https://github.com/ianstormtaylor/slate/pull/5362) [`43999356`](https://github.com/ianstormtaylor/slate/commit/439993569001f8c5dc9e68194c198d430a4ef4bc) Thanks [@jason0x43](https://github.com/jason0x43)! - Fix an issue where pastes in Safari wouldn't include application/x-slate-fragment data
+
+* [#5359](https://github.com/ianstormtaylor/slate/pull/5359) [`9825d29b`](https://github.com/ianstormtaylor/slate/commit/9825d29b87ffff96b4cdfd7339028cc1a92c6f68) Thanks [@jason0x43](https://github.com/jason0x43)! - Fix an issue on Android where content containing a newline wouldn't be pasted properly
+
+## 0.91.10
+
+### Patch Changes
+
+- [#5346](https://github.com/ianstormtaylor/slate/pull/5346) [`a5e833f6`](https://github.com/ianstormtaylor/slate/commit/a5e833f6550a823689e593317f4579127d8a7fd7) Thanks [@edhager](https://github.com/edhager)! - Fix a problem with Editable not calling the decorate function passed as a prop when it should.
+
+* [#5343](https://github.com/ianstormtaylor/slate/pull/5343) [`f7f02a8b`](https://github.com/ianstormtaylor/slate/commit/f7f02a8b23f16a3f3103302343e3326549917892) Thanks [@12joan](https://github.com/12joan)! - Fix error when triple-clicking a word preceding a `contenteditable="false"` DOM node in Chrome
+
+## 0.91.9
+
+### Patch Changes
+
+- [#5339](https://github.com/ianstormtaylor/slate/pull/5339) [`62f8ddd9`](https://github.com/ianstormtaylor/slate/commit/62f8ddd9713617bf474968a10b69c24b71074b41) Thanks [@12joan](https://github.com/12joan)! - Fixes #5335. To prevent performance issues, make sure to wrap custom `renderPlaceholder` values in `useCallback`.
+
+## 0.91.8
+
+### Patch Changes
+
+- [#5325](https://github.com/ianstormtaylor/slate/pull/5325) [`af3f828b`](https://github.com/ianstormtaylor/slate/commit/af3f828b1206409951708b823fb32965b67c798f) Thanks [@clauderic](https://github.com/clauderic)! - Fix edge-cases in the Android input manager when text leaf nodes are deleted, such as when deleting text leaf nodes adjacent to inline void nodes.
+
+* [#5327](https://github.com/ianstormtaylor/slate/pull/5327) [`4205e0f0`](https://github.com/ianstormtaylor/slate/commit/4205e0f002929f575f34ef8bb5d3bf9d2670d9d7) Thanks [@YasinChan](https://github.com/YasinChan)! - Fix the issue of composition API and beforeinput event triggering between Chrome versions 60-75 on the Android platform.
+
+## 0.91.7
+
+### Patch Changes
+
+- [#5322](https://github.com/ianstormtaylor/slate/pull/5322) [`836f6600`](https://github.com/ianstormtaylor/slate/commit/836f660054c6fd4ced793cfa28349543b8db9890) Thanks [@edhager](https://github.com/edhager)! - Add checks to Editable selection change handler to avoid errors
+
+## 0.91.6
+
+### Patch Changes
+
+- [#5315](https://github.com/ianstormtaylor/slate/pull/5315) [`5784a38b`](https://github.com/ianstormtaylor/slate/commit/5784a38b6bd0f7de50efc890a4d6ceb8fafe191b) Thanks [@clauderic](https://github.com/clauderic)! - The `RestoreDOM` manager that is used Android no longer restores the DOM to its previous state for text mutations. This allows the editor state to be reconciled during a composition without interrupting the composition, as programatically updating the `textContent` of a text node ends the current composition.
+
+* [#5315](https://github.com/ianstormtaylor/slate/pull/5315) [`5784a38b`](https://github.com/ianstormtaylor/slate/commit/5784a38b6bd0f7de50efc890a4d6ceb8fafe191b) Thanks [@clauderic](https://github.com/clauderic)! - Fixed consumer defined `onInput` event handler not being invoked when passed to the `` component.
+
+## 0.91.5
+
+### Patch Changes
+
+- [#5313](https://github.com/ianstormtaylor/slate/pull/5313) [`3bf568ed`](https://github.com/ianstormtaylor/slate/commit/3bf568ede2a1df91ff4f88402e0cdd848848f2fb) Thanks [@edhager](https://github.com/edhager)! - Some code clean-up in Editable.
+
+* [#5306](https://github.com/ianstormtaylor/slate/pull/5306) [`213edbbf`](https://github.com/ianstormtaylor/slate/commit/213edbbf3abc407532aeda72e40d6f01d368c33c) Thanks [@clauderic](https://github.com/clauderic)! - Use memoization to avoid unnecessary `textContent` updates in `` component.
+
+## 0.91.4
+
+### Patch Changes
+
+- [#5310](https://github.com/ianstormtaylor/slate/pull/5310) [`b94254d6`](https://github.com/ianstormtaylor/slate/commit/b94254d694c6ea6c88cacd661e7bd77165cd2607) Thanks [@etrepum](https://github.com/etrepum)! - Fix to ensure that the latest versions of onChange and renderPlaceholder are used
+
+## 0.91.3
+
+### Patch Changes
+
+- [#5305](https://github.com/ianstormtaylor/slate/pull/5305) [`11adbf96`](https://github.com/ianstormtaylor/slate/commit/11adbf966c764ffde866be38ada2d32e00105e48) Thanks [@alex-vladut](https://github.com/alex-vladut)! - Allow pasting plain text into editable voids
+
+## 0.91.2
+
+### Patch Changes
+
+- [#5297](https://github.com/ianstormtaylor/slate/pull/5297) [`967d99eb`](https://github.com/ianstormtaylor/slate/commit/967d99eb36df798ac3163c7d15a01e64fee0668c) Thanks [@edhager](https://github.com/edhager)! - Fix memory leaks by adding clean-up code that looks for ref resets in Editable and Text.
+
+## 0.91.1
+
+### Patch Changes
+
+- [#5251](https://github.com/ianstormtaylor/slate/pull/5251) [`6fa4b954`](https://github.com/ianstormtaylor/slate/commit/6fa4b954a5e4c67cff87d00b1253b2a838c0db94) Thanks [@YaoKaiLun](https://github.com/YaoKaiLun)! - Fix the cursor jump to an unexpected position after deleting in android
+
+## 0.91.0
+
+### Minor Changes
+
+- [#5267](https://github.com/ianstormtaylor/slate/pull/5267) [`463edbd2`](https://github.com/ianstormtaylor/slate/commit/463edbd27ed78a4b4a3d38886da4d9e3e8b8efd5) Thanks [@ilya2204](https://github.com/ilya2204)! - Allow to change clipboard fragment format name
+
+* [#5271](https://github.com/ianstormtaylor/slate/pull/5271) [`9635b992`](https://github.com/ianstormtaylor/slate/commit/9635b992a0d91cecd45e3b6a883a860f14bcaaea) Thanks [@dsvgit](https://github.com/dsvgit)! - If TextComponent decorations keep the same offsets and only paths are changed, prevent re-rendering because only decoration offsets matter when leaves are calculated.
+
+## 0.90.0
+
+### Minor Changes
+
+- [#5278](https://github.com/ianstormtaylor/slate/pull/5278) [`9c4097a2`](https://github.com/ianstormtaylor/slate/commit/9c4097a26fa92718e6f4fc1f984a70fb5af42ca2) Thanks [@kylemclean](https://github.com/kylemclean)! - Revert to using inline styles for default editor styles
+
+## 0.89.0
+
+### Minor Changes
+
+- [#5275](https://github.com/ianstormtaylor/slate/pull/5275) [`5bc69d8d`](https://github.com/ianstormtaylor/slate/commit/5bc69d8d657c57eef06aeaa1fa198840d36939c7) Thanks [@12joan](https://github.com/12joan)! - Firefox: fix wrong text highlighting with double-click
+
+### Patch Changes
+
+- [#5265](https://github.com/ianstormtaylor/slate/pull/5265) [`3cf51f4d`](https://github.com/ianstormtaylor/slate/commit/3cf51f4d88e8e91faa6ab5d1f2c5f8c8e505ae89) Thanks [@kylemclean](https://github.com/kylemclean)! - Improve compatibility for browsers that do not support ResizeObserver or :where selector
+
+## 0.88.2
+
+### Patch Changes
+
+- [#5259](https://github.com/ianstormtaylor/slate/pull/5259) [`d7de564d`](https://github.com/ianstormtaylor/slate/commit/d7de564d62848dd8e14535993083c5e9aa1bfacc) Thanks [@Jacfem](https://github.com/Jacfem)! - Updates the selection correctly in readonly shadowdom
+
+* [#5252](https://github.com/ianstormtaylor/slate/pull/5252) [`179d5c92`](https://github.com/ianstormtaylor/slate/commit/179d5c926eecfdb2b3d8a75c07cb89181c348ad1) Thanks [@frellica](https://github.com/frellica)! - remove qq browser from `beforeinput` compat list because it had updated its chromium core to version 94
+
+## 0.88.0
+
+### Minor Changes
+
+- [#5226](https://github.com/ianstormtaylor/slate/pull/5226) [`0141f683`](https://github.com/ianstormtaylor/slate/commit/0141f683659025c7e851c11274cf200da05fd31e) Thanks [@laufeyrut](https://github.com/laufeyrut)! - Check if getBoundingClientRect exist before trying to call bind on it. Makes unit testing experience agains Editable nicer
+
+## 0.87.1
+
+### Patch Changes
+
+- [#5223](https://github.com/ianstormtaylor/slate/pull/5223) [`120437d6`](https://github.com/ianstormtaylor/slate/commit/120437d61237eeb8df4ed0db92af31698e910eda) Thanks [@alex-vladut](https://github.com/alex-vladut)! - Fix issue preventing editing and copy/paste into editable voids
+
+## 0.87.0
+
+### Minor Changes
+
+- [#5206](https://github.com/ianstormtaylor/slate/pull/5206) [`96b7fcdb`](https://github.com/ianstormtaylor/slate/commit/96b7fcdbf98a7c8908f5d9613d9898cb24a8ae47) Thanks [@kylemclean](https://github.com/kylemclean)! - Use stylesheet for default styles on Editable components
+
+## 0.86.0
+
+### Minor Changes
+
+- [#5121](https://github.com/ianstormtaylor/slate/pull/5121) [`06942c6d`](https://github.com/ianstormtaylor/slate/commit/06942c6d7e4b8418a467f022750b010491dbdbe7) Thanks [@laufeyrut](https://github.com/laufeyrut)! - Make it possible to copy/paste void elements
+
+## 0.83.2
+
+### Patch Changes
+
+- [#5148](https://github.com/ianstormtaylor/slate/pull/5148) [`a2b6786d`](https://github.com/ianstormtaylor/slate/commit/a2b6786d19f8bd5f779c526742a4dc3da971f696) Thanks [@ksimons](https://github.com/ksimons)! - Ensure the min-height for placeholders is set on the correct editor
+- [#5155](https://github.com/ianstormtaylor/slate/pull/5154) [`1b14de5`](https://github.com/ianstormtaylor/slate/commit/1b14de5f8e5961ac36eced229abea9abb5be71f9) Thanks [@jameshfisher](https://github.com/jameshfisher) - Revert insertText breaking change that deletes fragment
+
+## 0.83.1
+
+### Patch Changes
+
+- [#5143](https://github.com/ianstormtaylor/slate/pull/5143) [`347865ca`](https://github.com/ianstormtaylor/slate/commit/347865cafc1f2f3b0fc3d74d8758e082480df6ca) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Fix scrollIntoView when selection is collapsed inside mark placeholder
+
+## 0.83.0
+
+### Minor Changes
+
+- [#5123](https://github.com/ianstormtaylor/slate/pull/5123) [`0eb37e79`](https://github.com/ianstormtaylor/slate/commit/0eb37e79150275d3535f1694d8972751a83d826f) Thanks [@laufeyrut](https://github.com/laufeyrut)! - Make it possible to delete block elements with backspace in Chrome and Safari
+
+### Patch Changes
+
+- [#5127](https://github.com/ianstormtaylor/slate/pull/5127) [`341041f0`](https://github.com/ianstormtaylor/slate/commit/341041f0b721926cca3f9dee98dc4589f2c96797) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Cleanup and fix insertion placeholder mark compare
+
+## 0.82.2
+
+### Patch Changes
+
+- [#5120](https://github.com/ianstormtaylor/slate/pull/5120) [`9815bdab`](https://github.com/ianstormtaylor/slate/commit/9815bdabdd34221ed86f68b556cfa43d845e2db0) Thanks [@hueyhe](https://github.com/hueyhe)! - Fix editor selection out of sync in readonly mode
+
+* [#5100](https://github.com/ianstormtaylor/slate/pull/5100) [`8eb1972b`](https://github.com/ianstormtaylor/slate/commit/8eb1972b5b2f9489936b1759afb76574040af5a0) Thanks [@KittyGiraudel](https://github.com/KittyGiraudel)! - Add `aria-multiline` attribute to textbox editor
+
+- [#5105](https://github.com/ianstormtaylor/slate/pull/5105) [`55b95740`](https://github.com/ianstormtaylor/slate/commit/55b9574097f6008bda7ed8e3cb7aa9dd607d9f49) Thanks [@yume-chan](https://github.com/yume-chan)! - Change `Element` component to use callback-style ref to reliably track DOM node of rendered custom elements
+
+## 0.82.1
+
+### Patch Changes
+
+- [#5084](https://github.com/ianstormtaylor/slate/pull/5084) [`50de780b`](https://github.com/ianstormtaylor/slate/commit/50de780b1c32fa2c52ad88d42031748f9d3944e9) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Fix selection handling with slow flush in mark placeholders on android, fix auto-capitalize after placeholder
+
+* [#5091](https://github.com/ianstormtaylor/slate/pull/5091) [`e18879e7`](https://github.com/ianstormtaylor/slate/commit/e18879e728077b09580b29e9a6683aaa66629bc5) Thanks [@e1himself](https://github.com/e1himself)! - Fix `withReact()` function type definition
+
+## 0.82.0
+
+### Minor Changes
+
+- [#5041](https://github.com/ianstormtaylor/slate/pull/5041) [`9bc0b613`](https://github.com/ianstormtaylor/slate/commit/9bc0b6132aa288a37ae9a85d0e59a9d5a75ebdd7) Thanks [@bryanph](https://github.com/bryanph)! - - Introduces a `useSlateSelection` hook that triggers whenever the selection changes.
+ - This also changes the implementation of SlateContext to use an incrementing value instead of an array replace to trigger updates
+ - Introduces a `useSlateWithV` hook that includes the version counter which can be used to prevent re-renders
+
+* [#4988](https://github.com/ianstormtaylor/slate/pull/4988) [`fbab6331`](https://github.com/ianstormtaylor/slate/commit/fbab6331a5ecebd9e98c6c8c87d6f4b3b7c43bd0) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Android input handling rewrite, replace composition insert prefixes with decoration based mark placeholders
+
+## 0.81.3
+
+### Patch Changes
+
+- [#5054](https://github.com/ianstormtaylor/slate/pull/5054) [`1cc0797f`](https://github.com/ianstormtaylor/slate/commit/1cc0797f53f22e650198c83192ba5fc35c525a15) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Fix toSlatePoint in void nodes with nested editors if children are rendered as the last child
+
+* [#5042](https://github.com/ianstormtaylor/slate/pull/5042) [`11a93e65`](https://github.com/ianstormtaylor/slate/commit/11a93e65de4b197a43777e575caf13d7a05d5dc9) Thanks [@bryanph](https://github.com/bryanph)! - Upgrade next.js and source-map-loader packages
+
+## 0.81.2
+
+### Patch Changes
+
+- [#5045](https://github.com/ianstormtaylor/slate/pull/5045) [`0b2e6c79`](https://github.com/ianstormtaylor/slate/commit/0b2e6c79c08fc4eba32be7a424da758ba74573c3) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Don't native insert inside blocks with whitespace="pre" containing tab chars to work around https://bugs.chromium.org/p/chromium/issues/detail?id=1219139
+
+* [#5046](https://github.com/ianstormtaylor/slate/pull/5046) [`f96b6597`](https://github.com/ianstormtaylor/slate/commit/f96b659755673375ef1b6a1cc925c73ce4934a03) Thanks [@BitPhinix](https://github.com/BitPhinix)! - fix macos accent menu when using arrow keys
+
+## 0.81.0
+
+### Minor Changes
+
+- [#4999](https://github.com/ianstormtaylor/slate/pull/4999) [`fe13a8f9`](https://github.com/ianstormtaylor/slate/commit/fe13a8f9e750569342ee004951e34233ab6614bf) Thanks [@alexandercampbell](https://github.com/alexandercampbell)! - Add new Slate.Scrubber interface to allow scrubbing end user data from exception
+ text. The default behavior remains unchanged.
+
+## 0.80.0
+
+### Patch Changes
+
+- [#5007](https://github.com/ianstormtaylor/slate/pull/5007) [`92c5730a`](https://github.com/ianstormtaylor/slate/commit/92c5730a96223a683b3c95651eb4c90a5caca21a) Thanks [@jasonphillips](https://github.com/jasonphillips)! - Revert #4876 & #4910 to restore original decorations behavior
+
+## 0.79.0
+
+### Minor Changes
+
+- [#4981](https://github.com/ianstormtaylor/slate/pull/4981) [`cb8a5515`](https://github.com/ianstormtaylor/slate/commit/cb8a551508c023346fd3aa0af1a5a80ffd6a37cd) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Add `ReactEditor.isComposing(editor)` to get the current `isComposing` state
+
+## 0.78.1
+
+### Patch Changes
+
+- [#4979](https://github.com/ianstormtaylor/slate/pull/4979) [`6afa9f6a`](https://github.com/ianstormtaylor/slate/commit/6afa9f6a719092368b92dc3342e21e44d457d77e) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Unset isComposing on keydown with isCompsing false
+
+## 0.77.4
+
+### Patch Changes
+
+- [#4965](https://github.com/ianstormtaylor/slate/pull/4965) [`a4536e2a`](https://github.com/ianstormtaylor/slate/commit/a4536e2aa2703d4c4460a54f87997ce76a722689) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Fix triple click handling in nested blocks
+
+## 0.77.3
+
+### Patch Changes
+
+- [#4957](https://github.com/ianstormtaylor/slate/pull/4957) [`c1e3fbaa`](https://github.com/ianstormtaylor/slate/commit/c1e3fbaab969f2e78303f9ba00f26b88c575cdd1) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Don't treat events with non-selection target range as native
+
+## 0.77.2
+
+### Patch Changes
+
+- [#4951](https://github.com/ianstormtaylor/slate/pull/4951) [`5b51e87d`](https://github.com/ianstormtaylor/slate/commit/5b51e87d511e3a8c05a679903650cb256f3bf044) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Fix double insert in anchor element decorations
+
+## 0.77.1
+
+### Patch Changes
+
+- [#4948](https://github.com/ianstormtaylor/slate/pull/4948) [`9957c214`](https://github.com/ianstormtaylor/slate/commit/9957c214357dbbd5492ec4761fd6e1c7b14310f5) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Prevent native insert at the end of anchor elements
+
+## 0.77.0
+
+### Minor Changes
+
+- [#4926](https://github.com/ianstormtaylor/slate/pull/4926) [`076ab9a6`](https://github.com/ianstormtaylor/slate/commit/076ab9a67a5d7bf54062e551e6c29b1464da7e99) Thanks [@Auralytical](https://github.com/Auralytical)! - Fix firefox three digit version check
+
+### Patch Changes
+
+- [#4944](https://github.com/ianstormtaylor/slate/pull/4944) [`486c385b`](https://github.com/ianstormtaylor/slate/commit/486c385bc52ae76890f67ee9e8965955a6de3f61) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Fix crash when tripple clicking editor root
+
+## 0.76.1
+
+### Patch Changes
+
+- [#4923](https://github.com/ianstormtaylor/slate/pull/4923) [`08d5a12c`](https://github.com/ianstormtaylor/slate/commit/08d5a12c9131c715ba75d3cc6f87108f8e6a8f59) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Call keyDown handler while composing
+
+* [#4920](https://github.com/ianstormtaylor/slate/pull/4920) [`f6b7ca1f`](https://github.com/ianstormtaylor/slate/commit/f6b7ca1f97bcc9e6136ab6ba6c7e9bcb1c4fd9bb) Thanks [@adri1wald](https://github.com/adri1wald)! - fix useFocused hook in react >= 17
+
+- [#4914](https://github.com/ianstormtaylor/slate/pull/4914) [`aff67312`](https://github.com/ianstormtaylor/slate/commit/aff67312cbfa7e45df5cf6abcaec9f4f7d5f1a89) Thanks [@sennpang](https://github.com/sennpang)! - Fixed Triple click selection and copy&paste in read-only mode
+
+* [#4919](https://github.com/ianstormtaylor/slate/pull/4919) [`7de7cdcf`](https://github.com/ianstormtaylor/slate/commit/7de7cdcf5625d44dbb2dc9faf52675374e51499f) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Restore user selection after applying beforeinput with target range
+
+- [#4922](https://github.com/ianstormtaylor/slate/pull/4922) [`9892cf0f`](https://github.com/ianstormtaylor/slate/commit/9892cf0ffbd741cc2880d1f0bd0d7c1b36145bbd) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Make Slate component onChange optional
+
+## 0.76.0
+
+### Minor Changes
+
+- [#4873](https://github.com/ianstormtaylor/slate/pull/4873) [`20acca4b`](https://github.com/ianstormtaylor/slate/commit/20acca4bc8f31bd1aa6fbca2c49aaae5f31cadfe) Thanks [@bryanph](https://github.com/bryanph)! - A different behavior for inserting a soft break with shift+enter is quite common in rich text editors. Right now you have to do this in onKeyDown which is not so nice. This adds a separate insertSoftBreak method on the editor instance that gets called when a soft break is inserted. This maintains the current default behavior for backwards compatibility (it just splits the block). But at least you can easily overwrite it now.
+
+ If you rely on overwriting editor.insertBreak for extra behavior for soft breaks this might be a breaking change for you and you should overwrite editor.insertSoftBreak instead.
+
+### Patch Changes
+
+- [#4901](https://github.com/ianstormtaylor/slate/pull/4901) [`5ef346fe`](https://github.com/ianstormtaylor/slate/commit/5ef346feb9e6430b3b6af66f196e5445a9ee3ff2) Thanks [@bryanph](https://github.com/bryanph)! - Fixes a bug where nodes remounted on split_node and merge_node
+
+* [#4885](https://github.com/ianstormtaylor/slate/pull/4885) [`07669dca`](https://github.com/ianstormtaylor/slate/commit/07669dca4b0641506ca857bd781c460dae7606a9) Thanks [@ryanmitts](https://github.com/ryanmitts)! - toSlatePoint should not consider a selection within a void node if the void node isn't in the editor itself.
+
+ Prior to this fix, a nested Slate editor inside a void node in a parent editor would not allow you to start typing text in a blank editor state correctly. After the first character insertion, the selection would jump back to the start of the nested editor.
+
+- [#4910](https://github.com/ianstormtaylor/slate/pull/4910) [`2a8d86f1`](https://github.com/ianstormtaylor/slate/commit/2a8d86f1a40bcc806422e6fe3658ddd810ce73a5) Thanks [@jasonphillips](https://github.com/jasonphillips)! - Fix decorations applied across nested elements
+
+## 0.75.0
+
+### Minor Changes
+
+- [#4883](https://github.com/ianstormtaylor/slate/pull/4883) [`3b3b0e32`](https://github.com/ianstormtaylor/slate/commit/3b3b0e32df4df9fb4cf1d82c0c09b7242c708169) Thanks [@always-maap](https://github.com/always-maap)! - Fix chrome and edge three digit version check
+
+## 0.74.2
+
+### Patch Changes
+
+- [#4876](https://github.com/ianstormtaylor/slate/pull/4876) [`1b205c08`](https://github.com/ianstormtaylor/slate/commit/1b205c087bef2f2360679c46801804d6d30a8139) Thanks [@nemanja-tosic](https://github.com/nemanja-tosic)! - Fix decorations not getting applied for children unless parent changes
+- [#4874](https://github.com/ianstormtaylor/slate/pull/4874) [`4d28948`](https://github.com/ianstormtaylor/slate/commit/4d28948b901b1724493dd0a782e3001149546533) Thanks [@bryanph](https://github.com/bryanph)! - Revert #4755
+
+## 0.74.1
+
+### Patch Changes
+
+- [#4868](https://github.com/ianstormtaylor/slate/pull/4868) [`7499d4b4`](https://github.com/ianstormtaylor/slate/commit/7499d4b4c01a089906a96f30f6c04256204ca65e) Thanks [@sennpang](https://github.com/sennpang)! - fixed cursor when triple clicking on text and type over it, fixes #4862
+
+## 0.74.0
+
+### Minor Changes
+
+- [#4841](https://github.com/ianstormtaylor/slate/pull/4841) [`47f2403e`](https://github.com/ianstormtaylor/slate/commit/47f2403e3a46d84b8d8f99c6e2bf41f2699e30df) Thanks [@Fibs7000](https://github.com/Fibs7000)! - Added redux-style useSlateSelector to improve and prevent unneccessary rerendering with the useSlate hook
+
+## 0.73.0
+
+### Patch Changes
+
+- [#4840](https://github.com/ianstormtaylor/slate/pull/4840) [`100448d5`](https://github.com/ianstormtaylor/slate/commit/100448d55c018351d5a5ffbe18efa207e668f1fb) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Render void spacer in readonly mode
+
+## 0.72.9
+
+### Patch Changes
+
+- [#4828](https://github.com/ianstormtaylor/slate/pull/4828) [`d5ac8237`](https://github.com/ianstormtaylor/slate/commit/d5ac82373b97e389528688ec6dbc7c72715cc360) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Reset isDraggingInternally onDragEnd and onDrop even if the event is handled by the editable handler
+
+* [#4819](https://github.com/ianstormtaylor/slate/pull/4819) [`80661509`](https://github.com/ianstormtaylor/slate/commit/80661509ecf39b5d8256fa387c7eff15f60bf612) Thanks [@ugaya40](https://github.com/ugaya40)! - Fix a possible update of react state after Slate component is unmounted
+
+## 0.72.8
+
+### Patch Changes
+
+- [#4816](https://github.com/ianstormtaylor/slate/pull/4816) [`6d62abc1`](https://github.com/ianstormtaylor/slate/commit/6d62abc1039cf93ce90bd9332a505471df8118ba) Thanks [@dylans](https://github.com/dylans)! - - Revert #4749, DOM & Slate selection are mismatching
+
+## 0.72.7
+
+### Patch Changes
+
+- [#4813](https://github.com/ianstormtaylor/slate/pull/4813) [`a5fd62dd`](https://github.com/ianstormtaylor/slate/commit/a5fd62ddd646553841a54616f7a5528e310bfd22) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Don't throw in toSlatePoint while using supressThrow if leaf has no text node
+
+* [#4798](https://github.com/ianstormtaylor/slate/pull/4798) [`3796c514`](https://github.com/ianstormtaylor/slate/commit/3796c514d6da0db8656486151147d92e73a2350a) Thanks [@hueyhe](https://github.com/hueyhe)! - Fix text not rendered on ssr
+
+- [#4809](https://github.com/ianstormtaylor/slate/pull/4809) [`e9987529`](https://github.com/ianstormtaylor/slate/commit/e9987529895d3ef2740f8f466a9ef9ce4c3e37c2) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Flush onDOMSelectionChange on onDOMBeforeInput
+
+## 0.72.6
+
+### Patch Changes
+
+- [#4796](https://github.com/ianstormtaylor/slate/pull/4796) [`5d8a1606`](https://github.com/ianstormtaylor/slate/commit/5d8a16066949981ead34466af26bcb8e4ffa994b) Thanks [@hueyhe](https://github.com/hueyhe)! - Fix text not rendered on server-side rendering
+
+## 0.72.5
+
+### Patch Changes
+
+- [#4749](https://github.com/ianstormtaylor/slate/pull/4749) [`a3dfb151`](https://github.com/ianstormtaylor/slate/commit/a3dfb151d432ec67f10847997fc71b009bcf5c00) Thanks [@Jabher](https://github.com/Jabher)! - Fix "cannot resolve DOM point" error when switching between multiple errors
+
+## 0.72.4
+
+### Patch Changes
+
+- [#4753](https://github.com/ianstormtaylor/slate/pull/4753) [`e9a46ad2`](https://github.com/ianstormtaylor/slate/commit/e9a46ad29e0376a45051c4a8100c5678784b785c) Thanks [@alessiogaldy](https://github.com/alessiogaldy)! - Fix "editor.insertText never gets called inside plugins on android"
+
+* [#4779](https://github.com/ianstormtaylor/slate/pull/4779) [`345b8fc9`](https://github.com/ianstormtaylor/slate/commit/345b8fc9e8f073674c006098bd843823309db2e2) Thanks [@alessiogaldy](https://github.com/alessiogaldy)! - Android editable updates
+
+ - Remove logic to delay handling of text insertion
+ - Call Transforms.setSelection before Editor.insertText to adjust position
+
+- [#4786](https://github.com/ianstormtaylor/slate/pull/4786) [`67aa1f10`](https://github.com/ianstormtaylor/slate/commit/67aa1f10106e15486031b4103285c7fae4373056) Thanks [@alessiogaldy](https://github.com/alessiogaldy)! - - Restore logic to delay text insertion on android
+ - Always call Trasform.setSelection before calling Editor.insertText
+
+* [#4755](https://github.com/ianstormtaylor/slate/pull/4755) [`8daa77e9`](https://github.com/ianstormtaylor/slate/commit/8daa77e9fab6b222ad796b420b86f3ec88999a39) Thanks [@jhurwitz](https://github.com/jhurwitz)! - fix useFocused hook
+
+- [#4788](https://github.com/ianstormtaylor/slate/pull/4788) [`a8c08a4e`](https://github.com/ianstormtaylor/slate/commit/a8c08a4e0107bccfe972149a512643fbbfcfb9bf) Thanks [@YasinChan](https://github.com/YasinChan)! - Android merge `Editor.insertText` logic.
+
+## 0.72.2
+
+### Patch Changes
+
+- [#4734](https://github.com/ianstormtaylor/slate/pull/4734) [`3c07a870`](https://github.com/ianstormtaylor/slate/commit/3c07a8706ef152fe35c5363a2316aa291c12c2f0) Thanks [@YasinChan](https://github.com/YasinChan)! - [AndroidEditor] Solve input association problems and add click events.
+- [#4733](https://github.com/ianstormtaylor/slate/pull/4733) [`ccafb69`](https://github.com/ianstormtaylor/slate/commit/ccafb6982f56364becc8ca39ed6c1953a5febac3) Thanks [@Schipy](https://github.com/Schipy)! - Optimize TextString rendering to support browser/OS text features, e.g. fix native spellcheck.
+
+## 0.72.1
+
+### Patch Changes
+
+- [#4720](https://github.com/ianstormtaylor/slate/pull/4720) [`1217021a`](https://github.com/ianstormtaylor/slate/commit/1217021a9a42563c9ee951ab670255c209863452) Thanks [@bryanph](https://github.com/bryanph)! - Add origin event type to setFragmentData to be able to distinguish copy, cut and drag
+
+* [#4727](https://github.com/ianstormtaylor/slate/pull/4727) [`0334851c`](https://github.com/ianstormtaylor/slate/commit/0334851cb1da3fd194278e48985166eb658eaf24) Thanks [@ahoisl](https://github.com/ahoisl)! - Fix "Cannot resolve from DOM point" error on onDomSelectionChange for readonly void elements
+
+## 0.72.0
+
+### Minor Changes
+
+- [#4702](https://github.com/ianstormtaylor/slate/pull/4702) [`8bc6a464`](https://github.com/ianstormtaylor/slate/commit/8bc6a464600d6820d85f55fdaf71e9ea01702eb5) Thanks [@ttitoo](https://github.com/ttitoo)! - Fix CJK IME (e.g. Chinese or Japanese) double input
+
+### Patch Changes
+
+- [#4706](https://github.com/ianstormtaylor/slate/pull/4706) [`6d194077`](https://github.com/ianstormtaylor/slate/commit/6d194077763ec0a9d5642be5cafef20e65dbce8e) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Update android restoreDOM to use partial dom restoring
+
+* [#4707](https://github.com/ianstormtaylor/slate/pull/4707) [`c020ca23`](https://github.com/ianstormtaylor/slate/commit/c020ca23b6f5d2307eb62630566ec711def89fcf) Thanks [@ahoisl](https://github.com/ahoisl)! - fix: add 'readonly' dependency for onDragStart callback
+
+## 0.71.0
+
+### Minor Changes
+
+- [#4682](https://github.com/ianstormtaylor/slate/pull/4682) [`e5380655`](https://github.com/ianstormtaylor/slate/commit/e53806557217b08bce6217b7d871cd5ae7dad31c) Thanks [@matthewkeil](https://github.com/matthewkeil)! - Support SSR for autoCorrect, spellCheck and autoCapitalize.
+ Fixes prop mismatch between server and client.
+ Removes the need to add
+
+
+## 0.70.2
+
+### Patch Changes
+
+- [#4669](https://github.com/ianstormtaylor/slate/pull/4669) [`807716d7`](https://github.com/ianstormtaylor/slate/commit/807716d7dfb0fa5791cdcdfeaf4ac027a003127b) Thanks [@BitPhinix](https://github.com/BitPhinix)! - Flush scheduleOnDOMSelectionChange on beforeinput
+
+* [#4661](https://github.com/ianstormtaylor/slate/pull/4661) [`0f194a86`](https://github.com/ianstormtaylor/slate/commit/0f194a86a08d5de07e58e20fb95c9dc760e9d52d) Thanks [@leoc4e](https://github.com/leoc4e)! - use ownerDocument to create element
+
+## 0.70.1
+
+### Patch Changes
+
+- [#4654](https://github.com/ianstormtaylor/slate/pull/4654) [`2c7750ca`](https://github.com/ianstormtaylor/slate/commit/2c7750cac5949a935a570a9590a82187673b9a44) Thanks [@anho](https://github.com/anho)! - weak guard on DataTransfer to not rely on current window
+
+* [#4652](https://github.com/ianstormtaylor/slate/pull/4652) [`95389ed7`](https://github.com/ianstormtaylor/slate/commit/95389ed7b03487aa066af277afcccba440c32f24) Thanks [@karthikcodes6](https://github.com/karthikcodes6)! - Disabled the auto scroll behaviour when the editor has any active selection
+
+- [#4650](https://github.com/ianstormtaylor/slate/pull/4650) [`b6643132`](https://github.com/ianstormtaylor/slate/commit/b6643132f1f3b64f019a601ee2f44a521c122ad3) Thanks [@e1himself](https://github.com/e1himself)! - Do not disable Grammarly extension in Slate editors
+
+## 0.70.0
+
+### Patch Changes
+
+- [#4636](https://github.com/ianstormtaylor/slate/pull/4636) [`9e8d5e2b`](https://github.com/ianstormtaylor/slate/commit/9e8d5e2b9bbff1ec7161e292635a074ba3538774) Thanks [@cmmartin](https://github.com/cmmartin)! - Fixes drop actions in editors rendered in iFrames.
+
+## 0.69.0
+
+### Minor Changes
+
+- [#4625](https://github.com/ianstormtaylor/slate/pull/4625) [`e54f2a0e`](https://github.com/ianstormtaylor/slate/commit/e54f2a0ea01ddc94f3ad14e812602b0ed824aeb3) Thanks [@echarles](https://github.com/echarles)! - insertTextData and insertFragmentData return a boolean (true if some content has been effectively inserted)
+
+## 0.68.1
+
+### Patch Changes
+
+- [#4627](https://github.com/ianstormtaylor/slate/pull/4627) [`ec01e75f`](https://github.com/ianstormtaylor/slate/commit/ec01e75fff29b3e7b710b59a6ba8106d9aa9ca5e) Thanks [@jameshfisher](https://github.com/jameshfisher)! - Fixed issues where cursor jumps to wrong location
+
+## 0.68.0
+
+### Minor Changes
+
+- [#4620](https://github.com/ianstormtaylor/slate/pull/4620) [`0b59ad54`](https://github.com/ianstormtaylor/slate/commit/0b59ad5414f682b510453696b6f45d5a46cb66bb) Thanks [@NicklasAndersson](https://github.com/NicklasAndersson)! - Support selection in readOnly=true editors.
+
+## 0.67.1
+
+### Patch Changes
+
+- [#4616](https://github.com/ianstormtaylor/slate/pull/4616) [`77d9f60a`](https://github.com/ianstormtaylor/slate/commit/77d9f60ab5e497aadf2d0c9564b1e79525984734) Thanks [@jameshfisher](https://github.com/jameshfisher)! - Fixed crash on self-deleting void node
+
+* [#4617](https://github.com/ianstormtaylor/slate/pull/4617) [`b186d3ea`](https://github.com/ianstormtaylor/slate/commit/b186d3ea12ce59c024a56fcbad4604c919757d36) Thanks [@imdbsd](https://github.com/imdbsd)! - Fix crash on drag and drop image on readOnly editable
+
+- [#4614](https://github.com/ianstormtaylor/slate/pull/4614) [`72160fac`](https://github.com/ianstormtaylor/slate/commit/72160fac08fde98d223c9dd2b4263897d23454f6) Thanks [@echarles](https://github.com/echarles)! - Add insertFragmentData and insertTextData to the ReactEditor API
+
+## 0.67.0
+
+### Minor Changes
+
+- [#4540](https://github.com/ianstormtaylor/slate/pull/4540) [`11ef83b4`](https://github.com/ianstormtaylor/slate/commit/11ef83b47fca84d1f908b5c9eeefada516fe9fed) Thanks [@bryanph](https://github.com/bryanph)! - The Slate Provider's "value" prop is now only used as initial state for editor.children as was intended before. If your code relies on replacing editor.children you should do so by replacing it directly instead of relying on the "value" prop to do this for you.
+
+### Patch Changes
+
+- [#4577](https://github.com/ianstormtaylor/slate/pull/4577) [`4b2e4000`](https://github.com/ianstormtaylor/slate/commit/4b2e4000d6253bd86fab237b6f2c70e9f8d30f09) Thanks [@jameshfisher](https://github.com/jameshfisher)! - Fixed a bug that removed the selection when hovering over a non-selectable DOM element
+
+* [#4605](https://github.com/ianstormtaylor/slate/pull/4605) [`87ab2efa`](https://github.com/ianstormtaylor/slate/commit/87ab2efa41a5b7a1324b3fc97117a1cdd3b41d66) Thanks [@jaked](https://github.com/jaked)! - defer native events within Editable to avoid bugs with Editor
+
+- [#4584](https://github.com/ianstormtaylor/slate/pull/4584) [`f40e515d`](https://github.com/ianstormtaylor/slate/commit/f40e515dc7f956b7fd859688c0170f2c1763fecf) Thanks [@jameshfisher](https://github.com/jameshfisher)! - Fixed bug: setting selection from `contentEditable:false` element causes crash
+
+## 0.66.7
+
+### Patch Changes
+
+- [#4588](https://github.com/ianstormtaylor/slate/pull/4588) [`ae65ae5f`](https://github.com/ianstormtaylor/slate/commit/ae65ae5f717c877eee0e3f839b76fc18d8b44999) Thanks [@jaked](https://github.com/jaked)! - revert #4455 / #4512; fix triple-click by unhanging range with void
+
+## 0.66.6
+
+### Patch Changes
+
+- [#4556](https://github.com/ianstormtaylor/slate/pull/4556) [`b1084918`](https://github.com/ianstormtaylor/slate/commit/b10849182086699d4bb18209a37ea6247f712bd0) Thanks [@jaked](https://github.com/jaked)! - fix forced update in TextString in case of double render
+
+## 0.66.4
+
+### Patch Changes
+
+- [#4304](https://github.com/ianstormtaylor/slate/pull/4304) [`7ba486aa`](https://github.com/ianstormtaylor/slate/commit/7ba486aa397411a3e83ab636b0982167d95319c0) Thanks [@davidruisinger](https://github.com/davidruisinger)! - Fixed a bug where text was typed backwards within nested editor
+
+## 0.66.3
+
+### Patch Changes
+
+- [#4547](https://github.com/ianstormtaylor/slate/pull/4547) [`677da0ca`](https://github.com/ianstormtaylor/slate/commit/677da0ca87ffefb36676200fee5cf5cf0136b22e) Thanks [@clauderic](https://github.com/clauderic)! - Fixed a bug that caused the editor to be unable to resolve a Slate point from a DOM point when selecting an entire document that ended in a new line in Firefox.
+
+* [#4526](https://github.com/ianstormtaylor/slate/pull/4526) [`bc85497d`](https://github.com/ianstormtaylor/slate/commit/bc85497d58dc2eddb0918eed4c7d25d040fa653f) Thanks [@VictorBaron](https://github.com/VictorBaron)! - Fix - delete selected inline void in chrome
+
+- [#4549](https://github.com/ianstormtaylor/slate/pull/4549) [`f9c41a56`](https://github.com/ianstormtaylor/slate/commit/f9c41a569cab2000bd14df5f516c80089b3bf0ac) Thanks [@nemanja-tosic](https://github.com/nemanja-tosic)! - Fix deletion of expanded range (#4546)
+
+## 0.66.2
+
+### Patch Changes
+
+- [#4529](https://github.com/ianstormtaylor/slate/pull/4529) [`bd80a0b8`](https://github.com/ianstormtaylor/slate/commit/bd80a0b8dc108a05addb6e599b7f6272acc8aa57) Thanks [@nemanja-tosic](https://github.com/nemanja-tosic)! - Fix erroneous text after native insert
+
+## 0.66.1
+
+### Patch Changes
+
+- [#4514](https://github.com/ianstormtaylor/slate/pull/4514) [`8b5dbc3d`](https://github.com/ianstormtaylor/slate/commit/8b5dbc3dc7716b51b74a3b7a3dbe2609642f2f6c) Thanks [@dylans](https://github.com/dylans)! - fix(react-editor): reset focus offset when triple clicking
+
+## 0.66.0
+
+### Minor Changes
+
+- [#3888](https://github.com/ianstormtaylor/slate/pull/3888) [`25afbd43`](https://github.com/ianstormtaylor/slate/commit/25afbd43001cdee852af6386d2b701d943b788da) Thanks [@bkrausz](https://github.com/bkrausz)! - Use native character insertion to fix browser/OS text features
+
+### Patch Changes
+
+- [#4475](https://github.com/ianstormtaylor/slate/pull/4475) [`c1433f56`](https://github.com/ianstormtaylor/slate/commit/c1433f56cfe13feb826264989bb4f68a0eefab62) Thanks [@skogsmaskin](https://github.com/skogsmaskin)! - [slate-react]: fix selection bugs when multiple editors share value
+
+* [#4132](https://github.com/ianstormtaylor/slate/pull/4132) [`48b71294`](https://github.com/ianstormtaylor/slate/commit/48b7129447347c9cf7a0535026287896ef59779b) Thanks [@ulion](https://github.com/ulion)! - Make onDomSelectionChange trigger after onClick.
+
+- [#4493](https://github.com/ianstormtaylor/slate/pull/4493) [`3dd74dd5`](https://github.com/ianstormtaylor/slate/commit/3dd74dd58daa907bfa1fb44bc5655ae2fc8ddb35) Thanks [@dylans](https://github.com/dylans)! - Update error message for useSlate
+
+* [#4450](https://github.com/ianstormtaylor/slate/pull/4450) [`220f2d2c`](https://github.com/ianstormtaylor/slate/commit/220f2d2ce6dffcc1a0f2ea1e8725601b8ea1949b) Thanks [@neko-neko](https://github.com/neko-neko)! - Changed so that the onKeyDown event do not fired while IME converting.
+
+- [#4452](https://github.com/ianstormtaylor/slate/pull/4452) [`935b3a79`](https://github.com/ianstormtaylor/slate/commit/935b3a79d6ec7d7e8f20804b2703e984e9c396e0) Thanks [@dylans](https://github.com/dylans)! - double ime fix for qq browser
+
+* [#4500](https://github.com/ianstormtaylor/slate/pull/4500) [`50bb3d7e`](https://github.com/ianstormtaylor/slate/commit/50bb3d7e32d640957018831526235ca656963f1d) Thanks [@tubbo](https://github.com/tubbo)! - Upgrade `is-plain-object` to v5.0.0
+
+- [#4480](https://github.com/ianstormtaylor/slate/pull/4480) [`e51566ad`](https://github.com/ianstormtaylor/slate/commit/e51566ada84cfa107c445cc6f3908e78c18656b6) Thanks [@imdbsd](https://github.com/imdbsd)! - Add key for Children SelectedContext.Provider
+
+* [#4454](https://github.com/ianstormtaylor/slate/pull/4454) [`d06706c9`](https://github.com/ianstormtaylor/slate/commit/d06706c9e15bbbdd7cdd9a1bbb38c87d37c85ea1) Thanks [@imdbsd](https://github.com/imdbsd)! - Fix to read fragment from data-slate-fragment when application/x-slate-fragment is missing
+
+- [#4460](https://github.com/ianstormtaylor/slate/pull/4460) [`ace397f9`](https://github.com/ianstormtaylor/slate/commit/ace397f96602d93ab9216e3d3434f55eef981e4d) Thanks [@dylans](https://github.com/dylans)! - fix double character insertion regression due to unnecessary memo
+
+* [#4451](https://github.com/ianstormtaylor/slate/pull/4451) [`8e4120ae`](https://github.com/ianstormtaylor/slate/commit/8e4120ae315151705152e62944737ca4f62ad446) Thanks [@githoniel](https://github.com/githoniel)! - fix IME double input with editor mark
+
+- [#4503](https://github.com/ianstormtaylor/slate/pull/4503) [`2065c5bd`](https://github.com/ianstormtaylor/slate/commit/2065c5bdfd0de9f7d5ea049b23cd22b71bb80225) Thanks [@bytrangle](https://github.com/bytrangle)! - Fix incorrect selection when triple clicking blocks in Editable component
+
+* [#4433](https://github.com/ianstormtaylor/slate/pull/4433) [`a1f925bd`](https://github.com/ianstormtaylor/slate/commit/a1f925bddfb8e4507977b3449972d4521d05b148) Thanks [@imdbsd](https://github.com/imdbsd)! - Fix copy-paste a slate fragment on android editable
+
+- [#4365](https://github.com/ianstormtaylor/slate/pull/4365) [`906e5af1`](https://github.com/ianstormtaylor/slate/commit/906e5af1b1af07454da0a93490fca70b58fd9986) Thanks [@samarsault](https://github.com/samarsault)! - fix a bug where element selections were not captured by useSelected
+
+* [#4342](https://github.com/ianstormtaylor/slate/pull/4342) [`834ce348`](https://github.com/ianstormtaylor/slate/commit/834ce3483dc407a6293ba29cac8f192c13f57b01) Thanks [@imdbsd](https://github.com/imdbsd)! - Fix editor mark is not inserted on android
+
+## 0.65.3
+
+### Patch Changes
+
+- [#4175](https://github.com/ianstormtaylor/slate/pull/4175) [`bde6e804`](https://github.com/ianstormtaylor/slate/commit/bde6e80476ee0ba7a14c8c7625b51de9e58bb170) Thanks [@gyh9457](https://github.com/gyh9457)! - Fixed a bug in the memoization logic for the leaves of text nodes.
+
+* [#4394](https://github.com/ianstormtaylor/slate/pull/4394) [`01889807`](https://github.com/ianstormtaylor/slate/commit/0188980796b7a4b23ef2ee9e7e468532c1f5c8c4) Thanks [@jaked](https://github.com/jaked)! - fix bug where decorate is not called on immediate children of editor
+
+- [#4049](https://github.com/ianstormtaylor/slate/pull/4049) [`6c844227`](https://github.com/ianstormtaylor/slate/commit/6c8442272105ec78b88d38efecb7aab9bb4e41de) Thanks [@ulion](https://github.com/ulion)! - Fix ios chrome ime double input issue.
+
+* [#4427](https://github.com/ianstormtaylor/slate/pull/4427) [`3f69a9f3`](https://github.com/ianstormtaylor/slate/commit/3f69a9f3951b5beca77b065aaa5eba0737e68a8e) Thanks [@ben10code](https://github.com/ben10code)! - Fix crash when unmounting an editor rendered within a React portal. The issue was arising at unmount time, because `getRootNode` returned the dettached portal node and it is not an instance of `Document` or `ShadowRoot`. As a fix, `getDocumentOrShadowRoot` has been refactored to return a root node instead of throwing. In sum, this patch fixes a regression bug introduced by https://github.com/ianstormtaylor/slate/pull/3749/
+
+- [#4369](https://github.com/ianstormtaylor/slate/pull/4369) [`c217dbb5`](https://github.com/ianstormtaylor/slate/commit/c217dbb5b9190753298bbc117a49af940a3a0d53) Thanks [@thesunny](https://github.com/thesunny)! - Scroll when inserting new text will now scroll parent scrollables
+
+* [#4333](https://github.com/ianstormtaylor/slate/pull/4333) [`e0776c5c`](https://github.com/ianstormtaylor/slate/commit/e0776c5c923f1fb33a130599e558e6dffdde40f4) Thanks [@dylans](https://github.com/dylans)! - Allow setFragmentData to work without copy/paste or DnD data structure
+
+- [#4421](https://github.com/ianstormtaylor/slate/pull/4421) [`237edc6e`](https://github.com/ianstormtaylor/slate/commit/237edc6ea616c9171611e632e146872a245bdb0e) Thanks [@jaked](https://github.com/jaked)! - fix decorate bug (#4277) without adding extra layers of render tree
+
+* [#4347](https://github.com/ianstormtaylor/slate/pull/4347) [`46c8871c`](https://github.com/ianstormtaylor/slate/commit/46c8871c9cafd3017b2c9afff9b36f0527c2205f) Thanks [@aiwenar](https://github.com/aiwenar)! - Re-render leaf when new properties were added to it
+
+- [#4352](https://github.com/ianstormtaylor/slate/pull/4352) [`4b373dc2`](https://github.com/ianstormtaylor/slate/commit/4b373dc29055a6fb3e2cdb26dd4cd023787603a5) Thanks [@hueyhe](https://github.com/hueyhe)! - Do not display placeholder when composing
+
+## 0.65.2
+
+### Patch Changes
+
+- [#4331](https://github.com/ianstormtaylor/slate/pull/4331) [`a3bc97af`](https://github.com/ianstormtaylor/slate/commit/a3bc97af3e3bc88ccf9ab7eadb1a56c0bc92f436) Thanks [@golota60](https://github.com/golota60)! - Fix deletion of selected inline void nodes in Safari when presssing `backspace` or `delete`. This is a bug that [was originally fixed only for Google Chrome](https://github.com/ianstormtaylor/slate/issues/3456), but the fix also needs to be applied in Safari.
+
+## 0.65.1
+
+### Patch Changes
+
+- [#4324](https://github.com/ianstormtaylor/slate/pull/4324) [`61171a23`](https://github.com/ianstormtaylor/slate/commit/61171a23821b882116deabceec15f7e2649d271c) Thanks [@clauderic](https://github.com/clauderic)! - Fix backward typing bug in Safari by ensuring the selection is always removed on blur.
+ Safari doesn't always remove the selection, even if the contenteditable element no longer has focus.
+ In this scenario, we need to forcefully remove the selection on blur.
+ Refer to https://stackoverflow.com/questions/12353247/force-contenteditable-div-to-stop-accepting-input-after-it-loses-focus-under-web
+
+## 0.65.0
+
+### Minor Changes
+
+- [#4299](https://github.com/ianstormtaylor/slate/pull/4299) [`2c17e2b7`](https://github.com/ianstormtaylor/slate/commit/2c17e2b7f9dbfa4a821b05668bd00f465da175ad) Thanks [@georgberecz](https://github.com/georgberecz)! - Allow custom event handlers on Editable component to return boolean flag to specify whether the event can be treated as being handled.
+
+ By default, the `Editable` component comes with a set of event handlers that handle typical rich-text editing behaviors (for example, it implements its own `onCopy`, `onPaste`, `onDrop`, and `onKeyDown` handlers).
+
+ In some cases you may want to extend or override Slate's default behavior, which can be done by passing your own event handler(s) to the `Editable` component.
+
+ Your custom event handler can control whether or not Slate should execute its own event handling for a given event after your handler runs depending on the return value of your event handler as described below.
+
+ ```jsx
+ import {Editable} from 'slate-react';
+
+ function MyEditor() {
+ const onClick = event => {
+ // Implement custom event logic...
+
+ // When no value is returned, Slate will execute its own event handler when
+ // neither isDefaultPrevented nor isPropagationStopped was set on the event
+ };
+
+ const onDrop = event => {
+ // Implement custom event logic...
+
+ // No matter the state of the event, treat it as being handled by returning
+ // true here, Slate will skip its own event handler
+ return true;
+ };
+
+ const onDragStart = event => {
+ // Implement custom event logic...
+
+ // No matter the status of the event, treat event as *not* being handled by
+ // returning false, Slate will exectue its own event handler afterward
+ return false;
+ };
+
+ return (
+
+ )
+ }
+ ```
+
+### Patch Changes
+
+- [#4266](https://github.com/ianstormtaylor/slate/pull/4266) [`411e5a19`](https://github.com/ianstormtaylor/slate/commit/411e5a193bd639fb743c2253a5f5e43a5949b100) Thanks [@TheSpyder](https://github.com/TheSpyder)! - Removed accidental bundling of `slate-history` inside `slate-react`
+
+* [#4307](https://github.com/ianstormtaylor/slate/pull/4307) [`a7e3a181`](https://github.com/ianstormtaylor/slate/commit/a7e3a18187d1c29744d78875542abd035220ebdc) Thanks [@clauderic](https://github.com/clauderic)! - Fix deletion of selected inline void nodes in Chrome. Chrome does not fire a `beforeinput` event when deleting backwards within an inline void node, so we need to add special logic to handle this edge-case for Chrome only.
+
+- [#4272](https://github.com/ianstormtaylor/slate/pull/4272) [`294d5120`](https://github.com/ianstormtaylor/slate/commit/294d5120aed89f4e1c7a818e0d1339f4fa1cbaf5) Thanks [@clauderic](https://github.com/clauderic)! - Fix errors accessing `globalThis` in browsers that do not implement it
+
+* [#4295](https://github.com/ianstormtaylor/slate/pull/4295) [`dfc03960`](https://github.com/ianstormtaylor/slate/commit/dfc039601f7b4d74592dfe39c31b67c0f0619bca) Thanks [@dubzzz](https://github.com/dubzzz)! - Fix React warnings related to `autoCorrect` and `autoCapitalize` attributes being passed as a boolean instead of a string.
+
+- [#4271](https://github.com/ianstormtaylor/slate/pull/4271) [`ff267767`](https://github.com/ianstormtaylor/slate/commit/ff267767f61577fdbd68119a1c978e9856e3bb31) Thanks [@omerg](https://github.com/omerg)! - Fixed typo: Renamed `toSlatePoint` argument `extractMatch` to `exactMatch`
+
+## 0.64.0
+
+### Minor Changes
+
+- [#4257](https://github.com/ianstormtaylor/slate/pull/4257) [`4f0d1120`](https://github.com/ianstormtaylor/slate/commit/4f0d1120d46d1024d94e3c2742026f6c54357e1f) Thanks [@clauderic](https://github.com/clauderic)! - Added support for Android devices using a `MutationObserver` based reconciliation layer.
+
+ Bugs should be expected; translating mutations into a set of operations that need to be reconciled onto the Slate model is not an absolute science, and requires a lot of guesswork and handling of edge cases. There are still edge cases that aren't being handled.
+
+ This reconciliation layer aims to support Android 10 and 11. Earlier versions of Android work to a certain extent, but have more bugs and edge cases that currently aren't well supported.
+
+## 0.63.0
+
+### Patch Changes
+
+- [#4238](https://github.com/ianstormtaylor/slate/pull/4238) [`c14e1fbc`](https://github.com/ianstormtaylor/slate/commit/c14e1fbc77c51f7928ba8ab089c76f3e3438fb97) Thanks [@clauderic](https://github.com/clauderic)! - Fix duplicated content and other bugs related to drag and drop handling
+
+* [#4237](https://github.com/ianstormtaylor/slate/pull/4237) [`623960a7`](https://github.com/ianstormtaylor/slate/commit/623960a7d14103b8cebe667019b4de5f8ad1fd61) Thanks [@dylans](https://github.com/dylans)! - Fixed text insertion logic to prevent crashing in newer Firefox versions.
+
+## 0.62.1
+
+### Patch Changes
+
+- [#4118](https://github.com/ianstormtaylor/slate/pull/4118) [`6a137633`](https://github.com/ianstormtaylor/slate/commit/6a1376332bbd2567336c444c57c1e64fdf706feb) Thanks [@kamilkazmierczak](https://github.com/kamilkazmierczak)! - Improved detection of legacy browsers that don't have proper `beforeinput` support.
+
+* [#4190](https://github.com/ianstormtaylor/slate/pull/4190) [`ea2eefef`](https://github.com/ianstormtaylor/slate/commit/ea2eefefb84365eb969e91151afc861e0dbefefd) Thanks [@juliankrispel](https://github.com/juliankrispel)! - Added a `renderPlaceholder` prop to the `` component for customizing how placeholders are rendered.
+
+- [#4157](https://github.com/ianstormtaylor/slate/pull/4157) [`de5cc7e5`](https://github.com/ianstormtaylor/slate/commit/de5cc7e5ed97fdca9e3766a8d947ab6391e6ccb2) Thanks [@githoniel](https://github.com/githoniel)! - Fixed a bug when syncing the selection for IME-based editing.
+
+* [#4158](https://github.com/ianstormtaylor/slate/pull/4158) [`ea6dc089`](https://github.com/ianstormtaylor/slate/commit/ea6dc08913d9dd671eeb05796dca522a4a35904e) Thanks [@githoniel](https://github.com/githoniel)! - Fixed a bug that resulted in doubly-input characters when using an IME.
+
+- [#4211](https://github.com/ianstormtaylor/slate/pull/4211) [`1c32b97d`](https://github.com/ianstormtaylor/slate/commit/1c32b97d23138d301e9ecb567263e3001cc4dbfa) Thanks [@clauderic](https://github.com/clauderic)! - Collapse expanded selection before handling `moveWordBackward` (`alt + left`) and `moveWordForward` (`alt + right`) hotkeys.
+
+* [#4219](https://github.com/ianstormtaylor/slate/pull/4219) [`737aaa9c`](https://github.com/ianstormtaylor/slate/commit/737aaa9cde2d4a2d6d64b83256aa5d9d1b5ce720) Thanks [@juliankrispel](https://github.com/juliankrispel)! - Fixes error that occurs when Editor is rendered inside iframe
+
+## 0.62.0
+
+### Minor Changes
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - **Add directional awareness to `Editor.deleteFragment`.** This is an obscure change, but is a required distinction when implementing features that need to "fake delete" content (like Google Docs's suggestions). Previously deleting always collapsed to the end of a range, but now it can collapse forwards as well.
+
+* [#4154](https://github.com/ianstormtaylor/slate/pull/4154) [`7283c51f`](https://github.com/ianstormtaylor/slate/commit/7283c51feb83cb8522bc16efce09bb01c29400b9) Thanks [@ianstormtaylor](https://github.com/ianstormtaylor)! - **Start using [🦋 Changesets](https://github.com/atlassian/changesets) to manage releases.** Going forward, whenever a pull request is made that fixes or adds functionality to Slate, it will need to be accompanied by a changset Markdown file describing the change. These files will be automatically used in the release process when bump the versions of Slate and compiling the changelog.
+
+### Patch Changes
+
+- [#4150](https://github.com/ianstormtaylor/slate/pull/4150) [`bbd7d9c3`](https://github.com/ianstormtaylor/slate/commit/bbd7d9c33023add223ef9f1a33b657a468552caf) Thanks [@nivekithan](https://github.com/nivekithan)! - Added support for using the new `beforeInput` events in the latest Firefox.
+
+* [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed spellcheck disabling logic to always work in older versions of Firefox.
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed browser-detection behavior to work with Deno.
+
+* [`d5589279`](https://github.com/ianstormtaylor/slate/commit/d5589279e8792185c1082af720a73f55b16797dd) - Updated placeholder styles to allow for wrapping long placeholder text.
+
+- [#3698](https://github.com/ianstormtaylor/slate/pull/3698) [`bf83f333`](https://github.com/ianstormtaylor/slate/commit/bf83f333e689bc17b96504b497bb7fcdf6dc7fc1) Thanks [@pubuzhixing8](https://github.com/pubuzhixing8)! - Fixed selection updating with IME inputs in browsers that support `beforeinput`.
+
+* [#3652](https://github.com/ianstormtaylor/slate/pull/3652) [`f3fb40cc`](https://github.com/ianstormtaylor/slate/commit/f3fb40cce044b73b6da13013f90bd7018f2f5d8a) Thanks [@Andarist](https://github.com/Andarist)! - Fixed selection logic when a controlled editor's nodes change out from under it.
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed a bug where memoization logic would prevent placeholders from re-rendering properly.
+
+* [#3326](https://github.com/ianstormtaylor/slate/pull/3326) [`d5b2d7f5`](https://github.com/ianstormtaylor/slate/commit/d5b2d7f55e2982019b246fdea1e9eb845d0e2fc2) Thanks [@rockettomatooo](https://github.com/rockettomatooo)! - Added invariants when passing invalud `value` or `editor` props to ``.
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed cursor movement in RTL text.
+
+* [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed a bug in the conversion of DOM points to Slate points.
+
+- [#3746](https://github.com/ianstormtaylor/slate/pull/3746) [`f8be509e`](https://github.com/ianstormtaylor/slate/commit/f8be509e4d0b5c13bb791e0fd5702242319d114f) Thanks [@gztomas](https://github.com/gztomas)! - Fixed auto-scrolling behavior when a block is bigger than the viewport.
+
+* [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed a bug that occurred when using Babel's `loose` mode.
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed deleting void elements when using cut-and-paste.
+
+* [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed a bug that crashed the editor when using IME input.
+
+- [#3396](https://github.com/ianstormtaylor/slate/pull/3396) [`469e6b26`](https://github.com/ianstormtaylor/slate/commit/469e6b26f50857ef0d68cdf5a54793f8fe9033fd) Thanks [@cvlmtg](https://github.com/cvlmtg)! - Fixed allowing the `onPaste` handler to be overridden in all browsers.
+
+* [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed internal decoration logic to be faster and require fewer re-renders.
+
+- [#3894](https://github.com/ianstormtaylor/slate/pull/3894) [`7fe41f15`](https://github.com/ianstormtaylor/slate/commit/7fe41f156614453479cb9ea649fe5665b616d3a7) Thanks [@msc117](https://github.com/msc117)! - Fixed an error that happened when selecting void nodes in a read-only editor.
+
+* [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed `move_node` operations to not always require a full re-render.
+
+- [`d5589279`](https://github.com/ianstormtaylor/slate/commit/d5589279e8792185c1082af720a73f55b16797dd) - Fixed normalization of DOM points to be more accurate when triple-clicking.
+
+* [`d5589279`](https://github.com/ianstormtaylor/slate/commit/d5589279e8792185c1082af720a73f55b16797dd) - Fixed a bug that prevented `isFocused` from updating on certain focus changes.
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed IME input to not insert repeated characters.
+
+* [#3749](https://github.com/ianstormtaylor/slate/pull/3749) [`0473d0bf`](https://github.com/ianstormtaylor/slate/commit/0473d0bf93808b0e4e98abe833b7f7f4f5aff3b1) Thanks [@davidruisinger](https://github.com/davidruisinger)! - Fixes Slate to work with the Shadow DOM.
+
+- [`c6002024`](https://github.com/ianstormtaylor/slate/commit/c60020244b9d25094edb0ffcca8b49dead9b31dc) - Fixed deleting by line to account for the line breaks in the browser.
diff --git a/Readme.md b/Readme.md
index fdc81fb..8117839 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,12 +1,8 @@
-To run locally:
-1. Clone the version v0.57.1 of the slate repo: https://github.com/ianstormtaylor/slate/commit/22d9095c39a0e201878e1df04ef5e35d4d86a596
-2. cd slate/packages and remove slate-react
-3. Pull this repo in slate/packages
-4. cd slate && yarn start
+This package contains the React-specific logic for Slate. It's separated further into a series of directories:
-To deploy:
-1. cd slate && yarn build
-2. cd packages/slate-react
-3. npm version [patch | minor ...] && git push
-4. Update the commit hash in hs-editor/package.json
-5. cd hs-monorepo && yarn
+- [**Components**](./src/components) — containing the React components for rendering Slate editors.
+- [**Hooks**](./src/hooks) — containing a few React hooks for Slate editors.
+- [**Plugins**](./src/plugin) — containing the React-specific plugins for Slate editors.
+- [**Utils**](./src/utils) — containing a few private convenience modules.
+
+Feel free to poke around in each of them to learn more!
diff --git a/dist/components/children.d.ts b/dist/components/children.d.ts
deleted file mode 100644
index 138e762..0000000
--- a/dist/components/children.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { Range, NodeEntry, Ancestor } from 'slate';
-import { RenderElementProps, RenderLeafProps } from './editable';
-/**
- * Children.
- */
-declare const Children: (props: {
- decorate: (entry: NodeEntry) => Range[];
- decorations: Range[];
- node: Ancestor;
- renderElement?: ((props: RenderElementProps) => JSX.Element) | undefined;
- renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | undefined;
- selection: Range | null;
- ReactHappyWindow: React.Component<{}, {}, any> | undefined;
- reactHappyWindowProps: Object | undefined;
-}) => JSX.Element;
-export default Children;
-//# sourceMappingURL=children.d.ts.map
\ No newline at end of file
diff --git a/dist/components/children.d.ts.map b/dist/components/children.d.ts.map
deleted file mode 100644
index 74a23aa..0000000
--- a/dist/components/children.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"children.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/children.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAU,KAAK,EAAW,SAAS,EAAE,QAAQ,EAAc,MAAM,OAAO,CAAA;AAO/E,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEhE;;GAEG;AAEH,QAAA,MAAM,QAAQ;;;;;;;;;iBA2Fb,CAAA;AAED,eAAe,QAAQ,CAAA"}
\ No newline at end of file
diff --git a/dist/components/editable.d.ts b/dist/components/editable.d.ts
deleted file mode 100644
index 75b00b2..0000000
--- a/dist/components/editable.d.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react';
-import { Element, NodeEntry, Range, Text } from 'slate';
-/**
- * `RenderElementProps` are passed to the `renderElement` handler.
- */
-export interface RenderElementProps {
- children: any;
- element: Element;
- attributes: {
- 'data-slate-node': 'element';
- 'data-slate-inline'?: true;
- 'data-slate-void'?: true;
- dir?: 'rtl';
- ref: any;
- };
-}
-/**
- * `RenderLeafProps` are passed to the `renderLeaf` handler.
- */
-export interface RenderLeafProps {
- children: any;
- leaf: Text;
- text: Text;
- attributes: {
- 'data-slate-leaf': true;
- };
-}
-/**
- * `EditableProps` are passed to the `` component.
- */
-export declare type EditableProps = {
- decorate?: (entry: NodeEntry) => Range[];
- onDOMBeforeInput?: (event: Event) => void;
- placeholder?: string;
- readOnly?: boolean;
- role?: string;
- style?: React.CSSProperties;
- renderElement?: (props: RenderElementProps) => JSX.Element;
- renderLeaf?: (props: RenderLeafProps) => JSX.Element;
- as?: React.ElementType;
- ReactHappyWindow?: React.Component;
- reactHappyWindowProps?: object;
-} & React.TextareaHTMLAttributes;
-/**
- * Editable.
- */
-export declare const Editable: (props: EditableProps) => JSX.Element;
-//# sourceMappingURL=editable.d.ts.map
\ No newline at end of file
diff --git a/dist/components/editable.d.ts.map b/dist/components/editable.d.ts.map
deleted file mode 100644
index 6c6d319..0000000
--- a/dist/components/editable.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"editable.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/editable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAA;AACtE,OAAO,EAEL,OAAO,EACP,SAAS,EAET,KAAK,EACL,IAAI,EAEL,MAAM,OAAO,CAAA;AA8Bd;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,GAAG,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE;QACV,iBAAiB,EAAE,SAAS,CAAA;QAC5B,mBAAmB,CAAC,EAAE,IAAI,CAAA;QAC1B,iBAAiB,CAAC,EAAE,IAAI,CAAA;QACxB,GAAG,CAAC,EAAE,KAAK,CAAA;QACX,GAAG,EAAE,GAAG,CAAA;KACT,CAAA;CACF;AAED;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,GAAG,CAAA;IACb,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,IAAI,CAAA;IACV,UAAU,EAAE;QACV,iBAAiB,EAAE,IAAI,CAAA;KACxB,CAAA;CACF;AAED;;GAEG;AAEH,oBAAY,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,KAAK,EAAE,CAAA;IACxC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,GAAG,CAAC,OAAO,CAAA;IAC1D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,GAAG,CAAC,OAAO,CAAA;IACpD,EAAE,CAAC,EAAE,KAAK,CAAC,WAAW,CAAA;IACtB,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B,GAAG,KAAK,CAAC,sBAAsB,CAAC,cAAc,CAAC,CAAA;AAEhD;;GAEG;AAEH,eAAO,MAAM,QAAQ,uCA+0BpB,CAAA"}
\ No newline at end of file
diff --git a/dist/components/element.d.ts b/dist/components/element.d.ts
deleted file mode 100644
index a93c33f..0000000
--- a/dist/components/element.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { Node, Range, NodeEntry, Element as SlateElement } from 'slate';
-import { RenderElementProps, RenderLeafProps } from './editable';
-declare const MemoizedElement: React.MemoExoticComponent<(props: {
- decorate: (entry: NodeEntry) => Range[];
- decorations: Range[];
- element: SlateElement;
- renderElement?: ((props: RenderElementProps) => JSX.Element) | undefined;
- renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | undefined;
- selection: Range | null;
- elementIndex: Number;
-}) => JSX.Element>;
-/**
- * The default element renderer.
- */
-export declare const DefaultElement: (props: RenderElementProps) => JSX.Element;
-export default MemoizedElement;
-//# sourceMappingURL=element.d.ts.map
\ No newline at end of file
diff --git a/dist/components/element.d.ts.map b/dist/components/element.d.ts.map
deleted file mode 100644
index c56bac1..0000000
--- a/dist/components/element.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/element.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiB,MAAM,OAAO,CAAA;AAErC,OAAO,EAAU,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,OAAO,CAAA;AAc/E,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAwHhE,QAAA,MAAM,eAAe;;;;;;;;kBAYnB,CAAA;AAEF;;GAEG;AAEH,eAAO,MAAM,cAAc,4CAS1B,CAAA;AA2BD,eAAe,eAAe,CAAA"}
\ No newline at end of file
diff --git a/dist/components/leaf.d.ts b/dist/components/leaf.d.ts
deleted file mode 100644
index 751e354..0000000
--- a/dist/components/leaf.d.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { Text, Element } from 'slate';
-import { RenderLeafProps } from './editable';
-declare const MemoizedLeaf: React.MemoExoticComponent<(props: {
- isLast: boolean;
- leaf: Text;
- parent: Element;
- renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | undefined;
- text: Text;
-}) => JSX.Element>;
-/**
- * The default custom leaf renderer.
- */
-export declare const DefaultLeaf: (props: RenderLeafProps) => JSX.Element;
-export default MemoizedLeaf;
-//# sourceMappingURL=leaf.d.ts.map
\ No newline at end of file
diff --git a/dist/components/leaf.d.ts.map b/dist/components/leaf.d.ts.map
deleted file mode 100644
index e52bc2c..0000000
--- a/dist/components/leaf.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"leaf.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/leaf.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAIrC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA2D5C,QAAA,MAAM,YAAY;;;;;;kBAQhB,CAAA;AAEF;;GAEG;AAEH,eAAO,MAAM,WAAW,yCAGvB,CAAA;AAED,eAAe,YAAY,CAAA"}
\ No newline at end of file
diff --git a/dist/components/slate.d.ts b/dist/components/slate.d.ts
deleted file mode 100644
index d55cee8..0000000
--- a/dist/components/slate.d.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { Node } from 'slate';
-import { ReactEditor } from '../plugin/react-editor';
-/**
- * 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.
- */
-export declare const Slate: (props: {
- [key: string]: any;
- editor: ReactEditor;
- value: Node[];
- children: React.ReactNode;
- onChange: (value: Node[]) => void;
-}) => JSX.Element;
-//# sourceMappingURL=slate.d.ts.map
\ No newline at end of file
diff --git a/dist/components/slate.d.ts.map b/dist/components/slate.d.ts.map
deleted file mode 100644
index 5264bc6..0000000
--- a/dist/components/slate.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"slate.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/slate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAMpD;;;GAGG;AAEH,eAAO,MAAM,KAAK;;;;;;iBA+BjB,CAAA"}
\ No newline at end of file
diff --git a/dist/components/string.d.ts b/dist/components/string.d.ts
deleted file mode 100644
index 33d29ea..0000000
--- a/dist/components/string.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Text, Element } from 'slate';
-/**
- * Leaf content strings.
- */
-declare const String: (props: {
- isLast: boolean;
- leaf: Text;
- parent: Element;
- text: Text;
-}) => JSX.Element;
-export default String;
-//# sourceMappingURL=string.d.ts.map
\ No newline at end of file
diff --git a/dist/components/string.d.ts.map b/dist/components/string.d.ts.map
deleted file mode 100644
index 05092be..0000000
--- a/dist/components/string.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/string.tsx"],"names":[],"mappings":"AACA,OAAO,EAAU,IAAI,EAAQ,OAAO,EAAQ,MAAM,OAAO,CAAA;AAIzD;;GAEG;AAEH,QAAA,MAAM,MAAM;;;;;iBA2CX,CAAA;AAiCD,eAAe,MAAM,CAAA"}
\ No newline at end of file
diff --git a/dist/components/text.d.ts b/dist/components/text.d.ts
deleted file mode 100644
index 90acace..0000000
--- a/dist/components/text.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { Range, Element, Text as SlateText } from 'slate';
-import { RenderLeafProps } from './editable';
-declare const MemoizedText: React.MemoExoticComponent<(props: {
- decorations: Range[];
- isLast: boolean;
- parent: Element;
- renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | undefined;
- text: SlateText;
-}) => JSX.Element>;
-export default MemoizedText;
-//# sourceMappingURL=text.d.ts.map
\ No newline at end of file
diff --git a/dist/components/text.d.ts.map b/dist/components/text.d.ts.map
deleted file mode 100644
index 475884f..0000000
--- a/dist/components/text.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/components/text.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiB,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,OAAO,CAAA;AAIzD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA4D5C,QAAA,MAAM,YAAY;;;;;;kBAOhB,CAAA;AAEF,eAAe,YAAY,CAAA"}
\ No newline at end of file
diff --git a/dist/hooks/use-editor.d.ts b/dist/hooks/use-editor.d.ts
deleted file mode 100644
index d0a0c73..0000000
--- a/dist/hooks/use-editor.d.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-///
-import { ReactEditor } from '../plugin/react-editor';
-/**
- * A React context for sharing the editor object.
- */
-export declare const EditorContext: import("react").Context;
-/**
- * Get the current editor object from the React context.
- */
-export declare const useEditor: () => ReactEditor;
-//# sourceMappingURL=use-editor.d.ts.map
\ No newline at end of file
diff --git a/dist/hooks/use-editor.d.ts.map b/dist/hooks/use-editor.d.ts.map
deleted file mode 100644
index 91f7978..0000000
--- a/dist/hooks/use-editor.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"use-editor.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/hooks/use-editor.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD;;GAEG;AAEH,eAAO,MAAM,aAAa,6CAA0C,CAAA;AAEpE;;GAEG;AAEH,eAAO,MAAM,SAAS,mBAUrB,CAAA"}
\ No newline at end of file
diff --git a/dist/hooks/use-focused.d.ts b/dist/hooks/use-focused.d.ts
deleted file mode 100644
index 1972f76..0000000
--- a/dist/hooks/use-focused.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-///
-/**
- * A React context for sharing the `focused` state of the editor.
- */
-export declare const FocusedContext: import("react").Context;
-/**
- * Get the current `focused` state of the editor.
- */
-export declare const useFocused: () => boolean;
-//# sourceMappingURL=use-focused.d.ts.map
\ No newline at end of file
diff --git a/dist/hooks/use-focused.d.ts.map b/dist/hooks/use-focused.d.ts.map
deleted file mode 100644
index 15ae1f9..0000000
--- a/dist/hooks/use-focused.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"use-focused.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/hooks/use-focused.ts"],"names":[],"mappings":";AAEA;;GAEG;AAEH,eAAO,MAAM,cAAc,kCAAuB,CAAA;AAElD;;GAEG;AAEH,eAAO,MAAM,UAAU,eAEtB,CAAA"}
\ No newline at end of file
diff --git a/dist/hooks/use-isomorphic-layout-effect.d.ts b/dist/hooks/use-isomorphic-layout-effect.d.ts
deleted file mode 100644
index 0ec130b..0000000
--- a/dist/hooks/use-isomorphic-layout-effect.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { useLayoutEffect } from 'react';
-/**
- * Prevent warning on SSR by falling back to useEffect when window is not defined
- */
-export declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
-//# sourceMappingURL=use-isomorphic-layout-effect.d.ts.map
\ No newline at end of file
diff --git a/dist/hooks/use-isomorphic-layout-effect.d.ts.map b/dist/hooks/use-isomorphic-layout-effect.d.ts.map
deleted file mode 100644
index 15061dd..0000000
--- a/dist/hooks/use-isomorphic-layout-effect.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"use-isomorphic-layout-effect.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/hooks/use-isomorphic-layout-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAa,MAAM,OAAO,CAAA;AAElD;;GAEG;AACH,eAAO,MAAM,yBAAyB,wBACuB,CAAA"}
\ No newline at end of file
diff --git a/dist/hooks/use-read-only.d.ts b/dist/hooks/use-read-only.d.ts
deleted file mode 100644
index 993116c..0000000
--- a/dist/hooks/use-read-only.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-///
-/**
- * A React context for sharing the `readOnly` state of the editor.
- */
-export declare const ReadOnlyContext: import("react").Context;
-/**
- * Get the current `readOnly` state of the editor.
- */
-export declare const useReadOnly: () => boolean;
-//# sourceMappingURL=use-read-only.d.ts.map
\ No newline at end of file
diff --git a/dist/hooks/use-read-only.d.ts.map b/dist/hooks/use-read-only.d.ts.map
deleted file mode 100644
index 82fadd4..0000000
--- a/dist/hooks/use-read-only.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"use-read-only.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/hooks/use-read-only.ts"],"names":[],"mappings":";AAEA;;GAEG;AAEH,eAAO,MAAM,eAAe,kCAAuB,CAAA;AAEnD;;GAEG;AAEH,eAAO,MAAM,WAAW,eAEvB,CAAA"}
\ No newline at end of file
diff --git a/dist/hooks/use-selected.d.ts b/dist/hooks/use-selected.d.ts
deleted file mode 100644
index ad1e941..0000000
--- a/dist/hooks/use-selected.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-///
-/**
- * A React context for sharing the `selected` state of an element.
- */
-export declare const SelectedContext: import("react").Context;
-/**
- * Get the current `selected` state of an element.
- */
-export declare const useSelected: () => boolean;
-//# sourceMappingURL=use-selected.d.ts.map
\ No newline at end of file
diff --git a/dist/hooks/use-selected.d.ts.map b/dist/hooks/use-selected.d.ts.map
deleted file mode 100644
index 49e6aa4..0000000
--- a/dist/hooks/use-selected.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"use-selected.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/hooks/use-selected.ts"],"names":[],"mappings":";AAEA;;GAEG;AAEH,eAAO,MAAM,eAAe,kCAAuB,CAAA;AAEnD;;GAEG;AAEH,eAAO,MAAM,WAAW,eAEvB,CAAA"}
\ No newline at end of file
diff --git a/dist/hooks/use-slate.d.ts b/dist/hooks/use-slate.d.ts
deleted file mode 100644
index 5bd990f..0000000
--- a/dist/hooks/use-slate.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-///
-import { ReactEditor } from '../plugin/react-editor';
-/**
- * A React context for sharing the editor object, in a way that re-renders the
- * context whenever changes occur.
- */
-export declare const SlateContext: import("react").Context<[ReactEditor] | null>;
-/**
- * Get the current editor object from the React context.
- */
-export declare const useSlate: () => ReactEditor;
-//# sourceMappingURL=use-slate.d.ts.map
\ No newline at end of file
diff --git a/dist/hooks/use-slate.d.ts.map b/dist/hooks/use-slate.d.ts.map
deleted file mode 100644
index f0a0664..0000000
--- a/dist/hooks/use-slate.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"use-slate.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/hooks/use-slate.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD;;;GAGG;AAEH,eAAO,MAAM,YAAY,+CAA4C,CAAA;AAErE;;GAEG;AAEH,eAAO,MAAM,QAAQ,mBAWpB,CAAA"}
\ No newline at end of file
diff --git a/dist/index.d.ts b/dist/index.d.ts
deleted file mode 100644
index 171b544..0000000
--- a/dist/index.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export { RenderElementProps, RenderLeafProps, Editable, } from './components/editable';
-export { DefaultElement } from './components/element';
-export { DefaultLeaf } from './components/leaf';
-export { Slate } from './components/slate';
-export { useEditor } from './hooks/use-editor';
-export { useFocused } from './hooks/use-focused';
-export { useReadOnly } from './hooks/use-read-only';
-export { useSelected } from './hooks/use-selected';
-export { useSlate } from './hooks/use-slate';
-export { ReactEditor } from './plugin/react-editor';
-export { withReact } from './plugin/with-react';
-//# sourceMappingURL=index.d.ts.map
\ No newline at end of file
diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map
deleted file mode 100644
index dcaab91..0000000
--- a/dist/index.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["packages/slate-react/src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,QAAQ,GACT,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA"}
\ No newline at end of file
diff --git a/dist/index.es.js b/dist/index.es.js
deleted file mode 100644
index 4aff277..0000000
--- a/dist/index.es.js
+++ /dev/null
@@ -1,1915 +0,0 @@
-import React, { useLayoutEffect, useEffect, useRef, createContext, useContext, useMemo, useCallback, useState } from 'react';
-import { Path, Node as Node$1, Editor, Text as Text$1, Range, Element as Element$1, Transforms } from 'slate';
-import getDirection from 'direction';
-import debounce from 'debounce';
-import scrollIntoView from 'scroll-into-view-if-needed';
-import { isKeyHotkey } from 'is-hotkey';
-import ReactDOM from 'react-dom';
-
-/**
- * Leaf content strings.
- */
-const String = (props) => {
- const { isLast, leaf, parent, text } = props;
- const editor = useEditor();
- const path = ReactEditor.findPath(editor, text);
- const parentPath = Path.parent(path);
- // 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 React.createElement(ZeroWidthString, { length: Node$1.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) &&
- Editor.string(editor, parentPath) === '') {
- return React.createElement(ZeroWidthString, { isLineBreak: true });
- }
- // 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 React.createElement(ZeroWidthString, null);
- }
- // 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 React.createElement(TextString, { isTrailing: true, text: leaf.text });
- }
- return React.createElement(TextString, { text: leaf.text });
-};
-/**
- * Leaf strings with text in them.
- */
-const TextString = (props) => {
- const { text, isTrailing = false } = props;
- return (React.createElement("span", { "data-slate-string": true },
- text,
- isTrailing ? '\n' : null));
-};
-/**
- * Leaf strings without text, render as zero-width strings.
- */
-const ZeroWidthString = (props) => {
- const { length = 0, isLineBreak = false } = props;
- return (React.createElement("span", { "data-slate-zero-width": isLineBreak ? 'n' : 'z', "data-slate-length": length },
- '\uFEFF',
- isLineBreak ? React.createElement("br", null) : null));
-};
-
-/**
- * Two weak maps that allow us rebuild a path given a node. They are populated
- * at render time such that after a render occurs we can always backtrack.
- */
-var NODE_TO_INDEX = new WeakMap();
-var NODE_TO_PARENT = new WeakMap();
-/**
- * Weak maps that allow us to go between Slate nodes and DOM nodes. These
- * are used to resolve DOM event-related logic into Slate actions.
- */
-
-var EDITOR_TO_ELEMENT = new WeakMap();
-var ELEMENT_TO_NODE = new WeakMap();
-var KEY_TO_ELEMENT = new WeakMap();
-var NODE_TO_ELEMENT = new WeakMap();
-var NODE_TO_KEY = new WeakMap();
-/**
- * Weak maps for storing editor-related state.
- */
-
-var IS_READ_ONLY = new WeakMap();
-var IS_FOCUSED = new WeakMap();
-/**
- * Weak map for associating the context `onChange` context with the plugin.
- */
-
-var EDITOR_TO_ON_CHANGE = new WeakMap();
-/**
- * Symbols.
- */
-
-var PLACEHOLDER_SYMBOL = Symbol('placeholder');
-
-/**
- * Individual leaves in a text node with unique formatting.
- */
-const Leaf = (props) => {
- const { leaf, isLast, text, parent, renderLeaf = (props) => React.createElement(DefaultLeaf, Object.assign({}, props)), } = props;
- let children = (React.createElement(String, { isLast: isLast, leaf: leaf, parent: parent, text: text }));
- if (leaf[PLACEHOLDER_SYMBOL]) {
- children = (React.createElement(React.Fragment, null,
- React.createElement("span", { contentEditable: false, style: {
- pointerEvents: 'none',
- display: 'inline-block',
- verticalAlign: 'text-top',
- width: '0',
- maxWidth: '100%',
- whiteSpace: 'nowrap',
- opacity: '0.333',
- } }, leaf.placeholder),
- children));
- }
- // 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)
- const attributes = {
- 'data-slate-leaf': true,
- };
- return renderLeaf({ attributes, children, leaf, text });
-};
-const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
- return (next.parent === prev.parent &&
- next.isLast === prev.isLast &&
- next.renderLeaf === prev.renderLeaf &&
- next.text === prev.text &&
- Text$1.matches(next.leaf, prev.leaf));
-});
-/**
- * The default custom leaf renderer.
- */
-const DefaultLeaf = (props) => {
- const { attributes, children } = props;
- return React.createElement("span", Object.assign({}, attributes), children);
-};
-
-/**
- * Prevent warning on SSR by falling back to useEffect when window is not defined
- */
-
-var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
-
-/**
- * Text.
- */
-const Text = (props) => {
- const { decorations, isLast, parent, renderLeaf, text } = props;
- const editor = useEditor();
- const ref = useRef(null);
- const leaves = Text$1.decorations(text, decorations);
- const key = ReactEditor.findKey(editor, text);
- const children = [];
- for (let i = 0; i < leaves.length; i++) {
- const leaf = leaves[i];
- children.push(React.createElement(MemoizedLeaf, { isLast: isLast && i === leaves.length - 1, key: `${key.id}-${i}`, leaf: leaf, text: text, parent: parent, renderLeaf: renderLeaf }));
- }
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- KEY_TO_ELEMENT.set(key, ref.current);
- NODE_TO_ELEMENT.set(text, ref.current);
- ELEMENT_TO_NODE.set(ref.current, text);
- }
- else {
- KEY_TO_ELEMENT.delete(key);
- NODE_TO_ELEMENT.delete(text);
- }
- });
- return (React.createElement("span", { "data-slate-node": "text", ref: ref }, children));
-};
-const MemoizedText = React.memo(Text, (prev, next) => {
- return (next.parent === prev.parent &&
- next.isLast === prev.isLast &&
- next.renderLeaf === prev.renderLeaf &&
- next.text === prev.text);
-});
-
-/**
- * A React context for sharing the `selected` state of an element.
- */
-
-var SelectedContext = createContext(false);
-/**
- * Get the current `selected` state of an element.
- */
-
-var useSelected = () => {
- return useContext(SelectedContext);
-};
-
-/**
- * Element.
- */
-const Element = (props) => {
- const { decorate, decorations, element, renderElement = (p) => React.createElement(DefaultElement, Object.assign({}, p)), renderLeaf, selection, elementIndex, } = props;
- const ref = useRef(null);
- const editor = useEditor();
- const readOnly = useReadOnly();
- const isInline = editor.isInline(element);
- const key = ReactEditor.findKey(editor, element);
- let children = (React.createElement(Children, { decorate: decorate, decorations: decorations, node: element, renderElement: renderElement, renderLeaf: renderLeaf, selection: selection }));
- // Attributes that the developer must mix into the element in their
- // custom node renderer component.
- const attributes = {
- 'data-slate-node': 'element',
- ref,
- elementIndex,
- };
- 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 && Editor.hasInlines(editor, element)) {
- const text = Node$1.string(element);
- const dir = getDirection(text);
- if (dir === 'rtl') {
- attributes.dir = dir;
- }
- }
- // If it's a void node, wrap the children in extra void-specific elements.
- if (Editor.isVoid(editor, element)) {
- attributes['data-slate-void'] = true;
- if (!readOnly && isInline) {
- attributes.contentEditable = false;
- }
- const Tag = isInline ? 'span' : 'div';
- const [[text]] = Node$1.texts(element);
- children = readOnly ? null : (React.createElement(Tag, { "data-slate-spacer": true, style: {
- height: '0',
- color: 'transparent',
- outline: 'none',
- position: 'absolute',
- } },
- React.createElement(MemoizedText, { decorations: [], isLast: false, parent: element, text: text })));
- NODE_TO_INDEX.set(text, 0);
- NODE_TO_PARENT.set(text, element);
- }
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- KEY_TO_ELEMENT.set(key, ref.current);
- NODE_TO_ELEMENT.set(element, ref.current);
- ELEMENT_TO_NODE.set(ref.current, element);
- }
- else {
- KEY_TO_ELEMENT.delete(key);
- NODE_TO_ELEMENT.delete(element);
- }
- });
- return (React.createElement(SelectedContext.Provider, { value: !!selection }, renderElement({ attributes, children, element })));
-};
-const MemoizedElement = React.memo(Element, (prev, next) => {
- return (prev.decorate === next.decorate &&
- prev.element === next.element &&
- prev.renderElement === next.renderElement &&
- prev.renderLeaf === next.renderLeaf &&
- isRangeListEqual(prev.decorations, next.decorations) &&
- (prev.selection === next.selection ||
- (!!prev.selection &&
- !!next.selection &&
- Range.equals(prev.selection, next.selection))));
-});
-/**
- * The default element renderer.
- */
-const DefaultElement = (props) => {
- const { attributes, children, element } = props;
- const editor = useEditor();
- const Tag = editor.isInline(element) ? 'span' : 'div';
- return (React.createElement(Tag, Object.assign({}, attributes, { style: { position: 'relative' } }), children));
-};
-/**
- * Check if a list of ranges is equal to another.
- *
- * PERF: this requires the two lists to also have the ranges inside them in the
- * same order, but this is an okay constraint for us since decorations are
- * kept in order, and the odd case where they aren't is okay to re-render for.
- */
-const isRangeListEqual = (list, another) => {
- if (list.length !== another.length) {
- return false;
- }
- for (let i = 0; i < list.length; i++) {
- const range = list[i];
- const other = another[i];
- if (!Range.equals(range, other)) {
- return false;
- }
- }
- return true;
-};
-
-/**
- * A React context for sharing the editor object.
- */
-const EditorContext = createContext(null);
-/**
- * Get the current editor object from the React context.
- */
-const useEditor = () => {
- const editor = useContext(EditorContext);
- if (!editor) {
- throw new Error(`The \`useEditor\` hook must be used inside the component's context.`);
- }
- return editor;
-};
-
-/**
- * Children.
- */
-const Children = (props) => {
- const { decorate, decorations, node, renderElement, renderLeaf, selection, ReactHappyWindow, reactHappyWindowProps = {}, } = props;
- const editor = useEditor();
- const path = ReactEditor.findPath(editor, node);
- const children = [];
- const isLeafBlock = Element$1.isElement(node) &&
- !editor.isInline(node) &&
- Editor.hasInlines(editor, node);
- const renderChild = (i) => {
- const p = path.concat(i);
- const n = node.children[i];
- const key = ReactEditor.findKey(editor, n);
- const range = Editor.range(editor, p);
- const sel = selection && Range.intersection(range, selection);
- // Commented out to improve performance. We don't use decorations
- // const ds = decorate([n, p])
- const ds = [];
- // for (const dec of decorations) {
- // const d = Range.intersection(dec, range)
- // if (d) {
- // ds.push(d)
- // }
- // }
- NODE_TO_INDEX.set(n, i);
- NODE_TO_PARENT.set(n, node);
- if (Element$1.isElement(n)) {
- return (React.createElement(MemoizedElement, { decorate: decorate, decorations: ds, element: n, key: key.id, renderElement: renderElement, renderLeaf: renderLeaf, selection: sel, elementIndex: i }));
- }
- else {
- return (React.createElement(MemoizedText, { decorations: ds, key: key.id, isLast: isLeafBlock && i === node.children.length - 1, parent: node, renderLeaf: renderLeaf, text: n }));
- }
- };
- if (ReactHappyWindow) {
- return (React.createElement(ReactHappyWindow, Object.assign({ itemCount: node.children.length, renderItem: renderChild }, reactHappyWindowProps)));
- }
- for (let i = 0; i < node.children.length; i++) {
- children.push(renderChild(i));
- }
- return React.createElement(React.Fragment, null, children);
-};
-
-var IS_IOS = typeof navigator !== 'undefined' && typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-var IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
-var IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
-var IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
-
-/**
- * Hotkey mappings for each platform.
- */
-
-var HOTKEYS = {
- bold: 'mod+b',
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
- moveBackward: 'left',
- moveForward: 'right',
- moveWordBackward: 'ctrl+left',
- moveWordForward: 'ctrl+right',
- deleteBackward: 'shift?+backspace',
- deleteForward: 'shift?+delete',
- extendBackward: 'shift+left',
- extendForward: 'shift+right',
- italic: 'mod+i',
- splitBlock: 'shift?+enter',
- undo: 'mod+z'
-};
-var APPLE_HOTKEYS = {
- moveLineBackward: 'opt+up',
- moveLineForward: 'opt+down',
- moveWordBackward: 'opt+left',
- moveWordForward: 'opt+right',
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
- deleteForward: ['ctrl+delete', 'ctrl+d'],
- deleteLineBackward: 'cmd+shift?+backspace',
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
- deleteWordBackward: 'opt+shift?+backspace',
- deleteWordForward: 'opt+shift?+delete',
- extendLineBackward: 'opt+shift+up',
- extendLineForward: 'opt+shift+down',
- redo: 'cmd+shift+z',
- transposeCharacter: 'ctrl+t'
-};
-var WINDOWS_HOTKEYS = {
- deleteWordBackward: 'ctrl+shift?+backspace',
- deleteWordForward: 'ctrl+shift?+delete',
- redo: ['ctrl+y', 'ctrl+shift+z']
-};
-/**
- * Create a platform-aware hotkey checker.
- */
-
-var create = key => {
- var generic = HOTKEYS[key];
- var apple = APPLE_HOTKEYS[key];
- var windows = WINDOWS_HOTKEYS[key];
- var isGeneric = generic && isKeyHotkey(generic);
- var isApple = apple && isKeyHotkey(apple);
- var isWindows = windows && isKeyHotkey(windows);
- return event => {
- if (isGeneric && isGeneric(event)) return true;
- if (IS_APPLE && isApple && isApple(event)) return true;
- if (!IS_APPLE && isWindows && isWindows(event)) return true;
- return false;
- };
-};
-/**
- * Hotkeys.
- */
-
-
-var Hotkeys = {
- isBold: create('bold'),
- isCompose: create('compose'),
- isMoveBackward: create('moveBackward'),
- isMoveForward: create('moveForward'),
- isDeleteBackward: create('deleteBackward'),
- isDeleteForward: create('deleteForward'),
- isDeleteLineBackward: create('deleteLineBackward'),
- isDeleteLineForward: create('deleteLineForward'),
- isDeleteWordBackward: create('deleteWordBackward'),
- isDeleteWordForward: create('deleteWordForward'),
- isExtendBackward: create('extendBackward'),
- isExtendForward: create('extendForward'),
- isExtendLineBackward: create('extendLineBackward'),
- isExtendLineForward: create('extendLineForward'),
- isItalic: create('italic'),
- isMoveLineBackward: create('moveLineBackward'),
- isMoveLineForward: create('moveLineForward'),
- isMoveWordBackward: create('moveWordBackward'),
- isMoveWordForward: create('moveWordForward'),
- isRedo: create('redo'),
- isSplitBlock: create('splitBlock'),
- isTransposeCharacter: create('transposeCharacter'),
- isUndo: create('undo')
-};
-
-/**
- * A React context for sharing the `readOnly` state of the editor.
- */
-
-var ReadOnlyContext = createContext(false);
-/**
- * Get the current `readOnly` state of the editor.
- */
-
-var useReadOnly = () => {
- return useContext(ReadOnlyContext);
-};
-
-/**
- * A React context for sharing the editor object, in a way that re-renders the
- * context whenever changes occur.
- */
-const SlateContext = createContext(null);
-/**
- * Get the current editor object from the React context.
- */
-const useSlate = () => {
- const context = useContext(SlateContext);
- if (!context) {
- throw new Error(`The \`useSlate\` hook must be used inside the component's context.`);
- }
- const [editor] = context;
- return editor;
-};
-
-/**
- * Types.
- */
-/**
- * Check if a DOM node is a comment node.
- */
-
-var isDOMComment = value => {
- return isDOMNode(value) && value.nodeType === 8;
-};
-/**
- * Check if a DOM node is an element node.
- */
-
-var isDOMElement = value => {
- return isDOMNode(value) && value.nodeType === 1;
-};
-/**
- * Check if a value is a DOM node.
- */
-
-var isDOMNode = value => {
- return value instanceof Node;
-};
-/**
- * Check if a DOM node is an element node.
- */
-
-var isDOMText = value => {
- return isDOMNode(value) && value.nodeType === 3;
-};
-/**
- * Normalize a DOM point so that it always refers to a text node.
- */
-
-var normalizeDOMPoint = domPoint => {
- var [node, offset] = domPoint; // If it's an element node, its offset refers to the index of its children
- // including comment nodes, so try to find the right text child node.
-
- if (isDOMElement(node) && node.childNodes.length) {
- var isLast = offset === node.childNodes.length;
- var direction = isLast ? 'backward' : 'forward';
- var index = isLast ? offset - 1 : offset;
- node = getEditableChild(node, index, direction); // If the node has children, traverse until we have a leaf node. Leaf nodes
- // can be either text nodes, or other void DOM nodes.
-
- while (isDOMElement(node) && node.childNodes.length) {
- var i = isLast ? node.childNodes.length - 1 : 0;
- node = getEditableChild(node, i, direction);
- } // Determine the new offset inside the text node.
-
-
- offset = isLast && node.textContent != null ? node.textContent.length : 0;
- } // Return the node and offset.
-
-
- return [node, offset];
-};
-/**
- * Get the nearest editable child at `index` in a `parent`, preferring
- * `direction`.
- */
-
-var getEditableChild = (parent, index, direction) => {
- var {
- childNodes
- } = parent;
- var child = childNodes[index];
- var i = index;
- var triedForward = false;
- var triedBackward = false; // While the child is a comment node, or an element node with no children,
- // keep iterating to find a sibling non-void, non-comment node.
-
- while (isDOMComment(child) || isDOMElement(child) && child.childNodes.length === 0 || isDOMElement(child) && child.getAttribute('contenteditable') === 'false') {
- if (triedForward && triedBackward) {
- break;
- }
-
- if (i >= childNodes.length) {
- triedForward = true;
- i = index - 1;
- direction = 'backward';
- continue;
- }
-
- if (i < 0) {
- triedBackward = true;
- i = index + 1;
- direction = 'forward';
- continue;
- }
-
- child = childNodes[i];
- i += direction === 'forward' ? 1 : -1;
- }
-
- return child;
-};
-
-/**
- * Editable.
- */
-const Editable = (props) => {
- const { autoFocus, decorate = defaultDecorate, onDOMBeforeInput: propsOnDOMBeforeInput, placeholder, readOnly = false, renderElement, renderLeaf, style = {}, as: Component = 'div', ReactHappyWindow, reactHappyWindowProps, happyWindowRef, ...attributes } = props;
- const editor = useSlate();
- const ref = useRef(null);
- // Update internal state on each render.
- IS_READ_ONLY.set(editor, readOnly);
- // Keep track of some state for the event handler logic.
- const state = useMemo(() => ({
- isComposing: false,
- isUpdatingSelection: false,
- latestElement: null,
- }), []);
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- EDITOR_TO_ELEMENT.set(editor, ref.current);
- NODE_TO_ELEMENT.set(editor, ref.current);
- ELEMENT_TO_NODE.set(ref.current, editor);
- }
- else {
- NODE_TO_ELEMENT.delete(editor);
- }
- });
- // 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
- useIsomorphicLayoutEffect(() => {
- window.document.addEventListener('selectionchange', onDOMSelectionChange);
- return () => {
- window.document.removeEventListener('selectionchange', onDOMSelectionChange);
- };
- }, []);
- // 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
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- // @ts-ignore The `beforeinput` event isn't recognized.
- ref.current.addEventListener('beforeinput', onDOMBeforeInput);
- }
- return () => {
- if (ref.current) {
- // @ts-ignore The `beforeinput` event isn't recognized.
- ref.current.removeEventListener('beforeinput', onDOMBeforeInput);
- }
- };
- }, []);
- // Whenever the editor updates, make sure the DOM selection state is in sync.
- useIsomorphicLayoutEffect(() => {
- const { selection } = editor;
- const domSelection = window.getSelection();
- if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {
- return;
- }
- const hasDomSelection = domSelection.type !== 'None';
- // If the DOM selection is properly unset, we're done.
- if (!selection && !hasDomSelection) {
- return;
- }
- const newDomRange = selection && ReactEditor.toDOMRange(editor, selection);
- // If the DOM selection is already correct, we're done.
- if (hasDomSelection &&
- newDomRange &&
- isRangeEqual(domSelection.getRangeAt(0), newDomRange)) {
- return;
- }
- // Otherwise the DOM selection is out of sync, so update it.
- const el = ReactEditor.toDOMNode(editor, editor);
- state.isUpdatingSelection = true;
- domSelection.removeAllRanges();
- if (newDomRange) {
- domSelection.addRange(newDomRange);
- const leafEl = newDomRange.startContainer.parentElement;
- scrollIntoView(leafEl, { scrollMode: 'if-needed' });
- }
- setTimeout(() => {
- // COMPAT: In Firefox, it's not enough to create a range, you also need
- // to focus the contenteditable element too. (2016/11/16)
- if (newDomRange && IS_FIREFOX) {
- el.focus();
- }
- state.isUpdatingSelection = false;
- });
- });
- // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
- // needs to be manually focused.
- useEffect(() => {
- if (ref.current && autoFocus) {
- ref.current.focus();
- }
- }, [autoFocus]);
- // 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
- const onDOMBeforeInput = useCallback((event) => {
- if (!readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isDOMEventHandled(event, propsOnDOMBeforeInput)) {
- const { selection } = editor;
- const { inputType: type } = event;
- const data = event.dataTransfer || event.data || undefined;
- // These two types occur while a user is composing text and can't be
- // cancelled. Let them through and wait for the composition to end.
- if (type === 'insertCompositionText' ||
- type === 'deleteCompositionText') {
- return;
- }
- event.preventDefault();
- // 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 (!type.startsWith('delete') || type.startsWith('deleteBy')) {
- const [targetRange] = event.getTargetRanges();
- if (targetRange) {
- const range = ReactEditor.toSlateRange(editor, targetRange);
- if (!range) {
- return;
- }
- if (!selection || !Range.equals(selection, range)) {
- Transforms.select(editor, range);
- }
- }
- }
- // COMPAT: If the selection is expanded, even if the command seems like
- // a delete forward/backward command it should delete the selection.
- if (selection &&
- Range.isExpanded(selection) &&
- type.startsWith('delete')) {
- Editor.deleteFragment(editor);
- return;
- }
- switch (type) {
- case 'deleteByComposition':
- case 'deleteByCut':
- case 'deleteByDrag': {
- Editor.deleteFragment(editor);
- break;
- }
- case 'deleteContent':
- case 'deleteContentForward': {
- Editor.deleteForward(editor);
- break;
- }
- case 'deleteContentBackward': {
- Editor.deleteBackward(editor);
- break;
- }
- case 'deleteEntireSoftLine': {
- Editor.deleteBackward(editor, { unit: 'line' });
- Editor.deleteForward(editor, { unit: 'line' });
- break;
- }
- case 'deleteHardLineBackward': {
- Editor.deleteBackward(editor, { unit: 'block' });
- break;
- }
- case 'deleteSoftLineBackward': {
- Editor.deleteBackward(editor, { unit: 'line' });
- break;
- }
- case 'deleteHardLineForward': {
- Editor.deleteForward(editor, { unit: 'block' });
- break;
- }
- case 'deleteSoftLineForward': {
- Editor.deleteForward(editor, { unit: 'line' });
- break;
- }
- case 'deleteWordBackward': {
- Editor.deleteBackward(editor, { unit: 'word' });
- break;
- }
- case 'deleteWordForward': {
- Editor.deleteForward(editor, { unit: 'word' });
- break;
- }
- case 'insertLineBreak':
- case 'insertParagraph': {
- Editor.insertBreak(editor);
- break;
- }
- case 'insertFromComposition':
- case 'insertFromDrop':
- case 'insertFromPaste':
- case 'insertFromYank':
- case 'insertReplacementText':
- case 'insertText': {
- if (data instanceof DataTransfer) {
- ReactEditor.insertData(editor, data);
- }
- else if (typeof data === 'string') {
- Editor.insertText(editor, data);
- }
- break;
- }
- }
- }
- }, []);
- // 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.
- const onDOMSelectionChange = useCallback(debounce(() => {
- if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {
- const { activeElement } = window.document;
- const el = ReactEditor.toDOMNode(editor, editor);
- const domSelection = window.getSelection();
- const domRange = domSelection &&
- domSelection.rangeCount > 0 &&
- domSelection.getRangeAt(0);
- if (activeElement === el) {
- state.latestElement = activeElement;
- IS_FOCUSED.set(editor, true);
- }
- else {
- IS_FOCUSED.delete(editor);
- }
- if (domRange &&
- hasEditableTarget(editor, domRange.startContainer) &&
- hasEditableTarget(editor, domRange.endContainer)) {
- const range = ReactEditor.toSlateRange(editor, domRange);
- Transforms.select(editor, range);
- }
- else {
- Transforms.deselect(editor);
- }
- }
- }, 100), []);
- const decorations = decorate([editor, []]);
- if (placeholder &&
- editor.children.length === 1 &&
- Array.from(Node$1.texts(editor)).length === 1 &&
- Node$1.string(editor) === '') {
- const start = Editor.start(editor, []);
- decorations.push({
- [PLACEHOLDER_SYMBOL]: true,
- placeholder,
- anchor: start,
- focus: start,
- });
- }
- return (React.createElement(ReadOnlyContext.Provider, { value: readOnly },
- React.createElement(Component
- // COMPAT: The Grammarly Chrome extension works by changing the DOM
- // out from under `contenteditable` elements, which leads to weird
- // behaviors so we have to disable it like editor. (2017/04/24)
- , Object.assign({ "data-gramm": false, role: readOnly ? undefined : 'textbox' }, attributes, {
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we'd
- // have to use hacks to make these replacement-based features work.
- spellCheck: IS_FIREFOX ? undefined : attributes.spellCheck, autoCorrect: IS_FIREFOX ? undefined : attributes.autoCorrect, autoCapitalize: IS_FIREFOX ? undefined : attributes.autoCapitalize, "data-slate-editor": true, "data-slate-node": "value", contentEditable: readOnly ? undefined : true, suppressContentEditableWarning: true, ref: ref, style: {
- // Prevent the default outline styles.
- outline: 'none',
- // Preserve adjacent whitespace and new lines.
- whiteSpace: 'pre-wrap',
- // Allow words to break if they are too long.
- wordWrap: 'break-word',
- // Allow for passed-in styles to override anything.
- ...style,
- }, onBeforeInput: useCallback((event) => {
- // COMPAT: Firefox doesn'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 (IS_FIREFOX && !readOnly && ReactEditor.isFocused(editor)) {
- event.preventDefault();
- const text = event.data;
- Editor.insertText(editor, text);
- }
- }, [readOnly]), onBlur: useCallback((event) => {
- if (readOnly ||
- state.isUpdatingSelection ||
- !hasEditableTarget(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.
- if (state.latestElement === window.document.activeElement) {
- return;
- }
- const { relatedTarget } = event;
- const 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
- // 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 (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 &&
- isDOMNode(relatedTarget) &&
- ReactEditor.hasDOMNode(editor, relatedTarget)) {
- const node = ReactEditor.toSlateNode(editor, relatedTarget);
- if (Element$1.isElement(node) && !editor.isVoid(node)) {
- return;
- }
- }
- IS_FOCUSED.delete(editor);
- }, [readOnly, attributes.onBlur]), onClick: useCallback((event) => {
- if (!readOnly &&
- hasTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onClick) &&
- isDOMNode(event.target)) {
- const node = ReactEditor.toSlateNode(editor, event.target);
- const path = ReactEditor.findPath(editor, node);
- const start = Editor.start(editor, path);
- if (Editor.void(editor, { at: start })) {
- const range = Editor.range(editor, start);
- Transforms.select(editor, range);
- }
- }
- }, [readOnly, attributes.onClick]), onCompositionEnd: useCallback((event) => {
- if (hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCompositionEnd)) {
- state.isComposing = false;
- // 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 (!IS_SAFARI && !IS_FIREFOX && event.data) {
- Editor.insertText(editor, event.data);
- }
- }
- }, [attributes.onCompositionEnd]), onCompositionStart: useCallback((event) => {
- if (hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCompositionStart)) {
- state.isComposing = true;
- }
- }, [attributes.onCompositionStart]), onCopy: useCallback((event) => {
- if (hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCopy)) {
- event.preventDefault();
- setFragmentData(event.clipboardData, editor);
- }
- }, [attributes.onCopy]), onCut: useCallback((event) => {
- if (!readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCut)) {
- event.preventDefault();
- setFragmentData(event.clipboardData, editor);
- const { selection } = editor;
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- }
- }, [readOnly, attributes.onCut]), onDragOver: useCallback((event) => {
- if (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.
- const node = ReactEditor.toSlateNode(editor, event.target);
- if (Editor.isVoid(editor, node)) {
- event.preventDefault();
- }
- }
- }, [attributes.onDragOver]), onDragStart: useCallback((event) => {
- if (hasTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onDragStart)) {
- const node = ReactEditor.toSlateNode(editor, event.target);
- const path = ReactEditor.findPath(editor, node);
- const voidMatch = Editor.void(editor, { at: path });
- // 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) {
- const range = Editor.range(editor, path);
- Transforms.select(editor, range);
- }
- setFragmentData(event.dataTransfer, editor);
- }
- }, [attributes.onDragStart]), onDrop: useCallback((event) => {
- if (hasTarget(editor, event.target) &&
- !readOnly &&
- !isEventHandled(event, attributes.onDrop)) {
- // COMPAT: Firefox doesn't fire `beforeinput` events at all, and
- // Chromium browsers don't properly fire them for files being
- // dropped into a `contenteditable`. (2019/11/26)
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
- if (IS_FIREFOX ||
- (!IS_SAFARI && event.dataTransfer.files.length > 0)) {
- event.preventDefault();
- const range = ReactEditor.findEventRange(editor, event);
- const data = event.dataTransfer;
- Transforms.select(editor, range);
- ReactEditor.insertData(editor, data);
- }
- }
- }, [readOnly, attributes.onDrop]), onFocus: useCallback((event) => {
- if (!readOnly &&
- !state.isUpdatingSelection &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onFocus)) {
- const el = ReactEditor.toDOMNode(editor, editor);
- state.latestElement = window.document.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 (IS_FIREFOX && event.target !== el) {
- el.focus();
- return;
- }
- IS_FOCUSED.set(editor, true);
- }
- }, [readOnly, attributes.onFocus]), onKeyDown: useCallback((event) => {
- if (!readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onKeyDown)) {
- const { nativeEvent } = event;
- const { selection } = editor;
- const element = editor.children[selection !== null ? selection.focus.path[0] : 0];
- const isRTL = getDirection(Node$1.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 (Hotkeys.isRedo(nativeEvent)) {
- event.preventDefault();
- if (editor.redo) {
- editor.redo();
- }
- return;
- }
- if (Hotkeys.isUndo(nativeEvent)) {
- event.preventDefault();
- if (editor.undo) {
- editor.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 (Hotkeys.isMoveLineBackward(nativeEvent)) {
- event.preventDefault();
- Transforms.move(editor, { unit: 'line', reverse: true });
- return;
- }
- if (Hotkeys.isMoveLineForward(nativeEvent)) {
- event.preventDefault();
- Transforms.move(editor, { unit: 'line' });
- return;
- }
- if (Hotkeys.isExtendLineBackward(nativeEvent)) {
- event.preventDefault();
- Transforms.move(editor, {
- unit: 'line',
- edge: 'focus',
- reverse: true,
- });
- return;
- }
- if (Hotkeys.isExtendLineForward(nativeEvent)) {
- event.preventDefault();
- 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 (Hotkeys.isMoveBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isCollapsed(selection)) {
- const { anchor } = selection;
- if (anchor.offset === 1 && anchor.path[1] > 0) {
- // Hack to position the cursor at the end of the previous text node
- Transforms.move(editor, { reverse: !isRTL, distance: 2 });
- Transforms.move(editor, { reverse: isRTL });
- }
- else {
- Transforms.move(editor, { reverse: !isRTL });
- }
- }
- else {
- Transforms.collapse(editor, { edge: 'start' });
- }
- return;
- }
- if (Hotkeys.isMoveForward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isCollapsed(selection)) {
- Transforms.move(editor, { reverse: isRTL });
- }
- else {
- Transforms.collapse(editor, { edge: 'end' });
- }
- return;
- }
- if (Hotkeys.isMoveWordBackward(nativeEvent)) {
- event.preventDefault();
- Transforms.move(editor, { unit: 'word', reverse: !isRTL });
- return;
- }
- if (Hotkeys.isMoveWordForward(nativeEvent)) {
- event.preventDefault();
- Transforms.move(editor, { unit: 'word', reverse: isRTL });
- return;
- }
- // COMPAT: Firefox doesn'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 (IS_FIREFOX) {
- // 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 (Hotkeys.isBold(nativeEvent) ||
- Hotkeys.isItalic(nativeEvent) ||
- Hotkeys.isTransposeCharacter(nativeEvent)) {
- event.preventDefault();
- return;
- }
- if (Hotkeys.isSplitBlock(nativeEvent)) {
- event.preventDefault();
- Editor.insertBreak(editor);
- return;
- }
- if (Hotkeys.isDeleteBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- else {
- Editor.deleteBackward(editor);
- }
- return;
- }
- if (Hotkeys.isDeleteForward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- else {
- Editor.deleteForward(editor);
- }
- return;
- }
- if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- else {
- Editor.deleteBackward(editor, { unit: 'line' });
- }
- return;
- }
- if (Hotkeys.isDeleteLineForward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- else {
- Editor.deleteForward(editor, { unit: 'line' });
- }
- return;
- }
- if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- else {
- Editor.deleteBackward(editor, { unit: 'word' });
- }
- return;
- }
- if (Hotkeys.isDeleteWordForward(nativeEvent)) {
- event.preventDefault();
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor);
- }
- else {
- Editor.deleteForward(editor, { unit: 'word' });
- }
- return;
- }
- }
- }
- }, [readOnly, attributes.onKeyDown]), onPaste: useCallback((event) => {
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we
- // fall back to React's `onPaste` here instead.
- if (IS_FIREFOX &&
- !readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onPaste)) {
- event.preventDefault();
- ReactEditor.insertData(editor, event.clipboardData);
- }
- }, [readOnly, attributes.onPaste]) }),
- React.createElement(Children, { decorate: decorate, decorations: decorations, node: editor, renderElement: renderElement, renderLeaf: renderLeaf, selection: editor.selection, ReactHappyWindow: ReactHappyWindow, reactHappyWindowProps: reactHappyWindowProps }))));
-};
-/**
- * A default memoized decorate function.
- */
-const defaultDecorate = () => [];
-/**
- * Check if two DOM range objects are equal.
- */
-const isRangeEqual = (a, b) => {
- return ((a.startContainer === b.startContainer &&
- a.startOffset === b.startOffset &&
- a.endContainer === b.endContainer &&
- a.endOffset === b.endOffset) ||
- (a.startContainer === b.endContainer &&
- a.startOffset === b.endOffset &&
- a.endContainer === b.startContainer &&
- a.endOffset === b.startOffset));
-};
-/**
- * Check if the target is in the editor.
- */
-const hasTarget = (editor, target) => {
- return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target);
-};
-/**
- * Check if the target is editable and in the editor.
- */
-const hasEditableTarget = (editor, target) => {
- return (isDOMNode(target) &&
- ReactEditor.hasDOMNode(editor, target, { editable: true }));
-};
-/**
- * Check if an event is overrided by a handler.
- */
-const isEventHandled = (event, handler) => {
- if (!handler) {
- return false;
- }
- handler(event);
- return event.isDefaultPrevented() || event.isPropagationStopped();
-};
-/**
- * Check if a DOM event is overrided by a handler.
- */
-const isDOMEventHandled = (event, handler) => {
- if (!handler) {
- return false;
- }
- handler(event);
- return event.defaultPrevented;
-};
-/**
- * Set the currently selected fragment to the clipboard.
- */
-const setFragmentData = (dataTransfer, editor) => {
- const { selection } = editor;
- if (!selection) {
- return;
- }
- const [start, end] = Range.edges(selection);
- const startVoid = Editor.void(editor, { at: start.path });
- const endVoid = Editor.void(editor, { at: end.path });
- if (Range.isCollapsed(selection) && !startVoid) {
- return;
- }
- // Create a fake selection so that we can add a Base64-encoded copy of the
- // fragment to the HTML, to decode on future pastes.
- const domRange = ReactEditor.toDOMRange(editor, selection);
- let contents = domRange.cloneContents();
- let attach = contents.childNodes[0];
- // Make sure attach is non-empty, since empty nodes will not get copied.
- contents.childNodes.forEach(node => {
- if (node.textContent && node.textContent.trim() !== '') {
- attach = node;
- }
- });
- // COMPAT: If the end node is a void node, we need to move the end of the
- // range from the void node's spacer span, to the end of the void node's
- // content, since the spacer is before void's content in the DOM.
- if (endVoid) {
- const [voidNode] = endVoid;
- const r = domRange.cloneRange();
- const domNode = ReactEditor.toDOMNode(editor, voidNode);
- r.setEndAfter(domNode);
- contents = r.cloneContents();
- }
- // COMPAT: If the start node is a void node, we need to attach the encoded
- // fragment to the void node's content node instead of the spacer, because
- // attaching it to empty `
/` nodes will end up having it erased by
- // most browsers. (2018/04/27)
- if (startVoid) {
- attach = contents.querySelector('[data-slate-spacer]');
- }
- // Remove any zero-width space spans from the cloned DOM so that they don't
- // show up elsewhere when pasted.
- Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
- const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
- zw.textContent = isNewline ? '\n' : '';
- });
- // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
- // in the HTML, and can be used for intra-Slate pasting. If it's a text
- // node, wrap it in a `` so we have something to set an attribute on.
- if (isDOMText(attach)) {
- const span = document.createElement('span');
- // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
- // then leading and trailing spaces will be ignored. (2017/09/21)
- span.style.whiteSpace = 'pre';
- span.appendChild(attach);
- contents.appendChild(span);
- attach = span;
- }
- const fragment = Node$1.fragment(editor, selection);
- const string = JSON.stringify(fragment);
- const encoded = window.btoa(encodeURIComponent(string));
- attach.setAttribute('data-slate-fragment', encoded);
- // Overwriting the default functionality
- const { getFormattedSelection, getHTMLFormattedSelection } = editor;
- if (typeof getFormattedSelection === 'function' &&
- typeof getHTMLFormattedSelection === 'function') {
- try {
- const plainText = getFormattedSelection();
- const htmlText = getHTMLFormattedSelection();
- dataTransfer.setData('text/plain', plainText);
- dataTransfer.setData('text/html', htmlText);
- return;
- }
- catch (e) {
- // eslint-disable-next-line no-console
- console.log('Error in slate-react/src/components/editable.tsx: ', e);
- // Only setData application/x-slate-fragment as a fallback because
- // we don't want to copy the timestamps of words
- dataTransfer.setData('application/x-slate-fragment', encoded);
- }
- }
- // Add the content to a
so that we can get its inner HTML.
- const div = document.createElement('div');
- div.appendChild(contents);
- dataTransfer.setData('text/html', div.innerHTML);
- dataTransfer.setData('text/plain', getPlainText(div));
-};
-/**
- * Get a plaintext representation of the content of a node, accounting for block
- * elements which get a newline appended.
- */
-const getPlainText = (domNode) => {
- let text = '';
- if (isDOMText(domNode) && domNode.nodeValue) {
- return domNode.nodeValue;
- }
- if (isDOMElement(domNode)) {
- for (const childNode of Array.from(domNode.childNodes)) {
- text += getPlainText(childNode);
- }
- const display = getComputedStyle(domNode).getPropertyValue('display');
- if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
- text += '\n';
- }
- }
- return text;
-};
-
-/**
- * An auto-incrementing identifier for keys.
- */
-var n = 0;
-/**
- * A class that keeps track of a key string. We use a full class here because we
- * want to be able to use them as keys in `WeakMap` objects.
- */
-
-class Key {
- constructor() {
- this.id = "".concat(n++);
- }
-
-}
-
-var ReactEditor = {
- /**
- * Find a key for a Slate node.
- */
- findKey(editor, node) {
- var key = NODE_TO_KEY.get(node);
-
- if (!key) {
- key = new Key();
- NODE_TO_KEY.set(node, key);
- }
-
- return key;
- },
-
- /**
- * Find the path of Slate node.
- */
- findPath(editor, node) {
- var path = [];
- var child = node;
-
- while (true) {
- var parent = NODE_TO_PARENT.get(child);
-
- if (parent == null) {
- if (Editor.isEditor(child)) {
- return path;
- } else {
- break;
- }
- }
-
- var i = NODE_TO_INDEX.get(child);
-
- if (i == null) {
- break;
- }
-
- path.unshift(i);
- child = parent;
- }
-
- throw new Error("Unable to find the path for Slate node: ".concat(JSON.stringify(node)));
- },
-
- /**
- * Check if the editor is focused.
- */
- isFocused(editor) {
- return !!IS_FOCUSED.get(editor);
- },
-
- /**
- * Check if the editor is in read-only mode.
- */
- isReadOnly(editor) {
- return !!IS_READ_ONLY.get(editor);
- },
-
- /**
- * Blur the editor.
- */
- blur(editor) {
- var el = ReactEditor.toDOMNode(editor, editor);
- IS_FOCUSED.set(editor, false);
-
- if (window.document.activeElement === el) {
- el.blur();
- }
- },
-
- /**
- * Focus the editor.
- */
- focus(editor) {
- var el = ReactEditor.toDOMNode(editor, editor);
- IS_FOCUSED.set(editor, true);
-
- if (window.document.activeElement !== el) {
- el.focus({
- preventScroll: true
- });
- }
- },
-
- /**
- * Deselect the editor.
- */
- deselect(editor) {
- var {
- selection
- } = editor;
- var domSelection = window.getSelection();
-
- if (domSelection && domSelection.rangeCount > 0) {
- domSelection.removeAllRanges();
- }
-
- if (selection) {
- Transforms.deselect(editor);
- }
- },
-
- /**
- * Check if a DOM node is within the editor.
- */
- hasDOMNode(editor, target) {
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
- var {
- editable = false
- } = options;
- var el = ReactEditor.toDOMNode(editor, editor);
- var element; // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
- // target is originating from an internal "restricted" element (e.g. a
- // stepper arrow on a number input). (2018/05/04)
- // https://github.com/ianstormtaylor/slate/issues/1819
-
- try {
- element = isDOMElement(target) ? target : target.parentElement;
- } catch (err) {
- if (!err.message.includes('Permission denied to access property "nodeType"')) {
- throw err;
- }
- }
-
- if (!element) {
- return false;
- }
-
- return element.closest("[data-slate-editor]") === el && (!editable || el.isContentEditable);
- },
-
- /**
- * Insert data from a `DataTransfer` into the editor.
- */
- insertData(editor, data) {
- editor.insertData(data);
- },
-
- /**
- * Find the native DOM element from a Slate node.
- */
- toDOMNode(editor, node) {
- var domNode = Editor.isEditor(node) ? EDITOR_TO_ELEMENT.get(editor) : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node));
-
- if (!domNode) {
- throw new Error("Cannot resolve a DOM node from Slate node: ".concat(JSON.stringify(node)));
- }
-
- return domNode;
- },
-
- /**
- * Find a native DOM selection point from a Slate point.
- */
- toDOMPoint(editor, point) {
- var [node] = Editor.node(editor, point.path);
- var el = ReactEditor.toDOMNode(editor, node);
- var domPoint; // If we're inside a void node, force the offset to 0, otherwise the zero
- // width spacing character will result in an incorrect offset of 1
-
- if (Editor.void(editor, {
- at: point
- })) {
- point = {
- path: point.path,
- offset: 0
- };
- } // For each leaf, we need to isolate its content, which means filtering
- // to its direct text and zero-width spans. (We have to filter out any
- // other siblings that may have been rendered alongside them.)
-
-
- var selector = "[data-slate-string], [data-slate-zero-width]";
- var texts = Array.from(el.querySelectorAll(selector));
- var start = 0;
-
- for (var text of texts) {
- var domNode = text.childNodes[0];
-
- if (domNode == null || domNode.textContent == null) {
- continue;
- }
-
- var {
- length
- } = domNode.textContent;
- var attr = text.getAttribute('data-slate-length');
- var trueLength = attr == null ? length : parseInt(attr, 10);
- var end = start + trueLength;
-
- if (point.offset <= end) {
- var offset = Math.min(length, Math.max(0, point.offset - start));
- domPoint = [domNode, offset];
- break;
- }
-
- start = end;
- }
-
- if (!domPoint) {
- throw new Error("Cannot resolve a DOM point from Slate point: ".concat(JSON.stringify(point)));
- }
-
- return domPoint;
- },
-
- /**
- * Find a native DOM range from a Slate `range`.
- */
- toDOMRange(editor, range) {
- var {
- anchor,
- focus
- } = range;
- var domAnchor = ReactEditor.toDOMPoint(editor, anchor);
- var domFocus = Range.isCollapsed(range) ? domAnchor : ReactEditor.toDOMPoint(editor, focus);
- var domRange = window.document.createRange();
- var start = Range.isBackward(range) ? domFocus : domAnchor;
- var end = Range.isBackward(range) ? domAnchor : domFocus;
- domRange.setStart(start[0], start[1]);
- domRange.setEnd(end[0], end[1]);
- return domRange;
- },
-
- /**
- * Find a Slate node from a native DOM `element`.
- */
- toSlateNode(editor, domNode) {
- var domEl = isDOMElement(domNode) ? domNode : domNode.parentElement;
-
- if (domEl && !domEl.hasAttribute('data-slate-node')) {
- domEl = domEl.closest("[data-slate-node]");
- }
-
- var node = domEl ? ELEMENT_TO_NODE.get(domEl) : null;
-
- if (!node) {
- throw new Error("Cannot resolve a Slate node from DOM node: ".concat(domEl));
- }
-
- return node;
- },
-
- /**
- * Get the target range from a DOM `event`.
- */
- findEventRange(editor, event) {
- if ('nativeEvent' in event) {
- event = event.nativeEvent;
- }
-
- var {
- clientX: x,
- clientY: y,
- target
- } = event;
-
- if (x == null || y == null) {
- throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
- }
-
- var node = ReactEditor.toSlateNode(editor, event.target);
- var path = ReactEditor.findPath(editor, node); // If the drop target is inside a void node, move it into either the
- // next or previous node, depending on which side the `x` and `y`
- // coordinates are closest to.
-
- if (Editor.isVoid(editor, node)) {
- var rect = target.getBoundingClientRect();
- var isPrev = editor.isInline(node) ? x - rect.left < rect.left + rect.width - x : y - rect.top < rect.top + rect.height - y;
- var edge = Editor.point(editor, path, {
- edge: isPrev ? 'start' : 'end'
- });
- var point = isPrev ? Editor.before(editor, edge) : Editor.after(editor, edge);
-
- if (point) {
- var _range = Editor.range(editor, point);
-
- return _range;
- }
- } // Else resolve a range from the caret position where the drop occured.
-
-
- var domRange;
- var {
- document
- } = window; // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
-
- if (document.caretRangeFromPoint) {
- domRange = document.caretRangeFromPoint(x, y);
- } else {
- var position = document.caretPositionFromPoint(x, y);
-
- if (position) {
- domRange = document.createRange();
- domRange.setStart(position.offsetNode, position.offset);
- domRange.setEnd(position.offsetNode, position.offset);
- }
- }
-
- if (!domRange) {
- throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
- } // Resolve a Slate range from the DOM range.
-
-
- var range = ReactEditor.toSlateRange(editor, domRange);
- return range;
- },
-
- /**
- * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
- */
- toSlatePoint(editor, domPoint) {
- var [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint);
- var parentNode = nearestNode.parentNode;
- var textNode = null;
- var offset = 0;
-
- if (parentNode) {
- var voidNode = parentNode.closest('[data-slate-void="true"]');
- var leafNode = parentNode.closest('[data-slate-leaf]');
- var domNode = null; // Calculate how far into the text node the `nearestNode` is, so that we
- // can determine what the offset relative to the text node is.
-
- if (leafNode) {
- textNode = leafNode.closest('[data-slate-node="text"]');
- var range = window.document.createRange();
- range.setStart(textNode, 0);
- range.setEnd(nearestNode, nearestOffset);
- var contents = range.cloneContents();
- var removals = [...contents.querySelectorAll('[data-slate-zero-width]'), ...contents.querySelectorAll('[contenteditable=false]')];
- removals.forEach(el => {
- el.parentNode.removeChild(el);
- }); // COMPAT: Edge has a bug where Range.prototype.toString() will
- // convert \n into \r\n. The bug causes a loop when slate-react
- // attempts to reposition its cursor to match the native position. Use
- // textContent.length instead.
- // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
-
- offset = contents.textContent.length;
- domNode = textNode;
- } else if (voidNode) {
- // For void nodes, the element with the offset key will be a cousin, not an
- // ancestor, so find it by going down from the nearest void parent.
- leafNode = voidNode.querySelector('[data-slate-leaf]');
- textNode = leafNode.closest('[data-slate-node="text"]');
- domNode = leafNode;
- offset = domNode.textContent.length;
- } // COMPAT: If the parent node is a Slate zero-width space, editor is
- // because the text node should have no characters. However, during IME
- // composition the ASCII characters will be prepended to the zero-width
- // space, so subtract 1 from the offset to account for the zero-width
- // space character.
-
-
- if (domNode && offset === domNode.textContent.length && parentNode.hasAttribute('data-slate-zero-width')) {
- offset--;
- }
- }
-
- if (!textNode) {
- throw new Error("Cannot resolve a Slate point from DOM point: ".concat(domPoint));
- } // COMPAT: If someone is clicking from one Slate editor into another,
- // the select event fires twice, once for the old editor's `element`
- // first, and then afterwards for the correct `element`. (2017/03/03)
-
-
- var slateNode = ReactEditor.toSlateNode(editor, textNode);
- var path = ReactEditor.findPath(editor, slateNode);
- return {
- path,
- offset
- };
- },
-
- /**
- * Find a Slate range from a DOM range or selection.
- */
- toSlateRange(editor, domRange) {
- var el = domRange instanceof Selection ? domRange.anchorNode : domRange.startContainer;
- var anchorNode;
- var anchorOffset;
- var focusNode;
- var focusOffset;
- var isCollapsed;
-
- if (el) {
- if (domRange instanceof Selection) {
- anchorNode = domRange.anchorNode;
- anchorOffset = domRange.anchorOffset;
- focusNode = domRange.focusNode;
- focusOffset = domRange.focusOffset;
- isCollapsed = domRange.isCollapsed;
- } else {
- anchorNode = domRange.startContainer;
- anchorOffset = domRange.startOffset;
- focusNode = domRange.endContainer;
- focusOffset = domRange.endOffset;
- isCollapsed = domRange.collapsed;
- }
- }
-
- if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
- throw new Error("Cannot resolve a Slate range from DOM range: ".concat(domRange));
- }
-
- var anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset]);
- var focus = isCollapsed ? anchor : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset]);
- return {
- anchor,
- focus
- };
- }
-
-};
-
-/**
- * A React context for sharing the `focused` state of the editor.
- */
-
-var FocusedContext = createContext(false);
-/**
- * Get the current `focused` state of the editor.
- */
-
-var useFocused = () => {
- return useContext(FocusedContext);
-};
-
-/**
- * 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.
- */
-const Slate = (props) => {
- const { editor, children, onChange, value, ...rest } = props;
- const [key, setKey] = useState(0);
- const context = useMemo(() => {
- editor.children = value;
- Object.assign(editor, rest);
- return [editor];
- }, [key, value, ...Object.values(rest)]);
- const onContextChange = useCallback(() => {
- onChange(editor.children);
- setKey(key + 1);
- }, [key, onChange]);
- EDITOR_TO_ON_CHANGE.set(editor, onContextChange);
- return (React.createElement(SlateContext.Provider, { value: context },
- React.createElement(EditorContext.Provider, { value: editor },
- React.createElement(FocusedContext.Provider, { value: ReactEditor.isFocused(editor) }, children))));
-};
-
-/**
- * `withReact` adds React and DOM specific behaviors to the editor.
- */
-
-var withReact = editor => {
- var e = editor;
- var {
- apply,
- onChange
- } = e;
-
- e.apply = op => {
- var matches = [];
-
- switch (op.type) {
- case 'insert_text':
- case 'remove_text':
- case 'set_node':
- {
- for (var [node, path] of Editor.levels(e, {
- at: op.path
- })) {
- var key = ReactEditor.findKey(e, node);
- matches.push([path, key]);
- }
-
- break;
- }
-
- case 'insert_node':
- case 'remove_node':
- case 'merge_node':
- case 'split_node':
- {
- for (var [_node, _path] of Editor.levels(e, {
- at: Path.parent(op.path)
- })) {
- var _key = ReactEditor.findKey(e, _node);
-
- matches.push([_path, _key]);
- }
-
- break;
- }
- }
-
- apply(op);
-
- for (var [_path2, _key2] of matches) {
- var [_node2] = Editor.node(e, _path2);
- NODE_TO_KEY.set(_node2, _key2);
- }
- };
-
- e.insertData = data => {
- var fragment = data.getData('application/x-slate-fragment');
-
- if (fragment) {
- var decoded = decodeURIComponent(window.atob(fragment));
- var parsed = JSON.parse(decoded);
- Transforms.insertFragment(e, parsed);
- return;
- }
-
- var text = data.getData('text/plain');
-
- if (text) {
- var lines = text.split('\n');
- var split = false;
-
- for (var line of lines) {
- if (split) {
- Transforms.splitNodes(e);
- }
-
- Transforms.insertText(e, line);
- split = true;
- }
- }
- };
-
- e.onChange = () => {
- // COMPAT: React 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
- ReactDOM.unstable_batchedUpdates(() => {
- var onContextChange = EDITOR_TO_ON_CHANGE.get(e);
-
- if (onContextChange) {
- onContextChange();
- }
-
- onChange();
- });
- };
-
- return e;
-};
-
-export { DefaultElement, DefaultLeaf, Editable, ReactEditor, Slate, useEditor, useFocused, useReadOnly, useSelected, useSlate, withReact };
-//# sourceMappingURL=index.es.js.map
diff --git a/dist/index.es.js.map b/dist/index.es.js.map
deleted file mode 100644
index ce5d572..0000000
--- a/dist/index.es.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"index.es.js","sources":["../src/components/string.tsx","../src/utils/weak-maps.ts","../src/components/leaf.tsx","../src/hooks/use-isomorphic-layout-effect.ts","../src/components/text.tsx","../src/hooks/use-selected.ts","../src/components/element.tsx","../src/hooks/use-editor.tsx","../src/components/children.tsx","../src/utils/environment.ts","../src/utils/hotkeys.ts","../src/hooks/use-read-only.ts","../src/hooks/use-slate.tsx","../src/utils/dom.ts","../src/components/editable.tsx","../src/utils/key.ts","../src/plugin/react-editor.ts","../src/hooks/use-focused.ts","../src/components/slate.tsx","../src/plugin/with-react.ts"],"sourcesContent":["import React from 'react'\nimport { Editor, Text, Path, Element, Node } from 'slate'\n\nimport { ReactEditor, useEditor } from '..'\n\n/**\n * Leaf content strings.\n */\n\nconst String = (props: {\n isLast: boolean\n leaf: Text\n parent: Element\n text: Text\n}) => {\n const { isLast, leaf, parent, text } = props\n const editor = useEditor()\n const path = ReactEditor.findPath(editor, text)\n const parentPath = Path.parent(path)\n\n // COMPAT: Render text inside void nodes with a zero-width space.\n // So the node can contain selection but the text is not visible.\n if (editor.isVoid(parent)) {\n return \n }\n\n // COMPAT: If this is the last text node in an empty block, render a zero-\n // width space that will convert into a line break when copying and pasting\n // to support expected plain text.\n if (\n leaf.text === '' &&\n parent.children[parent.children.length - 1] === text &&\n !editor.isInline(parent) &&\n Editor.string(editor, parentPath) === ''\n ) {\n return \n }\n\n // COMPAT: If the text is empty, it's because it's on the edge of an inline\n // node, so we render a zero-width space so that the selection can be\n // inserted next to it still.\n if (leaf.text === '') {\n return \n }\n\n // COMPAT: Browsers will collapse trailing new lines at the end of blocks,\n // so we need to add an extra trailing new lines to prevent that.\n if (isLast && leaf.text.slice(-1) === '\\n') {\n return \n }\n\n return \n}\n\n/**\n * Leaf strings with text in them.\n */\n\nconst TextString = (props: { text: string; isTrailing?: boolean }) => {\n const { text, isTrailing = false } = props\n return (\n \n {text}\n {isTrailing ? '\\n' : null}\n \n )\n}\n\n/**\n * Leaf strings without text, render as zero-width strings.\n */\n\nconst ZeroWidthString = (props: { length?: number; isLineBreak?: boolean }) => {\n const { length = 0, isLineBreak = false } = props\n return (\n \n {'\\uFEFF'}\n {isLineBreak ? : null}\n \n )\n}\n\nexport default String\n","import { Node, Ancestor, Editor, Range } from 'slate'\n\nimport { Key } from './key'\n\n/**\n * Two weak maps that allow us rebuild a path given a node. They are populated\n * at render time such that after a render occurs we can always backtrack.\n */\n\nexport const NODE_TO_INDEX: WeakMap = new WeakMap()\nexport const NODE_TO_PARENT: WeakMap = new WeakMap()\n\n/**\n * Weak maps that allow us to go between Slate nodes and DOM nodes. These\n * are used to resolve DOM event-related logic into Slate actions.\n */\n\nexport const EDITOR_TO_ELEMENT: WeakMap = new WeakMap()\nexport const EDITOR_TO_PLACEHOLDER: WeakMap = new WeakMap()\nexport const ELEMENT_TO_NODE: WeakMap = new WeakMap()\nexport const KEY_TO_ELEMENT: WeakMap = new WeakMap()\nexport const NODE_TO_ELEMENT: WeakMap = new WeakMap()\nexport const NODE_TO_KEY: WeakMap = new WeakMap()\n\n/**\n * Weak maps for storing editor-related state.\n */\n\nexport const IS_READ_ONLY: WeakMap = new WeakMap()\nexport const IS_FOCUSED: WeakMap = new WeakMap()\nexport const IS_DRAGGING: WeakMap = new WeakMap()\nexport const IS_CLICKING: WeakMap = new WeakMap()\n\n/**\n * Weak map for associating the context `onChange` context with the plugin.\n */\n\nexport const EDITOR_TO_ON_CHANGE = new WeakMap void>()\n\n/**\n * Symbols.\n */\n\nexport const PLACEHOLDER_SYMBOL = (Symbol('placeholder') as unknown) as string\n","import React from 'react'\nimport { Text, Element } from 'slate'\n\nimport String from './string'\nimport { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'\nimport { RenderLeafProps } from './editable'\n\n/**\n * Individual leaves in a text node with unique formatting.\n */\n\nconst Leaf = (props: {\n isLast: boolean\n leaf: Text\n parent: Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n text: Text\n}) => {\n const {\n leaf,\n isLast,\n text,\n parent,\n renderLeaf = (props: RenderLeafProps) => ,\n } = props\n\n let children = (\n \n )\n\n if (leaf[PLACEHOLDER_SYMBOL]) {\n children = (\n \n \n {leaf.placeholder}\n \n {children}\n \n )\n }\n\n // COMPAT: Having the `data-` attributes on these leaf elements ensures that\n // in certain misbehaving browsers they aren't weirdly cloned/destroyed by\n // contenteditable behaviors. (2019/05/08)\n const attributes: {\n 'data-slate-leaf': true\n } = {\n 'data-slate-leaf': true,\n }\n\n return renderLeaf({ attributes, children, leaf, text })\n}\n\nconst MemoizedLeaf = React.memo(Leaf, (prev, next) => {\n return (\n next.parent === prev.parent &&\n next.isLast === prev.isLast &&\n next.renderLeaf === prev.renderLeaf &&\n next.text === prev.text &&\n Text.matches(next.leaf, prev.leaf)\n )\n})\n\n/**\n * The default custom leaf renderer.\n */\n\nexport const DefaultLeaf = (props: RenderLeafProps) => {\n const { attributes, children } = props\n return {children}\n}\n\nexport default MemoizedLeaf\n","import { useLayoutEffect, useEffect } from 'react'\n\n/**\n * Prevent warning on SSR by falling back to useEffect when window is not defined\n */\nexport const useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? useLayoutEffect : useEffect\n","import React, { useRef } from 'react'\nimport { Range, Element, Text as SlateText } from 'slate'\n\nimport Leaf from './leaf'\nimport { ReactEditor, useEditor } from '..'\nimport { RenderLeafProps } from './editable'\nimport { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'\nimport {\n KEY_TO_ELEMENT,\n NODE_TO_ELEMENT,\n ELEMENT_TO_NODE,\n} from '../utils/weak-maps'\n\n/**\n * Text.\n */\n\nconst Text = (props: {\n decorations: Range[]\n isLast: boolean\n parent: Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n text: SlateText\n}) => {\n const { decorations, isLast, parent, renderLeaf, text } = props\n const editor = useEditor()\n const ref = useRef(null)\n const leaves = SlateText.decorations(text, decorations)\n const key = ReactEditor.findKey(editor, text)\n const children = []\n\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i]\n\n children.push(\n \n )\n }\n\n // Update element-related weak maps with the DOM element ref.\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n KEY_TO_ELEMENT.set(key, ref.current)\n NODE_TO_ELEMENT.set(text, ref.current)\n ELEMENT_TO_NODE.set(ref.current, text)\n } else {\n KEY_TO_ELEMENT.delete(key)\n NODE_TO_ELEMENT.delete(text)\n }\n })\n\n return (\n \n {children}\n \n )\n}\n\nconst MemoizedText = React.memo(Text, (prev, next) => {\n return (\n next.parent === prev.parent &&\n next.isLast === prev.isLast &&\n next.renderLeaf === prev.renderLeaf &&\n next.text === prev.text\n )\n})\n\nexport default MemoizedText\n","import { createContext, useContext } from 'react'\n\n/**\n * A React context for sharing the `selected` state of an element.\n */\n\nexport const SelectedContext = createContext(false)\n\n/**\n * Get the current `selected` state of an element.\n */\n\nexport const useSelected = (): boolean => {\n return useContext(SelectedContext)\n}\n","import React, { useRef } from 'react'\nimport getDirection from 'direction'\nimport { Editor, Node, Range, NodeEntry, Element as SlateElement } from 'slate'\n\nimport Text from './text'\nimport Children from './children'\nimport { ReactEditor, useEditor, useReadOnly } from '..'\nimport { SelectedContext } from '../hooks/use-selected'\nimport { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'\nimport {\n NODE_TO_ELEMENT,\n ELEMENT_TO_NODE,\n NODE_TO_PARENT,\n NODE_TO_INDEX,\n KEY_TO_ELEMENT,\n} from '../utils/weak-maps'\nimport { RenderElementProps, RenderLeafProps } from './editable'\n\n/**\n * Element.\n */\n\nconst Element = (props: {\n decorate: (entry: NodeEntry) => Range[]\n decorations: Range[]\n element: SlateElement\n renderElement?: (props: RenderElementProps) => JSX.Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n selection: Range | null\n elementIndex: Number\n}) => {\n const {\n decorate,\n decorations,\n element,\n renderElement = (p: RenderElementProps) => ,\n renderLeaf,\n selection,\n elementIndex,\n } = props\n const ref = useRef(null)\n const editor = useEditor()\n const readOnly = useReadOnly()\n const isInline = editor.isInline(element)\n const key = ReactEditor.findKey(editor, element)\n\n let children: JSX.Element | null = (\n \n )\n\n // Attributes that the developer must mix into the element in their\n // custom node renderer component.\n const attributes: {\n 'data-slate-node': 'element'\n 'data-slate-void'?: true\n 'data-slate-inline'?: true\n contentEditable?: false\n dir?: 'rtl'\n ref: any\n elementIndex: Number\n } = {\n 'data-slate-node': 'element',\n ref,\n elementIndex,\n }\n\n if (isInline) {\n attributes['data-slate-inline'] = true\n }\n\n // If it's a block node with inline children, add the proper `dir` attribute\n // for text direction.\n if (!isInline && Editor.hasInlines(editor, element)) {\n const text = Node.string(element)\n const dir = getDirection(text)\n\n if (dir === 'rtl') {\n attributes.dir = dir\n }\n }\n\n // If it's a void node, wrap the children in extra void-specific elements.\n if (Editor.isVoid(editor, element)) {\n attributes['data-slate-void'] = true\n\n if (!readOnly && isInline) {\n attributes.contentEditable = false\n }\n\n const Tag = isInline ? 'span' : 'div'\n const [[text]] = Node.texts(element)\n\n children = readOnly ? null : (\n \n \n \n )\n\n NODE_TO_INDEX.set(text, 0)\n NODE_TO_PARENT.set(text, element)\n }\n\n // Update element-related weak maps with the DOM element ref.\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n KEY_TO_ELEMENT.set(key, ref.current)\n NODE_TO_ELEMENT.set(element, ref.current)\n ELEMENT_TO_NODE.set(ref.current, element)\n } else {\n KEY_TO_ELEMENT.delete(key)\n NODE_TO_ELEMENT.delete(element)\n }\n })\n\n return (\n \n {renderElement({ attributes, children, element })}\n \n )\n}\n\nconst MemoizedElement = React.memo(Element, (prev, next) => {\n return (\n prev.decorate === next.decorate &&\n prev.element === next.element &&\n prev.renderElement === next.renderElement &&\n prev.renderLeaf === next.renderLeaf &&\n isRangeListEqual(prev.decorations, next.decorations) &&\n (prev.selection === next.selection ||\n (!!prev.selection &&\n !!next.selection &&\n Range.equals(prev.selection, next.selection)))\n )\n})\n\n/**\n * The default element renderer.\n */\n\nexport const DefaultElement = (props: RenderElementProps) => {\n const { attributes, children, element } = props\n const editor = useEditor()\n const Tag = editor.isInline(element) ? 'span' : 'div'\n return (\n \n {children}\n \n )\n}\n\n/**\n * Check if a list of ranges is equal to another.\n *\n * PERF: this requires the two lists to also have the ranges inside them in the\n * same order, but this is an okay constraint for us since decorations are\n * kept in order, and the odd case where they aren't is okay to re-render for.\n */\n\nconst isRangeListEqual = (list: Range[], another: Range[]): boolean => {\n if (list.length !== another.length) {\n return false\n }\n\n for (let i = 0; i < list.length; i++) {\n const range = list[i]\n const other = another[i]\n\n if (!Range.equals(range, other)) {\n return false\n }\n }\n\n return true\n}\n\nexport default MemoizedElement\n","import { createContext, useContext } from 'react'\n\nimport { ReactEditor } from '../plugin/react-editor'\n\n/**\n * A React context for sharing the editor object.\n */\n\nexport const EditorContext = createContext(null)\n\n/**\n * Get the current editor object from the React context.\n */\n\nexport const useEditor = () => {\n const editor = useContext(EditorContext)\n\n if (!editor) {\n throw new Error(\n `The \\`useEditor\\` hook must be used inside the component's context.`\n )\n }\n\n return editor\n}\n","import React from 'react'\nimport { Editor, Range, Element, NodeEntry, Ancestor, Descendant } from 'slate'\n\nimport ElementComponent from './element'\nimport TextComponent from './text'\nimport { ReactEditor } from '..'\nimport { useEditor } from '../hooks/use-editor'\nimport { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'\nimport { RenderElementProps, RenderLeafProps } from './editable'\n\n/**\n * Children.\n */\n\nconst Children = (props: {\n decorate: (entry: NodeEntry) => Range[]\n decorations: Range[]\n node: Ancestor\n renderElement?: (props: RenderElementProps) => JSX.Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n selection: Range | null\n ReactHappyWindow: React.Component | undefined\n reactHappyWindowProps: Object | undefined\n}) => {\n const {\n decorate,\n decorations,\n node,\n renderElement,\n renderLeaf,\n selection,\n ReactHappyWindow,\n reactHappyWindowProps = {},\n } = props\n const editor = useEditor()\n const path = ReactEditor.findPath(editor, node)\n const children = []\n const isLeafBlock =\n Element.isElement(node) &&\n !editor.isInline(node) &&\n Editor.hasInlines(editor, node)\n\n const renderChild = (i: number) => {\n const p = path.concat(i)\n const n = node.children[i] as Descendant\n const key = ReactEditor.findKey(editor, n)\n const range = Editor.range(editor, p)\n const sel = selection && Range.intersection(range, selection)\n\n // Commented out to improve performance. We don't use decorations\n // const ds = decorate([n, p])\n const ds = [] as Range[]\n // for (const dec of decorations) {\n // const d = Range.intersection(dec, range)\n\n // if (d) {\n // ds.push(d)\n // }\n // }\n\n NODE_TO_INDEX.set(n, i)\n NODE_TO_PARENT.set(n, node)\n\n if (Element.isElement(n)) {\n return (\n \n )\n } else {\n return (\n \n )\n }\n }\n\n if (ReactHappyWindow) {\n return (\n \n )\n }\n\n for (let i = 0; i < node.children.length; i++) {\n children.push(renderChild(i))\n }\n\n return {children}\n}\n\nexport default Children\n","export const IS_IOS =\n typeof navigator !== 'undefined' &&\n typeof window !== 'undefined' &&\n /iPad|iPhone|iPod/.test(navigator.userAgent) &&\n !window.MSStream\n\nexport const IS_APPLE =\n typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)\n\nexport const IS_FIREFOX =\n typeof navigator !== 'undefined' &&\n /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)\n\nexport const IS_SAFARI =\n typeof navigator !== 'undefined' &&\n /Version\\/[\\d\\.]+.*Safari/.test(navigator.userAgent)\n","import { isKeyHotkey } from 'is-hotkey'\nimport { IS_APPLE } from './environment'\n\n/**\n * Hotkey mappings for each platform.\n */\n\nconst HOTKEYS = {\n bold: 'mod+b',\n compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],\n moveBackward: 'left',\n moveForward: 'right',\n moveWordBackward: 'ctrl+left',\n moveWordForward: 'ctrl+right',\n deleteBackward: 'shift?+backspace',\n deleteForward: 'shift?+delete',\n extendBackward: 'shift+left',\n extendForward: 'shift+right',\n italic: 'mod+i',\n splitBlock: 'shift?+enter',\n undo: 'mod+z',\n}\n\nconst APPLE_HOTKEYS = {\n moveLineBackward: 'opt+up',\n moveLineForward: 'opt+down',\n moveWordBackward: 'opt+left',\n moveWordForward: 'opt+right',\n deleteBackward: ['ctrl+backspace', 'ctrl+h'],\n deleteForward: ['ctrl+delete', 'ctrl+d'],\n deleteLineBackward: 'cmd+shift?+backspace',\n deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],\n deleteWordBackward: 'opt+shift?+backspace',\n deleteWordForward: 'opt+shift?+delete',\n extendLineBackward: 'opt+shift+up',\n extendLineForward: 'opt+shift+down',\n redo: 'cmd+shift+z',\n transposeCharacter: 'ctrl+t',\n}\n\nconst WINDOWS_HOTKEYS = {\n deleteWordBackward: 'ctrl+shift?+backspace',\n deleteWordForward: 'ctrl+shift?+delete',\n redo: ['ctrl+y', 'ctrl+shift+z'],\n}\n\n/**\n * Create a platform-aware hotkey checker.\n */\n\nconst create = (key: string) => {\n const generic = HOTKEYS[key]\n const apple = APPLE_HOTKEYS[key]\n const windows = WINDOWS_HOTKEYS[key]\n const isGeneric = generic && isKeyHotkey(generic)\n const isApple = apple && isKeyHotkey(apple)\n const isWindows = windows && isKeyHotkey(windows)\n\n return (event: KeyboardEvent) => {\n if (isGeneric && isGeneric(event)) return true\n if (IS_APPLE && isApple && isApple(event)) return true\n if (!IS_APPLE && isWindows && isWindows(event)) return true\n return false\n }\n}\n\n/**\n * Hotkeys.\n */\n\nexport default {\n isBold: create('bold'),\n isCompose: create('compose'),\n isMoveBackward: create('moveBackward'),\n isMoveForward: create('moveForward'),\n isDeleteBackward: create('deleteBackward'),\n isDeleteForward: create('deleteForward'),\n isDeleteLineBackward: create('deleteLineBackward'),\n isDeleteLineForward: create('deleteLineForward'),\n isDeleteWordBackward: create('deleteWordBackward'),\n isDeleteWordForward: create('deleteWordForward'),\n isExtendBackward: create('extendBackward'),\n isExtendForward: create('extendForward'),\n isExtendLineBackward: create('extendLineBackward'),\n isExtendLineForward: create('extendLineForward'),\n isItalic: create('italic'),\n isMoveLineBackward: create('moveLineBackward'),\n isMoveLineForward: create('moveLineForward'),\n isMoveWordBackward: create('moveWordBackward'),\n isMoveWordForward: create('moveWordForward'),\n isRedo: create('redo'),\n isSplitBlock: create('splitBlock'),\n isTransposeCharacter: create('transposeCharacter'),\n isUndo: create('undo'),\n}\n","import { createContext, useContext } from 'react'\n\n/**\n * A React context for sharing the `readOnly` state of the editor.\n */\n\nexport const ReadOnlyContext = createContext(false)\n\n/**\n * Get the current `readOnly` state of the editor.\n */\n\nexport const useReadOnly = (): boolean => {\n return useContext(ReadOnlyContext)\n}\n","import { createContext, useContext } from 'react'\n\nimport { ReactEditor } from '../plugin/react-editor'\n\n/**\n * A React context for sharing the editor object, in a way that re-renders the\n * context whenever changes occur.\n */\n\nexport const SlateContext = createContext<[ReactEditor] | null>(null)\n\n/**\n * Get the current editor object from the React context.\n */\n\nexport const useSlate = () => {\n const context = useContext(SlateContext)\n\n if (!context) {\n throw new Error(\n `The \\`useSlate\\` hook must be used inside the component's context.`\n )\n }\n\n const [editor] = context\n return editor\n}\n","/**\n * Types.\n */\n\n// COMPAT: This is required to prevent TypeScript aliases from doing some very\n// weird things for Slate's types with the same name as globals. (2019/11/27)\n// https://github.com/microsoft/TypeScript/issues/35002\nimport DOMNode = globalThis.Node\nimport DOMComment = globalThis.Comment\nimport DOMElement = globalThis.Element\nimport DOMText = globalThis.Text\nimport DOMRange = globalThis.Range\nimport DOMSelection = globalThis.Selection\nimport DOMStaticRange = globalThis.StaticRange\nexport {\n DOMNode,\n DOMComment,\n DOMElement,\n DOMText,\n DOMRange,\n DOMSelection,\n DOMStaticRange,\n}\n\nexport type DOMPoint = [Node, number]\n\n/**\n * Check if a DOM node is a comment node.\n */\n\nexport const isDOMComment = (value: any): value is DOMComment => {\n return isDOMNode(value) && value.nodeType === 8\n}\n\n/**\n * Check if a DOM node is an element node.\n */\n\nexport const isDOMElement = (value: any): value is DOMElement => {\n return isDOMNode(value) && value.nodeType === 1\n}\n\n/**\n * Check if a value is a DOM node.\n */\n\nexport const isDOMNode = (value: any): value is DOMNode => {\n return value instanceof Node\n}\n\n/**\n * Check if a DOM node is an element node.\n */\n\nexport const isDOMText = (value: any): value is DOMText => {\n return isDOMNode(value) && value.nodeType === 3\n}\n\n/**\n * Normalize a DOM point so that it always refers to a text node.\n */\n\nexport const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => {\n let [node, offset] = domPoint\n\n // If it's an element node, its offset refers to the index of its children\n // including comment nodes, so try to find the right text child node.\n if (isDOMElement(node) && node.childNodes.length) {\n const isLast = offset === node.childNodes.length\n const direction = isLast ? 'backward' : 'forward'\n const index = isLast ? offset - 1 : offset\n node = getEditableChild(node, index, direction)\n\n // If the node has children, traverse until we have a leaf node. Leaf nodes\n // can be either text nodes, or other void DOM nodes.\n while (isDOMElement(node) && node.childNodes.length) {\n const i = isLast ? node.childNodes.length - 1 : 0\n node = getEditableChild(node, i, direction)\n }\n\n // Determine the new offset inside the text node.\n offset = isLast && node.textContent != null ? node.textContent.length : 0\n }\n\n // Return the node and offset.\n return [node, offset]\n}\n\n/**\n * Get the nearest editable child at `index` in a `parent`, preferring\n * `direction`.\n */\n\nexport const getEditableChild = (\n parent: DOMElement,\n index: number,\n direction: 'forward' | 'backward'\n): DOMNode => {\n const { childNodes } = parent\n let child = childNodes[index]\n let i = index\n let triedForward = false\n let triedBackward = false\n\n // While the child is a comment node, or an element node with no children,\n // keep iterating to find a sibling non-void, non-comment node.\n while (\n isDOMComment(child) ||\n (isDOMElement(child) && child.childNodes.length === 0) ||\n (isDOMElement(child) && child.getAttribute('contenteditable') === 'false')\n ) {\n if (triedForward && triedBackward) {\n break\n }\n\n if (i >= childNodes.length) {\n triedForward = true\n i = index - 1\n direction = 'backward'\n continue\n }\n\n if (i < 0) {\n triedBackward = true\n i = index + 1\n direction = 'forward'\n continue\n }\n\n child = childNodes[i]\n i += direction === 'forward' ? 1 : -1\n }\n\n return child\n}\n","import React, { useEffect, useRef, useMemo, useCallback } from 'react'\nimport {\n Editor,\n Element,\n NodeEntry,\n Node,\n Range,\n Text,\n Transforms,\n} from 'slate'\nimport getDirection from 'direction'\nimport debounce from 'debounce'\nimport scrollIntoView from 'scroll-into-view-if-needed'\n\nimport Children from './children'\nimport Hotkeys from '../utils/hotkeys'\nimport { IS_FIREFOX, IS_SAFARI } from '../utils/environment'\nimport { ReactEditor } from '..'\nimport { ReadOnlyContext } from '../hooks/use-read-only'\nimport { useSlate } from '../hooks/use-slate'\nimport { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'\nimport {\n DOMElement,\n DOMNode,\n DOMRange,\n isDOMElement,\n isDOMNode,\n isDOMText,\n DOMStaticRange,\n} from '../utils/dom'\nimport {\n EDITOR_TO_ELEMENT,\n ELEMENT_TO_NODE,\n IS_READ_ONLY,\n NODE_TO_ELEMENT,\n IS_FOCUSED,\n PLACEHOLDER_SYMBOL,\n} from '../utils/weak-maps'\n\n/**\n * `RenderElementProps` are passed to the `renderElement` handler.\n */\n\nexport interface RenderElementProps {\n children: any\n element: Element\n attributes: {\n 'data-slate-node': 'element'\n 'data-slate-inline'?: true\n 'data-slate-void'?: true\n dir?: 'rtl'\n ref: any\n }\n}\n\n/**\n * `RenderLeafProps` are passed to the `renderLeaf` handler.\n */\n\nexport interface RenderLeafProps {\n children: any\n leaf: Text\n text: Text\n attributes: {\n 'data-slate-leaf': true\n }\n}\n\n/**\n * `EditableProps` are passed to the `` component.\n */\n\nexport type EditableProps = {\n decorate?: (entry: NodeEntry) => Range[]\n onDOMBeforeInput?: (event: Event) => void\n placeholder?: string\n readOnly?: boolean\n role?: string\n style?: React.CSSProperties\n renderElement?: (props: RenderElementProps) => JSX.Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n as?: React.ElementType\n ReactHappyWindow?: React.Component\n reactHappyWindowProps?: object\n} & React.TextareaHTMLAttributes\n\n/**\n * Editable.\n */\n\nexport const Editable = (props: EditableProps) => {\n const {\n autoFocus,\n decorate = defaultDecorate,\n onDOMBeforeInput: propsOnDOMBeforeInput,\n placeholder,\n readOnly = false,\n renderElement,\n renderLeaf,\n style = {},\n as: Component = 'div',\n ReactHappyWindow,\n reactHappyWindowProps,\n happyWindowRef,\n ...attributes\n } = props\n const editor = useSlate()\n const ref = useRef(null)\n\n // Update internal state on each render.\n IS_READ_ONLY.set(editor, readOnly)\n\n // Keep track of some state for the event handler logic.\n const state = useMemo(\n () => ({\n isComposing: false,\n isUpdatingSelection: false,\n latestElement: null as DOMElement | null,\n }),\n []\n )\n\n // Update element-related weak maps with the DOM element ref.\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n EDITOR_TO_ELEMENT.set(editor, ref.current)\n NODE_TO_ELEMENT.set(editor, ref.current)\n ELEMENT_TO_NODE.set(ref.current, editor)\n } else {\n NODE_TO_ELEMENT.delete(editor)\n }\n })\n\n // Attach a native DOM event handler for `selectionchange`, because React's\n // built-in `onSelect` handler doesn't fire for all selection changes. It's a\n // leaky polyfill that only fires on keypresses or clicks. Instead, we want to\n // fire for any change to the selection inside the editor. (2019/11/04)\n // https://github.com/facebook/react/issues/5785\n useIsomorphicLayoutEffect(() => {\n window.document.addEventListener('selectionchange', onDOMSelectionChange)\n\n return () => {\n window.document.removeEventListener(\n 'selectionchange',\n onDOMSelectionChange\n )\n }\n }, [])\n\n // Attach a native DOM event handler for `beforeinput` events, because React's\n // built-in `onBeforeInput` is actually a leaky polyfill that doesn't expose\n // real `beforeinput` events sadly... (2019/11/04)\n // https://github.com/facebook/react/issues/11211\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n // @ts-ignore The `beforeinput` event isn't recognized.\n ref.current.addEventListener('beforeinput', onDOMBeforeInput)\n }\n\n return () => {\n if (ref.current) {\n // @ts-ignore The `beforeinput` event isn't recognized.\n ref.current.removeEventListener('beforeinput', onDOMBeforeInput)\n }\n }\n }, [])\n\n // Whenever the editor updates, make sure the DOM selection state is in sync.\n useIsomorphicLayoutEffect(() => {\n const { selection } = editor\n const domSelection = window.getSelection()\n\n if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {\n return\n }\n\n const hasDomSelection = domSelection.type !== 'None'\n\n // If the DOM selection is properly unset, we're done.\n if (!selection && !hasDomSelection) {\n return\n }\n\n const newDomRange = selection && ReactEditor.toDOMRange(editor, selection)\n\n // If the DOM selection is already correct, we're done.\n if (\n hasDomSelection &&\n newDomRange &&\n isRangeEqual(domSelection.getRangeAt(0), newDomRange)\n ) {\n return\n }\n\n // Otherwise the DOM selection is out of sync, so update it.\n const el = ReactEditor.toDOMNode(editor, editor)\n state.isUpdatingSelection = true\n domSelection.removeAllRanges()\n\n if (newDomRange) {\n domSelection.addRange(newDomRange!)\n const leafEl = newDomRange.startContainer.parentElement!\n scrollIntoView(leafEl, { scrollMode: 'if-needed' })\n }\n\n setTimeout(() => {\n // COMPAT: In Firefox, it's not enough to create a range, you also need\n // to focus the contenteditable element too. (2016/11/16)\n if (newDomRange && IS_FIREFOX) {\n el.focus()\n }\n\n state.isUpdatingSelection = false\n })\n })\n\n // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it\n // needs to be manually focused.\n useEffect(() => {\n if (ref.current && autoFocus) {\n ref.current.focus()\n }\n }, [autoFocus])\n\n // Listen on the native `beforeinput` event to get real \"Level 2\" events. This\n // is required because React's `beforeinput` is fake and never really attaches\n // to the real event sadly. (2019/11/01)\n // https://github.com/facebook/react/issues/11211\n const onDOMBeforeInput = useCallback(\n (\n event: Event & {\n data: string | null\n dataTransfer: DataTransfer | null\n getTargetRanges(): DOMStaticRange[]\n inputType: string\n isComposing: boolean\n }\n ) => {\n if (\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isDOMEventHandled(event, propsOnDOMBeforeInput)\n ) {\n const { selection } = editor\n const { inputType: type } = event\n const data = event.dataTransfer || event.data || undefined\n\n // These two types occur while a user is composing text and can't be\n // cancelled. Let them through and wait for the composition to end.\n if (\n type === 'insertCompositionText' ||\n type === 'deleteCompositionText'\n ) {\n return\n }\n\n event.preventDefault()\n\n // COMPAT: For the deleting forward/backward input types we don't want\n // to change the selection because it is the range that will be deleted,\n // and those commands determine that for themselves.\n if (!type.startsWith('delete') || type.startsWith('deleteBy')) {\n const [targetRange] = event.getTargetRanges()\n\n if (targetRange) {\n const range = ReactEditor.toSlateRange(editor, targetRange)\n if (!range) {\n return\n }\n if (!selection || !Range.equals(selection, range)) {\n Transforms.select(editor, range)\n }\n }\n }\n\n // COMPAT: If the selection is expanded, even if the command seems like\n // a delete forward/backward command it should delete the selection.\n if (\n selection &&\n Range.isExpanded(selection) &&\n type.startsWith('delete')\n ) {\n Editor.deleteFragment(editor)\n return\n }\n\n switch (type) {\n case 'deleteByComposition':\n case 'deleteByCut':\n case 'deleteByDrag': {\n Editor.deleteFragment(editor)\n break\n }\n\n case 'deleteContent':\n case 'deleteContentForward': {\n Editor.deleteForward(editor)\n break\n }\n\n case 'deleteContentBackward': {\n Editor.deleteBackward(editor)\n break\n }\n\n case 'deleteEntireSoftLine': {\n Editor.deleteBackward(editor, { unit: 'line' })\n Editor.deleteForward(editor, { unit: 'line' })\n break\n }\n\n case 'deleteHardLineBackward': {\n Editor.deleteBackward(editor, { unit: 'block' })\n break\n }\n\n case 'deleteSoftLineBackward': {\n Editor.deleteBackward(editor, { unit: 'line' })\n break\n }\n\n case 'deleteHardLineForward': {\n Editor.deleteForward(editor, { unit: 'block' })\n break\n }\n\n case 'deleteSoftLineForward': {\n Editor.deleteForward(editor, { unit: 'line' })\n break\n }\n\n case 'deleteWordBackward': {\n Editor.deleteBackward(editor, { unit: 'word' })\n break\n }\n\n case 'deleteWordForward': {\n Editor.deleteForward(editor, { unit: 'word' })\n break\n }\n\n case 'insertLineBreak':\n case 'insertParagraph': {\n Editor.insertBreak(editor)\n break\n }\n\n case 'insertFromComposition':\n case 'insertFromDrop':\n case 'insertFromPaste':\n case 'insertFromYank':\n case 'insertReplacementText':\n case 'insertText': {\n if (data instanceof DataTransfer) {\n ReactEditor.insertData(editor, data)\n } else if (typeof data === 'string') {\n Editor.insertText(editor, data)\n }\n\n break\n }\n }\n }\n },\n []\n )\n\n // Listen on the native `selectionchange` event to be able to update any time\n // the selection changes. This is required because React's `onSelect` is leaky\n // and non-standard so it doesn't fire until after a selection has been\n // released. This causes issues in situations where another change happens\n // while a selection is being dragged.\n const onDOMSelectionChange = useCallback(\n debounce(() => {\n if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {\n const { activeElement } = window.document\n const el = ReactEditor.toDOMNode(editor, editor)\n const domSelection = window.getSelection()\n const domRange =\n domSelection &&\n domSelection.rangeCount > 0 &&\n domSelection.getRangeAt(0)\n\n if (activeElement === el) {\n state.latestElement = activeElement\n IS_FOCUSED.set(editor, true)\n } else {\n IS_FOCUSED.delete(editor)\n }\n\n if (\n domRange &&\n hasEditableTarget(editor, domRange.startContainer) &&\n hasEditableTarget(editor, domRange.endContainer)\n ) {\n const range = ReactEditor.toSlateRange(editor, domRange)\n Transforms.select(editor, range)\n } else {\n Transforms.deselect(editor)\n }\n }\n }, 100),\n []\n )\n\n const decorations = decorate([editor, []])\n\n if (\n placeholder &&\n editor.children.length === 1 &&\n Array.from(Node.texts(editor)).length === 1 &&\n Node.string(editor) === ''\n ) {\n const start = Editor.start(editor, [])\n decorations.push({\n [PLACEHOLDER_SYMBOL]: true,\n placeholder,\n anchor: start,\n focus: start,\n })\n }\n\n return (\n \n {\n // COMPAT: Firefox doesn't support the `beforeinput` event, so we\n // fall back to React's leaky polyfill instead just for it. It\n // only works for the `insertText` input type.\n if (IS_FIREFOX && !readOnly && ReactEditor.isFocused(editor)) {\n event.preventDefault()\n const text = (event as any).data as string\n Editor.insertText(editor, text)\n }\n },\n [readOnly]\n )}\n onBlur={useCallback(\n (event: React.FocusEvent) => {\n if (\n readOnly ||\n state.isUpdatingSelection ||\n !hasEditableTarget(editor, event.target) ||\n isEventHandled(event, attributes.onBlur)\n ) {\n return\n }\n\n // COMPAT: If the current `activeElement` is still the previous\n // one, this is due to the window being blurred when the tab\n // itself becomes unfocused, so we want to abort early to allow to\n // editor to stay focused when the tab becomes focused again.\n if (state.latestElement === window.document.activeElement) {\n return\n }\n\n const { relatedTarget } = event\n const el = ReactEditor.toDOMNode(editor, editor)\n\n // COMPAT: The event should be ignored if the focus is returning\n // to the editor from an embedded editable element (eg. an \n // element inside a void node).\n if (relatedTarget === el) {\n return\n }\n\n // COMPAT: The event should be ignored if the focus is moving from\n // the editor to inside a void node's spacer element.\n if (\n isDOMElement(relatedTarget) &&\n relatedTarget.hasAttribute('data-slate-spacer')\n ) {\n return\n }\n\n // COMPAT: The event should be ignored if the focus is moving to a\n // non- editable section of an element that isn't a void node (eg.\n // a list item of the check list example).\n if (\n relatedTarget != null &&\n isDOMNode(relatedTarget) &&\n ReactEditor.hasDOMNode(editor, relatedTarget)\n ) {\n const node = ReactEditor.toSlateNode(editor, relatedTarget)\n\n if (Element.isElement(node) && !editor.isVoid(node)) {\n return\n }\n }\n\n IS_FOCUSED.delete(editor)\n },\n [readOnly, attributes.onBlur]\n )}\n onClick={useCallback(\n (event: React.MouseEvent) => {\n if (\n !readOnly &&\n hasTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onClick) &&\n isDOMNode(event.target)\n ) {\n const node = ReactEditor.toSlateNode(editor, event.target)\n const path = ReactEditor.findPath(editor, node)\n const start = Editor.start(editor, path)\n\n if (Editor.void(editor, { at: start })) {\n const range = Editor.range(editor, start)\n Transforms.select(editor, range)\n }\n }\n },\n [readOnly, attributes.onClick]\n )}\n onCompositionEnd={useCallback(\n (event: React.CompositionEvent) => {\n if (\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCompositionEnd)\n ) {\n state.isComposing = false\n\n // COMPAT: In Chrome, `beforeinput` events for compositions\n // aren't correct and never fire the \"insertFromComposition\"\n // type that we need. So instead, insert whenever a composition\n // ends since it will already have been committed to the DOM.\n if (!IS_SAFARI && !IS_FIREFOX && event.data) {\n Editor.insertText(editor, event.data)\n }\n }\n },\n [attributes.onCompositionEnd]\n )}\n onCompositionStart={useCallback(\n (event: React.CompositionEvent) => {\n if (\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCompositionStart)\n ) {\n state.isComposing = true\n }\n },\n [attributes.onCompositionStart]\n )}\n onCopy={useCallback(\n (event: React.ClipboardEvent) => {\n if (\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCopy)\n ) {\n event.preventDefault()\n setFragmentData(event.clipboardData, editor)\n }\n },\n [attributes.onCopy]\n )}\n onCut={useCallback(\n (event: React.ClipboardEvent) => {\n if (\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCut)\n ) {\n event.preventDefault()\n setFragmentData(event.clipboardData, editor)\n const { selection } = editor\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n }\n }\n },\n [readOnly, attributes.onCut]\n )}\n onDragOver={useCallback(\n (event: React.DragEvent) => {\n if (\n hasTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onDragOver)\n ) {\n // Only when the target is void, call `preventDefault` to signal\n // that drops are allowed. Editable content is droppable by\n // default, and calling `preventDefault` hides the cursor.\n const node = ReactEditor.toSlateNode(editor, event.target)\n\n if (Editor.isVoid(editor, node)) {\n event.preventDefault()\n }\n }\n },\n [attributes.onDragOver]\n )}\n onDragStart={useCallback(\n (event: React.DragEvent) => {\n if (\n hasTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onDragStart)\n ) {\n const node = ReactEditor.toSlateNode(editor, event.target)\n const path = ReactEditor.findPath(editor, node)\n const voidMatch = Editor.void(editor, { at: path })\n\n // If starting a drag on a void node, make sure it is selected\n // so that it shows up in the selection's fragment.\n if (voidMatch) {\n const range = Editor.range(editor, path)\n Transforms.select(editor, range)\n }\n\n setFragmentData(event.dataTransfer, editor)\n }\n },\n [attributes.onDragStart]\n )}\n onDrop={useCallback(\n (event: React.DragEvent) => {\n if (\n hasTarget(editor, event.target) &&\n !readOnly &&\n !isEventHandled(event, attributes.onDrop)\n ) {\n // COMPAT: Firefox doesn't fire `beforeinput` events at all, and\n // Chromium browsers don't properly fire them for files being\n // dropped into a `contenteditable`. (2019/11/26)\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668\n if (\n IS_FIREFOX ||\n (!IS_SAFARI && event.dataTransfer.files.length > 0)\n ) {\n event.preventDefault()\n const range = ReactEditor.findEventRange(editor, event)\n const data = event.dataTransfer\n Transforms.select(editor, range)\n ReactEditor.insertData(editor, data)\n }\n }\n },\n [readOnly, attributes.onDrop]\n )}\n onFocus={useCallback(\n (event: React.FocusEvent) => {\n if (\n !readOnly &&\n !state.isUpdatingSelection &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onFocus)\n ) {\n const el = ReactEditor.toDOMNode(editor, editor)\n state.latestElement = window.document.activeElement\n\n // COMPAT: If the editor has nested editable elements, the focus\n // can go to them. In Firefox, this must be prevented because it\n // results in issues with keyboard navigation. (2017/03/30)\n if (IS_FIREFOX && event.target !== el) {\n el.focus()\n return\n }\n\n IS_FOCUSED.set(editor, true)\n }\n },\n [readOnly, attributes.onFocus]\n )}\n onKeyDown={useCallback(\n (event: React.KeyboardEvent) => {\n if (\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onKeyDown)\n ) {\n const { nativeEvent } = event\n const { selection } = editor\n\n const element =\n editor.children[\n selection !== null ? selection.focus.path[0] : 0\n ]\n const isRTL = getDirection(Node.string(element)) === 'rtl'\n\n // COMPAT: Since we prevent the default behavior on\n // `beforeinput` events, the browser doesn't think there's ever\n // any history stack to undo or redo, so we have to manage these\n // hotkeys ourselves. (2019/11/06)\n if (Hotkeys.isRedo(nativeEvent)) {\n event.preventDefault()\n\n if (editor.redo) {\n editor.redo()\n }\n\n return\n }\n\n if (Hotkeys.isUndo(nativeEvent)) {\n event.preventDefault()\n\n if (editor.undo) {\n editor.undo()\n }\n\n return\n }\n\n // COMPAT: Certain browsers don't handle the selection updates\n // properly. In Chrome, the selection isn't properly extended.\n // And in Firefox, the selection isn't properly collapsed.\n // (2017/10/17)\n if (Hotkeys.isMoveLineBackward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'line', reverse: true })\n return\n }\n\n if (Hotkeys.isMoveLineForward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'line' })\n return\n }\n\n if (Hotkeys.isExtendLineBackward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, {\n unit: 'line',\n edge: 'focus',\n reverse: true,\n })\n return\n }\n\n if (Hotkeys.isExtendLineForward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'line', edge: 'focus' })\n return\n }\n\n // COMPAT: If a void node is selected, or a zero-width text node\n // adjacent to an inline is selected, we need to handle these\n // hotkeys manually because browsers won't be able to skip over\n // the void node with the zero-width space not being an empty\n // string.\n if (Hotkeys.isMoveBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isCollapsed(selection)) {\n const { anchor } = selection\n if (anchor.offset === 1 && anchor.path[1] > 0) {\n // Hack to position the cursor at the end of the previous text node\n Transforms.move(editor, { reverse: !isRTL, distance: 2 })\n Transforms.move(editor, { reverse: isRTL })\n } else {\n Transforms.move(editor, { reverse: !isRTL })\n }\n } else {\n Transforms.collapse(editor, { edge: 'start' })\n }\n\n return\n }\n\n if (Hotkeys.isMoveForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isCollapsed(selection)) {\n Transforms.move(editor, { reverse: isRTL })\n } else {\n Transforms.collapse(editor, { edge: 'end' })\n }\n\n return\n }\n\n if (Hotkeys.isMoveWordBackward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'word', reverse: !isRTL })\n return\n }\n\n if (Hotkeys.isMoveWordForward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'word', reverse: isRTL })\n return\n }\n\n // COMPAT: Firefox doesn't support the `beforeinput` event, so we\n // fall back to guessing at the input intention for hotkeys.\n // COMPAT: In iOS, some of these hotkeys are handled in the\n if (IS_FIREFOX) {\n // We don't have a core behavior for these, but they change the\n // DOM if we don't prevent them, so we have to.\n if (\n Hotkeys.isBold(nativeEvent) ||\n Hotkeys.isItalic(nativeEvent) ||\n Hotkeys.isTransposeCharacter(nativeEvent)\n ) {\n event.preventDefault()\n return\n }\n\n if (Hotkeys.isSplitBlock(nativeEvent)) {\n event.preventDefault()\n Editor.insertBreak(editor)\n return\n }\n\n if (Hotkeys.isDeleteBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteBackward(editor)\n }\n\n return\n }\n\n if (Hotkeys.isDeleteForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteForward(editor)\n }\n\n return\n }\n\n if (Hotkeys.isDeleteLineBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteBackward(editor, { unit: 'line' })\n }\n\n return\n }\n\n if (Hotkeys.isDeleteLineForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteForward(editor, { unit: 'line' })\n }\n\n return\n }\n\n if (Hotkeys.isDeleteWordBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteBackward(editor, { unit: 'word' })\n }\n\n return\n }\n\n if (Hotkeys.isDeleteWordForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteForward(editor, { unit: 'word' })\n }\n\n return\n }\n }\n }\n },\n [readOnly, attributes.onKeyDown]\n )}\n onPaste={useCallback(\n (event: React.ClipboardEvent) => {\n // COMPAT: Firefox doesn't support the `beforeinput` event, so we\n // fall back to React's `onPaste` here instead.\n if (\n IS_FIREFOX &&\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onPaste)\n ) {\n event.preventDefault()\n ReactEditor.insertData(editor, event.clipboardData)\n }\n },\n [readOnly, attributes.onPaste]\n )}\n >\n \n \n \n )\n}\n\n/**\n * A default memoized decorate function.\n */\n\nconst defaultDecorate = () => []\n\n/**\n * Check if two DOM range objects are equal.\n */\n\nconst isRangeEqual = (a: DOMRange, b: DOMRange) => {\n return (\n (a.startContainer === b.startContainer &&\n a.startOffset === b.startOffset &&\n a.endContainer === b.endContainer &&\n a.endOffset === b.endOffset) ||\n (a.startContainer === b.endContainer &&\n a.startOffset === b.endOffset &&\n a.endContainer === b.startContainer &&\n a.endOffset === b.startOffset)\n )\n}\n\n/**\n * Check if the target is in the editor.\n */\n\nconst hasTarget = (\n editor: ReactEditor,\n target: EventTarget | null\n): target is DOMNode => {\n return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target)\n}\n\n/**\n * Check if the target is editable and in the editor.\n */\n\nconst hasEditableTarget = (\n editor: ReactEditor,\n target: EventTarget | null\n): target is DOMNode => {\n return (\n isDOMNode(target) &&\n ReactEditor.hasDOMNode(editor, target, { editable: true })\n )\n}\n\n/**\n * Check if an event is overrided by a handler.\n */\n\nconst isEventHandled = <\n EventType extends React.SyntheticEvent\n>(\n event: EventType,\n handler?: (event: EventType) => void\n) => {\n if (!handler) {\n return false\n }\n\n handler(event)\n return event.isDefaultPrevented() || event.isPropagationStopped()\n}\n\n/**\n * Check if a DOM event is overrided by a handler.\n */\n\nconst isDOMEventHandled = (event: Event, handler?: (event: Event) => void) => {\n if (!handler) {\n return false\n }\n\n handler(event)\n return event.defaultPrevented\n}\n\n/**\n * Set the currently selected fragment to the clipboard.\n */\n\nconst setFragmentData = (\n dataTransfer: DataTransfer,\n editor: ReactEditor\n): void => {\n const { selection } = editor\n\n if (!selection) {\n return\n }\n\n const [start, end] = Range.edges(selection)\n const startVoid = Editor.void(editor, { at: start.path })\n const endVoid = Editor.void(editor, { at: end.path })\n\n if (Range.isCollapsed(selection) && !startVoid) {\n return\n }\n\n // Create a fake selection so that we can add a Base64-encoded copy of the\n // fragment to the HTML, to decode on future pastes.\n const domRange = ReactEditor.toDOMRange(editor, selection)\n let contents = domRange.cloneContents()\n let attach = contents.childNodes[0] as HTMLElement\n\n // Make sure attach is non-empty, since empty nodes will not get copied.\n contents.childNodes.forEach(node => {\n if (node.textContent && node.textContent.trim() !== '') {\n attach = node as HTMLElement\n }\n })\n\n // COMPAT: If the end node is a void node, we need to move the end of the\n // range from the void node's spacer span, to the end of the void node's\n // content, since the spacer is before void's content in the DOM.\n if (endVoid) {\n const [voidNode] = endVoid\n const r = domRange.cloneRange()\n const domNode = ReactEditor.toDOMNode(editor, voidNode)\n r.setEndAfter(domNode)\n contents = r.cloneContents()\n }\n\n // COMPAT: If the start node is a void node, we need to attach the encoded\n // fragment to the void node's content node instead of the spacer, because\n // attaching it to empty `
/` nodes will end up having it erased by\n // most browsers. (2018/04/27)\n if (startVoid) {\n attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement\n }\n\n // Remove any zero-width space spans from the cloned DOM so that they don't\n // show up elsewhere when pasted.\n Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(\n zw => {\n const isNewline = zw.getAttribute('data-slate-zero-width') === 'n'\n zw.textContent = isNewline ? '\\n' : ''\n }\n )\n\n // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up\n // in the HTML, and can be used for intra-Slate pasting. If it's a text\n // node, wrap it in a `` so we have something to set an attribute on.\n if (isDOMText(attach)) {\n const span = document.createElement('span')\n // COMPAT: In Chrome and Safari, if we don't add the `white-space` style\n // then leading and trailing spaces will be ignored. (2017/09/21)\n span.style.whiteSpace = 'pre'\n span.appendChild(attach)\n contents.appendChild(span)\n attach = span\n }\n\n const fragment = Node.fragment(editor, selection)\n const string = JSON.stringify(fragment)\n const encoded = window.btoa(encodeURIComponent(string))\n attach.setAttribute('data-slate-fragment', encoded)\n\n // Overwriting the default functionality\n const { getFormattedSelection, getHTMLFormattedSelection } = editor\n if (\n typeof getFormattedSelection === 'function' &&\n typeof getHTMLFormattedSelection === 'function'\n ) {\n try {\n const plainText = getFormattedSelection()\n const htmlText = getHTMLFormattedSelection()\n dataTransfer.setData('text/plain', plainText)\n dataTransfer.setData('text/html', htmlText)\n return\n } catch (e) {\n // eslint-disable-next-line no-console\n console.log('Error in slate-react/src/components/editable.tsx: ', e)\n // Only setData application/x-slate-fragment as a fallback because\n // we don't want to copy the timestamps of words\n dataTransfer.setData('application/x-slate-fragment', encoded)\n }\n }\n\n // Add the content to a
so that we can get its inner HTML.\n const div = document.createElement('div')\n div.appendChild(contents)\n dataTransfer.setData('text/html', div.innerHTML)\n dataTransfer.setData('text/plain', getPlainText(div))\n}\n\n/**\n * Get a plaintext representation of the content of a node, accounting for block\n * elements which get a newline appended.\n */\n\nconst getPlainText = (domNode: DOMNode) => {\n let text = ''\n\n if (isDOMText(domNode) && domNode.nodeValue) {\n return domNode.nodeValue\n }\n\n if (isDOMElement(domNode)) {\n for (const childNode of Array.from(domNode.childNodes)) {\n text += getPlainText(childNode)\n }\n\n const display = getComputedStyle(domNode).getPropertyValue('display')\n\n if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {\n text += '\\n'\n }\n }\n\n return text\n}\n","/**\n * An auto-incrementing identifier for keys.\n */\n\nlet n = 0\n\n/**\n * A class that keeps track of a key string. We use a full class here because we\n * want to be able to use them as keys in `WeakMap` objects.\n */\n\nexport class Key {\n id: string\n\n constructor() {\n this.id = `${n++}`\n }\n}\n","import { Editor, Node, Path, Point, Range, Transforms } from 'slate'\n\nimport { Key } from '../utils/key'\nimport {\n EDITOR_TO_ELEMENT,\n ELEMENT_TO_NODE,\n IS_FOCUSED,\n IS_READ_ONLY,\n KEY_TO_ELEMENT,\n NODE_TO_INDEX,\n NODE_TO_KEY,\n NODE_TO_PARENT,\n} from '../utils/weak-maps'\nimport {\n DOMElement,\n DOMNode,\n DOMPoint,\n DOMRange,\n DOMSelection,\n DOMStaticRange,\n isDOMElement,\n normalizeDOMPoint,\n} from '../utils/dom'\n\n/**\n * A React and DOM-specific version of the `Editor` interface.\n */\n\nexport interface ReactEditor extends Editor {\n insertData: (data: DataTransfer) => void\n}\n\nexport const ReactEditor = {\n /**\n * Find a key for a Slate node.\n */\n\n findKey(editor: ReactEditor, node: Node): Key {\n let key = NODE_TO_KEY.get(node)\n\n if (!key) {\n key = new Key()\n NODE_TO_KEY.set(node, key)\n }\n\n return key\n },\n\n /**\n * Find the path of Slate node.\n */\n\n findPath(editor: ReactEditor, node: Node): Path {\n const path: Path = []\n let child = node\n\n while (true) {\n const parent = NODE_TO_PARENT.get(child)\n\n if (parent == null) {\n if (Editor.isEditor(child)) {\n return path\n } else {\n break\n }\n }\n\n const i = NODE_TO_INDEX.get(child)\n\n if (i == null) {\n break\n }\n\n path.unshift(i)\n child = parent\n }\n\n throw new Error(\n `Unable to find the path for Slate node: ${JSON.stringify(node)}`\n )\n },\n\n /**\n * Check if the editor is focused.\n */\n\n isFocused(editor: ReactEditor): boolean {\n return !!IS_FOCUSED.get(editor)\n },\n\n /**\n * Check if the editor is in read-only mode.\n */\n\n isReadOnly(editor: ReactEditor): boolean {\n return !!IS_READ_ONLY.get(editor)\n },\n\n /**\n * Blur the editor.\n */\n\n blur(editor: ReactEditor): void {\n const el = ReactEditor.toDOMNode(editor, editor)\n IS_FOCUSED.set(editor, false)\n\n if (window.document.activeElement === el) {\n el.blur()\n }\n },\n\n /**\n * Focus the editor.\n */\n\n focus(editor: ReactEditor): void {\n const el = ReactEditor.toDOMNode(editor, editor)\n IS_FOCUSED.set(editor, true)\n\n if (window.document.activeElement !== el) {\n el.focus({ preventScroll: true })\n }\n },\n\n /**\n * Deselect the editor.\n */\n\n deselect(editor: ReactEditor): void {\n const { selection } = editor\n const domSelection = window.getSelection()\n\n if (domSelection && domSelection.rangeCount > 0) {\n domSelection.removeAllRanges()\n }\n\n if (selection) {\n Transforms.deselect(editor)\n }\n },\n\n /**\n * Check if a DOM node is within the editor.\n */\n\n hasDOMNode(\n editor: ReactEditor,\n target: DOMNode,\n options: { editable?: boolean } = {}\n ): boolean {\n const { editable = false } = options\n const el = ReactEditor.toDOMNode(editor, editor)\n let element\n\n // COMPAT: In Firefox, reading `target.nodeType` will throw an error if\n // target is originating from an internal \"restricted\" element (e.g. a\n // stepper arrow on a number input). (2018/05/04)\n // https://github.com/ianstormtaylor/slate/issues/1819\n try {\n element = isDOMElement(target) ? target : target.parentElement\n } catch (err) {\n if (\n !err.message.includes('Permission denied to access property \"nodeType\"')\n ) {\n throw err\n }\n }\n\n if (!element) {\n return false\n }\n\n return (\n element.closest(`[data-slate-editor]`) === el &&\n (!editable || el.isContentEditable)\n )\n },\n\n /**\n * Insert data from a `DataTransfer` into the editor.\n */\n\n insertData(editor: ReactEditor, data: DataTransfer): void {\n editor.insertData(data)\n },\n\n /**\n * Find the native DOM element from a Slate node.\n */\n\n toDOMNode(editor: ReactEditor, node: Node): HTMLElement {\n const domNode = Editor.isEditor(node)\n ? EDITOR_TO_ELEMENT.get(editor)\n : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node))\n\n if (!domNode) {\n throw new Error(\n `Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`\n )\n }\n\n return domNode\n },\n\n /**\n * Find a native DOM selection point from a Slate point.\n */\n\n toDOMPoint(editor: ReactEditor, point: Point): DOMPoint {\n const [node] = Editor.node(editor, point.path)\n const el = ReactEditor.toDOMNode(editor, node)\n let domPoint: DOMPoint | undefined\n\n // If we're inside a void node, force the offset to 0, otherwise the zero\n // width spacing character will result in an incorrect offset of 1\n if (Editor.void(editor, { at: point })) {\n point = { path: point.path, offset: 0 }\n }\n\n // For each leaf, we need to isolate its content, which means filtering\n // to its direct text and zero-width spans. (We have to filter out any\n // other siblings that may have been rendered alongside them.)\n const selector = `[data-slate-string], [data-slate-zero-width]`\n const texts = Array.from(el.querySelectorAll(selector))\n let start = 0\n\n for (const text of texts) {\n const domNode = text.childNodes[0] as HTMLElement\n\n if (domNode == null || domNode.textContent == null) {\n continue\n }\n\n const { length } = domNode.textContent\n const attr = text.getAttribute('data-slate-length')\n const trueLength = attr == null ? length : parseInt(attr, 10)\n const end = start + trueLength\n\n if (point.offset <= end) {\n const offset = Math.min(length, Math.max(0, point.offset - start))\n domPoint = [domNode, offset]\n break\n }\n\n start = end\n }\n\n if (!domPoint) {\n throw new Error(\n `Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`\n )\n }\n\n return domPoint\n },\n\n /**\n * Find a native DOM range from a Slate `range`.\n */\n\n toDOMRange(editor: ReactEditor, range: Range): DOMRange {\n const { anchor, focus } = range\n const domAnchor = ReactEditor.toDOMPoint(editor, anchor)\n const domFocus = Range.isCollapsed(range)\n ? domAnchor\n : ReactEditor.toDOMPoint(editor, focus)\n\n const domRange = window.document.createRange()\n const start = Range.isBackward(range) ? domFocus : domAnchor\n const end = Range.isBackward(range) ? domAnchor : domFocus\n domRange.setStart(start[0], start[1])\n domRange.setEnd(end[0], end[1])\n return domRange\n },\n\n /**\n * Find a Slate node from a native DOM `element`.\n */\n\n toSlateNode(editor: ReactEditor, domNode: DOMNode): Node {\n let domEl = isDOMElement(domNode) ? domNode : domNode.parentElement\n\n if (domEl && !domEl.hasAttribute('data-slate-node')) {\n domEl = domEl.closest(`[data-slate-node]`)\n }\n\n const node = domEl ? ELEMENT_TO_NODE.get(domEl as HTMLElement) : null\n\n if (!node) {\n throw new Error(`Cannot resolve a Slate node from DOM node: ${domEl}`)\n }\n\n return node\n },\n\n /**\n * Get the target range from a DOM `event`.\n */\n\n findEventRange(editor: ReactEditor, event: any): Range {\n if ('nativeEvent' in event) {\n event = event.nativeEvent\n }\n\n const { clientX: x, clientY: y, target } = event\n\n if (x == null || y == null) {\n throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)\n }\n\n const node = ReactEditor.toSlateNode(editor, event.target)\n const path = ReactEditor.findPath(editor, node)\n\n // If the drop target is inside a void node, move it into either the\n // next or previous node, depending on which side the `x` and `y`\n // coordinates are closest to.\n if (Editor.isVoid(editor, node)) {\n const rect = target.getBoundingClientRect()\n const isPrev = editor.isInline(node)\n ? x - rect.left < rect.left + rect.width - x\n : y - rect.top < rect.top + rect.height - y\n\n const edge = Editor.point(editor, path, {\n edge: isPrev ? 'start' : 'end',\n })\n const point = isPrev\n ? Editor.before(editor, edge)\n : Editor.after(editor, edge)\n\n if (point) {\n const range = Editor.range(editor, point)\n return range\n }\n }\n\n // Else resolve a range from the caret position where the drop occured.\n let domRange\n const { document } = window\n\n // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)\n if (document.caretRangeFromPoint) {\n domRange = document.caretRangeFromPoint(x, y)\n } else {\n const position = document.caretPositionFromPoint(x, y)\n\n if (position) {\n domRange = document.createRange()\n domRange.setStart(position.offsetNode, position.offset)\n domRange.setEnd(position.offsetNode, position.offset)\n }\n }\n\n if (!domRange) {\n throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)\n }\n\n // Resolve a Slate range from the DOM range.\n const range = ReactEditor.toSlateRange(editor, domRange)\n return range\n },\n\n /**\n * Find a Slate point from a DOM selection's `domNode` and `domOffset`.\n */\n\n toSlatePoint(editor: ReactEditor, domPoint: DOMPoint): Point {\n const [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint)\n const parentNode = nearestNode.parentNode as DOMElement\n let textNode: DOMElement | null = null\n let offset = 0\n\n if (parentNode) {\n const voidNode = parentNode.closest('[data-slate-void=\"true\"]')\n let leafNode = parentNode.closest('[data-slate-leaf]')\n let domNode: DOMElement | null = null\n\n // Calculate how far into the text node the `nearestNode` is, so that we\n // can determine what the offset relative to the text node is.\n if (leafNode) {\n textNode = leafNode.closest('[data-slate-node=\"text\"]')!\n const range = window.document.createRange()\n range.setStart(textNode, 0)\n range.setEnd(nearestNode, nearestOffset)\n const contents = range.cloneContents()\n const removals = [\n ...contents.querySelectorAll('[data-slate-zero-width]'),\n ...contents.querySelectorAll('[contenteditable=false]'),\n ]\n\n removals.forEach(el => {\n el!.parentNode!.removeChild(el)\n })\n\n // COMPAT: Edge has a bug where Range.prototype.toString() will\n // convert \\n into \\r\\n. The bug causes a loop when slate-react\n // attempts to reposition its cursor to match the native position. Use\n // textContent.length instead.\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/\n offset = contents.textContent!.length\n domNode = textNode\n } else if (voidNode) {\n // For void nodes, the element with the offset key will be a cousin, not an\n // ancestor, so find it by going down from the nearest void parent.\n\n leafNode = voidNode.querySelector('[data-slate-leaf]')!\n textNode = leafNode.closest('[data-slate-node=\"text\"]')!\n domNode = leafNode\n offset = domNode.textContent!.length\n }\n\n // COMPAT: If the parent node is a Slate zero-width space, editor is\n // because the text node should have no characters. However, during IME\n // composition the ASCII characters will be prepended to the zero-width\n // space, so subtract 1 from the offset to account for the zero-width\n // space character.\n if (\n domNode &&\n offset === domNode.textContent!.length &&\n parentNode.hasAttribute('data-slate-zero-width')\n ) {\n offset--\n }\n }\n\n if (!textNode) {\n throw new Error(\n `Cannot resolve a Slate point from DOM point: ${domPoint}`\n )\n }\n\n // COMPAT: If someone is clicking from one Slate editor into another,\n // the select event fires twice, once for the old editor's `element`\n // first, and then afterwards for the correct `element`. (2017/03/03)\n const slateNode = ReactEditor.toSlateNode(editor, textNode!)\n const path = ReactEditor.findPath(editor, slateNode)\n return { path, offset }\n },\n\n /**\n * Find a Slate range from a DOM range or selection.\n */\n\n toSlateRange(\n editor: ReactEditor,\n domRange: DOMRange | DOMStaticRange | DOMSelection\n ): Range {\n const el =\n domRange instanceof Selection\n ? domRange.anchorNode\n : domRange.startContainer\n let anchorNode\n let anchorOffset\n let focusNode\n let focusOffset\n let isCollapsed\n\n if (el) {\n if (domRange instanceof Selection) {\n anchorNode = domRange.anchorNode\n anchorOffset = domRange.anchorOffset\n focusNode = domRange.focusNode\n focusOffset = domRange.focusOffset\n isCollapsed = domRange.isCollapsed\n } else {\n anchorNode = domRange.startContainer\n anchorOffset = domRange.startOffset\n focusNode = domRange.endContainer\n focusOffset = domRange.endOffset\n isCollapsed = domRange.collapsed\n }\n }\n\n if (\n anchorNode == null ||\n focusNode == null ||\n anchorOffset == null ||\n focusOffset == null\n ) {\n throw new Error(\n `Cannot resolve a Slate range from DOM range: ${domRange}`\n )\n }\n\n const anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset])\n const focus = isCollapsed\n ? anchor\n : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset])\n\n return { anchor, focus }\n },\n}\n","import { createContext, useContext } from 'react'\n\n/**\n * A React context for sharing the `focused` state of the editor.\n */\n\nexport const FocusedContext = createContext(false)\n\n/**\n * Get the current `focused` state of the editor.\n */\n\nexport const useFocused = (): boolean => {\n return useContext(FocusedContext)\n}\n","import React, { useMemo, useState, useCallback } from 'react'\nimport { Node } from 'slate'\n\nimport { ReactEditor } from '../plugin/react-editor'\nimport { FocusedContext } from '../hooks/use-focused'\nimport { EditorContext } from '../hooks/use-editor'\nimport { SlateContext } from '../hooks/use-slate'\nimport { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'\n\n/**\n * A wrapper around the provider to handle `onChange` events, because the editor\n * is a mutable singleton so it won't ever register as \"changed\" otherwise.\n */\n\nexport const Slate = (props: {\n editor: ReactEditor\n value: Node[]\n children: React.ReactNode\n onChange: (value: Node[]) => void\n [key: string]: any\n}) => {\n const { editor, children, onChange, value, ...rest } = props\n const [key, setKey] = useState(0)\n const context: [ReactEditor] = useMemo(() => {\n editor.children = value\n Object.assign(editor, rest)\n return [editor]\n }, [key, value, ...Object.values(rest)])\n\n const onContextChange = useCallback(() => {\n onChange(editor.children)\n setKey(key + 1)\n }, [key, onChange])\n\n EDITOR_TO_ON_CHANGE.set(editor, onContextChange)\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n","import ReactDOM from 'react-dom'\nimport { Editor, Node, Path, Operation, Transforms } from 'slate'\n\nimport { ReactEditor } from './react-editor'\nimport { Key } from '../utils/key'\nimport { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'\n\n/**\n * `withReact` adds React and DOM specific behaviors to the editor.\n */\n\nexport const withReact = (editor: T) => {\n const e = editor as T & ReactEditor\n const { apply, onChange } = e\n\n e.apply = (op: Operation) => {\n const matches: [Path, Key][] = []\n\n switch (op.type) {\n case 'insert_text':\n case 'remove_text':\n case 'set_node': {\n for (const [node, path] of Editor.levels(e, { at: op.path })) {\n const key = ReactEditor.findKey(e, node)\n matches.push([path, key])\n }\n\n break\n }\n\n case 'insert_node':\n case 'remove_node':\n case 'merge_node':\n case 'split_node': {\n for (const [node, path] of Editor.levels(e, {\n at: Path.parent(op.path),\n })) {\n const key = ReactEditor.findKey(e, node)\n matches.push([path, key])\n }\n\n break\n }\n\n case 'move_node': {\n // TODO\n break\n }\n }\n\n apply(op)\n\n for (const [path, key] of matches) {\n const [node] = Editor.node(e, path)\n NODE_TO_KEY.set(node, key)\n }\n }\n\n e.insertData = (data: DataTransfer) => {\n const fragment = data.getData('application/x-slate-fragment')\n\n if (fragment) {\n const decoded = decodeURIComponent(window.atob(fragment))\n const parsed = JSON.parse(decoded) as Node[]\n Transforms.insertFragment(e, parsed)\n return\n }\n\n const text = data.getData('text/plain')\n\n if (text) {\n const lines = text.split('\\n')\n let split = false\n\n for (const line of lines) {\n if (split) {\n Transforms.splitNodes(e)\n }\n\n Transforms.insertText(e, line)\n split = true\n }\n }\n }\n\n e.onChange = () => {\n // COMPAT: React doesn't batch `setState` hook calls, which means that the\n // children and selection can get out of sync for one render pass. So we\n // have to use this unstable API to ensure it batches them. (2019/12/03)\n // https://github.com/facebook/react/issues/14259#issuecomment-439702367\n ReactDOM.unstable_batchedUpdates(() => {\n const onContextChange = EDITOR_TO_ON_CHANGE.get(e)\n\n if (onContextChange) {\n onContextChange()\n }\n\n onChange()\n })\n }\n\n return e\n}\n"],"names":["Node","NODE_TO_INDEX","WeakMap","NODE_TO_PARENT","EDITOR_TO_ELEMENT","ELEMENT_TO_NODE","KEY_TO_ELEMENT","NODE_TO_ELEMENT","NODE_TO_KEY","IS_READ_ONLY","IS_FOCUSED","EDITOR_TO_ON_CHANGE","PLACEHOLDER_SYMBOL","Symbol","Text","useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","SlateText","Leaf","SelectedContext","createContext","useSelected","useContext","Element","ElementComponent","TextComponent","IS_IOS","navigator","test","userAgent","MSStream","IS_APPLE","IS_FIREFOX","IS_SAFARI","HOTKEYS","bold","compose","moveBackward","moveForward","moveWordBackward","moveWordForward","deleteBackward","deleteForward","extendBackward","extendForward","italic","splitBlock","undo","APPLE_HOTKEYS","moveLineBackward","moveLineForward","deleteLineBackward","deleteLineForward","deleteWordBackward","deleteWordForward","extendLineBackward","extendLineForward","redo","transposeCharacter","WINDOWS_HOTKEYS","create","key","generic","apple","windows","isGeneric","isKeyHotkey","isApple","isWindows","event","isBold","isCompose","isMoveBackward","isMoveForward","isDeleteBackward","isDeleteForward","isDeleteLineBackward","isDeleteLineForward","isDeleteWordBackward","isDeleteWordForward","isExtendBackward","isExtendForward","isExtendLineBackward","isExtendLineForward","isItalic","isMoveLineBackward","isMoveLineForward","isMoveWordBackward","isMoveWordForward","isRedo","isSplitBlock","isTransposeCharacter","isUndo","ReadOnlyContext","useReadOnly","isDOMComment","value","isDOMNode","nodeType","isDOMElement","isDOMText","normalizeDOMPoint","domPoint","node","offset","childNodes","length","isLast","direction","index","getEditableChild","i","textContent","parent","child","triedForward","triedBackward","getAttribute","n","Key","constructor","id","ReactEditor","findKey","editor","get","set","findPath","path","Editor","isEditor","unshift","Error","JSON","stringify","isFocused","isReadOnly","blur","el","toDOMNode","document","activeElement","focus","preventScroll","deselect","selection","domSelection","getSelection","rangeCount","removeAllRanges","Transforms","hasDOMNode","target","options","editable","element","parentElement","err","message","includes","closest","isContentEditable","insertData","data","domNode","toDOMPoint","point","void","at","selector","texts","Array","from","querySelectorAll","start","text","attr","trueLength","parseInt","end","Math","min","max","toDOMRange","range","anchor","domAnchor","domFocus","Range","isCollapsed","domRange","createRange","isBackward","setStart","setEnd","toSlateNode","domEl","hasAttribute","findEventRange","nativeEvent","clientX","x","clientY","y","isVoid","rect","getBoundingClientRect","isPrev","isInline","left","width","top","height","edge","before","after","caretRangeFromPoint","position","caretPositionFromPoint","offsetNode","toSlateRange","toSlatePoint","nearestNode","nearestOffset","parentNode","textNode","voidNode","leafNode","contents","cloneContents","removals","forEach","removeChild","querySelector","slateNode","Selection","anchorNode","startContainer","anchorOffset","focusNode","focusOffset","startOffset","endContainer","endOffset","collapsed","FocusedContext","useFocused","withReact","e","apply","onChange","op","matches","type","levels","push","Path","fragment","getData","decoded","decodeURIComponent","atob","parsed","parse","insertFragment","lines","split","line","splitNodes","insertText","ReactDOM","unstable_batchedUpdates","onContextChange"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
deleted file mode 100644
index 03955d1..0000000
--- a/dist/index.js
+++ /dev/null
@@ -1,1932 +0,0 @@
-'use strict';
-
-Object.defineProperty(exports, '__esModule', { value: true });
-
-function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
-
-var React = require('react');
-var React__default = _interopDefault(React);
-var slate = require('slate');
-var getDirection = _interopDefault(require('direction'));
-var debounce = _interopDefault(require('debounce'));
-var scrollIntoView = _interopDefault(require('scroll-into-view-if-needed'));
-var isHotkey = require('is-hotkey');
-var ReactDOM = _interopDefault(require('react-dom'));
-
-/**
- * Leaf content strings.
- */
-const String = (props) => {
- const { isLast, leaf, parent, text } = props;
- const editor = useEditor();
- const path = ReactEditor.findPath(editor, text);
- const parentPath = slate.Path.parent(path);
- // 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 React__default.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 React__default.createElement(ZeroWidthString, { isLineBreak: true });
- }
- // 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 React__default.createElement(ZeroWidthString, null);
- }
- // 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 React__default.createElement(TextString, { isTrailing: true, text: leaf.text });
- }
- return React__default.createElement(TextString, { text: leaf.text });
-};
-/**
- * Leaf strings with text in them.
- */
-const TextString = (props) => {
- const { text, isTrailing = false } = props;
- return (React__default.createElement("span", { "data-slate-string": true },
- text,
- isTrailing ? '\n' : null));
-};
-/**
- * Leaf strings without text, render as zero-width strings.
- */
-const ZeroWidthString = (props) => {
- const { length = 0, isLineBreak = false } = props;
- return (React__default.createElement("span", { "data-slate-zero-width": isLineBreak ? 'n' : 'z', "data-slate-length": length },
- '\uFEFF',
- isLineBreak ? React__default.createElement("br", null) : null));
-};
-
-/**
- * Two weak maps that allow us rebuild a path given a node. They are populated
- * at render time such that after a render occurs we can always backtrack.
- */
-var NODE_TO_INDEX = new WeakMap();
-var NODE_TO_PARENT = new WeakMap();
-/**
- * Weak maps that allow us to go between Slate nodes and DOM nodes. These
- * are used to resolve DOM event-related logic into Slate actions.
- */
-
-var EDITOR_TO_ELEMENT = new WeakMap();
-var ELEMENT_TO_NODE = new WeakMap();
-var KEY_TO_ELEMENT = new WeakMap();
-var NODE_TO_ELEMENT = new WeakMap();
-var NODE_TO_KEY = new WeakMap();
-/**
- * Weak maps for storing editor-related state.
- */
-
-var IS_READ_ONLY = new WeakMap();
-var IS_FOCUSED = new WeakMap();
-/**
- * Weak map for associating the context `onChange` context with the plugin.
- */
-
-var EDITOR_TO_ON_CHANGE = new WeakMap();
-/**
- * Symbols.
- */
-
-var PLACEHOLDER_SYMBOL = Symbol('placeholder');
-
-/**
- * Individual leaves in a text node with unique formatting.
- */
-const Leaf = (props) => {
- const { leaf, isLast, text, parent, renderLeaf = (props) => React__default.createElement(DefaultLeaf, Object.assign({}, props)), } = props;
- let children = (React__default.createElement(String, { isLast: isLast, leaf: leaf, parent: parent, text: text }));
- if (leaf[PLACEHOLDER_SYMBOL]) {
- children = (React__default.createElement(React__default.Fragment, null,
- React__default.createElement("span", { contentEditable: false, style: {
- pointerEvents: 'none',
- display: 'inline-block',
- verticalAlign: 'text-top',
- width: '0',
- maxWidth: '100%',
- whiteSpace: 'nowrap',
- opacity: '0.333',
- } }, leaf.placeholder),
- children));
- }
- // 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)
- const attributes = {
- 'data-slate-leaf': true,
- };
- return renderLeaf({ attributes, children, leaf, text });
-};
-const MemoizedLeaf = React__default.memo(Leaf, (prev, next) => {
- return (next.parent === prev.parent &&
- next.isLast === prev.isLast &&
- next.renderLeaf === prev.renderLeaf &&
- next.text === prev.text &&
- slate.Text.matches(next.leaf, prev.leaf));
-});
-/**
- * The default custom leaf renderer.
- */
-const DefaultLeaf = (props) => {
- const { attributes, children } = props;
- return React__default.createElement("span", Object.assign({}, attributes), children);
-};
-
-/**
- * Prevent warning on SSR by falling back to useEffect when window is not defined
- */
-
-var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
-
-/**
- * Text.
- */
-const Text = (props) => {
- const { decorations, isLast, parent, renderLeaf, text } = props;
- const editor = useEditor();
- const ref = React.useRef(null);
- const leaves = slate.Text.decorations(text, decorations);
- const key = ReactEditor.findKey(editor, text);
- const children = [];
- for (let i = 0; i < leaves.length; i++) {
- const leaf = leaves[i];
- children.push(React__default.createElement(MemoizedLeaf, { isLast: isLast && i === leaves.length - 1, key: `${key.id}-${i}`, leaf: leaf, text: text, parent: parent, renderLeaf: renderLeaf }));
- }
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- KEY_TO_ELEMENT.set(key, ref.current);
- NODE_TO_ELEMENT.set(text, ref.current);
- ELEMENT_TO_NODE.set(ref.current, text);
- }
- else {
- KEY_TO_ELEMENT.delete(key);
- NODE_TO_ELEMENT.delete(text);
- }
- });
- return (React__default.createElement("span", { "data-slate-node": "text", ref: ref }, children));
-};
-const MemoizedText = React__default.memo(Text, (prev, next) => {
- return (next.parent === prev.parent &&
- next.isLast === prev.isLast &&
- next.renderLeaf === prev.renderLeaf &&
- next.text === prev.text);
-});
-
-/**
- * A React context for sharing the `selected` state of an element.
- */
-
-var SelectedContext = React.createContext(false);
-/**
- * Get the current `selected` state of an element.
- */
-
-var useSelected = () => {
- return React.useContext(SelectedContext);
-};
-
-/**
- * Element.
- */
-const Element = (props) => {
- const { decorate, decorations, element, renderElement = (p) => React__default.createElement(DefaultElement, Object.assign({}, p)), renderLeaf, selection, elementIndex, } = props;
- const ref = React.useRef(null);
- const editor = useEditor();
- const readOnly = useReadOnly();
- const isInline = editor.isInline(element);
- const key = ReactEditor.findKey(editor, element);
- let children = (React__default.createElement(Children, { decorate: decorate, decorations: decorations, node: element, renderElement: renderElement, renderLeaf: renderLeaf, selection: selection }));
- // Attributes that the developer must mix into the element in their
- // custom node renderer component.
- const attributes = {
- 'data-slate-node': 'element',
- ref,
- elementIndex,
- };
- 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)) {
- const text = slate.Node.string(element);
- const 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;
- }
- const Tag = isInline ? 'span' : 'div';
- const [[text]] = slate.Node.texts(element);
- children = readOnly ? null : (React__default.createElement(Tag, { "data-slate-spacer": true, style: {
- height: '0',
- color: 'transparent',
- outline: 'none',
- position: 'absolute',
- } },
- React__default.createElement(MemoizedText, { decorations: [], isLast: false, parent: element, text: text })));
- NODE_TO_INDEX.set(text, 0);
- NODE_TO_PARENT.set(text, element);
- }
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- KEY_TO_ELEMENT.set(key, ref.current);
- NODE_TO_ELEMENT.set(element, ref.current);
- ELEMENT_TO_NODE.set(ref.current, element);
- }
- else {
- KEY_TO_ELEMENT.delete(key);
- NODE_TO_ELEMENT.delete(element);
- }
- });
- return (React__default.createElement(SelectedContext.Provider, { value: !!selection }, renderElement({ attributes, children, element })));
-};
-const MemoizedElement = React__default.memo(Element, (prev, next) => {
- return (prev.decorate === next.decorate &&
- prev.element === next.element &&
- prev.renderElement === next.renderElement &&
- prev.renderLeaf === next.renderLeaf &&
- isRangeListEqual(prev.decorations, next.decorations) &&
- (prev.selection === next.selection ||
- (!!prev.selection &&
- !!next.selection &&
- slate.Range.equals(prev.selection, next.selection))));
-});
-/**
- * The default element renderer.
- */
-const DefaultElement = (props) => {
- const { attributes, children, element } = props;
- const editor = useEditor();
- const Tag = editor.isInline(element) ? 'span' : 'div';
- return (React__default.createElement(Tag, Object.assign({}, attributes, { style: { position: 'relative' } }), children));
-};
-/**
- * Check if a list of ranges is equal to another.
- *
- * PERF: this requires the two lists to also have the ranges inside them in the
- * same order, but this is an okay constraint for us since decorations are
- * kept in order, and the odd case where they aren't is okay to re-render for.
- */
-const isRangeListEqual = (list, another) => {
- if (list.length !== another.length) {
- return false;
- }
- for (let i = 0; i < list.length; i++) {
- const range = list[i];
- const other = another[i];
- if (!slate.Range.equals(range, other)) {
- return false;
- }
- }
- return true;
-};
-
-/**
- * A React context for sharing the editor object.
- */
-const EditorContext = React.createContext(null);
-/**
- * Get the current editor object from the React context.
- */
-const useEditor = () => {
- const editor = React.useContext(EditorContext);
- if (!editor) {
- throw new Error(`The \`useEditor\` hook must be used inside the component's context.`);
- }
- return editor;
-};
-
-/**
- * Children.
- */
-const Children = (props) => {
- const { decorate, decorations, node, renderElement, renderLeaf, selection, ReactHappyWindow, reactHappyWindowProps = {}, } = props;
- const editor = useEditor();
- const path = ReactEditor.findPath(editor, node);
- const children = [];
- const isLeafBlock = slate.Element.isElement(node) &&
- !editor.isInline(node) &&
- slate.Editor.hasInlines(editor, node);
- const renderChild = (i) => {
- const p = path.concat(i);
- const n = node.children[i];
- const key = ReactEditor.findKey(editor, n);
- const range = slate.Editor.range(editor, p);
- const sel = selection && slate.Range.intersection(range, selection);
- // Commented out to improve performance. We don't use decorations
- // const ds = decorate([n, p])
- const ds = [];
- // for (const dec of decorations) {
- // const d = Range.intersection(dec, range)
- // if (d) {
- // ds.push(d)
- // }
- // }
- NODE_TO_INDEX.set(n, i);
- NODE_TO_PARENT.set(n, node);
- if (slate.Element.isElement(n)) {
- return (React__default.createElement(MemoizedElement, { decorate: decorate, decorations: ds, element: n, key: key.id, renderElement: renderElement, renderLeaf: renderLeaf, selection: sel, elementIndex: i }));
- }
- else {
- return (React__default.createElement(MemoizedText, { decorations: ds, key: key.id, isLast: isLeafBlock && i === node.children.length - 1, parent: node, renderLeaf: renderLeaf, text: n }));
- }
- };
- if (ReactHappyWindow) {
- return (React__default.createElement(ReactHappyWindow, Object.assign({ itemCount: node.children.length, renderItem: renderChild }, reactHappyWindowProps)));
- }
- for (let i = 0; i < node.children.length; i++) {
- children.push(renderChild(i));
- }
- return React__default.createElement(React__default.Fragment, null, children);
-};
-
-var IS_IOS = typeof navigator !== 'undefined' && typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-var IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
-var IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
-var IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
-
-/**
- * Hotkey mappings for each platform.
- */
-
-var HOTKEYS = {
- bold: 'mod+b',
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
- moveBackward: 'left',
- moveForward: 'right',
- moveWordBackward: 'ctrl+left',
- moveWordForward: 'ctrl+right',
- deleteBackward: 'shift?+backspace',
- deleteForward: 'shift?+delete',
- extendBackward: 'shift+left',
- extendForward: 'shift+right',
- italic: 'mod+i',
- splitBlock: 'shift?+enter',
- undo: 'mod+z'
-};
-var APPLE_HOTKEYS = {
- moveLineBackward: 'opt+up',
- moveLineForward: 'opt+down',
- moveWordBackward: 'opt+left',
- moveWordForward: 'opt+right',
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
- deleteForward: ['ctrl+delete', 'ctrl+d'],
- deleteLineBackward: 'cmd+shift?+backspace',
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
- deleteWordBackward: 'opt+shift?+backspace',
- deleteWordForward: 'opt+shift?+delete',
- extendLineBackward: 'opt+shift+up',
- extendLineForward: 'opt+shift+down',
- redo: 'cmd+shift+z',
- transposeCharacter: 'ctrl+t'
-};
-var WINDOWS_HOTKEYS = {
- deleteWordBackward: 'ctrl+shift?+backspace',
- deleteWordForward: 'ctrl+shift?+delete',
- redo: ['ctrl+y', 'ctrl+shift+z']
-};
-/**
- * Create a platform-aware hotkey checker.
- */
-
-var create = key => {
- var generic = HOTKEYS[key];
- var apple = APPLE_HOTKEYS[key];
- var windows = WINDOWS_HOTKEYS[key];
- var isGeneric = generic && isHotkey.isKeyHotkey(generic);
- var isApple = apple && isHotkey.isKeyHotkey(apple);
- var isWindows = windows && isHotkey.isKeyHotkey(windows);
- return event => {
- if (isGeneric && isGeneric(event)) return true;
- if (IS_APPLE && isApple && isApple(event)) return true;
- if (!IS_APPLE && isWindows && isWindows(event)) return true;
- return false;
- };
-};
-/**
- * Hotkeys.
- */
-
-
-var Hotkeys = {
- isBold: create('bold'),
- isCompose: create('compose'),
- isMoveBackward: create('moveBackward'),
- isMoveForward: create('moveForward'),
- isDeleteBackward: create('deleteBackward'),
- isDeleteForward: create('deleteForward'),
- isDeleteLineBackward: create('deleteLineBackward'),
- isDeleteLineForward: create('deleteLineForward'),
- isDeleteWordBackward: create('deleteWordBackward'),
- isDeleteWordForward: create('deleteWordForward'),
- isExtendBackward: create('extendBackward'),
- isExtendForward: create('extendForward'),
- isExtendLineBackward: create('extendLineBackward'),
- isExtendLineForward: create('extendLineForward'),
- isItalic: create('italic'),
- isMoveLineBackward: create('moveLineBackward'),
- isMoveLineForward: create('moveLineForward'),
- isMoveWordBackward: create('moveWordBackward'),
- isMoveWordForward: create('moveWordForward'),
- isRedo: create('redo'),
- isSplitBlock: create('splitBlock'),
- isTransposeCharacter: create('transposeCharacter'),
- isUndo: create('undo')
-};
-
-/**
- * A React context for sharing the `readOnly` state of the editor.
- */
-
-var ReadOnlyContext = React.createContext(false);
-/**
- * Get the current `readOnly` state of the editor.
- */
-
-var useReadOnly = () => {
- return React.useContext(ReadOnlyContext);
-};
-
-/**
- * A React context for sharing the editor object, in a way that re-renders the
- * context whenever changes occur.
- */
-const SlateContext = React.createContext(null);
-/**
- * Get the current editor object from the React context.
- */
-const useSlate = () => {
- const context = React.useContext(SlateContext);
- if (!context) {
- throw new Error(`The \`useSlate\` hook must be used inside the component's context.`);
- }
- const [editor] = context;
- return editor;
-};
-
-/**
- * Types.
- */
-/**
- * Check if a DOM node is a comment node.
- */
-
-var isDOMComment = value => {
- return isDOMNode(value) && value.nodeType === 8;
-};
-/**
- * Check if a DOM node is an element node.
- */
-
-var isDOMElement = value => {
- return isDOMNode(value) && value.nodeType === 1;
-};
-/**
- * Check if a value is a DOM node.
- */
-
-var isDOMNode = value => {
- return value instanceof Node;
-};
-/**
- * Check if a DOM node is an element node.
- */
-
-var isDOMText = value => {
- return isDOMNode(value) && value.nodeType === 3;
-};
-/**
- * Normalize a DOM point so that it always refers to a text node.
- */
-
-var normalizeDOMPoint = domPoint => {
- var [node, offset] = domPoint; // If it's an element node, its offset refers to the index of its children
- // including comment nodes, so try to find the right text child node.
-
- if (isDOMElement(node) && node.childNodes.length) {
- var isLast = offset === node.childNodes.length;
- var direction = isLast ? 'backward' : 'forward';
- var index = isLast ? offset - 1 : offset;
- node = getEditableChild(node, index, direction); // If the node has children, traverse until we have a leaf node. Leaf nodes
- // can be either text nodes, or other void DOM nodes.
-
- while (isDOMElement(node) && node.childNodes.length) {
- var i = isLast ? node.childNodes.length - 1 : 0;
- node = getEditableChild(node, i, direction);
- } // Determine the new offset inside the text node.
-
-
- offset = isLast && node.textContent != null ? node.textContent.length : 0;
- } // Return the node and offset.
-
-
- return [node, offset];
-};
-/**
- * Get the nearest editable child at `index` in a `parent`, preferring
- * `direction`.
- */
-
-var getEditableChild = (parent, index, direction) => {
- var {
- childNodes
- } = parent;
- var child = childNodes[index];
- var i = index;
- var triedForward = false;
- var triedBackward = false; // While the child is a comment node, or an element node with no children,
- // keep iterating to find a sibling non-void, non-comment node.
-
- while (isDOMComment(child) || isDOMElement(child) && child.childNodes.length === 0 || isDOMElement(child) && child.getAttribute('contenteditable') === 'false') {
- if (triedForward && triedBackward) {
- break;
- }
-
- if (i >= childNodes.length) {
- triedForward = true;
- i = index - 1;
- direction = 'backward';
- continue;
- }
-
- if (i < 0) {
- triedBackward = true;
- i = index + 1;
- direction = 'forward';
- continue;
- }
-
- child = childNodes[i];
- i += direction === 'forward' ? 1 : -1;
- }
-
- return child;
-};
-
-/**
- * Editable.
- */
-const Editable = (props) => {
- const { autoFocus, decorate = defaultDecorate, onDOMBeforeInput: propsOnDOMBeforeInput, placeholder, readOnly = false, renderElement, renderLeaf, style = {}, as: Component = 'div', ReactHappyWindow, reactHappyWindowProps, happyWindowRef, ...attributes } = props;
- const editor = useSlate();
- const ref = React.useRef(null);
- // Update internal state on each render.
- IS_READ_ONLY.set(editor, readOnly);
- // Keep track of some state for the event handler logic.
- const state = React.useMemo(() => ({
- isComposing: false,
- isUpdatingSelection: false,
- latestElement: null,
- }), []);
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- EDITOR_TO_ELEMENT.set(editor, ref.current);
- NODE_TO_ELEMENT.set(editor, ref.current);
- ELEMENT_TO_NODE.set(ref.current, editor);
- }
- else {
- NODE_TO_ELEMENT.delete(editor);
- }
- });
- // 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
- useIsomorphicLayoutEffect(() => {
- window.document.addEventListener('selectionchange', onDOMSelectionChange);
- return () => {
- window.document.removeEventListener('selectionchange', onDOMSelectionChange);
- };
- }, []);
- // 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
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- // @ts-ignore The `beforeinput` event isn't recognized.
- ref.current.addEventListener('beforeinput', onDOMBeforeInput);
- }
- return () => {
- if (ref.current) {
- // @ts-ignore The `beforeinput` event isn't recognized.
- ref.current.removeEventListener('beforeinput', onDOMBeforeInput);
- }
- };
- }, []);
- // Whenever the editor updates, make sure the DOM selection state is in sync.
- useIsomorphicLayoutEffect(() => {
- const { selection } = editor;
- const domSelection = window.getSelection();
- if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {
- return;
- }
- const hasDomSelection = domSelection.type !== 'None';
- // If the DOM selection is properly unset, we're done.
- if (!selection && !hasDomSelection) {
- return;
- }
- const newDomRange = selection && ReactEditor.toDOMRange(editor, selection);
- // If the DOM selection is already correct, we're done.
- if (hasDomSelection &&
- newDomRange &&
- isRangeEqual(domSelection.getRangeAt(0), newDomRange)) {
- return;
- }
- // Otherwise the DOM selection is out of sync, so update it.
- const el = ReactEditor.toDOMNode(editor, editor);
- state.isUpdatingSelection = true;
- domSelection.removeAllRanges();
- if (newDomRange) {
- domSelection.addRange(newDomRange);
- const leafEl = newDomRange.startContainer.parentElement;
- scrollIntoView(leafEl, { scrollMode: 'if-needed' });
- }
- setTimeout(() => {
- // COMPAT: In Firefox, it's not enough to create a range, you also need
- // to focus the contenteditable element too. (2016/11/16)
- if (newDomRange && IS_FIREFOX) {
- el.focus();
- }
- state.isUpdatingSelection = false;
- });
- });
- // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
- // needs to be manually focused.
- React.useEffect(() => {
- if (ref.current && autoFocus) {
- ref.current.focus();
- }
- }, [autoFocus]);
- // 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
- const onDOMBeforeInput = React.useCallback((event) => {
- if (!readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isDOMEventHandled(event, propsOnDOMBeforeInput)) {
- const { selection } = editor;
- const { inputType: type } = event;
- const data = event.dataTransfer || event.data || undefined;
- // These two types occur while a user is composing text and can't be
- // cancelled. Let them through and wait for the composition to end.
- if (type === 'insertCompositionText' ||
- type === 'deleteCompositionText') {
- return;
- }
- event.preventDefault();
- // 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 (!type.startsWith('delete') || type.startsWith('deleteBy')) {
- const [targetRange] = event.getTargetRanges();
- if (targetRange) {
- const range = ReactEditor.toSlateRange(editor, targetRange);
- if (!range) {
- return;
- }
- if (!selection || !slate.Range.equals(selection, range)) {
- slate.Transforms.select(editor, range);
- }
- }
- }
- // 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')) {
- slate.Editor.deleteFragment(editor);
- 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':
- case 'insertParagraph': {
- slate.Editor.insertBreak(editor);
- break;
- }
- case 'insertFromComposition':
- case 'insertFromDrop':
- case 'insertFromPaste':
- case 'insertFromYank':
- case 'insertReplacementText':
- case 'insertText': {
- if (data instanceof DataTransfer) {
- ReactEditor.insertData(editor, data);
- }
- else if (typeof data === 'string') {
- slate.Editor.insertText(editor, data);
- }
- break;
- }
- }
- }
- }, []);
- // 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.
- const onDOMSelectionChange = React.useCallback(debounce(() => {
- if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {
- const { activeElement } = window.document;
- const el = ReactEditor.toDOMNode(editor, editor);
- const domSelection = window.getSelection();
- const domRange = domSelection &&
- domSelection.rangeCount > 0 &&
- domSelection.getRangeAt(0);
- if (activeElement === el) {
- state.latestElement = activeElement;
- IS_FOCUSED.set(editor, true);
- }
- else {
- IS_FOCUSED.delete(editor);
- }
- if (domRange &&
- hasEditableTarget(editor, domRange.startContainer) &&
- hasEditableTarget(editor, domRange.endContainer)) {
- const range = ReactEditor.toSlateRange(editor, domRange);
- slate.Transforms.select(editor, range);
- }
- else {
- slate.Transforms.deselect(editor);
- }
- }
- }, 100), []);
- const decorations = decorate([editor, []]);
- if (placeholder &&
- editor.children.length === 1 &&
- Array.from(slate.Node.texts(editor)).length === 1 &&
- slate.Node.string(editor) === '') {
- const start = slate.Editor.start(editor, []);
- decorations.push({
- [PLACEHOLDER_SYMBOL]: true,
- placeholder,
- anchor: start,
- focus: start,
- });
- }
- return (React__default.createElement(ReadOnlyContext.Provider, { value: readOnly },
- React__default.createElement(Component
- // COMPAT: The Grammarly Chrome extension works by changing the DOM
- // out from under `contenteditable` elements, which leads to weird
- // behaviors so we have to disable it like editor. (2017/04/24)
- , Object.assign({ "data-gramm": false, role: readOnly ? undefined : 'textbox' }, attributes, {
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we'd
- // have to use hacks to make these replacement-based features work.
- spellCheck: IS_FIREFOX ? undefined : attributes.spellCheck, autoCorrect: IS_FIREFOX ? undefined : attributes.autoCorrect, autoCapitalize: IS_FIREFOX ? undefined : attributes.autoCapitalize, "data-slate-editor": true, "data-slate-node": "value", contentEditable: readOnly ? undefined : true, suppressContentEditableWarning: true, ref: ref, style: {
- // Prevent the default outline styles.
- outline: 'none',
- // Preserve adjacent whitespace and new lines.
- whiteSpace: 'pre-wrap',
- // Allow words to break if they are too long.
- wordWrap: 'break-word',
- // Allow for passed-in styles to override anything.
- ...style,
- }, onBeforeInput: React.useCallback((event) => {
- // COMPAT: Firefox doesn'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 (IS_FIREFOX && !readOnly && ReactEditor.isFocused(editor)) {
- event.preventDefault();
- const text = event.data;
- slate.Editor.insertText(editor, text);
- }
- }, [readOnly]), onBlur: React.useCallback((event) => {
- if (readOnly ||
- state.isUpdatingSelection ||
- !hasEditableTarget(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.
- if (state.latestElement === window.document.activeElement) {
- return;
- }
- const { relatedTarget } = event;
- const 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
- // 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 (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 &&
- isDOMNode(relatedTarget) &&
- ReactEditor.hasDOMNode(editor, relatedTarget)) {
- const node = ReactEditor.toSlateNode(editor, relatedTarget);
- if (slate.Element.isElement(node) && !editor.isVoid(node)) {
- return;
- }
- }
- IS_FOCUSED.delete(editor);
- }, [readOnly, attributes.onBlur]), onClick: React.useCallback((event) => {
- if (!readOnly &&
- hasTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onClick) &&
- isDOMNode(event.target)) {
- const node = ReactEditor.toSlateNode(editor, event.target);
- const path = ReactEditor.findPath(editor, node);
- const start = slate.Editor.start(editor, path);
- if (slate.Editor.void(editor, { at: start })) {
- const range = slate.Editor.range(editor, start);
- slate.Transforms.select(editor, range);
- }
- }
- }, [readOnly, attributes.onClick]), onCompositionEnd: React.useCallback((event) => {
- if (hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCompositionEnd)) {
- state.isComposing = false;
- // 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 (!IS_SAFARI && !IS_FIREFOX && event.data) {
- slate.Editor.insertText(editor, event.data);
- }
- }
- }, [attributes.onCompositionEnd]), onCompositionStart: React.useCallback((event) => {
- if (hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCompositionStart)) {
- state.isComposing = true;
- }
- }, [attributes.onCompositionStart]), onCopy: React.useCallback((event) => {
- if (hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCopy)) {
- event.preventDefault();
- setFragmentData(event.clipboardData, editor);
- }
- }, [attributes.onCopy]), onCut: React.useCallback((event) => {
- if (!readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCut)) {
- event.preventDefault();
- setFragmentData(event.clipboardData, editor);
- const { selection } = editor;
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- }
- }, [readOnly, attributes.onCut]), onDragOver: React.useCallback((event) => {
- if (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.
- const node = ReactEditor.toSlateNode(editor, event.target);
- if (slate.Editor.isVoid(editor, node)) {
- event.preventDefault();
- }
- }
- }, [attributes.onDragOver]), onDragStart: React.useCallback((event) => {
- if (hasTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onDragStart)) {
- const node = ReactEditor.toSlateNode(editor, event.target);
- const path = ReactEditor.findPath(editor, node);
- const voidMatch = slate.Editor.void(editor, { at: path });
- // 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) {
- const range = slate.Editor.range(editor, path);
- slate.Transforms.select(editor, range);
- }
- setFragmentData(event.dataTransfer, editor);
- }
- }, [attributes.onDragStart]), onDrop: React.useCallback((event) => {
- if (hasTarget(editor, event.target) &&
- !readOnly &&
- !isEventHandled(event, attributes.onDrop)) {
- // COMPAT: Firefox doesn't fire `beforeinput` events at all, and
- // Chromium browsers don't properly fire them for files being
- // dropped into a `contenteditable`. (2019/11/26)
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
- if (IS_FIREFOX ||
- (!IS_SAFARI && event.dataTransfer.files.length > 0)) {
- event.preventDefault();
- const range = ReactEditor.findEventRange(editor, event);
- const data = event.dataTransfer;
- slate.Transforms.select(editor, range);
- ReactEditor.insertData(editor, data);
- }
- }
- }, [readOnly, attributes.onDrop]), onFocus: React.useCallback((event) => {
- if (!readOnly &&
- !state.isUpdatingSelection &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onFocus)) {
- const el = ReactEditor.toDOMNode(editor, editor);
- state.latestElement = window.document.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 (IS_FIREFOX && event.target !== el) {
- el.focus();
- return;
- }
- IS_FOCUSED.set(editor, true);
- }
- }, [readOnly, attributes.onFocus]), onKeyDown: React.useCallback((event) => {
- if (!readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onKeyDown)) {
- const { nativeEvent } = event;
- const { selection } = editor;
- const element = editor.children[selection !== null ? selection.focus.path[0] : 0];
- const 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 (Hotkeys.isRedo(nativeEvent)) {
- event.preventDefault();
- if (editor.redo) {
- editor.redo();
- }
- return;
- }
- if (Hotkeys.isUndo(nativeEvent)) {
- event.preventDefault();
- if (editor.undo) {
- editor.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 (Hotkeys.isMoveLineBackward(nativeEvent)) {
- event.preventDefault();
- slate.Transforms.move(editor, { unit: 'line', reverse: true });
- return;
- }
- if (Hotkeys.isMoveLineForward(nativeEvent)) {
- event.preventDefault();
- slate.Transforms.move(editor, { unit: 'line' });
- return;
- }
- if (Hotkeys.isExtendLineBackward(nativeEvent)) {
- event.preventDefault();
- slate.Transforms.move(editor, {
- unit: 'line',
- edge: 'focus',
- reverse: true,
- });
- return;
- }
- if (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 (Hotkeys.isMoveBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isCollapsed(selection)) {
- const { anchor } = selection;
- if (anchor.offset === 1 && anchor.path[1] > 0) {
- // Hack to position the cursor at the end of the previous text node
- slate.Transforms.move(editor, { reverse: !isRTL, distance: 2 });
- slate.Transforms.move(editor, { reverse: isRTL });
- }
- else {
- slate.Transforms.move(editor, { reverse: !isRTL });
- }
- }
- else {
- slate.Transforms.collapse(editor, { edge: 'start' });
- }
- return;
- }
- if (Hotkeys.isMoveForward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isCollapsed(selection)) {
- slate.Transforms.move(editor, { reverse: isRTL });
- }
- else {
- slate.Transforms.collapse(editor, { edge: 'end' });
- }
- return;
- }
- if (Hotkeys.isMoveWordBackward(nativeEvent)) {
- event.preventDefault();
- slate.Transforms.move(editor, { unit: 'word', reverse: !isRTL });
- return;
- }
- if (Hotkeys.isMoveWordForward(nativeEvent)) {
- event.preventDefault();
- slate.Transforms.move(editor, { unit: 'word', reverse: isRTL });
- return;
- }
- // COMPAT: Firefox doesn'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 (IS_FIREFOX) {
- // 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 (Hotkeys.isBold(nativeEvent) ||
- Hotkeys.isItalic(nativeEvent) ||
- Hotkeys.isTransposeCharacter(nativeEvent)) {
- event.preventDefault();
- return;
- }
- if (Hotkeys.isSplitBlock(nativeEvent)) {
- event.preventDefault();
- slate.Editor.insertBreak(editor);
- return;
- }
- if (Hotkeys.isDeleteBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- else {
- slate.Editor.deleteBackward(editor);
- }
- return;
- }
- if (Hotkeys.isDeleteForward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- else {
- slate.Editor.deleteForward(editor);
- }
- return;
- }
- if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- else {
- slate.Editor.deleteBackward(editor, { unit: 'line' });
- }
- return;
- }
- if (Hotkeys.isDeleteLineForward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- else {
- slate.Editor.deleteForward(editor, { unit: 'line' });
- }
- return;
- }
- if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- else {
- slate.Editor.deleteBackward(editor, { unit: 'word' });
- }
- return;
- }
- if (Hotkeys.isDeleteWordForward(nativeEvent)) {
- event.preventDefault();
- if (selection && slate.Range.isExpanded(selection)) {
- slate.Editor.deleteFragment(editor);
- }
- else {
- slate.Editor.deleteForward(editor, { unit: 'word' });
- }
- return;
- }
- }
- }
- }, [readOnly, attributes.onKeyDown]), onPaste: React.useCallback((event) => {
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we
- // fall back to React's `onPaste` here instead.
- if (IS_FIREFOX &&
- !readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onPaste)) {
- event.preventDefault();
- ReactEditor.insertData(editor, event.clipboardData);
- }
- }, [readOnly, attributes.onPaste]) }),
- React__default.createElement(Children, { decorate: decorate, decorations: decorations, node: editor, renderElement: renderElement, renderLeaf: renderLeaf, selection: editor.selection, ReactHappyWindow: ReactHappyWindow, reactHappyWindowProps: reactHappyWindowProps }))));
-};
-/**
- * A default memoized decorate function.
- */
-const defaultDecorate = () => [];
-/**
- * Check if two DOM range objects are equal.
- */
-const isRangeEqual = (a, b) => {
- return ((a.startContainer === b.startContainer &&
- a.startOffset === b.startOffset &&
- a.endContainer === b.endContainer &&
- a.endOffset === b.endOffset) ||
- (a.startContainer === b.endContainer &&
- a.startOffset === b.endOffset &&
- a.endContainer === b.startContainer &&
- a.endOffset === b.startOffset));
-};
-/**
- * Check if the target is in the editor.
- */
-const hasTarget = (editor, target) => {
- return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target);
-};
-/**
- * Check if the target is editable and in the editor.
- */
-const hasEditableTarget = (editor, target) => {
- return (isDOMNode(target) &&
- ReactEditor.hasDOMNode(editor, target, { editable: true }));
-};
-/**
- * Check if an event is overrided by a handler.
- */
-const isEventHandled = (event, handler) => {
- if (!handler) {
- return false;
- }
- handler(event);
- return event.isDefaultPrevented() || event.isPropagationStopped();
-};
-/**
- * Check if a DOM event is overrided by a handler.
- */
-const isDOMEventHandled = (event, handler) => {
- if (!handler) {
- return false;
- }
- handler(event);
- return event.defaultPrevented;
-};
-/**
- * Set the currently selected fragment to the clipboard.
- */
-const setFragmentData = (dataTransfer, editor) => {
- const { selection } = editor;
- if (!selection) {
- return;
- }
- const [start, end] = slate.Range.edges(selection);
- const startVoid = slate.Editor.void(editor, { at: start.path });
- const endVoid = slate.Editor.void(editor, { at: end.path });
- if (slate.Range.isCollapsed(selection) && !startVoid) {
- return;
- }
- // Create a fake selection so that we can add a Base64-encoded copy of the
- // fragment to the HTML, to decode on future pastes.
- const domRange = ReactEditor.toDOMRange(editor, selection);
- let contents = domRange.cloneContents();
- let attach = contents.childNodes[0];
- // Make sure attach is non-empty, since empty nodes will not get copied.
- contents.childNodes.forEach(node => {
- if (node.textContent && node.textContent.trim() !== '') {
- attach = node;
- }
- });
- // COMPAT: If the end node is a void node, we need to move the end of the
- // range from the void node's spacer span, to the end of the void node's
- // content, since the spacer is before void's content in the DOM.
- if (endVoid) {
- const [voidNode] = endVoid;
- const r = domRange.cloneRange();
- const domNode = ReactEditor.toDOMNode(editor, voidNode);
- r.setEndAfter(domNode);
- contents = r.cloneContents();
- }
- // COMPAT: If the start node is a void node, we need to attach the encoded
- // fragment to the void node's content node instead of the spacer, because
- // attaching it to empty `
/` nodes will end up having it erased by
- // most browsers. (2018/04/27)
- if (startVoid) {
- attach = contents.querySelector('[data-slate-spacer]');
- }
- // Remove any zero-width space spans from the cloned DOM so that they don't
- // show up elsewhere when pasted.
- Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
- const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
- zw.textContent = isNewline ? '\n' : '';
- });
- // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
- // in the HTML, and can be used for intra-Slate pasting. If it's a text
- // node, wrap it in a `` so we have something to set an attribute on.
- if (isDOMText(attach)) {
- const span = document.createElement('span');
- // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
- // then leading and trailing spaces will be ignored. (2017/09/21)
- span.style.whiteSpace = 'pre';
- span.appendChild(attach);
- contents.appendChild(span);
- attach = span;
- }
- const fragment = slate.Node.fragment(editor, selection);
- const string = JSON.stringify(fragment);
- const encoded = window.btoa(encodeURIComponent(string));
- attach.setAttribute('data-slate-fragment', encoded);
- // Overwriting the default functionality
- const { getFormattedSelection, getHTMLFormattedSelection } = editor;
- if (typeof getFormattedSelection === 'function' &&
- typeof getHTMLFormattedSelection === 'function') {
- try {
- const plainText = getFormattedSelection();
- const htmlText = getHTMLFormattedSelection();
- dataTransfer.setData('text/plain', plainText);
- dataTransfer.setData('text/html', htmlText);
- return;
- }
- catch (e) {
- // eslint-disable-next-line no-console
- console.log('Error in slate-react/src/components/editable.tsx: ', e);
- // Only setData application/x-slate-fragment as a fallback because
- // we don't want to copy the timestamps of words
- dataTransfer.setData('application/x-slate-fragment', encoded);
- }
- }
- // Add the content to a
so that we can get its inner HTML.
- const div = document.createElement('div');
- div.appendChild(contents);
- dataTransfer.setData('text/html', div.innerHTML);
- dataTransfer.setData('text/plain', getPlainText(div));
-};
-/**
- * Get a plaintext representation of the content of a node, accounting for block
- * elements which get a newline appended.
- */
-const getPlainText = (domNode) => {
- let text = '';
- if (isDOMText(domNode) && domNode.nodeValue) {
- return domNode.nodeValue;
- }
- if (isDOMElement(domNode)) {
- for (const childNode of Array.from(domNode.childNodes)) {
- text += getPlainText(childNode);
- }
- const display = getComputedStyle(domNode).getPropertyValue('display');
- if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
- text += '\n';
- }
- }
- return text;
-};
-
-/**
- * An auto-incrementing identifier for keys.
- */
-var n = 0;
-/**
- * A class that keeps track of a key string. We use a full class here because we
- * want to be able to use them as keys in `WeakMap` objects.
- */
-
-class Key {
- constructor() {
- this.id = "".concat(n++);
- }
-
-}
-
-var ReactEditor = {
- /**
- * Find a key for a Slate node.
- */
- findKey(editor, node) {
- var key = NODE_TO_KEY.get(node);
-
- if (!key) {
- key = new Key();
- NODE_TO_KEY.set(node, key);
- }
-
- return key;
- },
-
- /**
- * Find the path of Slate node.
- */
- findPath(editor, node) {
- var path = [];
- var child = node;
-
- while (true) {
- var parent = NODE_TO_PARENT.get(child);
-
- if (parent == null) {
- if (slate.Editor.isEditor(child)) {
- return path;
- } else {
- break;
- }
- }
-
- var i = NODE_TO_INDEX.get(child);
-
- if (i == null) {
- break;
- }
-
- path.unshift(i);
- child = parent;
- }
-
- throw new Error("Unable to find the path for Slate node: ".concat(JSON.stringify(node)));
- },
-
- /**
- * Check if the editor is focused.
- */
- isFocused(editor) {
- return !!IS_FOCUSED.get(editor);
- },
-
- /**
- * Check if the editor is in read-only mode.
- */
- isReadOnly(editor) {
- return !!IS_READ_ONLY.get(editor);
- },
-
- /**
- * Blur the editor.
- */
- blur(editor) {
- var el = ReactEditor.toDOMNode(editor, editor);
- IS_FOCUSED.set(editor, false);
-
- if (window.document.activeElement === el) {
- el.blur();
- }
- },
-
- /**
- * Focus the editor.
- */
- focus(editor) {
- var el = ReactEditor.toDOMNode(editor, editor);
- IS_FOCUSED.set(editor, true);
-
- if (window.document.activeElement !== el) {
- el.focus({
- preventScroll: true
- });
- }
- },
-
- /**
- * Deselect the editor.
- */
- deselect(editor) {
- var {
- selection
- } = editor;
- var domSelection = window.getSelection();
-
- if (domSelection && domSelection.rangeCount > 0) {
- domSelection.removeAllRanges();
- }
-
- if (selection) {
- slate.Transforms.deselect(editor);
- }
- },
-
- /**
- * Check if a DOM node is within the editor.
- */
- hasDOMNode(editor, target) {
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
- var {
- editable = false
- } = options;
- var el = ReactEditor.toDOMNode(editor, editor);
- var element; // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
- // target is originating from an internal "restricted" element (e.g. a
- // stepper arrow on a number input). (2018/05/04)
- // https://github.com/ianstormtaylor/slate/issues/1819
-
- try {
- element = isDOMElement(target) ? target : target.parentElement;
- } catch (err) {
- if (!err.message.includes('Permission denied to access property "nodeType"')) {
- throw err;
- }
- }
-
- if (!element) {
- return false;
- }
-
- return element.closest("[data-slate-editor]") === el && (!editable || el.isContentEditable);
- },
-
- /**
- * Insert data from a `DataTransfer` into the editor.
- */
- insertData(editor, data) {
- editor.insertData(data);
- },
-
- /**
- * Find the native DOM element from a Slate node.
- */
- toDOMNode(editor, node) {
- var domNode = slate.Editor.isEditor(node) ? EDITOR_TO_ELEMENT.get(editor) : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node));
-
- if (!domNode) {
- throw new Error("Cannot resolve a DOM node from Slate node: ".concat(JSON.stringify(node)));
- }
-
- return domNode;
- },
-
- /**
- * Find a native DOM selection point from a Slate point.
- */
- toDOMPoint(editor, point) {
- var [node] = slate.Editor.node(editor, point.path);
- var el = ReactEditor.toDOMNode(editor, node);
- var domPoint; // If we're inside a void node, force the offset to 0, otherwise the zero
- // width spacing character will result in an incorrect offset of 1
-
- if (slate.Editor.void(editor, {
- at: point
- })) {
- point = {
- path: point.path,
- offset: 0
- };
- } // For each leaf, we need to isolate its content, which means filtering
- // to its direct text and zero-width spans. (We have to filter out any
- // other siblings that may have been rendered alongside them.)
-
-
- var selector = "[data-slate-string], [data-slate-zero-width]";
- var texts = Array.from(el.querySelectorAll(selector));
- var start = 0;
-
- for (var text of texts) {
- var domNode = text.childNodes[0];
-
- if (domNode == null || domNode.textContent == null) {
- continue;
- }
-
- var {
- length
- } = domNode.textContent;
- var attr = text.getAttribute('data-slate-length');
- var trueLength = attr == null ? length : parseInt(attr, 10);
- var end = start + trueLength;
-
- if (point.offset <= end) {
- var offset = Math.min(length, Math.max(0, point.offset - start));
- domPoint = [domNode, offset];
- break;
- }
-
- start = end;
- }
-
- if (!domPoint) {
- throw new Error("Cannot resolve a DOM point from Slate point: ".concat(JSON.stringify(point)));
- }
-
- return domPoint;
- },
-
- /**
- * Find a native DOM range from a Slate `range`.
- */
- toDOMRange(editor, range) {
- var {
- anchor,
- focus
- } = range;
- var domAnchor = ReactEditor.toDOMPoint(editor, anchor);
- var domFocus = slate.Range.isCollapsed(range) ? domAnchor : ReactEditor.toDOMPoint(editor, focus);
- var domRange = window.document.createRange();
- var start = slate.Range.isBackward(range) ? domFocus : domAnchor;
- var end = slate.Range.isBackward(range) ? domAnchor : domFocus;
- domRange.setStart(start[0], start[1]);
- domRange.setEnd(end[0], end[1]);
- return domRange;
- },
-
- /**
- * Find a Slate node from a native DOM `element`.
- */
- toSlateNode(editor, domNode) {
- var domEl = isDOMElement(domNode) ? domNode : domNode.parentElement;
-
- if (domEl && !domEl.hasAttribute('data-slate-node')) {
- domEl = domEl.closest("[data-slate-node]");
- }
-
- var node = domEl ? ELEMENT_TO_NODE.get(domEl) : null;
-
- if (!node) {
- throw new Error("Cannot resolve a Slate node from DOM node: ".concat(domEl));
- }
-
- return node;
- },
-
- /**
- * Get the target range from a DOM `event`.
- */
- findEventRange(editor, event) {
- if ('nativeEvent' in event) {
- event = event.nativeEvent;
- }
-
- var {
- clientX: x,
- clientY: y,
- target
- } = event;
-
- if (x == null || y == null) {
- throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
- }
-
- var node = ReactEditor.toSlateNode(editor, event.target);
- var path = ReactEditor.findPath(editor, node); // If the drop target is inside a void node, move it into either the
- // next or previous node, depending on which side the `x` and `y`
- // coordinates are closest to.
-
- if (slate.Editor.isVoid(editor, node)) {
- var rect = target.getBoundingClientRect();
- var isPrev = editor.isInline(node) ? x - rect.left < rect.left + rect.width - x : y - rect.top < rect.top + rect.height - y;
- var edge = slate.Editor.point(editor, path, {
- edge: isPrev ? 'start' : 'end'
- });
- var point = isPrev ? slate.Editor.before(editor, edge) : slate.Editor.after(editor, edge);
-
- if (point) {
- var _range = slate.Editor.range(editor, point);
-
- return _range;
- }
- } // Else resolve a range from the caret position where the drop occured.
-
-
- var domRange;
- var {
- document
- } = window; // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
-
- if (document.caretRangeFromPoint) {
- domRange = document.caretRangeFromPoint(x, y);
- } else {
- var position = document.caretPositionFromPoint(x, y);
-
- if (position) {
- domRange = document.createRange();
- domRange.setStart(position.offsetNode, position.offset);
- domRange.setEnd(position.offsetNode, position.offset);
- }
- }
-
- if (!domRange) {
- throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
- } // Resolve a Slate range from the DOM range.
-
-
- var range = ReactEditor.toSlateRange(editor, domRange);
- return range;
- },
-
- /**
- * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
- */
- toSlatePoint(editor, domPoint) {
- var [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint);
- var parentNode = nearestNode.parentNode;
- var textNode = null;
- var offset = 0;
-
- if (parentNode) {
- var voidNode = parentNode.closest('[data-slate-void="true"]');
- var leafNode = parentNode.closest('[data-slate-leaf]');
- var domNode = null; // Calculate how far into the text node the `nearestNode` is, so that we
- // can determine what the offset relative to the text node is.
-
- if (leafNode) {
- textNode = leafNode.closest('[data-slate-node="text"]');
- var range = window.document.createRange();
- range.setStart(textNode, 0);
- range.setEnd(nearestNode, nearestOffset);
- var contents = range.cloneContents();
- var removals = [...contents.querySelectorAll('[data-slate-zero-width]'), ...contents.querySelectorAll('[contenteditable=false]')];
- removals.forEach(el => {
- el.parentNode.removeChild(el);
- }); // COMPAT: Edge has a bug where Range.prototype.toString() will
- // convert \n into \r\n. The bug causes a loop when slate-react
- // attempts to reposition its cursor to match the native position. Use
- // textContent.length instead.
- // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
-
- offset = contents.textContent.length;
- domNode = textNode;
- } else if (voidNode) {
- // For void nodes, the element with the offset key will be a cousin, not an
- // ancestor, so find it by going down from the nearest void parent.
- leafNode = voidNode.querySelector('[data-slate-leaf]');
- textNode = leafNode.closest('[data-slate-node="text"]');
- domNode = leafNode;
- offset = domNode.textContent.length;
- } // COMPAT: If the parent node is a Slate zero-width space, editor is
- // because the text node should have no characters. However, during IME
- // composition the ASCII characters will be prepended to the zero-width
- // space, so subtract 1 from the offset to account for the zero-width
- // space character.
-
-
- if (domNode && offset === domNode.textContent.length && parentNode.hasAttribute('data-slate-zero-width')) {
- offset--;
- }
- }
-
- if (!textNode) {
- throw new Error("Cannot resolve a Slate point from DOM point: ".concat(domPoint));
- } // COMPAT: If someone is clicking from one Slate editor into another,
- // the select event fires twice, once for the old editor's `element`
- // first, and then afterwards for the correct `element`. (2017/03/03)
-
-
- var slateNode = ReactEditor.toSlateNode(editor, textNode);
- var path = ReactEditor.findPath(editor, slateNode);
- return {
- path,
- offset
- };
- },
-
- /**
- * Find a Slate range from a DOM range or selection.
- */
- toSlateRange(editor, domRange) {
- var el = domRange instanceof Selection ? domRange.anchorNode : domRange.startContainer;
- var anchorNode;
- var anchorOffset;
- var focusNode;
- var focusOffset;
- var isCollapsed;
-
- if (el) {
- if (domRange instanceof Selection) {
- anchorNode = domRange.anchorNode;
- anchorOffset = domRange.anchorOffset;
- focusNode = domRange.focusNode;
- focusOffset = domRange.focusOffset;
- isCollapsed = domRange.isCollapsed;
- } else {
- anchorNode = domRange.startContainer;
- anchorOffset = domRange.startOffset;
- focusNode = domRange.endContainer;
- focusOffset = domRange.endOffset;
- isCollapsed = domRange.collapsed;
- }
- }
-
- if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
- throw new Error("Cannot resolve a Slate range from DOM range: ".concat(domRange));
- }
-
- var anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset]);
- var focus = isCollapsed ? anchor : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset]);
- return {
- anchor,
- focus
- };
- }
-
-};
-
-/**
- * A React context for sharing the `focused` state of the editor.
- */
-
-var FocusedContext = React.createContext(false);
-/**
- * Get the current `focused` state of the editor.
- */
-
-var useFocused = () => {
- return React.useContext(FocusedContext);
-};
-
-/**
- * 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.
- */
-const Slate = (props) => {
- const { editor, children, onChange, value, ...rest } = props;
- const [key, setKey] = React.useState(0);
- const context = React.useMemo(() => {
- editor.children = value;
- Object.assign(editor, rest);
- return [editor];
- }, [key, value, ...Object.values(rest)]);
- const onContextChange = React.useCallback(() => {
- onChange(editor.children);
- setKey(key + 1);
- }, [key, onChange]);
- EDITOR_TO_ON_CHANGE.set(editor, onContextChange);
- return (React__default.createElement(SlateContext.Provider, { value: context },
- React__default.createElement(EditorContext.Provider, { value: editor },
- React__default.createElement(FocusedContext.Provider, { value: ReactEditor.isFocused(editor) }, children))));
-};
-
-/**
- * `withReact` adds React and DOM specific behaviors to the editor.
- */
-
-var withReact = editor => {
- var e = editor;
- var {
- apply,
- onChange
- } = e;
-
- e.apply = op => {
- var matches = [];
-
- switch (op.type) {
- case 'insert_text':
- case 'remove_text':
- case 'set_node':
- {
- for (var [node, path] of slate.Editor.levels(e, {
- at: op.path
- })) {
- var key = ReactEditor.findKey(e, node);
- matches.push([path, key]);
- }
-
- break;
- }
-
- case 'insert_node':
- case 'remove_node':
- case 'merge_node':
- case 'split_node':
- {
- for (var [_node, _path] of slate.Editor.levels(e, {
- at: slate.Path.parent(op.path)
- })) {
- var _key = ReactEditor.findKey(e, _node);
-
- matches.push([_path, _key]);
- }
-
- break;
- }
- }
-
- apply(op);
-
- for (var [_path2, _key2] of matches) {
- var [_node2] = slate.Editor.node(e, _path2);
- NODE_TO_KEY.set(_node2, _key2);
- }
- };
-
- e.insertData = data => {
- var fragment = data.getData('application/x-slate-fragment');
-
- if (fragment) {
- var decoded = decodeURIComponent(window.atob(fragment));
- var parsed = JSON.parse(decoded);
- slate.Transforms.insertFragment(e, parsed);
- return;
- }
-
- var text = data.getData('text/plain');
-
- if (text) {
- var lines = text.split('\n');
- var split = false;
-
- for (var line of lines) {
- if (split) {
- slate.Transforms.splitNodes(e);
- }
-
- slate.Transforms.insertText(e, line);
- split = true;
- }
- }
- };
-
- e.onChange = () => {
- // COMPAT: React 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
- ReactDOM.unstable_batchedUpdates(() => {
- var onContextChange = EDITOR_TO_ON_CHANGE.get(e);
-
- if (onContextChange) {
- onContextChange();
- }
-
- onChange();
- });
- };
-
- return e;
-};
-
-exports.DefaultElement = DefaultElement;
-exports.DefaultLeaf = DefaultLeaf;
-exports.Editable = Editable;
-exports.ReactEditor = ReactEditor;
-exports.Slate = Slate;
-exports.useEditor = useEditor;
-exports.useFocused = useFocused;
-exports.useReadOnly = useReadOnly;
-exports.useSelected = useSelected;
-exports.useSlate = useSlate;
-exports.withReact = withReact;
-//# sourceMappingURL=index.js.map
diff --git a/dist/index.js.map b/dist/index.js.map
deleted file mode 100644
index 5f9ff05..0000000
--- a/dist/index.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"index.js","sources":["../src/components/string.tsx","../src/utils/weak-maps.ts","../src/components/leaf.tsx","../src/hooks/use-isomorphic-layout-effect.ts","../src/components/text.tsx","../src/hooks/use-selected.ts","../src/components/element.tsx","../src/hooks/use-editor.tsx","../src/components/children.tsx","../src/utils/environment.ts","../src/utils/hotkeys.ts","../src/hooks/use-read-only.ts","../src/hooks/use-slate.tsx","../src/utils/dom.ts","../src/components/editable.tsx","../src/utils/key.ts","../src/plugin/react-editor.ts","../src/hooks/use-focused.ts","../src/components/slate.tsx","../src/plugin/with-react.ts"],"sourcesContent":["import React from 'react'\nimport { Editor, Text, Path, Element, Node } from 'slate'\n\nimport { ReactEditor, useEditor } from '..'\n\n/**\n * Leaf content strings.\n */\n\nconst String = (props: {\n isLast: boolean\n leaf: Text\n parent: Element\n text: Text\n}) => {\n const { isLast, leaf, parent, text } = props\n const editor = useEditor()\n const path = ReactEditor.findPath(editor, text)\n const parentPath = Path.parent(path)\n\n // COMPAT: Render text inside void nodes with a zero-width space.\n // So the node can contain selection but the text is not visible.\n if (editor.isVoid(parent)) {\n return \n }\n\n // COMPAT: If this is the last text node in an empty block, render a zero-\n // width space that will convert into a line break when copying and pasting\n // to support expected plain text.\n if (\n leaf.text === '' &&\n parent.children[parent.children.length - 1] === text &&\n !editor.isInline(parent) &&\n Editor.string(editor, parentPath) === ''\n ) {\n return \n }\n\n // COMPAT: If the text is empty, it's because it's on the edge of an inline\n // node, so we render a zero-width space so that the selection can be\n // inserted next to it still.\n if (leaf.text === '') {\n return \n }\n\n // COMPAT: Browsers will collapse trailing new lines at the end of blocks,\n // so we need to add an extra trailing new lines to prevent that.\n if (isLast && leaf.text.slice(-1) === '\\n') {\n return \n }\n\n return \n}\n\n/**\n * Leaf strings with text in them.\n */\n\nconst TextString = (props: { text: string; isTrailing?: boolean }) => {\n const { text, isTrailing = false } = props\n return (\n \n {text}\n {isTrailing ? '\\n' : null}\n \n )\n}\n\n/**\n * Leaf strings without text, render as zero-width strings.\n */\n\nconst ZeroWidthString = (props: { length?: number; isLineBreak?: boolean }) => {\n const { length = 0, isLineBreak = false } = props\n return (\n \n {'\\uFEFF'}\n {isLineBreak ? : null}\n \n )\n}\n\nexport default String\n","import { Node, Ancestor, Editor, Range } from 'slate'\n\nimport { Key } from './key'\n\n/**\n * Two weak maps that allow us rebuild a path given a node. They are populated\n * at render time such that after a render occurs we can always backtrack.\n */\n\nexport const NODE_TO_INDEX: WeakMap = new WeakMap()\nexport const NODE_TO_PARENT: WeakMap = new WeakMap()\n\n/**\n * Weak maps that allow us to go between Slate nodes and DOM nodes. These\n * are used to resolve DOM event-related logic into Slate actions.\n */\n\nexport const EDITOR_TO_ELEMENT: WeakMap = new WeakMap()\nexport const EDITOR_TO_PLACEHOLDER: WeakMap = new WeakMap()\nexport const ELEMENT_TO_NODE: WeakMap = new WeakMap()\nexport const KEY_TO_ELEMENT: WeakMap = new WeakMap()\nexport const NODE_TO_ELEMENT: WeakMap = new WeakMap()\nexport const NODE_TO_KEY: WeakMap = new WeakMap()\n\n/**\n * Weak maps for storing editor-related state.\n */\n\nexport const IS_READ_ONLY: WeakMap = new WeakMap()\nexport const IS_FOCUSED: WeakMap = new WeakMap()\nexport const IS_DRAGGING: WeakMap = new WeakMap()\nexport const IS_CLICKING: WeakMap = new WeakMap()\n\n/**\n * Weak map for associating the context `onChange` context with the plugin.\n */\n\nexport const EDITOR_TO_ON_CHANGE = new WeakMap void>()\n\n/**\n * Symbols.\n */\n\nexport const PLACEHOLDER_SYMBOL = (Symbol('placeholder') as unknown) as string\n","import React from 'react'\nimport { Text, Element } from 'slate'\n\nimport String from './string'\nimport { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'\nimport { RenderLeafProps } from './editable'\n\n/**\n * Individual leaves in a text node with unique formatting.\n */\n\nconst Leaf = (props: {\n isLast: boolean\n leaf: Text\n parent: Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n text: Text\n}) => {\n const {\n leaf,\n isLast,\n text,\n parent,\n renderLeaf = (props: RenderLeafProps) => ,\n } = props\n\n let children = (\n \n )\n\n if (leaf[PLACEHOLDER_SYMBOL]) {\n children = (\n \n \n {leaf.placeholder}\n \n {children}\n \n )\n }\n\n // COMPAT: Having the `data-` attributes on these leaf elements ensures that\n // in certain misbehaving browsers they aren't weirdly cloned/destroyed by\n // contenteditable behaviors. (2019/05/08)\n const attributes: {\n 'data-slate-leaf': true\n } = {\n 'data-slate-leaf': true,\n }\n\n return renderLeaf({ attributes, children, leaf, text })\n}\n\nconst MemoizedLeaf = React.memo(Leaf, (prev, next) => {\n return (\n next.parent === prev.parent &&\n next.isLast === prev.isLast &&\n next.renderLeaf === prev.renderLeaf &&\n next.text === prev.text &&\n Text.matches(next.leaf, prev.leaf)\n )\n})\n\n/**\n * The default custom leaf renderer.\n */\n\nexport const DefaultLeaf = (props: RenderLeafProps) => {\n const { attributes, children } = props\n return {children}\n}\n\nexport default MemoizedLeaf\n","import { useLayoutEffect, useEffect } from 'react'\n\n/**\n * Prevent warning on SSR by falling back to useEffect when window is not defined\n */\nexport const useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? useLayoutEffect : useEffect\n","import React, { useRef } from 'react'\nimport { Range, Element, Text as SlateText } from 'slate'\n\nimport Leaf from './leaf'\nimport { ReactEditor, useEditor } from '..'\nimport { RenderLeafProps } from './editable'\nimport { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'\nimport {\n KEY_TO_ELEMENT,\n NODE_TO_ELEMENT,\n ELEMENT_TO_NODE,\n} from '../utils/weak-maps'\n\n/**\n * Text.\n */\n\nconst Text = (props: {\n decorations: Range[]\n isLast: boolean\n parent: Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n text: SlateText\n}) => {\n const { decorations, isLast, parent, renderLeaf, text } = props\n const editor = useEditor()\n const ref = useRef(null)\n const leaves = SlateText.decorations(text, decorations)\n const key = ReactEditor.findKey(editor, text)\n const children = []\n\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i]\n\n children.push(\n \n )\n }\n\n // Update element-related weak maps with the DOM element ref.\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n KEY_TO_ELEMENT.set(key, ref.current)\n NODE_TO_ELEMENT.set(text, ref.current)\n ELEMENT_TO_NODE.set(ref.current, text)\n } else {\n KEY_TO_ELEMENT.delete(key)\n NODE_TO_ELEMENT.delete(text)\n }\n })\n\n return (\n \n {children}\n \n )\n}\n\nconst MemoizedText = React.memo(Text, (prev, next) => {\n return (\n next.parent === prev.parent &&\n next.isLast === prev.isLast &&\n next.renderLeaf === prev.renderLeaf &&\n next.text === prev.text\n )\n})\n\nexport default MemoizedText\n","import { createContext, useContext } from 'react'\n\n/**\n * A React context for sharing the `selected` state of an element.\n */\n\nexport const SelectedContext = createContext(false)\n\n/**\n * Get the current `selected` state of an element.\n */\n\nexport const useSelected = (): boolean => {\n return useContext(SelectedContext)\n}\n","import React, { useRef } from 'react'\nimport getDirection from 'direction'\nimport { Editor, Node, Range, NodeEntry, Element as SlateElement } from 'slate'\n\nimport Text from './text'\nimport Children from './children'\nimport { ReactEditor, useEditor, useReadOnly } from '..'\nimport { SelectedContext } from '../hooks/use-selected'\nimport { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'\nimport {\n NODE_TO_ELEMENT,\n ELEMENT_TO_NODE,\n NODE_TO_PARENT,\n NODE_TO_INDEX,\n KEY_TO_ELEMENT,\n} from '../utils/weak-maps'\nimport { RenderElementProps, RenderLeafProps } from './editable'\n\n/**\n * Element.\n */\n\nconst Element = (props: {\n decorate: (entry: NodeEntry) => Range[]\n decorations: Range[]\n element: SlateElement\n renderElement?: (props: RenderElementProps) => JSX.Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n selection: Range | null\n elementIndex: Number\n}) => {\n const {\n decorate,\n decorations,\n element,\n renderElement = (p: RenderElementProps) => ,\n renderLeaf,\n selection,\n elementIndex,\n } = props\n const ref = useRef(null)\n const editor = useEditor()\n const readOnly = useReadOnly()\n const isInline = editor.isInline(element)\n const key = ReactEditor.findKey(editor, element)\n\n let children: JSX.Element | null = (\n \n )\n\n // Attributes that the developer must mix into the element in their\n // custom node renderer component.\n const attributes: {\n 'data-slate-node': 'element'\n 'data-slate-void'?: true\n 'data-slate-inline'?: true\n contentEditable?: false\n dir?: 'rtl'\n ref: any\n elementIndex: Number\n } = {\n 'data-slate-node': 'element',\n ref,\n elementIndex,\n }\n\n if (isInline) {\n attributes['data-slate-inline'] = true\n }\n\n // If it's a block node with inline children, add the proper `dir` attribute\n // for text direction.\n if (!isInline && Editor.hasInlines(editor, element)) {\n const text = Node.string(element)\n const dir = getDirection(text)\n\n if (dir === 'rtl') {\n attributes.dir = dir\n }\n }\n\n // If it's a void node, wrap the children in extra void-specific elements.\n if (Editor.isVoid(editor, element)) {\n attributes['data-slate-void'] = true\n\n if (!readOnly && isInline) {\n attributes.contentEditable = false\n }\n\n const Tag = isInline ? 'span' : 'div'\n const [[text]] = Node.texts(element)\n\n children = readOnly ? null : (\n \n \n \n )\n\n NODE_TO_INDEX.set(text, 0)\n NODE_TO_PARENT.set(text, element)\n }\n\n // Update element-related weak maps with the DOM element ref.\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n KEY_TO_ELEMENT.set(key, ref.current)\n NODE_TO_ELEMENT.set(element, ref.current)\n ELEMENT_TO_NODE.set(ref.current, element)\n } else {\n KEY_TO_ELEMENT.delete(key)\n NODE_TO_ELEMENT.delete(element)\n }\n })\n\n return (\n \n {renderElement({ attributes, children, element })}\n \n )\n}\n\nconst MemoizedElement = React.memo(Element, (prev, next) => {\n return (\n prev.decorate === next.decorate &&\n prev.element === next.element &&\n prev.renderElement === next.renderElement &&\n prev.renderLeaf === next.renderLeaf &&\n isRangeListEqual(prev.decorations, next.decorations) &&\n (prev.selection === next.selection ||\n (!!prev.selection &&\n !!next.selection &&\n Range.equals(prev.selection, next.selection)))\n )\n})\n\n/**\n * The default element renderer.\n */\n\nexport const DefaultElement = (props: RenderElementProps) => {\n const { attributes, children, element } = props\n const editor = useEditor()\n const Tag = editor.isInline(element) ? 'span' : 'div'\n return (\n \n {children}\n \n )\n}\n\n/**\n * Check if a list of ranges is equal to another.\n *\n * PERF: this requires the two lists to also have the ranges inside them in the\n * same order, but this is an okay constraint for us since decorations are\n * kept in order, and the odd case where they aren't is okay to re-render for.\n */\n\nconst isRangeListEqual = (list: Range[], another: Range[]): boolean => {\n if (list.length !== another.length) {\n return false\n }\n\n for (let i = 0; i < list.length; i++) {\n const range = list[i]\n const other = another[i]\n\n if (!Range.equals(range, other)) {\n return false\n }\n }\n\n return true\n}\n\nexport default MemoizedElement\n","import { createContext, useContext } from 'react'\n\nimport { ReactEditor } from '../plugin/react-editor'\n\n/**\n * A React context for sharing the editor object.\n */\n\nexport const EditorContext = createContext(null)\n\n/**\n * Get the current editor object from the React context.\n */\n\nexport const useEditor = () => {\n const editor = useContext(EditorContext)\n\n if (!editor) {\n throw new Error(\n `The \\`useEditor\\` hook must be used inside the component's context.`\n )\n }\n\n return editor\n}\n","import React from 'react'\nimport { Editor, Range, Element, NodeEntry, Ancestor, Descendant } from 'slate'\n\nimport ElementComponent from './element'\nimport TextComponent from './text'\nimport { ReactEditor } from '..'\nimport { useEditor } from '../hooks/use-editor'\nimport { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'\nimport { RenderElementProps, RenderLeafProps } from './editable'\n\n/**\n * Children.\n */\n\nconst Children = (props: {\n decorate: (entry: NodeEntry) => Range[]\n decorations: Range[]\n node: Ancestor\n renderElement?: (props: RenderElementProps) => JSX.Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n selection: Range | null\n ReactHappyWindow: React.Component | undefined\n reactHappyWindowProps: Object | undefined\n}) => {\n const {\n decorate,\n decorations,\n node,\n renderElement,\n renderLeaf,\n selection,\n ReactHappyWindow,\n reactHappyWindowProps = {},\n } = props\n const editor = useEditor()\n const path = ReactEditor.findPath(editor, node)\n const children = []\n const isLeafBlock =\n Element.isElement(node) &&\n !editor.isInline(node) &&\n Editor.hasInlines(editor, node)\n\n const renderChild = (i: number) => {\n const p = path.concat(i)\n const n = node.children[i] as Descendant\n const key = ReactEditor.findKey(editor, n)\n const range = Editor.range(editor, p)\n const sel = selection && Range.intersection(range, selection)\n\n // Commented out to improve performance. We don't use decorations\n // const ds = decorate([n, p])\n const ds = [] as Range[]\n // for (const dec of decorations) {\n // const d = Range.intersection(dec, range)\n\n // if (d) {\n // ds.push(d)\n // }\n // }\n\n NODE_TO_INDEX.set(n, i)\n NODE_TO_PARENT.set(n, node)\n\n if (Element.isElement(n)) {\n return (\n \n )\n } else {\n return (\n \n )\n }\n }\n\n if (ReactHappyWindow) {\n return (\n \n )\n }\n\n for (let i = 0; i < node.children.length; i++) {\n children.push(renderChild(i))\n }\n\n return {children}\n}\n\nexport default Children\n","export const IS_IOS =\n typeof navigator !== 'undefined' &&\n typeof window !== 'undefined' &&\n /iPad|iPhone|iPod/.test(navigator.userAgent) &&\n !window.MSStream\n\nexport const IS_APPLE =\n typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)\n\nexport const IS_FIREFOX =\n typeof navigator !== 'undefined' &&\n /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)\n\nexport const IS_SAFARI =\n typeof navigator !== 'undefined' &&\n /Version\\/[\\d\\.]+.*Safari/.test(navigator.userAgent)\n","import { isKeyHotkey } from 'is-hotkey'\nimport { IS_APPLE } from './environment'\n\n/**\n * Hotkey mappings for each platform.\n */\n\nconst HOTKEYS = {\n bold: 'mod+b',\n compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],\n moveBackward: 'left',\n moveForward: 'right',\n moveWordBackward: 'ctrl+left',\n moveWordForward: 'ctrl+right',\n deleteBackward: 'shift?+backspace',\n deleteForward: 'shift?+delete',\n extendBackward: 'shift+left',\n extendForward: 'shift+right',\n italic: 'mod+i',\n splitBlock: 'shift?+enter',\n undo: 'mod+z',\n}\n\nconst APPLE_HOTKEYS = {\n moveLineBackward: 'opt+up',\n moveLineForward: 'opt+down',\n moveWordBackward: 'opt+left',\n moveWordForward: 'opt+right',\n deleteBackward: ['ctrl+backspace', 'ctrl+h'],\n deleteForward: ['ctrl+delete', 'ctrl+d'],\n deleteLineBackward: 'cmd+shift?+backspace',\n deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],\n deleteWordBackward: 'opt+shift?+backspace',\n deleteWordForward: 'opt+shift?+delete',\n extendLineBackward: 'opt+shift+up',\n extendLineForward: 'opt+shift+down',\n redo: 'cmd+shift+z',\n transposeCharacter: 'ctrl+t',\n}\n\nconst WINDOWS_HOTKEYS = {\n deleteWordBackward: 'ctrl+shift?+backspace',\n deleteWordForward: 'ctrl+shift?+delete',\n redo: ['ctrl+y', 'ctrl+shift+z'],\n}\n\n/**\n * Create a platform-aware hotkey checker.\n */\n\nconst create = (key: string) => {\n const generic = HOTKEYS[key]\n const apple = APPLE_HOTKEYS[key]\n const windows = WINDOWS_HOTKEYS[key]\n const isGeneric = generic && isKeyHotkey(generic)\n const isApple = apple && isKeyHotkey(apple)\n const isWindows = windows && isKeyHotkey(windows)\n\n return (event: KeyboardEvent) => {\n if (isGeneric && isGeneric(event)) return true\n if (IS_APPLE && isApple && isApple(event)) return true\n if (!IS_APPLE && isWindows && isWindows(event)) return true\n return false\n }\n}\n\n/**\n * Hotkeys.\n */\n\nexport default {\n isBold: create('bold'),\n isCompose: create('compose'),\n isMoveBackward: create('moveBackward'),\n isMoveForward: create('moveForward'),\n isDeleteBackward: create('deleteBackward'),\n isDeleteForward: create('deleteForward'),\n isDeleteLineBackward: create('deleteLineBackward'),\n isDeleteLineForward: create('deleteLineForward'),\n isDeleteWordBackward: create('deleteWordBackward'),\n isDeleteWordForward: create('deleteWordForward'),\n isExtendBackward: create('extendBackward'),\n isExtendForward: create('extendForward'),\n isExtendLineBackward: create('extendLineBackward'),\n isExtendLineForward: create('extendLineForward'),\n isItalic: create('italic'),\n isMoveLineBackward: create('moveLineBackward'),\n isMoveLineForward: create('moveLineForward'),\n isMoveWordBackward: create('moveWordBackward'),\n isMoveWordForward: create('moveWordForward'),\n isRedo: create('redo'),\n isSplitBlock: create('splitBlock'),\n isTransposeCharacter: create('transposeCharacter'),\n isUndo: create('undo'),\n}\n","import { createContext, useContext } from 'react'\n\n/**\n * A React context for sharing the `readOnly` state of the editor.\n */\n\nexport const ReadOnlyContext = createContext(false)\n\n/**\n * Get the current `readOnly` state of the editor.\n */\n\nexport const useReadOnly = (): boolean => {\n return useContext(ReadOnlyContext)\n}\n","import { createContext, useContext } from 'react'\n\nimport { ReactEditor } from '../plugin/react-editor'\n\n/**\n * A React context for sharing the editor object, in a way that re-renders the\n * context whenever changes occur.\n */\n\nexport const SlateContext = createContext<[ReactEditor] | null>(null)\n\n/**\n * Get the current editor object from the React context.\n */\n\nexport const useSlate = () => {\n const context = useContext(SlateContext)\n\n if (!context) {\n throw new Error(\n `The \\`useSlate\\` hook must be used inside the component's context.`\n )\n }\n\n const [editor] = context\n return editor\n}\n","/**\n * Types.\n */\n\n// COMPAT: This is required to prevent TypeScript aliases from doing some very\n// weird things for Slate's types with the same name as globals. (2019/11/27)\n// https://github.com/microsoft/TypeScript/issues/35002\nimport DOMNode = globalThis.Node\nimport DOMComment = globalThis.Comment\nimport DOMElement = globalThis.Element\nimport DOMText = globalThis.Text\nimport DOMRange = globalThis.Range\nimport DOMSelection = globalThis.Selection\nimport DOMStaticRange = globalThis.StaticRange\nexport {\n DOMNode,\n DOMComment,\n DOMElement,\n DOMText,\n DOMRange,\n DOMSelection,\n DOMStaticRange,\n}\n\nexport type DOMPoint = [Node, number]\n\n/**\n * Check if a DOM node is a comment node.\n */\n\nexport const isDOMComment = (value: any): value is DOMComment => {\n return isDOMNode(value) && value.nodeType === 8\n}\n\n/**\n * Check if a DOM node is an element node.\n */\n\nexport const isDOMElement = (value: any): value is DOMElement => {\n return isDOMNode(value) && value.nodeType === 1\n}\n\n/**\n * Check if a value is a DOM node.\n */\n\nexport const isDOMNode = (value: any): value is DOMNode => {\n return value instanceof Node\n}\n\n/**\n * Check if a DOM node is an element node.\n */\n\nexport const isDOMText = (value: any): value is DOMText => {\n return isDOMNode(value) && value.nodeType === 3\n}\n\n/**\n * Normalize a DOM point so that it always refers to a text node.\n */\n\nexport const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => {\n let [node, offset] = domPoint\n\n // If it's an element node, its offset refers to the index of its children\n // including comment nodes, so try to find the right text child node.\n if (isDOMElement(node) && node.childNodes.length) {\n const isLast = offset === node.childNodes.length\n const direction = isLast ? 'backward' : 'forward'\n const index = isLast ? offset - 1 : offset\n node = getEditableChild(node, index, direction)\n\n // If the node has children, traverse until we have a leaf node. Leaf nodes\n // can be either text nodes, or other void DOM nodes.\n while (isDOMElement(node) && node.childNodes.length) {\n const i = isLast ? node.childNodes.length - 1 : 0\n node = getEditableChild(node, i, direction)\n }\n\n // Determine the new offset inside the text node.\n offset = isLast && node.textContent != null ? node.textContent.length : 0\n }\n\n // Return the node and offset.\n return [node, offset]\n}\n\n/**\n * Get the nearest editable child at `index` in a `parent`, preferring\n * `direction`.\n */\n\nexport const getEditableChild = (\n parent: DOMElement,\n index: number,\n direction: 'forward' | 'backward'\n): DOMNode => {\n const { childNodes } = parent\n let child = childNodes[index]\n let i = index\n let triedForward = false\n let triedBackward = false\n\n // While the child is a comment node, or an element node with no children,\n // keep iterating to find a sibling non-void, non-comment node.\n while (\n isDOMComment(child) ||\n (isDOMElement(child) && child.childNodes.length === 0) ||\n (isDOMElement(child) && child.getAttribute('contenteditable') === 'false')\n ) {\n if (triedForward && triedBackward) {\n break\n }\n\n if (i >= childNodes.length) {\n triedForward = true\n i = index - 1\n direction = 'backward'\n continue\n }\n\n if (i < 0) {\n triedBackward = true\n i = index + 1\n direction = 'forward'\n continue\n }\n\n child = childNodes[i]\n i += direction === 'forward' ? 1 : -1\n }\n\n return child\n}\n","import React, { useEffect, useRef, useMemo, useCallback } from 'react'\nimport {\n Editor,\n Element,\n NodeEntry,\n Node,\n Range,\n Text,\n Transforms,\n} from 'slate'\nimport getDirection from 'direction'\nimport debounce from 'debounce'\nimport scrollIntoView from 'scroll-into-view-if-needed'\n\nimport Children from './children'\nimport Hotkeys from '../utils/hotkeys'\nimport { IS_FIREFOX, IS_SAFARI } from '../utils/environment'\nimport { ReactEditor } from '..'\nimport { ReadOnlyContext } from '../hooks/use-read-only'\nimport { useSlate } from '../hooks/use-slate'\nimport { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'\nimport {\n DOMElement,\n DOMNode,\n DOMRange,\n isDOMElement,\n isDOMNode,\n isDOMText,\n DOMStaticRange,\n} from '../utils/dom'\nimport {\n EDITOR_TO_ELEMENT,\n ELEMENT_TO_NODE,\n IS_READ_ONLY,\n NODE_TO_ELEMENT,\n IS_FOCUSED,\n PLACEHOLDER_SYMBOL,\n} from '../utils/weak-maps'\n\n/**\n * `RenderElementProps` are passed to the `renderElement` handler.\n */\n\nexport interface RenderElementProps {\n children: any\n element: Element\n attributes: {\n 'data-slate-node': 'element'\n 'data-slate-inline'?: true\n 'data-slate-void'?: true\n dir?: 'rtl'\n ref: any\n }\n}\n\n/**\n * `RenderLeafProps` are passed to the `renderLeaf` handler.\n */\n\nexport interface RenderLeafProps {\n children: any\n leaf: Text\n text: Text\n attributes: {\n 'data-slate-leaf': true\n }\n}\n\n/**\n * `EditableProps` are passed to the `` component.\n */\n\nexport type EditableProps = {\n decorate?: (entry: NodeEntry) => Range[]\n onDOMBeforeInput?: (event: Event) => void\n placeholder?: string\n readOnly?: boolean\n role?: string\n style?: React.CSSProperties\n renderElement?: (props: RenderElementProps) => JSX.Element\n renderLeaf?: (props: RenderLeafProps) => JSX.Element\n as?: React.ElementType\n ReactHappyWindow?: React.Component\n reactHappyWindowProps?: object\n} & React.TextareaHTMLAttributes\n\n/**\n * Editable.\n */\n\nexport const Editable = (props: EditableProps) => {\n const {\n autoFocus,\n decorate = defaultDecorate,\n onDOMBeforeInput: propsOnDOMBeforeInput,\n placeholder,\n readOnly = false,\n renderElement,\n renderLeaf,\n style = {},\n as: Component = 'div',\n ReactHappyWindow,\n reactHappyWindowProps,\n happyWindowRef,\n ...attributes\n } = props\n const editor = useSlate()\n const ref = useRef(null)\n\n // Update internal state on each render.\n IS_READ_ONLY.set(editor, readOnly)\n\n // Keep track of some state for the event handler logic.\n const state = useMemo(\n () => ({\n isComposing: false,\n isUpdatingSelection: false,\n latestElement: null as DOMElement | null,\n }),\n []\n )\n\n // Update element-related weak maps with the DOM element ref.\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n EDITOR_TO_ELEMENT.set(editor, ref.current)\n NODE_TO_ELEMENT.set(editor, ref.current)\n ELEMENT_TO_NODE.set(ref.current, editor)\n } else {\n NODE_TO_ELEMENT.delete(editor)\n }\n })\n\n // Attach a native DOM event handler for `selectionchange`, because React's\n // built-in `onSelect` handler doesn't fire for all selection changes. It's a\n // leaky polyfill that only fires on keypresses or clicks. Instead, we want to\n // fire for any change to the selection inside the editor. (2019/11/04)\n // https://github.com/facebook/react/issues/5785\n useIsomorphicLayoutEffect(() => {\n window.document.addEventListener('selectionchange', onDOMSelectionChange)\n\n return () => {\n window.document.removeEventListener(\n 'selectionchange',\n onDOMSelectionChange\n )\n }\n }, [])\n\n // Attach a native DOM event handler for `beforeinput` events, because React's\n // built-in `onBeforeInput` is actually a leaky polyfill that doesn't expose\n // real `beforeinput` events sadly... (2019/11/04)\n // https://github.com/facebook/react/issues/11211\n useIsomorphicLayoutEffect(() => {\n if (ref.current) {\n // @ts-ignore The `beforeinput` event isn't recognized.\n ref.current.addEventListener('beforeinput', onDOMBeforeInput)\n }\n\n return () => {\n if (ref.current) {\n // @ts-ignore The `beforeinput` event isn't recognized.\n ref.current.removeEventListener('beforeinput', onDOMBeforeInput)\n }\n }\n }, [])\n\n // Whenever the editor updates, make sure the DOM selection state is in sync.\n useIsomorphicLayoutEffect(() => {\n const { selection } = editor\n const domSelection = window.getSelection()\n\n if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {\n return\n }\n\n const hasDomSelection = domSelection.type !== 'None'\n\n // If the DOM selection is properly unset, we're done.\n if (!selection && !hasDomSelection) {\n return\n }\n\n const newDomRange = selection && ReactEditor.toDOMRange(editor, selection)\n\n // If the DOM selection is already correct, we're done.\n if (\n hasDomSelection &&\n newDomRange &&\n isRangeEqual(domSelection.getRangeAt(0), newDomRange)\n ) {\n return\n }\n\n // Otherwise the DOM selection is out of sync, so update it.\n const el = ReactEditor.toDOMNode(editor, editor)\n state.isUpdatingSelection = true\n domSelection.removeAllRanges()\n\n if (newDomRange) {\n domSelection.addRange(newDomRange!)\n const leafEl = newDomRange.startContainer.parentElement!\n scrollIntoView(leafEl, { scrollMode: 'if-needed' })\n }\n\n setTimeout(() => {\n // COMPAT: In Firefox, it's not enough to create a range, you also need\n // to focus the contenteditable element too. (2016/11/16)\n if (newDomRange && IS_FIREFOX) {\n el.focus()\n }\n\n state.isUpdatingSelection = false\n })\n })\n\n // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it\n // needs to be manually focused.\n useEffect(() => {\n if (ref.current && autoFocus) {\n ref.current.focus()\n }\n }, [autoFocus])\n\n // Listen on the native `beforeinput` event to get real \"Level 2\" events. This\n // is required because React's `beforeinput` is fake and never really attaches\n // to the real event sadly. (2019/11/01)\n // https://github.com/facebook/react/issues/11211\n const onDOMBeforeInput = useCallback(\n (\n event: Event & {\n data: string | null\n dataTransfer: DataTransfer | null\n getTargetRanges(): DOMStaticRange[]\n inputType: string\n isComposing: boolean\n }\n ) => {\n if (\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isDOMEventHandled(event, propsOnDOMBeforeInput)\n ) {\n const { selection } = editor\n const { inputType: type } = event\n const data = event.dataTransfer || event.data || undefined\n\n // These two types occur while a user is composing text and can't be\n // cancelled. Let them through and wait for the composition to end.\n if (\n type === 'insertCompositionText' ||\n type === 'deleteCompositionText'\n ) {\n return\n }\n\n event.preventDefault()\n\n // COMPAT: For the deleting forward/backward input types we don't want\n // to change the selection because it is the range that will be deleted,\n // and those commands determine that for themselves.\n if (!type.startsWith('delete') || type.startsWith('deleteBy')) {\n const [targetRange] = event.getTargetRanges()\n\n if (targetRange) {\n const range = ReactEditor.toSlateRange(editor, targetRange)\n if (!range) {\n return\n }\n if (!selection || !Range.equals(selection, range)) {\n Transforms.select(editor, range)\n }\n }\n }\n\n // COMPAT: If the selection is expanded, even if the command seems like\n // a delete forward/backward command it should delete the selection.\n if (\n selection &&\n Range.isExpanded(selection) &&\n type.startsWith('delete')\n ) {\n Editor.deleteFragment(editor)\n return\n }\n\n switch (type) {\n case 'deleteByComposition':\n case 'deleteByCut':\n case 'deleteByDrag': {\n Editor.deleteFragment(editor)\n break\n }\n\n case 'deleteContent':\n case 'deleteContentForward': {\n Editor.deleteForward(editor)\n break\n }\n\n case 'deleteContentBackward': {\n Editor.deleteBackward(editor)\n break\n }\n\n case 'deleteEntireSoftLine': {\n Editor.deleteBackward(editor, { unit: 'line' })\n Editor.deleteForward(editor, { unit: 'line' })\n break\n }\n\n case 'deleteHardLineBackward': {\n Editor.deleteBackward(editor, { unit: 'block' })\n break\n }\n\n case 'deleteSoftLineBackward': {\n Editor.deleteBackward(editor, { unit: 'line' })\n break\n }\n\n case 'deleteHardLineForward': {\n Editor.deleteForward(editor, { unit: 'block' })\n break\n }\n\n case 'deleteSoftLineForward': {\n Editor.deleteForward(editor, { unit: 'line' })\n break\n }\n\n case 'deleteWordBackward': {\n Editor.deleteBackward(editor, { unit: 'word' })\n break\n }\n\n case 'deleteWordForward': {\n Editor.deleteForward(editor, { unit: 'word' })\n break\n }\n\n case 'insertLineBreak':\n case 'insertParagraph': {\n Editor.insertBreak(editor)\n break\n }\n\n case 'insertFromComposition':\n case 'insertFromDrop':\n case 'insertFromPaste':\n case 'insertFromYank':\n case 'insertReplacementText':\n case 'insertText': {\n if (data instanceof DataTransfer) {\n ReactEditor.insertData(editor, data)\n } else if (typeof data === 'string') {\n Editor.insertText(editor, data)\n }\n\n break\n }\n }\n }\n },\n []\n )\n\n // Listen on the native `selectionchange` event to be able to update any time\n // the selection changes. This is required because React's `onSelect` is leaky\n // and non-standard so it doesn't fire until after a selection has been\n // released. This causes issues in situations where another change happens\n // while a selection is being dragged.\n const onDOMSelectionChange = useCallback(\n debounce(() => {\n if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {\n const { activeElement } = window.document\n const el = ReactEditor.toDOMNode(editor, editor)\n const domSelection = window.getSelection()\n const domRange =\n domSelection &&\n domSelection.rangeCount > 0 &&\n domSelection.getRangeAt(0)\n\n if (activeElement === el) {\n state.latestElement = activeElement\n IS_FOCUSED.set(editor, true)\n } else {\n IS_FOCUSED.delete(editor)\n }\n\n if (\n domRange &&\n hasEditableTarget(editor, domRange.startContainer) &&\n hasEditableTarget(editor, domRange.endContainer)\n ) {\n const range = ReactEditor.toSlateRange(editor, domRange)\n Transforms.select(editor, range)\n } else {\n Transforms.deselect(editor)\n }\n }\n }, 100),\n []\n )\n\n const decorations = decorate([editor, []])\n\n if (\n placeholder &&\n editor.children.length === 1 &&\n Array.from(Node.texts(editor)).length === 1 &&\n Node.string(editor) === ''\n ) {\n const start = Editor.start(editor, [])\n decorations.push({\n [PLACEHOLDER_SYMBOL]: true,\n placeholder,\n anchor: start,\n focus: start,\n })\n }\n\n return (\n \n {\n // COMPAT: Firefox doesn't support the `beforeinput` event, so we\n // fall back to React's leaky polyfill instead just for it. It\n // only works for the `insertText` input type.\n if (IS_FIREFOX && !readOnly && ReactEditor.isFocused(editor)) {\n event.preventDefault()\n const text = (event as any).data as string\n Editor.insertText(editor, text)\n }\n },\n [readOnly]\n )}\n onBlur={useCallback(\n (event: React.FocusEvent) => {\n if (\n readOnly ||\n state.isUpdatingSelection ||\n !hasEditableTarget(editor, event.target) ||\n isEventHandled(event, attributes.onBlur)\n ) {\n return\n }\n\n // COMPAT: If the current `activeElement` is still the previous\n // one, this is due to the window being blurred when the tab\n // itself becomes unfocused, so we want to abort early to allow to\n // editor to stay focused when the tab becomes focused again.\n if (state.latestElement === window.document.activeElement) {\n return\n }\n\n const { relatedTarget } = event\n const el = ReactEditor.toDOMNode(editor, editor)\n\n // COMPAT: The event should be ignored if the focus is returning\n // to the editor from an embedded editable element (eg. an \n // element inside a void node).\n if (relatedTarget === el) {\n return\n }\n\n // COMPAT: The event should be ignored if the focus is moving from\n // the editor to inside a void node's spacer element.\n if (\n isDOMElement(relatedTarget) &&\n relatedTarget.hasAttribute('data-slate-spacer')\n ) {\n return\n }\n\n // COMPAT: The event should be ignored if the focus is moving to a\n // non- editable section of an element that isn't a void node (eg.\n // a list item of the check list example).\n if (\n relatedTarget != null &&\n isDOMNode(relatedTarget) &&\n ReactEditor.hasDOMNode(editor, relatedTarget)\n ) {\n const node = ReactEditor.toSlateNode(editor, relatedTarget)\n\n if (Element.isElement(node) && !editor.isVoid(node)) {\n return\n }\n }\n\n IS_FOCUSED.delete(editor)\n },\n [readOnly, attributes.onBlur]\n )}\n onClick={useCallback(\n (event: React.MouseEvent) => {\n if (\n !readOnly &&\n hasTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onClick) &&\n isDOMNode(event.target)\n ) {\n const node = ReactEditor.toSlateNode(editor, event.target)\n const path = ReactEditor.findPath(editor, node)\n const start = Editor.start(editor, path)\n\n if (Editor.void(editor, { at: start })) {\n const range = Editor.range(editor, start)\n Transforms.select(editor, range)\n }\n }\n },\n [readOnly, attributes.onClick]\n )}\n onCompositionEnd={useCallback(\n (event: React.CompositionEvent) => {\n if (\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCompositionEnd)\n ) {\n state.isComposing = false\n\n // COMPAT: In Chrome, `beforeinput` events for compositions\n // aren't correct and never fire the \"insertFromComposition\"\n // type that we need. So instead, insert whenever a composition\n // ends since it will already have been committed to the DOM.\n if (!IS_SAFARI && !IS_FIREFOX && event.data) {\n Editor.insertText(editor, event.data)\n }\n }\n },\n [attributes.onCompositionEnd]\n )}\n onCompositionStart={useCallback(\n (event: React.CompositionEvent) => {\n if (\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCompositionStart)\n ) {\n state.isComposing = true\n }\n },\n [attributes.onCompositionStart]\n )}\n onCopy={useCallback(\n (event: React.ClipboardEvent) => {\n if (\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCopy)\n ) {\n event.preventDefault()\n setFragmentData(event.clipboardData, editor)\n }\n },\n [attributes.onCopy]\n )}\n onCut={useCallback(\n (event: React.ClipboardEvent) => {\n if (\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onCut)\n ) {\n event.preventDefault()\n setFragmentData(event.clipboardData, editor)\n const { selection } = editor\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n }\n }\n },\n [readOnly, attributes.onCut]\n )}\n onDragOver={useCallback(\n (event: React.DragEvent) => {\n if (\n hasTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onDragOver)\n ) {\n // Only when the target is void, call `preventDefault` to signal\n // that drops are allowed. Editable content is droppable by\n // default, and calling `preventDefault` hides the cursor.\n const node = ReactEditor.toSlateNode(editor, event.target)\n\n if (Editor.isVoid(editor, node)) {\n event.preventDefault()\n }\n }\n },\n [attributes.onDragOver]\n )}\n onDragStart={useCallback(\n (event: React.DragEvent) => {\n if (\n hasTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onDragStart)\n ) {\n const node = ReactEditor.toSlateNode(editor, event.target)\n const path = ReactEditor.findPath(editor, node)\n const voidMatch = Editor.void(editor, { at: path })\n\n // If starting a drag on a void node, make sure it is selected\n // so that it shows up in the selection's fragment.\n if (voidMatch) {\n const range = Editor.range(editor, path)\n Transforms.select(editor, range)\n }\n\n setFragmentData(event.dataTransfer, editor)\n }\n },\n [attributes.onDragStart]\n )}\n onDrop={useCallback(\n (event: React.DragEvent) => {\n if (\n hasTarget(editor, event.target) &&\n !readOnly &&\n !isEventHandled(event, attributes.onDrop)\n ) {\n // COMPAT: Firefox doesn't fire `beforeinput` events at all, and\n // Chromium browsers don't properly fire them for files being\n // dropped into a `contenteditable`. (2019/11/26)\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668\n if (\n IS_FIREFOX ||\n (!IS_SAFARI && event.dataTransfer.files.length > 0)\n ) {\n event.preventDefault()\n const range = ReactEditor.findEventRange(editor, event)\n const data = event.dataTransfer\n Transforms.select(editor, range)\n ReactEditor.insertData(editor, data)\n }\n }\n },\n [readOnly, attributes.onDrop]\n )}\n onFocus={useCallback(\n (event: React.FocusEvent) => {\n if (\n !readOnly &&\n !state.isUpdatingSelection &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onFocus)\n ) {\n const el = ReactEditor.toDOMNode(editor, editor)\n state.latestElement = window.document.activeElement\n\n // COMPAT: If the editor has nested editable elements, the focus\n // can go to them. In Firefox, this must be prevented because it\n // results in issues with keyboard navigation. (2017/03/30)\n if (IS_FIREFOX && event.target !== el) {\n el.focus()\n return\n }\n\n IS_FOCUSED.set(editor, true)\n }\n },\n [readOnly, attributes.onFocus]\n )}\n onKeyDown={useCallback(\n (event: React.KeyboardEvent) => {\n if (\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onKeyDown)\n ) {\n const { nativeEvent } = event\n const { selection } = editor\n\n const element =\n editor.children[\n selection !== null ? selection.focus.path[0] : 0\n ]\n const isRTL = getDirection(Node.string(element)) === 'rtl'\n\n // COMPAT: Since we prevent the default behavior on\n // `beforeinput` events, the browser doesn't think there's ever\n // any history stack to undo or redo, so we have to manage these\n // hotkeys ourselves. (2019/11/06)\n if (Hotkeys.isRedo(nativeEvent)) {\n event.preventDefault()\n\n if (editor.redo) {\n editor.redo()\n }\n\n return\n }\n\n if (Hotkeys.isUndo(nativeEvent)) {\n event.preventDefault()\n\n if (editor.undo) {\n editor.undo()\n }\n\n return\n }\n\n // COMPAT: Certain browsers don't handle the selection updates\n // properly. In Chrome, the selection isn't properly extended.\n // And in Firefox, the selection isn't properly collapsed.\n // (2017/10/17)\n if (Hotkeys.isMoveLineBackward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'line', reverse: true })\n return\n }\n\n if (Hotkeys.isMoveLineForward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'line' })\n return\n }\n\n if (Hotkeys.isExtendLineBackward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, {\n unit: 'line',\n edge: 'focus',\n reverse: true,\n })\n return\n }\n\n if (Hotkeys.isExtendLineForward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'line', edge: 'focus' })\n return\n }\n\n // COMPAT: If a void node is selected, or a zero-width text node\n // adjacent to an inline is selected, we need to handle these\n // hotkeys manually because browsers won't be able to skip over\n // the void node with the zero-width space not being an empty\n // string.\n if (Hotkeys.isMoveBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isCollapsed(selection)) {\n const { anchor } = selection\n if (anchor.offset === 1 && anchor.path[1] > 0) {\n // Hack to position the cursor at the end of the previous text node\n Transforms.move(editor, { reverse: !isRTL, distance: 2 })\n Transforms.move(editor, { reverse: isRTL })\n } else {\n Transforms.move(editor, { reverse: !isRTL })\n }\n } else {\n Transforms.collapse(editor, { edge: 'start' })\n }\n\n return\n }\n\n if (Hotkeys.isMoveForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isCollapsed(selection)) {\n Transforms.move(editor, { reverse: isRTL })\n } else {\n Transforms.collapse(editor, { edge: 'end' })\n }\n\n return\n }\n\n if (Hotkeys.isMoveWordBackward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'word', reverse: !isRTL })\n return\n }\n\n if (Hotkeys.isMoveWordForward(nativeEvent)) {\n event.preventDefault()\n Transforms.move(editor, { unit: 'word', reverse: isRTL })\n return\n }\n\n // COMPAT: Firefox doesn't support the `beforeinput` event, so we\n // fall back to guessing at the input intention for hotkeys.\n // COMPAT: In iOS, some of these hotkeys are handled in the\n if (IS_FIREFOX) {\n // We don't have a core behavior for these, but they change the\n // DOM if we don't prevent them, so we have to.\n if (\n Hotkeys.isBold(nativeEvent) ||\n Hotkeys.isItalic(nativeEvent) ||\n Hotkeys.isTransposeCharacter(nativeEvent)\n ) {\n event.preventDefault()\n return\n }\n\n if (Hotkeys.isSplitBlock(nativeEvent)) {\n event.preventDefault()\n Editor.insertBreak(editor)\n return\n }\n\n if (Hotkeys.isDeleteBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteBackward(editor)\n }\n\n return\n }\n\n if (Hotkeys.isDeleteForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteForward(editor)\n }\n\n return\n }\n\n if (Hotkeys.isDeleteLineBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteBackward(editor, { unit: 'line' })\n }\n\n return\n }\n\n if (Hotkeys.isDeleteLineForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteForward(editor, { unit: 'line' })\n }\n\n return\n }\n\n if (Hotkeys.isDeleteWordBackward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteBackward(editor, { unit: 'word' })\n }\n\n return\n }\n\n if (Hotkeys.isDeleteWordForward(nativeEvent)) {\n event.preventDefault()\n\n if (selection && Range.isExpanded(selection)) {\n Editor.deleteFragment(editor)\n } else {\n Editor.deleteForward(editor, { unit: 'word' })\n }\n\n return\n }\n }\n }\n },\n [readOnly, attributes.onKeyDown]\n )}\n onPaste={useCallback(\n (event: React.ClipboardEvent) => {\n // COMPAT: Firefox doesn't support the `beforeinput` event, so we\n // fall back to React's `onPaste` here instead.\n if (\n IS_FIREFOX &&\n !readOnly &&\n hasEditableTarget(editor, event.target) &&\n !isEventHandled(event, attributes.onPaste)\n ) {\n event.preventDefault()\n ReactEditor.insertData(editor, event.clipboardData)\n }\n },\n [readOnly, attributes.onPaste]\n )}\n >\n \n \n \n )\n}\n\n/**\n * A default memoized decorate function.\n */\n\nconst defaultDecorate = () => []\n\n/**\n * Check if two DOM range objects are equal.\n */\n\nconst isRangeEqual = (a: DOMRange, b: DOMRange) => {\n return (\n (a.startContainer === b.startContainer &&\n a.startOffset === b.startOffset &&\n a.endContainer === b.endContainer &&\n a.endOffset === b.endOffset) ||\n (a.startContainer === b.endContainer &&\n a.startOffset === b.endOffset &&\n a.endContainer === b.startContainer &&\n a.endOffset === b.startOffset)\n )\n}\n\n/**\n * Check if the target is in the editor.\n */\n\nconst hasTarget = (\n editor: ReactEditor,\n target: EventTarget | null\n): target is DOMNode => {\n return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target)\n}\n\n/**\n * Check if the target is editable and in the editor.\n */\n\nconst hasEditableTarget = (\n editor: ReactEditor,\n target: EventTarget | null\n): target is DOMNode => {\n return (\n isDOMNode(target) &&\n ReactEditor.hasDOMNode(editor, target, { editable: true })\n )\n}\n\n/**\n * Check if an event is overrided by a handler.\n */\n\nconst isEventHandled = <\n EventType extends React.SyntheticEvent\n>(\n event: EventType,\n handler?: (event: EventType) => void\n) => {\n if (!handler) {\n return false\n }\n\n handler(event)\n return event.isDefaultPrevented() || event.isPropagationStopped()\n}\n\n/**\n * Check if a DOM event is overrided by a handler.\n */\n\nconst isDOMEventHandled = (event: Event, handler?: (event: Event) => void) => {\n if (!handler) {\n return false\n }\n\n handler(event)\n return event.defaultPrevented\n}\n\n/**\n * Set the currently selected fragment to the clipboard.\n */\n\nconst setFragmentData = (\n dataTransfer: DataTransfer,\n editor: ReactEditor\n): void => {\n const { selection } = editor\n\n if (!selection) {\n return\n }\n\n const [start, end] = Range.edges(selection)\n const startVoid = Editor.void(editor, { at: start.path })\n const endVoid = Editor.void(editor, { at: end.path })\n\n if (Range.isCollapsed(selection) && !startVoid) {\n return\n }\n\n // Create a fake selection so that we can add a Base64-encoded copy of the\n // fragment to the HTML, to decode on future pastes.\n const domRange = ReactEditor.toDOMRange(editor, selection)\n let contents = domRange.cloneContents()\n let attach = contents.childNodes[0] as HTMLElement\n\n // Make sure attach is non-empty, since empty nodes will not get copied.\n contents.childNodes.forEach(node => {\n if (node.textContent && node.textContent.trim() !== '') {\n attach = node as HTMLElement\n }\n })\n\n // COMPAT: If the end node is a void node, we need to move the end of the\n // range from the void node's spacer span, to the end of the void node's\n // content, since the spacer is before void's content in the DOM.\n if (endVoid) {\n const [voidNode] = endVoid\n const r = domRange.cloneRange()\n const domNode = ReactEditor.toDOMNode(editor, voidNode)\n r.setEndAfter(domNode)\n contents = r.cloneContents()\n }\n\n // COMPAT: If the start node is a void node, we need to attach the encoded\n // fragment to the void node's content node instead of the spacer, because\n // attaching it to empty `
/` nodes will end up having it erased by\n // most browsers. (2018/04/27)\n if (startVoid) {\n attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement\n }\n\n // Remove any zero-width space spans from the cloned DOM so that they don't\n // show up elsewhere when pasted.\n Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(\n zw => {\n const isNewline = zw.getAttribute('data-slate-zero-width') === 'n'\n zw.textContent = isNewline ? '\\n' : ''\n }\n )\n\n // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up\n // in the HTML, and can be used for intra-Slate pasting. If it's a text\n // node, wrap it in a `` so we have something to set an attribute on.\n if (isDOMText(attach)) {\n const span = document.createElement('span')\n // COMPAT: In Chrome and Safari, if we don't add the `white-space` style\n // then leading and trailing spaces will be ignored. (2017/09/21)\n span.style.whiteSpace = 'pre'\n span.appendChild(attach)\n contents.appendChild(span)\n attach = span\n }\n\n const fragment = Node.fragment(editor, selection)\n const string = JSON.stringify(fragment)\n const encoded = window.btoa(encodeURIComponent(string))\n attach.setAttribute('data-slate-fragment', encoded)\n\n // Overwriting the default functionality\n const { getFormattedSelection, getHTMLFormattedSelection } = editor\n if (\n typeof getFormattedSelection === 'function' &&\n typeof getHTMLFormattedSelection === 'function'\n ) {\n try {\n const plainText = getFormattedSelection()\n const htmlText = getHTMLFormattedSelection()\n dataTransfer.setData('text/plain', plainText)\n dataTransfer.setData('text/html', htmlText)\n return\n } catch (e) {\n // eslint-disable-next-line no-console\n console.log('Error in slate-react/src/components/editable.tsx: ', e)\n // Only setData application/x-slate-fragment as a fallback because\n // we don't want to copy the timestamps of words\n dataTransfer.setData('application/x-slate-fragment', encoded)\n }\n }\n\n // Add the content to a
so that we can get its inner HTML.\n const div = document.createElement('div')\n div.appendChild(contents)\n dataTransfer.setData('text/html', div.innerHTML)\n dataTransfer.setData('text/plain', getPlainText(div))\n}\n\n/**\n * Get a plaintext representation of the content of a node, accounting for block\n * elements which get a newline appended.\n */\n\nconst getPlainText = (domNode: DOMNode) => {\n let text = ''\n\n if (isDOMText(domNode) && domNode.nodeValue) {\n return domNode.nodeValue\n }\n\n if (isDOMElement(domNode)) {\n for (const childNode of Array.from(domNode.childNodes)) {\n text += getPlainText(childNode)\n }\n\n const display = getComputedStyle(domNode).getPropertyValue('display')\n\n if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {\n text += '\\n'\n }\n }\n\n return text\n}\n","/**\n * An auto-incrementing identifier for keys.\n */\n\nlet n = 0\n\n/**\n * A class that keeps track of a key string. We use a full class here because we\n * want to be able to use them as keys in `WeakMap` objects.\n */\n\nexport class Key {\n id: string\n\n constructor() {\n this.id = `${n++}`\n }\n}\n","import { Editor, Node, Path, Point, Range, Transforms } from 'slate'\n\nimport { Key } from '../utils/key'\nimport {\n EDITOR_TO_ELEMENT,\n ELEMENT_TO_NODE,\n IS_FOCUSED,\n IS_READ_ONLY,\n KEY_TO_ELEMENT,\n NODE_TO_INDEX,\n NODE_TO_KEY,\n NODE_TO_PARENT,\n} from '../utils/weak-maps'\nimport {\n DOMElement,\n DOMNode,\n DOMPoint,\n DOMRange,\n DOMSelection,\n DOMStaticRange,\n isDOMElement,\n normalizeDOMPoint,\n} from '../utils/dom'\n\n/**\n * A React and DOM-specific version of the `Editor` interface.\n */\n\nexport interface ReactEditor extends Editor {\n insertData: (data: DataTransfer) => void\n}\n\nexport const ReactEditor = {\n /**\n * Find a key for a Slate node.\n */\n\n findKey(editor: ReactEditor, node: Node): Key {\n let key = NODE_TO_KEY.get(node)\n\n if (!key) {\n key = new Key()\n NODE_TO_KEY.set(node, key)\n }\n\n return key\n },\n\n /**\n * Find the path of Slate node.\n */\n\n findPath(editor: ReactEditor, node: Node): Path {\n const path: Path = []\n let child = node\n\n while (true) {\n const parent = NODE_TO_PARENT.get(child)\n\n if (parent == null) {\n if (Editor.isEditor(child)) {\n return path\n } else {\n break\n }\n }\n\n const i = NODE_TO_INDEX.get(child)\n\n if (i == null) {\n break\n }\n\n path.unshift(i)\n child = parent\n }\n\n throw new Error(\n `Unable to find the path for Slate node: ${JSON.stringify(node)}`\n )\n },\n\n /**\n * Check if the editor is focused.\n */\n\n isFocused(editor: ReactEditor): boolean {\n return !!IS_FOCUSED.get(editor)\n },\n\n /**\n * Check if the editor is in read-only mode.\n */\n\n isReadOnly(editor: ReactEditor): boolean {\n return !!IS_READ_ONLY.get(editor)\n },\n\n /**\n * Blur the editor.\n */\n\n blur(editor: ReactEditor): void {\n const el = ReactEditor.toDOMNode(editor, editor)\n IS_FOCUSED.set(editor, false)\n\n if (window.document.activeElement === el) {\n el.blur()\n }\n },\n\n /**\n * Focus the editor.\n */\n\n focus(editor: ReactEditor): void {\n const el = ReactEditor.toDOMNode(editor, editor)\n IS_FOCUSED.set(editor, true)\n\n if (window.document.activeElement !== el) {\n el.focus({ preventScroll: true })\n }\n },\n\n /**\n * Deselect the editor.\n */\n\n deselect(editor: ReactEditor): void {\n const { selection } = editor\n const domSelection = window.getSelection()\n\n if (domSelection && domSelection.rangeCount > 0) {\n domSelection.removeAllRanges()\n }\n\n if (selection) {\n Transforms.deselect(editor)\n }\n },\n\n /**\n * Check if a DOM node is within the editor.\n */\n\n hasDOMNode(\n editor: ReactEditor,\n target: DOMNode,\n options: { editable?: boolean } = {}\n ): boolean {\n const { editable = false } = options\n const el = ReactEditor.toDOMNode(editor, editor)\n let element\n\n // COMPAT: In Firefox, reading `target.nodeType` will throw an error if\n // target is originating from an internal \"restricted\" element (e.g. a\n // stepper arrow on a number input). (2018/05/04)\n // https://github.com/ianstormtaylor/slate/issues/1819\n try {\n element = isDOMElement(target) ? target : target.parentElement\n } catch (err) {\n if (\n !err.message.includes('Permission denied to access property \"nodeType\"')\n ) {\n throw err\n }\n }\n\n if (!element) {\n return false\n }\n\n return (\n element.closest(`[data-slate-editor]`) === el &&\n (!editable || el.isContentEditable)\n )\n },\n\n /**\n * Insert data from a `DataTransfer` into the editor.\n */\n\n insertData(editor: ReactEditor, data: DataTransfer): void {\n editor.insertData(data)\n },\n\n /**\n * Find the native DOM element from a Slate node.\n */\n\n toDOMNode(editor: ReactEditor, node: Node): HTMLElement {\n const domNode = Editor.isEditor(node)\n ? EDITOR_TO_ELEMENT.get(editor)\n : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node))\n\n if (!domNode) {\n throw new Error(\n `Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`\n )\n }\n\n return domNode\n },\n\n /**\n * Find a native DOM selection point from a Slate point.\n */\n\n toDOMPoint(editor: ReactEditor, point: Point): DOMPoint {\n const [node] = Editor.node(editor, point.path)\n const el = ReactEditor.toDOMNode(editor, node)\n let domPoint: DOMPoint | undefined\n\n // If we're inside a void node, force the offset to 0, otherwise the zero\n // width spacing character will result in an incorrect offset of 1\n if (Editor.void(editor, { at: point })) {\n point = { path: point.path, offset: 0 }\n }\n\n // For each leaf, we need to isolate its content, which means filtering\n // to its direct text and zero-width spans. (We have to filter out any\n // other siblings that may have been rendered alongside them.)\n const selector = `[data-slate-string], [data-slate-zero-width]`\n const texts = Array.from(el.querySelectorAll(selector))\n let start = 0\n\n for (const text of texts) {\n const domNode = text.childNodes[0] as HTMLElement\n\n if (domNode == null || domNode.textContent == null) {\n continue\n }\n\n const { length } = domNode.textContent\n const attr = text.getAttribute('data-slate-length')\n const trueLength = attr == null ? length : parseInt(attr, 10)\n const end = start + trueLength\n\n if (point.offset <= end) {\n const offset = Math.min(length, Math.max(0, point.offset - start))\n domPoint = [domNode, offset]\n break\n }\n\n start = end\n }\n\n if (!domPoint) {\n throw new Error(\n `Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`\n )\n }\n\n return domPoint\n },\n\n /**\n * Find a native DOM range from a Slate `range`.\n */\n\n toDOMRange(editor: ReactEditor, range: Range): DOMRange {\n const { anchor, focus } = range\n const domAnchor = ReactEditor.toDOMPoint(editor, anchor)\n const domFocus = Range.isCollapsed(range)\n ? domAnchor\n : ReactEditor.toDOMPoint(editor, focus)\n\n const domRange = window.document.createRange()\n const start = Range.isBackward(range) ? domFocus : domAnchor\n const end = Range.isBackward(range) ? domAnchor : domFocus\n domRange.setStart(start[0], start[1])\n domRange.setEnd(end[0], end[1])\n return domRange\n },\n\n /**\n * Find a Slate node from a native DOM `element`.\n */\n\n toSlateNode(editor: ReactEditor, domNode: DOMNode): Node {\n let domEl = isDOMElement(domNode) ? domNode : domNode.parentElement\n\n if (domEl && !domEl.hasAttribute('data-slate-node')) {\n domEl = domEl.closest(`[data-slate-node]`)\n }\n\n const node = domEl ? ELEMENT_TO_NODE.get(domEl as HTMLElement) : null\n\n if (!node) {\n throw new Error(`Cannot resolve a Slate node from DOM node: ${domEl}`)\n }\n\n return node\n },\n\n /**\n * Get the target range from a DOM `event`.\n */\n\n findEventRange(editor: ReactEditor, event: any): Range {\n if ('nativeEvent' in event) {\n event = event.nativeEvent\n }\n\n const { clientX: x, clientY: y, target } = event\n\n if (x == null || y == null) {\n throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)\n }\n\n const node = ReactEditor.toSlateNode(editor, event.target)\n const path = ReactEditor.findPath(editor, node)\n\n // If the drop target is inside a void node, move it into either the\n // next or previous node, depending on which side the `x` and `y`\n // coordinates are closest to.\n if (Editor.isVoid(editor, node)) {\n const rect = target.getBoundingClientRect()\n const isPrev = editor.isInline(node)\n ? x - rect.left < rect.left + rect.width - x\n : y - rect.top < rect.top + rect.height - y\n\n const edge = Editor.point(editor, path, {\n edge: isPrev ? 'start' : 'end',\n })\n const point = isPrev\n ? Editor.before(editor, edge)\n : Editor.after(editor, edge)\n\n if (point) {\n const range = Editor.range(editor, point)\n return range\n }\n }\n\n // Else resolve a range from the caret position where the drop occured.\n let domRange\n const { document } = window\n\n // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)\n if (document.caretRangeFromPoint) {\n domRange = document.caretRangeFromPoint(x, y)\n } else {\n const position = document.caretPositionFromPoint(x, y)\n\n if (position) {\n domRange = document.createRange()\n domRange.setStart(position.offsetNode, position.offset)\n domRange.setEnd(position.offsetNode, position.offset)\n }\n }\n\n if (!domRange) {\n throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)\n }\n\n // Resolve a Slate range from the DOM range.\n const range = ReactEditor.toSlateRange(editor, domRange)\n return range\n },\n\n /**\n * Find a Slate point from a DOM selection's `domNode` and `domOffset`.\n */\n\n toSlatePoint(editor: ReactEditor, domPoint: DOMPoint): Point {\n const [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint)\n const parentNode = nearestNode.parentNode as DOMElement\n let textNode: DOMElement | null = null\n let offset = 0\n\n if (parentNode) {\n const voidNode = parentNode.closest('[data-slate-void=\"true\"]')\n let leafNode = parentNode.closest('[data-slate-leaf]')\n let domNode: DOMElement | null = null\n\n // Calculate how far into the text node the `nearestNode` is, so that we\n // can determine what the offset relative to the text node is.\n if (leafNode) {\n textNode = leafNode.closest('[data-slate-node=\"text\"]')!\n const range = window.document.createRange()\n range.setStart(textNode, 0)\n range.setEnd(nearestNode, nearestOffset)\n const contents = range.cloneContents()\n const removals = [\n ...contents.querySelectorAll('[data-slate-zero-width]'),\n ...contents.querySelectorAll('[contenteditable=false]'),\n ]\n\n removals.forEach(el => {\n el!.parentNode!.removeChild(el)\n })\n\n // COMPAT: Edge has a bug where Range.prototype.toString() will\n // convert \\n into \\r\\n. The bug causes a loop when slate-react\n // attempts to reposition its cursor to match the native position. Use\n // textContent.length instead.\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/\n offset = contents.textContent!.length\n domNode = textNode\n } else if (voidNode) {\n // For void nodes, the element with the offset key will be a cousin, not an\n // ancestor, so find it by going down from the nearest void parent.\n\n leafNode = voidNode.querySelector('[data-slate-leaf]')!\n textNode = leafNode.closest('[data-slate-node=\"text\"]')!\n domNode = leafNode\n offset = domNode.textContent!.length\n }\n\n // COMPAT: If the parent node is a Slate zero-width space, editor is\n // because the text node should have no characters. However, during IME\n // composition the ASCII characters will be prepended to the zero-width\n // space, so subtract 1 from the offset to account for the zero-width\n // space character.\n if (\n domNode &&\n offset === domNode.textContent!.length &&\n parentNode.hasAttribute('data-slate-zero-width')\n ) {\n offset--\n }\n }\n\n if (!textNode) {\n throw new Error(\n `Cannot resolve a Slate point from DOM point: ${domPoint}`\n )\n }\n\n // COMPAT: If someone is clicking from one Slate editor into another,\n // the select event fires twice, once for the old editor's `element`\n // first, and then afterwards for the correct `element`. (2017/03/03)\n const slateNode = ReactEditor.toSlateNode(editor, textNode!)\n const path = ReactEditor.findPath(editor, slateNode)\n return { path, offset }\n },\n\n /**\n * Find a Slate range from a DOM range or selection.\n */\n\n toSlateRange(\n editor: ReactEditor,\n domRange: DOMRange | DOMStaticRange | DOMSelection\n ): Range {\n const el =\n domRange instanceof Selection\n ? domRange.anchorNode\n : domRange.startContainer\n let anchorNode\n let anchorOffset\n let focusNode\n let focusOffset\n let isCollapsed\n\n if (el) {\n if (domRange instanceof Selection) {\n anchorNode = domRange.anchorNode\n anchorOffset = domRange.anchorOffset\n focusNode = domRange.focusNode\n focusOffset = domRange.focusOffset\n isCollapsed = domRange.isCollapsed\n } else {\n anchorNode = domRange.startContainer\n anchorOffset = domRange.startOffset\n focusNode = domRange.endContainer\n focusOffset = domRange.endOffset\n isCollapsed = domRange.collapsed\n }\n }\n\n if (\n anchorNode == null ||\n focusNode == null ||\n anchorOffset == null ||\n focusOffset == null\n ) {\n throw new Error(\n `Cannot resolve a Slate range from DOM range: ${domRange}`\n )\n }\n\n const anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset])\n const focus = isCollapsed\n ? anchor\n : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset])\n\n return { anchor, focus }\n },\n}\n","import { createContext, useContext } from 'react'\n\n/**\n * A React context for sharing the `focused` state of the editor.\n */\n\nexport const FocusedContext = createContext(false)\n\n/**\n * Get the current `focused` state of the editor.\n */\n\nexport const useFocused = (): boolean => {\n return useContext(FocusedContext)\n}\n","import React, { useMemo, useState, useCallback } from 'react'\nimport { Node } from 'slate'\n\nimport { ReactEditor } from '../plugin/react-editor'\nimport { FocusedContext } from '../hooks/use-focused'\nimport { EditorContext } from '../hooks/use-editor'\nimport { SlateContext } from '../hooks/use-slate'\nimport { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'\n\n/**\n * A wrapper around the provider to handle `onChange` events, because the editor\n * is a mutable singleton so it won't ever register as \"changed\" otherwise.\n */\n\nexport const Slate = (props: {\n editor: ReactEditor\n value: Node[]\n children: React.ReactNode\n onChange: (value: Node[]) => void\n [key: string]: any\n}) => {\n const { editor, children, onChange, value, ...rest } = props\n const [key, setKey] = useState(0)\n const context: [ReactEditor] = useMemo(() => {\n editor.children = value\n Object.assign(editor, rest)\n return [editor]\n }, [key, value, ...Object.values(rest)])\n\n const onContextChange = useCallback(() => {\n onChange(editor.children)\n setKey(key + 1)\n }, [key, onChange])\n\n EDITOR_TO_ON_CHANGE.set(editor, onContextChange)\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n","import ReactDOM from 'react-dom'\nimport { Editor, Node, Path, Operation, Transforms } from 'slate'\n\nimport { ReactEditor } from './react-editor'\nimport { Key } from '../utils/key'\nimport { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'\n\n/**\n * `withReact` adds React and DOM specific behaviors to the editor.\n */\n\nexport const withReact = (editor: T) => {\n const e = editor as T & ReactEditor\n const { apply, onChange } = e\n\n e.apply = (op: Operation) => {\n const matches: [Path, Key][] = []\n\n switch (op.type) {\n case 'insert_text':\n case 'remove_text':\n case 'set_node': {\n for (const [node, path] of Editor.levels(e, { at: op.path })) {\n const key = ReactEditor.findKey(e, node)\n matches.push([path, key])\n }\n\n break\n }\n\n case 'insert_node':\n case 'remove_node':\n case 'merge_node':\n case 'split_node': {\n for (const [node, path] of Editor.levels(e, {\n at: Path.parent(op.path),\n })) {\n const key = ReactEditor.findKey(e, node)\n matches.push([path, key])\n }\n\n break\n }\n\n case 'move_node': {\n // TODO\n break\n }\n }\n\n apply(op)\n\n for (const [path, key] of matches) {\n const [node] = Editor.node(e, path)\n NODE_TO_KEY.set(node, key)\n }\n }\n\n e.insertData = (data: DataTransfer) => {\n const fragment = data.getData('application/x-slate-fragment')\n\n if (fragment) {\n const decoded = decodeURIComponent(window.atob(fragment))\n const parsed = JSON.parse(decoded) as Node[]\n Transforms.insertFragment(e, parsed)\n return\n }\n\n const text = data.getData('text/plain')\n\n if (text) {\n const lines = text.split('\\n')\n let split = false\n\n for (const line of lines) {\n if (split) {\n Transforms.splitNodes(e)\n }\n\n Transforms.insertText(e, line)\n split = true\n }\n }\n }\n\n e.onChange = () => {\n // COMPAT: React doesn't batch `setState` hook calls, which means that the\n // children and selection can get out of sync for one render pass. So we\n // have to use this unstable API to ensure it batches them. (2019/12/03)\n // https://github.com/facebook/react/issues/14259#issuecomment-439702367\n ReactDOM.unstable_batchedUpdates(() => {\n const onContextChange = EDITOR_TO_ON_CHANGE.get(e)\n\n if (onContextChange) {\n onContextChange()\n }\n\n onChange()\n })\n }\n\n return e\n}\n"],"names":["Path","React","Node","Editor","NODE_TO_INDEX","WeakMap","NODE_TO_PARENT","EDITOR_TO_ELEMENT","ELEMENT_TO_NODE","KEY_TO_ELEMENT","NODE_TO_ELEMENT","NODE_TO_KEY","IS_READ_ONLY","IS_FOCUSED","EDITOR_TO_ON_CHANGE","PLACEHOLDER_SYMBOL","Symbol","Text","useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","useRef","SlateText","Leaf","SelectedContext","createContext","useSelected","useContext","Range","Element","ElementComponent","TextComponent","IS_IOS","navigator","test","userAgent","MSStream","IS_APPLE","IS_FIREFOX","IS_SAFARI","HOTKEYS","bold","compose","moveBackward","moveForward","moveWordBackward","moveWordForward","deleteBackward","deleteForward","extendBackward","extendForward","italic","splitBlock","undo","APPLE_HOTKEYS","moveLineBackward","moveLineForward","deleteLineBackward","deleteLineForward","deleteWordBackward","deleteWordForward","extendLineBackward","extendLineForward","redo","transposeCharacter","WINDOWS_HOTKEYS","create","key","generic","apple","windows","isGeneric","isKeyHotkey","isApple","isWindows","event","isBold","isCompose","isMoveBackward","isMoveForward","isDeleteBackward","isDeleteForward","isDeleteLineBackward","isDeleteLineForward","isDeleteWordBackward","isDeleteWordForward","isExtendBackward","isExtendForward","isExtendLineBackward","isExtendLineForward","isItalic","isMoveLineBackward","isMoveLineForward","isMoveWordBackward","isMoveWordForward","isRedo","isSplitBlock","isTransposeCharacter","isUndo","ReadOnlyContext","useReadOnly","isDOMComment","value","isDOMNode","nodeType","isDOMElement","isDOMText","normalizeDOMPoint","domPoint","node","offset","childNodes","length","isLast","direction","index","getEditableChild","i","textContent","parent","child","triedForward","triedBackward","getAttribute","useMemo","useCallback","Transforms","n","Key","constructor","id","ReactEditor","findKey","editor","get","set","findPath","path","isEditor","unshift","Error","JSON","stringify","isFocused","isReadOnly","blur","el","toDOMNode","document","activeElement","focus","preventScroll","deselect","selection","domSelection","getSelection","rangeCount","removeAllRanges","hasDOMNode","target","options","editable","element","parentElement","err","message","includes","closest","isContentEditable","insertData","data","domNode","toDOMPoint","point","void","at","selector","texts","Array","from","querySelectorAll","start","text","attr","trueLength","parseInt","end","Math","min","max","toDOMRange","range","anchor","domAnchor","domFocus","isCollapsed","domRange","createRange","isBackward","setStart","setEnd","toSlateNode","domEl","hasAttribute","findEventRange","nativeEvent","clientX","x","clientY","y","isVoid","rect","getBoundingClientRect","isPrev","isInline","left","width","top","height","edge","before","after","caretRangeFromPoint","position","caretPositionFromPoint","offsetNode","toSlateRange","toSlatePoint","nearestNode","nearestOffset","parentNode","textNode","voidNode","leafNode","contents","cloneContents","removals","forEach","removeChild","querySelector","slateNode","Selection","anchorNode","startContainer","anchorOffset","focusNode","focusOffset","startOffset","endContainer","endOffset","collapsed","FocusedContext","useFocused","useState","withReact","e","apply","onChange","op","matches","type","levels","push","fragment","getData","decoded","decodeURIComponent","atob","parsed","parse","insertFragment","lines","split","line","splitNodes","insertText","ReactDOM","unstable_batchedUpdates","onContextChange"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/plugin/react-editor.d.ts b/dist/plugin/react-editor.d.ts
deleted file mode 100644
index 6c7e484..0000000
--- a/dist/plugin/react-editor.d.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Editor, Node, Path, Point, Range } from 'slate';
-import { Key } from '../utils/key';
-import { DOMNode, DOMPoint, DOMRange, DOMSelection, DOMStaticRange } from '../utils/dom';
-/**
- * A React and DOM-specific version of the `Editor` interface.
- */
-export interface ReactEditor extends Editor {
- insertData: (data: DataTransfer) => void;
-}
-export declare const ReactEditor: {
- /**
- * Find a key for a Slate node.
- */
- findKey(editor: ReactEditor, node: Node): Key;
- /**
- * Find the path of Slate node.
- */
- findPath(editor: ReactEditor, node: Node): Path;
- /**
- * Check if the editor is focused.
- */
- isFocused(editor: ReactEditor): boolean;
- /**
- * Check if the editor is in read-only mode.
- */
- isReadOnly(editor: ReactEditor): boolean;
- /**
- * Blur the editor.
- */
- blur(editor: ReactEditor): void;
- /**
- * Focus the editor.
- */
- focus(editor: ReactEditor): void;
- /**
- * Deselect the editor.
- */
- deselect(editor: ReactEditor): void;
- /**
- * Check if a DOM node is within the editor.
- */
- hasDOMNode(editor: ReactEditor, target: DOMNode, options?: {
- editable?: boolean | undefined;
- }): boolean;
- /**
- * Insert data from a `DataTransfer` into the editor.
- */
- insertData(editor: ReactEditor, data: DataTransfer): void;
- /**
- * Find the native DOM element from a Slate node.
- */
- toDOMNode(editor: ReactEditor, node: Node): HTMLElement;
- /**
- * Find a native DOM selection point from a Slate point.
- */
- toDOMPoint(editor: ReactEditor, point: Point): DOMPoint;
- /**
- * Find a native DOM range from a Slate `range`.
- */
- toDOMRange(editor: ReactEditor, range: Range): DOMRange;
- /**
- * Find a Slate node from a native DOM `element`.
- */
- toSlateNode(editor: ReactEditor, domNode: DOMNode): Node;
- /**
- * Get the target range from a DOM `event`.
- */
- findEventRange(editor: ReactEditor, event: any): Range;
- /**
- * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
- */
- toSlatePoint(editor: ReactEditor, domPoint: DOMPoint): Point;
- /**
- * Find a Slate range from a DOM range or selection.
- */
- toSlateRange(editor: ReactEditor, domRange: DOMRange | DOMStaticRange | DOMSelection): Range;
-};
-//# sourceMappingURL=react-editor.d.ts.map
\ No newline at end of file
diff --git a/dist/plugin/react-editor.d.ts.map b/dist/plugin/react-editor.d.ts.map
deleted file mode 100644
index 9000f0d..0000000
--- a/dist/plugin/react-editor.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"react-editor.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/plugin/react-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAc,MAAM,OAAO,CAAA;AAEpE,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAWlC,OAAO,EAEL,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,cAAc,EAGf,MAAM,cAAc,CAAA;AAErB;;GAEG;AAEH,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;CACzC;AAED,eAAO,MAAM,WAAW;IACtB;;OAEG;;IAaH;;OAEG;;IAgCH;;OAEG;;IAMH;;OAEG;;IAMH;;OAEG;;IAWH;;OAEG;;IAWH;;OAEG;;IAeH;;OAEG;;;;IAmCH;;OAEG;;IAMH;;OAEG;;IAgBH;;OAEG;;IAkDH;;OAEG;;IAiBH;;OAEG;;IAkBH;;OAEG;;IAgEH;;OAEG;;IA2EH;;OAEG;;CAkDJ,CAAA"}
\ No newline at end of file
diff --git a/dist/plugin/with-react.d.ts b/dist/plugin/with-react.d.ts
deleted file mode 100644
index 5a97dd9..0000000
--- a/dist/plugin/with-react.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Editor } from 'slate';
-import { ReactEditor } from './react-editor';
-/**
- * `withReact` adds React and DOM specific behaviors to the editor.
- */
-export declare const withReact: (editor: T) => T & ReactEditor;
-//# sourceMappingURL=with-react.d.ts.map
\ No newline at end of file
diff --git a/dist/plugin/with-react.d.ts.map b/dist/plugin/with-react.d.ts.map
deleted file mode 100644
index 04093a1..0000000
--- a/dist/plugin/with-react.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"with-react.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/plugin/with-react.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAqC,MAAM,OAAO,CAAA;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C;;GAEG;AAEH,eAAO,MAAM,SAAS,kDA2FrB,CAAA"}
\ No newline at end of file
diff --git a/dist/utils/dom.d.ts b/dist/utils/dom.d.ts
deleted file mode 100644
index 7f93f8d..0000000
--- a/dist/utils/dom.d.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Types.
- */
-import DOMNode = globalThis.Node;
-import DOMComment = globalThis.Comment;
-import DOMElement = globalThis.Element;
-import DOMText = globalThis.Text;
-import DOMRange = globalThis.Range;
-import DOMSelection = globalThis.Selection;
-import DOMStaticRange = globalThis.StaticRange;
-export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange, };
-export declare type DOMPoint = [Node, number];
-/**
- * Check if a DOM node is a comment node.
- */
-export declare const isDOMComment: (value: any) => value is DOMComment;
-/**
- * Check if a DOM node is an element node.
- */
-export declare const isDOMElement: (value: any) => value is DOMElement;
-/**
- * Check if a value is a DOM node.
- */
-export declare const isDOMNode: (value: any) => value is DOMNode;
-/**
- * Check if a DOM node is an element node.
- */
-export declare const isDOMText: (value: any) => value is DOMText;
-/**
- * Normalize a DOM point so that it always refers to a text node.
- */
-export declare const normalizeDOMPoint: (domPoint: DOMPoint) => DOMPoint;
-/**
- * Get the nearest editable child at `index` in a `parent`, preferring
- * `direction`.
- */
-export declare const getEditableChild: (parent: DOMElement, index: number, direction: "forward" | "backward") => DOMNode;
-//# sourceMappingURL=dom.d.ts.map
\ No newline at end of file
diff --git a/dist/utils/dom.d.ts.map b/dist/utils/dom.d.ts.map
deleted file mode 100644
index 95978ea..0000000
--- a/dist/utils/dom.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/utils/dom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,OAAO,GAAG,UAAU,CAAC,IAAI,CAAA;AAChC,OAAO,UAAU,GAAG,UAAU,CAAC,OAAO,CAAA;AACtC,OAAO,UAAU,GAAG,UAAU,CAAC,OAAO,CAAA;AACtC,OAAO,OAAO,GAAG,UAAU,CAAC,IAAI,CAAA;AAChC,OAAO,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAA;AAClC,OAAO,YAAY,GAAG,UAAU,CAAC,SAAS,CAAA;AAC1C,OAAO,cAAc,GAAG,UAAU,CAAC,WAAW,CAAA;AAC9C,OAAO,EACL,OAAO,EACP,UAAU,EACV,UAAU,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,cAAc,GACf,CAAA;AAED,oBAAY,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAErC;;GAEG;AAEH,eAAO,MAAM,YAAY,qCAExB,CAAA;AAED;;GAEG;AAEH,eAAO,MAAM,YAAY,qCAExB,CAAA;AAED;;GAEG;AAEH,eAAO,MAAM,SAAS,kCAErB,CAAA;AAED;;GAEG;AAEH,eAAO,MAAM,SAAS,kCAErB,CAAA;AAED;;GAEG;AAEH,eAAO,MAAM,iBAAiB,kCAwB7B,CAAA;AAED;;;GAGG;AAEH,eAAO,MAAM,gBAAgB,mFAyC5B,CAAA"}
\ No newline at end of file
diff --git a/dist/utils/environment.d.ts b/dist/utils/environment.d.ts
deleted file mode 100644
index abd4123..0000000
--- a/dist/utils/environment.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export declare const IS_IOS: false;
-export declare const IS_APPLE: boolean;
-export declare const IS_FIREFOX: boolean;
-export declare const IS_SAFARI: boolean;
-//# sourceMappingURL=environment.d.ts.map
\ No newline at end of file
diff --git a/dist/utils/environment.d.ts.map b/dist/utils/environment.d.ts.map
deleted file mode 100644
index 3c6d91b..0000000
--- a/dist/utils/environment.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/utils/environment.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,OAID,CAAA;AAElB,eAAO,MAAM,QAAQ,SACqD,CAAA;AAE1E,eAAO,MAAM,UAAU,SAEuC,CAAA;AAE9D,eAAO,MAAM,SAAS,SAEgC,CAAA"}
\ No newline at end of file
diff --git a/dist/utils/hotkeys.d.ts b/dist/utils/hotkeys.d.ts
deleted file mode 100644
index 49d5497..0000000
--- a/dist/utils/hotkeys.d.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-declare const _default: {
- isBold: (event: KeyboardEvent) => boolean;
- isCompose: (event: KeyboardEvent) => boolean;
- isMoveBackward: (event: KeyboardEvent) => boolean;
- isMoveForward: (event: KeyboardEvent) => boolean;
- isDeleteBackward: (event: KeyboardEvent) => boolean;
- isDeleteForward: (event: KeyboardEvent) => boolean;
- isDeleteLineBackward: (event: KeyboardEvent) => boolean;
- isDeleteLineForward: (event: KeyboardEvent) => boolean;
- isDeleteWordBackward: (event: KeyboardEvent) => boolean;
- isDeleteWordForward: (event: KeyboardEvent) => boolean;
- isExtendBackward: (event: KeyboardEvent) => boolean;
- isExtendForward: (event: KeyboardEvent) => boolean;
- isExtendLineBackward: (event: KeyboardEvent) => boolean;
- isExtendLineForward: (event: KeyboardEvent) => boolean;
- isItalic: (event: KeyboardEvent) => boolean;
- isMoveLineBackward: (event: KeyboardEvent) => boolean;
- isMoveLineForward: (event: KeyboardEvent) => boolean;
- isMoveWordBackward: (event: KeyboardEvent) => boolean;
- isMoveWordForward: (event: KeyboardEvent) => boolean;
- isRedo: (event: KeyboardEvent) => boolean;
- isSplitBlock: (event: KeyboardEvent) => boolean;
- isTransposeCharacter: (event: KeyboardEvent) => boolean;
- isUndo: (event: KeyboardEvent) => boolean;
-};
-/**
- * Hotkeys.
- */
-export default _default;
-//# sourceMappingURL=hotkeys.d.ts.map
\ No newline at end of file
diff --git a/dist/utils/hotkeys.d.ts.map b/dist/utils/hotkeys.d.ts.map
deleted file mode 100644
index a26c834..0000000
--- a/dist/utils/hotkeys.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/utils/hotkeys.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkEA;;GAEG;AAEH,wBAwBC"}
\ No newline at end of file
diff --git a/dist/utils/key.d.ts b/dist/utils/key.d.ts
deleted file mode 100644
index 3dfeb2f..0000000
--- a/dist/utils/key.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * An auto-incrementing identifier for keys.
- */
-/**
- * A class that keeps track of a key string. We use a full class here because we
- * want to be able to use them as keys in `WeakMap` objects.
- */
-export declare class Key {
- id: string;
- constructor();
-}
-//# sourceMappingURL=key.d.ts.map
\ No newline at end of file
diff --git a/dist/utils/key.d.ts.map b/dist/utils/key.d.ts.map
deleted file mode 100644
index aec06dc..0000000
--- a/dist/utils/key.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/utils/key.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AAEH,qBAAa,GAAG;IACd,EAAE,EAAE,MAAM,CAAA;;CAKX"}
\ No newline at end of file
diff --git a/dist/utils/weak-maps.d.ts b/dist/utils/weak-maps.d.ts
deleted file mode 100644
index 3245d46..0000000
--- a/dist/utils/weak-maps.d.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Node, Ancestor, Editor } from 'slate';
-import { Key } from './key';
-/**
- * Two weak maps that allow us rebuild a path given a node. They are populated
- * at render time such that after a render occurs we can always backtrack.
- */
-export declare const NODE_TO_INDEX: WeakMap;
-export declare const NODE_TO_PARENT: WeakMap;
-/**
- * Weak maps that allow us to go between Slate nodes and DOM nodes. These
- * are used to resolve DOM event-related logic into Slate actions.
- */
-export declare const EDITOR_TO_ELEMENT: WeakMap;
-export declare const EDITOR_TO_PLACEHOLDER: WeakMap;
-export declare const ELEMENT_TO_NODE: WeakMap;
-export declare const KEY_TO_ELEMENT: WeakMap;
-export declare const NODE_TO_ELEMENT: WeakMap;
-export declare const NODE_TO_KEY: WeakMap;
-/**
- * Weak maps for storing editor-related state.
- */
-export declare const IS_READ_ONLY: WeakMap;
-export declare const IS_FOCUSED: WeakMap;
-export declare const IS_DRAGGING: WeakMap;
-export declare const IS_CLICKING: WeakMap;
-/**
- * Weak map for associating the context `onChange` context with the plugin.
- */
-export declare const EDITOR_TO_ON_CHANGE: WeakMap void>;
-/**
- * Symbols.
- */
-export declare const PLACEHOLDER_SYMBOL: string;
-//# sourceMappingURL=weak-maps.d.ts.map
\ No newline at end of file
diff --git a/dist/utils/weak-maps.d.ts.map b/dist/utils/weak-maps.d.ts.map
deleted file mode 100644
index be6318c..0000000
--- a/dist/utils/weak-maps.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"weak-maps.d.ts","sourceRoot":"","sources":["../packages/slate-react/src/utils/weak-maps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAS,MAAM,OAAO,CAAA;AAErD,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAE3B;;;GAGG;AAEH,eAAO,MAAM,aAAa,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAiB,CAAA;AACjE,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAiB,CAAA;AAEpE;;;GAGG;AAEH,eAAO,MAAM,iBAAiB,EAAE,OAAO,CAAC,MAAM,EAAE,WAAW,CAAiB,CAAA;AAC5E,eAAO,MAAM,qBAAqB,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAiB,CAAA;AAC3E,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,WAAW,EAAE,IAAI,CAAiB,CAAA;AACxE,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,GAAG,EAAE,WAAW,CAAiB,CAAA;AACtE,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAiB,CAAA;AACxE,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,CAAiB,CAAA;AAE5D;;GAEG;AAEH,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAiB,CAAA;AACnE,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAiB,CAAA;AACjE,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAiB,CAAA;AAClE,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAiB,CAAA;AAElE;;GAEG;AAEH,eAAO,MAAM,mBAAmB,6BAAoC,CAAA;AAEpE;;GAEG;AAEH,eAAO,MAAM,kBAAkB,QAA+C,CAAA"}
\ No newline at end of file
diff --git a/node_modules/.bin/direction b/node_modules/.bin/direction
deleted file mode 120000
index 665a528..0000000
--- a/node_modules/.bin/direction
+++ /dev/null
@@ -1 +0,0 @@
-../../../../node_modules/direction/cli.js
\ No newline at end of file
diff --git a/package.json b/package.json
index e2e30bc..523d92a 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
"name": "slate-react",
"description": "Tools for building completely customizable richtext editors with React.",
- "version": "0.59.12",
+ "version": "0.117.3",
"license": "MIT",
- "repository": "git://github.com/happyscribe/slate-react.git",
+ "repository": "git://github.com/ianstormtaylor/slate.git",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
@@ -14,22 +14,36 @@
"dist/"
],
"dependencies": {
- "@types/debounce": "^1.2.0",
- "@types/is-hotkey": "^0.1.1",
- "debounce": "^1.2.0",
- "direction": "^1.0.3",
- "is-hotkey": "^0.1.6",
- "is-plain-object": "^3.0.0",
- "scroll-into-view-if-needed": "^2.2.20"
+ "@juggle/resize-observer": "^3.4.0",
+ "direction": "^1.0.4",
+ "is-hotkey": "^0.2.0",
+ "lodash": "^4.17.21",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "tiny-invariant": "1.3.1"
},
"devDependencies": {
- "slate": "^0.57.1",
- "slate-hyperscript": "^0.57.1"
+ "@babel/runtime": "^7.23.2",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^14.0.0",
+ "@types/is-hotkey": "^0.1.8",
+ "@types/jest": "29.5.6",
+ "@types/jsdom": "^21.1.4",
+ "@types/lodash": "^4.14.200",
+ "@types/react": "^18.2.41",
+ "@types/react-dom": "^18.2.17",
+ "@types/resize-observer-browser": "^0.1.8",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "slate": "^0.117.2",
+ "slate-dom": "^0.116.0",
+ "slate-hyperscript": "^0.115.0",
+ "source-map-loader": "^4.0.1"
},
"peerDependencies": {
- "react": ">=16.8.0",
- "react-dom": ">=16.8.0",
- "slate": ">=0.55.0"
+ "react": ">=18.2.0",
+ "react-dom": ">=18.2.0",
+ "slate": ">=0.114.0",
+ "slate-dom": ">=0.116.0"
},
"umdGlobals": {
"react": "React",
diff --git a/src/chunking/children-helper.ts b/src/chunking/children-helper.ts
new file mode 100644
index 0000000..aa3bb61
--- /dev/null
+++ b/src/chunking/children-helper.ts
@@ -0,0 +1,122 @@
+import { Editor, Descendant } from 'slate'
+import { Key } from 'slate-dom'
+import { ChunkLeaf } from './types'
+import { ReactEditor } from '../plugin/react-editor'
+
+/**
+ * Traverse an array of children, providing helpers useful for reconciling the
+ * children array with a chunk tree
+ */
+export class ChildrenHelper {
+ private editor: Editor
+ private children: Descendant[]
+
+ /**
+ * 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.
+ */
+ private cachedKeys: Array
+
+ /**
+ * The index of the next node to be read in the children array
+ */
+ public pointerIndex: number
+
+ constructor(editor: Editor, children: Descendant[]) {
+ 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
+ */
+ public read(n: number): Descendant[] {
+ // 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++]]
+ }
+
+ const 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.
+ */
+ public remaining(maxChildren?: number): Descendant[] {
+ if (maxChildren === undefined) {
+ return this.children.slice(this.pointerIndex)
+ }
+
+ return this.children.slice(
+ this.pointerIndex,
+ this.pointerIndex + maxChildren
+ )
+ }
+
+ /**
+ * Whether all children have been read
+ */
+ public get reachedEnd() {
+ 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.
+ */
+ public lookAhead(node: Descendant, key: Key) {
+ const elementResult = this.children.indexOf(node, this.pointerIndex)
+ if (elementResult > -1) return elementResult - this.pointerIndex
+
+ for (let i = this.pointerIndex; i < this.children.length; i++) {
+ const candidateNode = this.children[i]
+ const 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
+ */
+ public toChunkLeaves(nodes: Descendant[], startIndex: number): ChunkLeaf[] {
+ return nodes.map((node, i) => ({
+ type: 'leaf',
+ node,
+ key: this.findKey(node, startIndex + i),
+ index: startIndex + i,
+ }))
+ }
+
+ /**
+ * Get the key for a Slate node, cached using the node's index
+ */
+ private findKey(node: Descendant, index: number): Key {
+ const cachedKey = this.cachedKeys[index]
+ if (cachedKey) return cachedKey
+ const key = ReactEditor.findKey(this.editor, node)
+ this.cachedKeys[index] = key
+ return key
+ }
+}
diff --git a/src/chunking/chunk-tree-helper.ts b/src/chunking/chunk-tree-helper.ts
new file mode 100644
index 0000000..34bd36a
--- /dev/null
+++ b/src/chunking/chunk-tree-helper.ts
@@ -0,0 +1,574 @@
+import { Path } from 'slate'
+import { Key } from 'slate-dom'
+import {
+ Chunk,
+ ChunkTree,
+ ChunkLeaf,
+ ChunkDescendant,
+ ChunkAncestor,
+} from './types'
+
+type SavedPointer =
+ | 'start'
+ | {
+ chunk: ChunkAncestor
+ node: ChunkDescendant
+ }
+
+export interface ChunkTreeHelperOptions {
+ chunkSize: number
+ debug?: boolean
+}
+
+/**
+ * Traverse and modify a chunk tree
+ */
+export class ChunkTreeHelper {
+ /**
+ * The root of the chunk tree
+ */
+ private root: ChunkTree
+
+ /**
+ * The ideal size of a chunk
+ */
+ private chunkSize: number
+
+ /**
+ * Whether debug mode is enabled
+ *
+ * If enabled, the pointer state will be checked for internal consistency
+ * after each mutating operation.
+ */
+ private debug: boolean
+
+ /**
+ * 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.
+ */
+ private reachedEnd: boolean
+
+ /**
+ * The chunk containing the current node
+ */
+ private pointerChunk: ChunkAncestor
+
+ /**
+ * The index of the current node within pointerChunk
+ *
+ * Can be -1 to indicate that the pointer is before the start of the tree.
+ */
+ private pointerIndex: number
+
+ /**
+ * 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.
+ */
+ private pointerIndexStack: number[]
+
+ /**
+ * 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.
+ */
+ private cachedPointerNode: ChunkDescendant | null | undefined
+
+ constructor(
+ chunkTree: ChunkTree,
+ { chunkSize, debug }: ChunkTreeHelperOptions
+ ) {
+ this.root = chunkTree
+ this.chunkSize = chunkSize
+ // istanbul ignore next
+ this.debug = 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
+ */
+ public readLeaf(): ChunkLeaf | null {
+ // 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 as ChunkLeaf
+ }
+
+ /**
+ * Move the pointer to the previous leaf in the chunk tree
+ */
+ public 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
+ */
+ public insertBefore(leaves: ChunkLeaf[]) {
+ 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.
+ */
+ public insertAfter(leaves: ChunkLeaf[]) {
+ // istanbul ignore next
+ if (leaves.length === 0) return
+
+ let beforeDepth = 0
+ let 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
+ ) {
+ const remainingCapacity = this.chunkSize - this.pointerSiblings.length
+ const toInsertCount = Math.min(remainingCapacity, leaves.length)
+
+ if (toInsertCount > 0) {
+ const 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
+ const 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
+ let finalPointer: SavedPointer | null = 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) {
+ const remainingCapacity = this.chunkSize - this.pointerSiblings.length
+ const toInsertCount = Math.min(remainingCapacity, leaves.length)
+
+ if (toInsertCount > 0) {
+ const 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
+ const 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
+ */
+ public 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
+ */
+ public invalidateChunk() {
+ for (let c = this.pointerChunk; c.type === 'chunk'; c = c.parent) {
+ this.root.modifiedChunks.add(c)
+ }
+ }
+
+ /**
+ * Whether the pointer is at the start of the tree
+ */
+ private get atStart() {
+ return this.pointerChunk.type === 'root' && this.pointerIndex === -1
+ }
+
+ /**
+ * The siblings of the current node
+ */
+ private get pointerSiblings(): ChunkDescendant[] {
+ 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.
+ */
+ private getPointerNode(): ChunkDescendant | null {
+ if (this.reachedEnd || this.pointerIndex === -1) {
+ return null
+ }
+
+ return this.pointerSiblings[this.pointerIndex]
+ }
+
+ /**
+ * Cached getter for the current node
+ */
+ private get pointerNode(): ChunkDescendant | null {
+ if (this.cachedPointerNode !== undefined) return this.cachedPointerNode
+ const 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
+ */
+ private getChunkPath(chunk: ChunkAncestor): number[] | null {
+ const path: number[] = []
+
+ for (let c = chunk; c.type === 'chunk'; c = c.parent) {
+ const 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
+ */
+ private savePointer(): SavedPointer {
+ 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
+ */
+ private restorePointer(savedPointer: 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.
+
+ const { chunk, node } = savedPointer
+ const 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'
+ )
+ }
+
+ const 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.
+ */
+ private enterChunk(end: boolean) {
+ // istanbul ignore next
+ if (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.
+ */
+ private enterChunkUntilLeaf(end: boolean) {
+ while (this.pointerNode?.type === 'chunk') {
+ this.enterChunk(end)
+ }
+ }
+
+ /**
+ * Move the pointer to the parent chunk
+ */
+ private exitChunk() {
+ // istanbul ignore next
+ if (this.pointerChunk.type === 'root') {
+ throw new Error('Cannot exit root')
+ }
+
+ const 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
+ */
+ private rawInsertAfter(leaves: ChunkLeaf[], minDepth: number) {
+ if (leaves.length === 0) return
+
+ const groupIntoChunks = (
+ leaves: ChunkLeaf[],
+ parent: ChunkAncestor,
+ perChunk: number
+ ): ChunkDescendant[] => {
+ if (perChunk === 1) return leaves
+ const chunks: Chunk[] = []
+
+ for (let i = 0; i < this.chunkSize; i++) {
+ const chunkNodes = leaves.slice(i * perChunk, (i + 1) * perChunk)
+ if (chunkNodes.length === 0) break
+
+ const chunk: Chunk = {
+ type: 'chunk',
+ key: new Key(),
+ 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
+ const newTotal = this.pointerSiblings.length + leaves.length
+ let depthForTotal = 0
+
+ for (let i = this.chunkSize; i < newTotal; i *= this.chunkSize) {
+ depthForTotal++
+ }
+
+ // A depth of 0 means no chunking
+ const depth = Math.max(depthForTotal, minDepth)
+ const perTopLevelChunk = Math.pow(this.chunkSize, depth)
+
+ const chunks = groupIntoChunks(leaves, this.pointerChunk, perTopLevelChunk)
+ this.pointerSiblings.splice(this.pointerIndex + 1, 0, ...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
+ private validateState() {
+ if (!this.debug) return
+
+ const validateDescendant = (node: ChunkDescendant) => {
+ if (node.type === 'chunk') {
+ const { parent, children } = node
+
+ if (!parent.children.includes(node)) {
+ throw new Error(
+ `Debug: Chunk ${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'
+ )
+ }
+
+ const actualIndexStack = this.getChunkPath(this.pointerChunk)
+
+ if (!actualIndexStack) {
+ throw new Error('Debug: The pointer chunk is not connected to the root')
+ }
+
+ if (!Path.equals(this.pointerIndexStack, actualIndexStack)) {
+ throw new Error(
+ `Debug: The cached index stack [${this.pointerIndexStack.join(
+ ', '
+ )}] does not match the path of the pointer chunk [${actualIndexStack.join(
+ ', '
+ )}]`
+ )
+ }
+ }
+}
diff --git a/src/chunking/get-chunk-tree-for-node.ts b/src/chunking/get-chunk-tree-for-node.ts
new file mode 100644
index 0000000..fd073b3
--- /dev/null
+++ b/src/chunking/get-chunk-tree-for-node.ts
@@ -0,0 +1,47 @@
+import { Ancestor, Editor } from 'slate'
+import { Key } from 'slate-dom'
+import { ChunkTree } from './types'
+import { ReconcileOptions, reconcileChildren } from './reconcile-children'
+import { ReactEditor } from '../plugin/react-editor'
+
+export const 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.
+ */
+export const getChunkTreeForNode = (
+ editor: Editor,
+ node: Ancestor,
+ // istanbul ignore next
+ options: {
+ reconcile?: Omit | false
+ } = {}
+) => {
+ const key = ReactEditor.findKey(editor, node)
+ let 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, {
+ chunkTree,
+ children: node.children,
+ ...options.reconcile,
+ })
+ }
+
+ return chunkTree
+}
diff --git a/src/chunking/index.ts b/src/chunking/index.ts
new file mode 100644
index 0000000..ee8ea2a
--- /dev/null
+++ b/src/chunking/index.ts
@@ -0,0 +1,2 @@
+export * from './get-chunk-tree-for-node'
+export * from './types'
diff --git a/src/chunking/reconcile-children.ts b/src/chunking/reconcile-children.ts
new file mode 100644
index 0000000..f8911a1
--- /dev/null
+++ b/src/chunking/reconcile-children.ts
@@ -0,0 +1,127 @@
+import { Editor, Descendant } from 'slate'
+import { ChunkTree, ChunkLeaf } from './types'
+import { ChunkTreeHelper, ChunkTreeHelperOptions } from './chunk-tree-helper'
+import { ChildrenHelper } from './children-helper'
+
+export interface ReconcileOptions extends ChunkTreeHelperOptions {
+ chunkTree: ChunkTree
+ children: Descendant[]
+ chunkSize: number
+ rerenderChildren?: number[]
+ onInsert?: (node: Descendant, index: number) => void
+ onUpdate?: (node: Descendant, index: number) => void
+ onIndexChange?: (node: Descendant, index: number) => void
+ debug?: boolean
+}
+
+/**
+ * Update the chunk tree to match the children array, inserting, removing and
+ * updating differing nodes
+ */
+export const reconcileChildren = (
+ editor: Editor,
+ {
+ chunkTree,
+ children,
+ chunkSize,
+ rerenderChildren = [],
+ onInsert,
+ onUpdate,
+ onIndexChange,
+ debug,
+ }: ReconcileOptions
+) => {
+ chunkTree.modifiedChunks.clear()
+
+ const chunkTreeHelper = new ChunkTreeHelper(chunkTree, { chunkSize, debug })
+ const childrenHelper = new ChildrenHelper(editor, children)
+
+ let treeLeaf: ChunkLeaf | null
+
+ // 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.
+ while ((treeLeaf = chunkTreeHelper.readLeaf())) {
+ // 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.
+ const 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.
+ const wasMoved = lookAhead > 0 && chunkTree.movedNodeKeys.has(treeLeaf.key)
+
+ // If the tree leaf was moved or removed, remove it
+ if (lookAhead === -1 || wasMoved) {
+ chunkTreeHelper.remove()
+ continue
+ }
+
+ // Get the matching Slate node and any nodes that may have been inserted
+ // prior to it. Insert these into the chunk tree.
+ const insertedChildrenStartIndex = childrenHelper.pointerIndex
+ const insertedChildren = childrenHelper.read(lookAhead + 1)
+ const matchingChild = insertedChildren.pop()!
+
+ if (insertedChildren.length) {
+ const leavesToInsert = childrenHelper.toChunkLeaves(
+ insertedChildren,
+ insertedChildrenStartIndex
+ )
+
+ chunkTreeHelper.insertBefore(leavesToInsert)
+
+ insertedChildren.forEach((node, relativeIndex) => {
+ onInsert?.(node, insertedChildrenStartIndex + relativeIndex)
+ })
+ }
+
+ const 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?.(matchingChild, matchingChildIndex)
+ }
+
+ // Update the index if it has changed
+ if (treeLeaf.index !== matchingChildIndex) {
+ treeLeaf.index = matchingChildIndex
+ onIndexChange?.(matchingChild, matchingChildIndex)
+ }
+
+ // Manually invalidate chunks containing specific children that we want to
+ // re-render
+ if (rerenderChildren.includes(matchingChildIndex)) {
+ chunkTreeHelper.invalidateChunk()
+ }
+ }
+
+ // 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) {
+ const remainingChildren = childrenHelper.remaining()
+
+ const 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((node, relativeIndex) => {
+ onInsert?.(node, childrenHelper.pointerIndex + relativeIndex)
+ })
+ }
+
+ chunkTree.movedNodeKeys.clear()
+}
diff --git a/src/chunking/types.ts b/src/chunking/types.ts
new file mode 100644
index 0000000..b860331
--- /dev/null
+++ b/src/chunking/types.ts
@@ -0,0 +1,52 @@
+import { Descendant } from 'slate'
+import { Key } from 'slate-dom'
+
+export interface ChunkTree {
+ type: 'root'
+ children: ChunkDescendant[]
+
+ /**
+ * The keys of any Slate nodes that have been moved using move_node since the
+ * last render
+ *
+ * Detecting when a node has been moved to a different position in the
+ * children array is impossible to do efficiently while reconciling the chunk
+ * tree. This interferes with the reconciliation logic since it is treated as
+ * if the intermediate nodes were inserted and removed, causing them to be
+ * re-chunked unnecessarily.
+ *
+ * This set is used to detect when a node has been moved so that this case
+ * can be handled correctly and efficiently.
+ */
+ movedNodeKeys: Set
+
+ /**
+ * The chunks whose descendants have been modified during the most recent
+ * reconciliation
+ *
+ * Used to determine when the otherwise memoized React components for each
+ * chunk should be re-rendered.
+ */
+ modifiedChunks: Set
+}
+
+export interface Chunk {
+ type: 'chunk'
+ key: Key
+ parent: ChunkAncestor
+ children: ChunkDescendant[]
+}
+
+// A chunk leaf is unrelated to a Slate leaf; it is a leaf of the chunk tree,
+// containing a single element that is a child of the Slate node the chunk tree
+// belongs to.
+export interface ChunkLeaf {
+ type: 'leaf'
+ key: Key
+ node: Descendant
+ index: number
+}
+
+export type ChunkAncestor = ChunkTree | Chunk
+export type ChunkDescendant = Chunk | ChunkLeaf
+export type ChunkNode = ChunkTree | Chunk | ChunkLeaf
diff --git a/src/components/children.tsx b/src/components/children.tsx
deleted file mode 100644
index 53e44fb..0000000
--- a/src/components/children.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from 'react'
-import { Editor, Range, Element, NodeEntry, Ancestor, Descendant } from 'slate'
-
-import ElementComponent from './element'
-import TextComponent from './text'
-import { ReactEditor } from '..'
-import { useEditor } from '../hooks/use-editor'
-import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
-import { RenderElementProps, RenderLeafProps } from './editable'
-
-/**
- * Children.
- */
-
-const Children = (props: {
- decorate: (entry: NodeEntry) => Range[]
- decorations: Range[]
- node: Ancestor
- renderElement?: (props: RenderElementProps) => JSX.Element
- renderLeaf?: (props: RenderLeafProps) => JSX.Element
- selection: Range | null
- ReactHappyWindow: React.Component | undefined
- reactHappyWindowProps: Object | undefined
-}) => {
- const {
- decorate,
- decorations,
- node,
- renderElement,
- renderLeaf,
- selection,
- ReactHappyWindow,
- reactHappyWindowProps = {},
- } = props
- const editor = useEditor()
- const path = ReactEditor.findPath(editor, node)
- const children = []
- const isLeafBlock =
- Element.isElement(node) &&
- !editor.isInline(node) &&
- Editor.hasInlines(editor, node)
-
- const renderChild = (i: number) => {
- const p = path.concat(i)
- const n = node.children[i] as Descendant
- const key = ReactEditor.findKey(editor, n)
- const range = Editor.range(editor, p)
- const sel = selection && Range.intersection(range, selection)
-
- // Commented out to improve performance. We don't use decorations
- // const ds = decorate([n, p])
- const ds = [] as Range[]
- // for (const dec of decorations) {
- // const d = Range.intersection(dec, range)
-
- // if (d) {
- // ds.push(d)
- // }
- // }
-
- NODE_TO_INDEX.set(n, i)
- NODE_TO_PARENT.set(n, node)
-
- if (Element.isElement(n)) {
- return (
-
- )
- } else {
- return (
-
- )
- }
- }
-
- if (ReactHappyWindow) {
- return (
-
- )
- }
-
- for (let i = 0; i < node.children.length; i++) {
- children.push(renderChild(i))
- }
-
- return {children}
-}
-
-export default Children
diff --git a/src/components/chunk-tree.tsx b/src/components/chunk-tree.tsx
new file mode 100644
index 0000000..d55b8c0
--- /dev/null
+++ b/src/components/chunk-tree.tsx
@@ -0,0 +1,65 @@
+import React, { Fragment } from 'react'
+import { Element } from 'slate'
+import { Key } from 'slate-dom'
+import { RenderChunkProps } from './editable'
+import {
+ Chunk as TChunk,
+ ChunkAncestor as TChunkAncestor,
+ ChunkTree as TChunkTree,
+} from '../chunking'
+
+const defaultRenderChunk = ({ children }: RenderChunkProps) => children
+
+const ChunkAncestor = (props: {
+ root: TChunkTree
+ ancestor: C
+ renderElement: (node: Element, index: number, key: Key) => JSX.Element
+ renderChunk?: (props: RenderChunkProps) => JSX.Element
+}) => {
+ const {
+ root,
+ ancestor,
+ renderElement,
+ renderChunk = defaultRenderChunk,
+ } = props
+
+ return ancestor.children.map(chunkNode => {
+ if (chunkNode.type === 'chunk') {
+ const key = chunkNode.key.id
+
+ const renderedChunk = renderChunk({
+ highest: ancestor === root,
+ lowest: chunkNode.children.some(c => c.type === 'leaf'),
+ attributes: { 'data-slate-chunk': true },
+ children: (
+
+ ),
+ })
+
+ return {renderedChunk}
+ }
+
+ // Only blocks containing no inlines are chunked
+ const element = chunkNode.node as Element
+
+ return renderElement(element, chunkNode.index, chunkNode.key)
+ })
+}
+
+const ChunkTree = ChunkAncestor
+
+const MemoizedChunk = React.memo(
+ ChunkAncestor,
+ (prev, next) =>
+ prev.root === next.root &&
+ prev.renderElement === next.renderElement &&
+ prev.renderChunk === next.renderChunk &&
+ !next.root.modifiedChunks.has(next.ancestor)
+)
+
+export default ChunkTree
diff --git a/src/components/editable.tsx b/src/components/editable.tsx
index 34d0dc7..3774a83 100644
--- a/src/components/editable.tsx
+++ b/src/components/editable.tsx
@@ -1,41 +1,89 @@
-import React, { useEffect, useRef, useMemo, useCallback } from 'react'
+import getDirection from 'direction'
+import debounce from 'lodash/debounce'
+import throttle from 'lodash/throttle'
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useReducer,
+ useRef,
+ useState,
+ forwardRef,
+ ForwardedRef,
+} from 'react'
+import { JSX } from 'react'
+import scrollIntoView from 'scroll-into-view-if-needed'
import {
Editor,
Element,
- NodeEntry,
Node,
+ NodeEntry,
+ Path,
Range,
Text,
Transforms,
+ DecoratedRange,
+ LeafPosition,
} from 'slate'
-import getDirection from 'direction'
-import debounce from 'debounce'
-import scrollIntoView from 'scroll-into-view-if-needed'
-
-import Children from './children'
-import Hotkeys from '../utils/hotkeys'
-import { IS_FIREFOX, IS_SAFARI } from '../utils/environment'
-import { ReactEditor } from '..'
+import { useAndroidInputManager } from '../hooks/android-input-manager/use-android-input-manager'
+import useChildren from '../hooks/use-children'
+import { DecorateContext, useDecorateContext } from '../hooks/use-decorations'
+import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
import { ReadOnlyContext } from '../hooks/use-read-only'
import { useSlate } from '../hooks/use-slate'
-import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
+import { useTrackUserInput } from '../hooks/use-track-user-input'
+import { ReactEditor } from '../plugin/react-editor'
+import { TRIPLE_CLICK } from 'slate-dom'
import {
DOMElement,
- DOMNode,
DOMRange,
+ DOMText,
+ getActiveElement,
+ getDefaultView,
+ getSelection,
isDOMElement,
isDOMNode,
- isDOMText,
- DOMStaticRange,
-} from '../utils/dom'
+ isPlainTextOnlyPaste,
+} from 'slate-dom'
import {
+ CAN_USE_DOM,
+ HAS_BEFORE_INPUT_SUPPORT,
+ IS_ANDROID,
+ IS_CHROME,
+ IS_FIREFOX,
+ IS_FIREFOX_LEGACY,
+ IS_IOS,
+ IS_WEBKIT,
+ IS_UC_MOBILE,
+ IS_WECHATBROWSER,
+} from 'slate-dom'
+import { Hotkeys } from 'slate-dom'
+import {
+ IS_NODE_MAP_DIRTY,
EDITOR_TO_ELEMENT,
+ EDITOR_TO_FORCE_RENDER,
+ EDITOR_TO_PENDING_INSERTION_MARKS,
+ EDITOR_TO_USER_MARKS,
+ EDITOR_TO_USER_SELECTION,
+ EDITOR_TO_WINDOW,
ELEMENT_TO_NODE,
+ IS_COMPOSING,
+ IS_FOCUSED,
IS_READ_ONLY,
+ MARK_PLACEHOLDER_SYMBOL,
NODE_TO_ELEMENT,
- IS_FOCUSED,
PLACEHOLDER_SYMBOL,
-} from '../utils/weak-maps'
+} from 'slate-dom'
+import { RestoreDOM } from './restore-dom/restore-dom'
+import { AndroidInputManager } from '../hooks/android-input-manager/android-input-manager'
+import { ComposingContext } from '../hooks/use-composing'
+import { useFlushDeferredSelectorsOnRender } from '../hooks/use-slate-selector'
+
+type DeferredOperation = () => void
+
+const Children = (props: Parameters[0]) => (
+ {useChildren(props)}
+)
/**
* `RenderElementProps` are passed to the `renderElement` handler.
@@ -53,17 +101,49 @@ export interface RenderElementProps {
}
}
+/**
+ * `RenderChunkProps` are passed to the `renderChunk` handler
+ */
+export interface RenderChunkProps {
+ highest: boolean
+ lowest: boolean
+ children: any
+ attributes: {
+ 'data-slate-chunk': true
+ }
+}
+
/**
* `RenderLeafProps` are passed to the `renderLeaf` handler.
*/
export interface RenderLeafProps {
children: any
+ /**
+ * The leaf node with any applied decorations.
+ * If no decorations are applied, it will be identical to the `text` property.
+ */
leaf: Text
text: Text
attributes: {
'data-slate-leaf': true
}
+ /**
+ * The position of the leaf within the Text node, only present when the text node is split by decorations.
+ */
+ leafPosition?: LeafPosition
+}
+
+/**
+ * `RenderTextProps` are passed to the `renderText` handler.
+ */
+export interface RenderTextProps {
+ text: Text
+ children: any
+ attributes: {
+ 'data-slate-node': 'text'
+ ref: any
+ }
}
/**
@@ -71,1082 +151,1902 @@ export interface RenderLeafProps {
*/
export type EditableProps = {
- decorate?: (entry: NodeEntry) => Range[]
- onDOMBeforeInput?: (event: Event) => void
+ decorate?: (entry: NodeEntry) => DecoratedRange[]
+ onDOMBeforeInput?: (event: InputEvent) => void
placeholder?: string
readOnly?: boolean
role?: string
style?: React.CSSProperties
renderElement?: (props: RenderElementProps) => JSX.Element
+ renderChunk?: (props: RenderChunkProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
+ renderText?: (props: RenderTextProps) => JSX.Element
+ renderPlaceholder?: (props: RenderPlaceholderProps) => JSX.Element
+ scrollSelectionIntoView?: (editor: ReactEditor, domRange: DOMRange) => void
as?: React.ElementType
- ReactHappyWindow?: React.Component
- reactHappyWindowProps?: object
+ disableDefaultStyles?: boolean
} & React.TextareaHTMLAttributes
/**
* Editable.
*/
-export const Editable = (props: EditableProps) => {
- const {
- autoFocus,
- decorate = defaultDecorate,
- onDOMBeforeInput: propsOnDOMBeforeInput,
- placeholder,
- readOnly = false,
- renderElement,
- renderLeaf,
- style = {},
- as: Component = 'div',
- ReactHappyWindow,
- reactHappyWindowProps,
- happyWindowRef,
- ...attributes
- } = props
- const editor = useSlate()
- const ref = useRef(null)
-
- // Update internal state on each render.
- IS_READ_ONLY.set(editor, readOnly)
-
- // Keep track of some state for the event handler logic.
- const state = useMemo(
- () => ({
- isComposing: false,
- isUpdatingSelection: false,
- latestElement: null as DOMElement | null,
- }),
- []
- )
+export const Editable = forwardRef(
+ (props: EditableProps, forwardedRef: ForwardedRef) => {
+ const defaultRenderPlaceholder = useCallback(
+ (props: RenderPlaceholderProps) => ,
+ []
+ )
+ const {
+ autoFocus,
+ decorate = defaultDecorate,
+ onDOMBeforeInput: propsOnDOMBeforeInput,
+ placeholder,
+ readOnly = false,
+ renderElement,
+ renderChunk,
+ renderLeaf,
+ renderText,
+ renderPlaceholder = defaultRenderPlaceholder,
+ scrollSelectionIntoView = defaultScrollSelectionIntoView,
+ style: userStyle = {},
+ as: Component = 'div',
+ disableDefaultStyles = false,
+ ...attributes
+ } = props
+ const editor = useSlate()
+ // Rerender editor when composition status changed
+ const [isComposing, setIsComposing] = useState(false)
+ const ref = useRef(null)
+ const deferredOperations = useRef([])
+ const [placeholderHeight, setPlaceholderHeight] = useState<
+ number | undefined
+ >()
+ const processing = useRef(false)
+
+ const { onUserInput, receivedUserInput } = useTrackUserInput()
+
+ const [, forceRender] = useReducer(s => s + 1, 0)
+ EDITOR_TO_FORCE_RENDER.set(editor, forceRender)
+
+ // Update internal state on each render.
+ IS_READ_ONLY.set(editor, readOnly)
+
+ // Keep track of some state for the event handler logic.
+ const state = useMemo(
+ () => ({
+ isDraggingInternally: false,
+ isUpdatingSelection: false,
+ latestElement: null as DOMElement | 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.
+ useEffect(() => {
+ 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.
+ */
+ const androidInputManagerRef = useRef<
+ AndroidInputManager | null | undefined
+ >()
+
+ // 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.
+ const onDOMSelectionChange = useMemo(
+ () =>
+ throttle(() => {
+ if (IS_NODE_MAP_DIRTY.get(editor)) {
+ onDOMSelectionChange()
+ return
+ }
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- EDITOR_TO_ELEMENT.set(editor, ref.current)
- NODE_TO_ELEMENT.set(editor, ref.current)
- ELEMENT_TO_NODE.set(ref.current, editor)
- } else {
- NODE_TO_ELEMENT.delete(editor)
- }
- })
-
- // 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
- useIsomorphicLayoutEffect(() => {
- window.document.addEventListener('selectionchange', onDOMSelectionChange)
-
- return () => {
- window.document.removeEventListener(
- 'selectionchange',
- onDOMSelectionChange
- )
- }
- }, [])
-
- // 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
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- // @ts-ignore The `beforeinput` event isn't recognized.
- ref.current.addEventListener('beforeinput', onDOMBeforeInput)
- }
+ const el = ReactEditor.toDOMNode(editor, editor)
+ const root = el.getRootNode()
- return () => {
- if (ref.current) {
- // @ts-ignore The `beforeinput` event isn't recognized.
- ref.current.removeEventListener('beforeinput', onDOMBeforeInput)
- }
- }
- }, [])
+ if (!processing.current && IS_WEBKIT && root instanceof ShadowRoot) {
+ processing.current = true
- // Whenever the editor updates, make sure the DOM selection state is in sync.
- useIsomorphicLayoutEffect(() => {
- const { selection } = editor
- const domSelection = window.getSelection()
+ const active = getActiveElement()
- if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {
- return
- }
+ if (active) {
+ document.execCommand('indent')
+ } else {
+ Transforms.deselect(editor)
+ }
- const hasDomSelection = domSelection.type !== 'None'
+ processing.current = false
+ return
+ }
- // If the DOM selection is properly unset, we're done.
- if (!selection && !hasDomSelection) {
- return
- }
+ const androidInputManager = androidInputManagerRef.current
+ if (
+ (IS_ANDROID || !ReactEditor.isComposing(editor)) &&
+ (!state.isUpdatingSelection || androidInputManager?.isFlushing()) &&
+ !state.isDraggingInternally
+ ) {
+ const root = ReactEditor.findDocumentOrShadowRoot(editor)
+ const { activeElement } = root
+ const el = ReactEditor.toDOMNode(editor, editor)
+ const domSelection = getSelection(root)
- const newDomRange = selection && ReactEditor.toDOMRange(editor, selection)
+ if (activeElement === el) {
+ state.latestElement = activeElement
+ IS_FOCUSED.set(editor, true)
+ } else {
+ IS_FOCUSED.delete(editor)
+ }
- // If the DOM selection is already correct, we're done.
- if (
- hasDomSelection &&
- newDomRange &&
- isRangeEqual(domSelection.getRangeAt(0), newDomRange)
- ) {
- return
- }
+ if (!domSelection) {
+ return Transforms.deselect(editor)
+ }
- // Otherwise the DOM selection is out of sync, so update it.
- const el = ReactEditor.toDOMNode(editor, editor)
- state.isUpdatingSelection = true
- domSelection.removeAllRanges()
+ const { anchorNode, focusNode } = domSelection
- if (newDomRange) {
- domSelection.addRange(newDomRange!)
- const leafEl = newDomRange.startContainer.parentElement!
- scrollIntoView(leafEl, { scrollMode: 'if-needed' })
- }
+ const anchorNodeSelectable =
+ ReactEditor.hasEditableTarget(editor, anchorNode) ||
+ ReactEditor.isTargetInsideNonReadonlyVoid(editor, anchorNode)
- setTimeout(() => {
- // COMPAT: In Firefox, it's not enough to create a range, you also need
- // to focus the contenteditable element too. (2016/11/16)
- if (newDomRange && IS_FIREFOX) {
- el.focus()
- }
+ const focusNodeInEditor = ReactEditor.hasTarget(editor, focusNode)
+
+ if (anchorNodeSelectable && focusNodeInEditor) {
+ const range = ReactEditor.toSlateRange(editor, domSelection, {
+ exactMatch: false,
+ suppressThrow: true,
+ })
+
+ if (range) {
+ if (
+ !ReactEditor.isComposing(editor) &&
+ !androidInputManager?.hasPendingChanges() &&
+ !androidInputManager?.isFlushing()
+ ) {
+ Transforms.select(editor, range)
+ } else {
+ androidInputManager?.handleUserSelect(range)
+ }
+ }
+ }
- state.isUpdatingSelection = false
+ // Deselect the editor if the dom selection is not selectable in readonly mode
+ if (readOnly && (!anchorNodeSelectable || !focusNodeInEditor)) {
+ Transforms.deselect(editor)
+ }
+ }
+ }, 100),
+ [editor, readOnly, state]
+ )
+
+ const scheduleOnDOMSelectionChange = useMemo(
+ () => debounce(onDOMSelectionChange, 0),
+ [onDOMSelectionChange]
+ )
+
+ androidInputManagerRef.current = useAndroidInputManager({
+ node: ref,
+ onDOMSelectionChange,
+ scheduleOnDOMSelectionChange,
})
- })
- // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
- // needs to be manually focused.
- useEffect(() => {
- if (ref.current && autoFocus) {
- ref.current.focus()
- }
- }, [autoFocus])
-
- // 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
- const onDOMBeforeInput = useCallback(
- (
- event: Event & {
- data: string | null
- dataTransfer: DataTransfer | null
- getTargetRanges(): DOMStaticRange[]
- inputType: string
- isComposing: boolean
+ useIsomorphicLayoutEffect(() => {
+ // Update element-related weak maps with the DOM element ref.
+ let window
+ if (ref.current && (window = getDefaultView(ref.current))) {
+ EDITOR_TO_WINDOW.set(editor, window)
+ EDITOR_TO_ELEMENT.set(editor, ref.current)
+ NODE_TO_ELEMENT.set(editor, ref.current)
+ ELEMENT_TO_NODE.set(ref.current, editor)
+ } else {
+ NODE_TO_ELEMENT.delete(editor)
}
- ) => {
+
+ // Make sure the DOM selection state is in sync.
+ const { selection } = editor
+ const root = ReactEditor.findDocumentOrShadowRoot(editor)
+ const domSelection = getSelection(root)
+
if (
- !readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isDOMEventHandled(event, propsOnDOMBeforeInput)
+ !domSelection ||
+ !ReactEditor.isFocused(editor) ||
+ androidInputManagerRef.current?.hasPendingAction()
) {
- const { selection } = editor
- const { inputType: type } = event
- const data = event.dataTransfer || event.data || undefined
+ return
+ }
+
+ const setDomSelection = (forceChange?: boolean) => {
+ const hasDomSelection = domSelection.type !== 'None'
- // These two types occur while a user is composing text and can't be
- // cancelled. Let them through and wait for the composition to end.
+ // If the DOM selection is properly unset, we're done.
+ if (!selection && !hasDomSelection) {
+ return
+ }
+
+ // Get anchorNode and focusNode
+ const focusNode = domSelection.focusNode
+ let anchorNode
+
+ // COMPAT: In firefox the normal selection way does not work
+ // (https://github.com/ianstormtaylor/slate/pull/5486#issue-1820720223)
+ if (IS_FIREFOX && domSelection.rangeCount > 1) {
+ const firstRange = domSelection.getRangeAt(0)
+ const 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
+ const editorElement = EDITOR_TO_ELEMENT.get(editor)!
+ let hasDomSelectionInEditor = false
if (
- type === 'insertCompositionText' ||
- type === 'deleteCompositionText'
+ editorElement.contains(anchorNode) &&
+ editorElement.contains(focusNode)
) {
- return
+ hasDomSelectionInEditor = true
}
- event.preventDefault()
+ // If the DOM selection is in the editor and the editor selection is already correct, we're done.
+ if (
+ hasDomSelection &&
+ hasDomSelectionInEditor &&
+ selection &&
+ !forceChange
+ ) {
+ const slateRange = ReactEditor.toSlateRange(editor, domSelection, {
+ exactMatch: true,
- // 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 (!type.startsWith('delete') || type.startsWith('deleteBy')) {
- const [targetRange] = event.getTargetRanges()
+ // domSelection is not necessarily a valid Slate range
+ // (e.g. when clicking on contentEditable:false element)
+ suppressThrow: true,
+ })
- if (targetRange) {
- const range = ReactEditor.toSlateRange(editor, targetRange)
- if (!range) {
+ if (slateRange && Range.equals(slateRange, selection)) {
+ if (!state.hasMarkPlaceholder) {
return
}
- if (!selection || !Range.equals(selection, range)) {
- Transforms.select(editor, range)
+
+ // Ensure selection is inside the mark placeholder
+ if (
+ anchorNode?.parentElement?.hasAttribute(
+ 'data-slate-mark-placeholder'
+ )
+ ) {
+ return
}
}
}
- // COMPAT: If the selection is expanded, even if the command seems like
- // a delete forward/backward command it should delete the selection.
- if (
- selection &&
- Range.isExpanded(selection) &&
- type.startsWith('delete')
- ) {
- Editor.deleteFragment(editor)
+ // when 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
}
- switch (type) {
- case 'deleteByComposition':
- case 'deleteByCut':
- case 'deleteByDrag': {
- Editor.deleteFragment(editor)
- break
- }
+ // Otherwise the DOM selection is out of sync, so update it.
+ state.isUpdatingSelection = true
- case 'deleteContent':
- case 'deleteContentForward': {
- Editor.deleteForward(editor)
- break
- }
+ let newDomRange: DOMRange | null = null
- case 'deleteContentBackward': {
- Editor.deleteBackward(editor)
- break
- }
-
- case 'deleteEntireSoftLine': {
- Editor.deleteBackward(editor, { unit: 'line' })
- Editor.deleteForward(editor, { unit: 'line' })
- break
- }
+ try {
+ newDomRange = selection && ReactEditor.toDOMRange(editor, selection)
+ } catch (e) {
+ // Ignore, dom and state might be out of sync
+ }
- case 'deleteHardLineBackward': {
- Editor.deleteBackward(editor, { unit: 'block' })
- break
+ if (newDomRange) {
+ if (ReactEditor.isComposing(editor) && !IS_ANDROID) {
+ domSelection.collapseToEnd()
+ } else if (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()
+ }
- case 'deleteSoftLineBackward': {
- Editor.deleteBackward(editor, { unit: 'line' })
- break
- }
+ return newDomRange
+ }
- case 'deleteHardLineForward': {
- Editor.deleteForward(editor, { unit: 'block' })
- break
- }
+ // 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()
+ }
- case 'deleteSoftLineForward': {
- Editor.deleteForward(editor, { unit: 'line' })
- break
- }
+ const ensureSelection =
+ androidInputManagerRef.current?.isFlushing() === 'action'
- case 'deleteWordBackward': {
- Editor.deleteBackward(editor, { unit: 'word' })
- break
- }
+ if (!IS_ANDROID || !ensureSelection) {
+ setTimeout(() => {
+ state.isUpdatingSelection = false
+ })
+ return
+ }
- case 'deleteWordForward': {
- Editor.deleteForward(editor, { unit: 'word' })
- break
- }
+ let timeoutId: ReturnType | null = null
+ const animationFrameId = requestAnimationFrame(() => {
+ if (ensureSelection) {
+ const ensureDomSelection = (forceChange?: boolean) => {
+ try {
+ const el = ReactEditor.toDOMNode(editor, editor)
+ el.focus()
- case 'insertLineBreak':
- case 'insertParagraph': {
- Editor.insertBreak(editor)
- break
+ setDomSelection(forceChange)
+ } catch (e) {
+ // Ignore, dom and state might be out of sync
+ }
}
- case 'insertFromComposition':
- case 'insertFromDrop':
- case 'insertFromPaste':
- case 'insertFromYank':
- case 'insertReplacementText':
- case 'insertText': {
- if (data instanceof DataTransfer) {
- ReactEditor.insertData(editor, data)
- } else if (typeof data === 'string') {
- Editor.insertText(editor, data)
- }
+ // 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(() => {
+ // 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
+ })
+ }
+ })
- break
- }
+ return () => {
+ cancelAnimationFrame(animationFrameId)
+ if (timeoutId) {
+ clearTimeout(timeoutId)
}
}
- },
- []
- )
+ })
- // 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.
- const onDOMSelectionChange = useCallback(
- debounce(() => {
- if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {
- const { activeElement } = window.document
+ // 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
+ const onDOMBeforeInput = useCallback(
+ (event: InputEvent) => {
+ handleNativeHistoryEvents(editor, event)
const el = ReactEditor.toDOMNode(editor, editor)
- const domSelection = window.getSelection()
- const domRange =
- domSelection &&
- domSelection.rangeCount > 0 &&
- domSelection.getRangeAt(0)
-
- if (activeElement === el) {
- state.latestElement = activeElement
- IS_FOCUSED.set(editor, true)
- } else {
- IS_FOCUSED.delete(editor)
+ const root = el.getRootNode()
+
+ if (processing?.current && IS_WEBKIT && root instanceof ShadowRoot) {
+ const ranges = event.getTargetRanges()
+ const range = ranges[0]
+
+ const newRange = new window.Range()
+
+ newRange.setStart(range.startContainer, range.startOffset)
+ newRange.setEnd(range.endContainer, range.endOffset)
+
+ // Translate the DOM Range into a Slate Range
+ const slateRange = ReactEditor.toSlateRange(editor, newRange, {
+ exactMatch: false,
+ suppressThrow: false,
+ })
+
+ Transforms.select(editor, slateRange)
+
+ event.preventDefault()
+ event.stopImmediatePropagation()
+ return
}
+ onUserInput()
if (
- domRange &&
- hasEditableTarget(editor, domRange.startContainer) &&
- hasEditableTarget(editor, domRange.endContainer)
+ !readOnly &&
+ ReactEditor.hasEditableTarget(editor, event.target) &&
+ !isDOMEventHandled(event, propsOnDOMBeforeInput)
) {
- const range = ReactEditor.toSlateRange(editor, domRange)
- Transforms.select(editor, range)
- } else {
- Transforms.deselect(editor)
- }
- }
- }, 100),
- []
- )
+ // 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)
+ }
- const decorations = decorate([editor, []])
+ // 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()
- if (
- placeholder &&
- editor.children.length === 1 &&
- Array.from(Node.texts(editor)).length === 1 &&
- Node.string(editor) === ''
- ) {
- const start = Editor.start(editor, [])
- decorations.push({
- [PLACEHOLDER_SYMBOL]: true,
- placeholder,
- anchor: start,
- focus: start,
- })
- }
+ const { selection } = editor
+ const { inputType: type } = event
+ const data = (event as any).dataTransfer || event.data || undefined
- return (
-
- {
- // COMPAT: Firefox doesn'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 (IS_FIREFOX && !readOnly && ReactEditor.isFocused(editor)) {
- event.preventDefault()
- const text = (event as any).data as string
- Editor.insertText(editor, text)
- }
- },
- [readOnly]
- )}
- onBlur={useCallback(
- (event: React.FocusEvent) => {
- if (
- readOnly ||
- state.isUpdatingSelection ||
- !hasEditableTarget(editor, event.target) ||
- isEventHandled(event, attributes.onBlur)
- ) {
- return
- }
+ const isCompositionChange =
+ type === 'insertCompositionText' || type === 'deleteCompositionText'
- // 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.
- if (state.latestElement === window.document.activeElement) {
- return
+ // 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
+ }
+
+ let native = false
+ if (
+ type === 'insertText' &&
+ selection &&
+ 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
}
- const { relatedTarget } = event
- const el = ReactEditor.toDOMNode(editor, editor)
+ // If the NODE_MAP is dirty, we can't trust the selection anchor (eg ReactEditor.toDOMPoint)
+ if (!IS_NODE_MAP_DIRTY.get(editor)) {
+ // 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.
+ const { anchor } = selection
- // COMPAT: The event should be ignored if the focus is returning
- // to the editor from an embedded editable element (eg. an
- // element inside a void node).
- if (relatedTarget === el) {
- return
- }
+ const [node, offset] = ReactEditor.toDOMPoint(editor, anchor)
+ const anchorNode = node.parentElement?.closest('a')
- // COMPAT: The event should be ignored if the focus is moving from
- // the editor to inside a void node's spacer element.
- if (
- isDOMElement(relatedTarget) &&
- relatedTarget.hasAttribute('data-slate-spacer')
- ) {
- return
- }
+ const window = ReactEditor.getWindow(editor)
- // 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 &&
- isDOMNode(relatedTarget) &&
- ReactEditor.hasDOMNode(editor, relatedTarget)
- ) {
- const node = ReactEditor.toSlateNode(editor, relatedTarget)
+ if (
+ native &&
+ anchorNode &&
+ ReactEditor.hasDOMNode(editor, anchorNode)
+ ) {
+ // Find the last text node inside the anchor.
+ const lastText = window?.document
+ .createTreeWalker(anchorNode, NodeFilter.SHOW_TEXT)
+ .lastChild() as DOMText | null
- if (Element.isElement(node) && !editor.isVoid(node)) {
- return
+ if (
+ lastText === node &&
+ lastText.textContent?.length === offset
+ ) {
+ native = false
+ }
}
- }
- IS_FOCUSED.delete(editor)
- },
- [readOnly, attributes.onBlur]
- )}
- onClick={useCallback(
- (event: React.MouseEvent) => {
- if (
- !readOnly &&
- hasTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onClick) &&
- isDOMNode(event.target)
- ) {
- const node = ReactEditor.toSlateNode(editor, event.target)
- const path = ReactEditor.findPath(editor, node)
- const start = Editor.start(editor, path)
+ // 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?.getComputedStyle(node.parentElement)?.whiteSpace ===
+ 'pre'
+ ) {
+ const block = Editor.above(editor, {
+ at: anchor.path,
+ match: n => Element.isElement(n) && Editor.isBlock(editor, n),
+ })
- if (Editor.void(editor, { at: start })) {
- const range = Editor.range(editor, start)
- Transforms.select(editor, range)
- }
- }
- },
- [readOnly, attributes.onClick]
- )}
- onCompositionEnd={useCallback(
- (event: React.CompositionEvent) => {
- if (
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCompositionEnd)
- ) {
- state.isComposing = false
-
- // 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 (!IS_SAFARI && !IS_FIREFOX && event.data) {
- Editor.insertText(editor, event.data)
+ if (block && Node.string(block[0]).includes('\t')) {
+ native = false
+ }
}
}
- },
- [attributes.onCompositionEnd]
- )}
- onCompositionStart={useCallback(
- (event: React.CompositionEvent) => {
- if (
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCompositionStart)
- ) {
- state.isComposing = true
- }
- },
- [attributes.onCompositionStart]
- )}
- onCopy={useCallback(
- (event: React.ClipboardEvent) => {
- if (
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCopy)
- ) {
- event.preventDefault()
- setFragmentData(event.clipboardData, editor)
- }
- },
- [attributes.onCopy]
- )}
- onCut={useCallback(
- (event: React.ClipboardEvent) => {
- if (
- !readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onCut)
- ) {
- event.preventDefault()
- setFragmentData(event.clipboardData, editor)
- const { selection } = editor
+ }
+ // 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')) &&
+ !IS_NODE_MAP_DIRTY.get(editor)
+ ) {
+ const [targetRange] = (event as any).getTargetRanges()
+
+ if (targetRange) {
+ const range = ReactEditor.toSlateRange(editor, targetRange, {
+ exactMatch: false,
+ suppressThrow: false,
+ })
+
+ if (!selection || !Range.equals(selection, range)) {
+ native = false
+
+ const selectionRef =
+ !isCompositionChange &&
+ editor.selection &&
+ Editor.rangeRef(editor, editor.selection)
+
+ Transforms.select(editor, range)
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor)
+ if (selectionRef) {
+ EDITOR_TO_USER_SELECTION.set(editor, selectionRef)
+ }
}
}
- },
- [readOnly, attributes.onCut]
- )}
- onDragOver={useCallback(
- (event: React.DragEvent) => {
- if (
- 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.
- const node = ReactEditor.toSlateNode(editor, event.target)
+ }
- if (Editor.isVoid(editor, node)) {
- event.preventDefault()
- }
+ // 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 &&
+ Range.isExpanded(selection) &&
+ type.startsWith('delete')
+ ) {
+ const direction = type.endsWith('Backward') ? 'backward' : 'forward'
+ Editor.deleteFragment(editor, { direction })
+ return
+ }
+
+ switch (type) {
+ case 'deleteByComposition':
+ case 'deleteByCut':
+ case 'deleteByDrag': {
+ Editor.deleteFragment(editor)
+ break
}
- },
- [attributes.onDragOver]
- )}
- onDragStart={useCallback(
- (event: React.DragEvent) => {
- if (
- hasTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onDragStart)
- ) {
- const node = ReactEditor.toSlateNode(editor, event.target)
- const path = ReactEditor.findPath(editor, node)
- const voidMatch = Editor.void(editor, { at: path })
-
- // 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) {
- const range = Editor.range(editor, path)
- Transforms.select(editor, range)
- }
- setFragmentData(event.dataTransfer, editor)
+ case 'deleteContent':
+ case 'deleteContentForward': {
+ Editor.deleteForward(editor)
+ break
}
- },
- [attributes.onDragStart]
- )}
- onDrop={useCallback(
- (event: React.DragEvent) => {
- if (
- hasTarget(editor, event.target) &&
- !readOnly &&
- !isEventHandled(event, attributes.onDrop)
- ) {
- // COMPAT: Firefox doesn't fire `beforeinput` events at all, and
- // Chromium browsers don't properly fire them for files being
- // dropped into a `contenteditable`. (2019/11/26)
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
- if (
- IS_FIREFOX ||
- (!IS_SAFARI && event.dataTransfer.files.length > 0)
- ) {
- event.preventDefault()
- const range = ReactEditor.findEventRange(editor, event)
- const data = event.dataTransfer
- Transforms.select(editor, range)
- ReactEditor.insertData(editor, data)
- }
+
+ case 'deleteContentBackward': {
+ Editor.deleteBackward(editor)
+ break
}
- },
- [readOnly, attributes.onDrop]
- )}
- onFocus={useCallback(
- (event: React.FocusEvent) => {
- if (
- !readOnly &&
- !state.isUpdatingSelection &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onFocus)
- ) {
- const el = ReactEditor.toDOMNode(editor, editor)
- state.latestElement = window.document.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 (IS_FIREFOX && event.target !== el) {
- el.focus()
- return
- }
- IS_FOCUSED.set(editor, true)
+ case 'deleteEntireSoftLine': {
+ Editor.deleteBackward(editor, { unit: 'line' })
+ Editor.deleteForward(editor, { unit: 'line' })
+ break
}
- },
- [readOnly, attributes.onFocus]
- )}
- onKeyDown={useCallback(
- (event: React.KeyboardEvent) => {
- if (
- !readOnly &&
- hasEditableTarget(editor, event.target) &&
- !isEventHandled(event, attributes.onKeyDown)
- ) {
- const { nativeEvent } = event
- const { selection } = editor
-
- const element =
- editor.children[
- selection !== null ? selection.focus.path[0] : 0
- ]
- const isRTL = getDirection(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 (Hotkeys.isRedo(nativeEvent)) {
- event.preventDefault()
-
- if (editor.redo) {
- editor.redo()
- }
- return
- }
+ case 'deleteHardLineBackward': {
+ Editor.deleteBackward(editor, { unit: 'block' })
+ break
+ }
- if (Hotkeys.isUndo(nativeEvent)) {
- event.preventDefault()
+ case 'deleteSoftLineBackward': {
+ Editor.deleteBackward(editor, { unit: 'line' })
+ break
+ }
- if (editor.undo) {
- editor.undo()
- }
+ case 'deleteHardLineForward': {
+ Editor.deleteForward(editor, { unit: 'block' })
+ break
+ }
- return
- }
+ case 'deleteSoftLineForward': {
+ Editor.deleteForward(editor, { unit: 'line' })
+ break
+ }
- // 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 (Hotkeys.isMoveLineBackward(nativeEvent)) {
- event.preventDefault()
- Transforms.move(editor, { unit: 'line', reverse: true })
- return
- }
+ case 'deleteWordBackward': {
+ Editor.deleteBackward(editor, { unit: 'word' })
+ break
+ }
- if (Hotkeys.isMoveLineForward(nativeEvent)) {
- event.preventDefault()
- Transforms.move(editor, { unit: 'line' })
- return
- }
+ case 'deleteWordForward': {
+ Editor.deleteForward(editor, { unit: 'word' })
+ break
+ }
- if (Hotkeys.isExtendLineBackward(nativeEvent)) {
- event.preventDefault()
- Transforms.move(editor, {
- unit: 'line',
- edge: 'focus',
- reverse: true,
- })
- return
- }
+ case 'insertLineBreak':
+ Editor.insertSoftBreak(editor)
+ break
- if (Hotkeys.isExtendLineForward(nativeEvent)) {
- event.preventDefault()
- Transforms.move(editor, { unit: 'line', edge: 'focus' })
- return
- }
+ case 'insertParagraph': {
+ Editor.insertBreak(editor)
+ break
+ }
- // 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 (Hotkeys.isMoveBackward(nativeEvent)) {
- event.preventDefault()
-
- if (selection && Range.isCollapsed(selection)) {
- const { anchor } = selection
- if (anchor.offset === 1 && anchor.path[1] > 0) {
- // Hack to position the cursor at the end of the previous text node
- Transforms.move(editor, { reverse: !isRTL, distance: 2 })
- Transforms.move(editor, { reverse: isRTL })
- } else {
- Transforms.move(editor, { reverse: !isRTL })
- }
- } else {
- Transforms.collapse(editor, { edge: 'start' })
+ 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)
+ IS_COMPOSING.set(editor, false)
}
-
- return
}
- if (Hotkeys.isMoveForward(nativeEvent)) {
- event.preventDefault()
-
- if (selection && Range.isCollapsed(selection)) {
- Transforms.move(editor, { reverse: isRTL })
+ // 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?.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(() =>
+ Editor.insertText(editor, data)
+ )
} else {
- Transforms.collapse(editor, { edge: 'end' })
+ Editor.insertText(editor, data)
}
-
- return
}
- if (Hotkeys.isMoveWordBackward(nativeEvent)) {
- event.preventDefault()
- Transforms.move(editor, { unit: 'word', reverse: !isRTL })
- return
- }
-
- if (Hotkeys.isMoveWordForward(nativeEvent)) {
- event.preventDefault()
- Transforms.move(editor, { unit: 'word', reverse: isRTL })
- return
- }
-
- // COMPAT: Firefox doesn'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 (IS_FIREFOX) {
- // 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 (
- Hotkeys.isBold(nativeEvent) ||
- Hotkeys.isItalic(nativeEvent) ||
- Hotkeys.isTransposeCharacter(nativeEvent)
- ) {
- event.preventDefault()
- return
- }
+ break
+ }
+ }
- if (Hotkeys.isSplitBlock(nativeEvent)) {
- event.preventDefault()
- Editor.insertBreak(editor)
- return
- }
+ // Restore the actual user section if nothing manually set it.
+ const toRestore = EDITOR_TO_USER_SELECTION.get(editor)?.unref()
+ EDITOR_TO_USER_SELECTION.delete(editor)
- if (Hotkeys.isDeleteBackward(nativeEvent)) {
- event.preventDefault()
+ if (
+ toRestore &&
+ (!editor.selection || !Range.equals(editor.selection, toRestore))
+ ) {
+ Transforms.select(editor, toRestore)
+ }
+ }
+ },
+ [
+ editor,
+ onDOMSelectionChange,
+ onUserInput,
+ propsOnDOMBeforeInput,
+ readOnly,
+ scheduleOnDOMSelectionChange,
+ ]
+ )
+
+ const callbackRef = useCallback(
+ (node: HTMLDivElement | null) => {
+ if (node == null) {
+ onDOMSelectionChange.cancel()
+ scheduleOnDOMSelectionChange.cancel()
+
+ EDITOR_TO_ELEMENT.delete(editor)
+ NODE_TO_ELEMENT.delete(editor)
+
+ if (ref.current && 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 (HAS_BEFORE_INPUT_SUPPORT) {
+ // @ts-ignore The `beforeinput` event isn't recognized.
+ node.addEventListener('beforeinput', onDOMBeforeInput)
+ }
+ }
- if (selection && Range.isExpanded(selection)) {
- Editor.deleteFragment(editor)
- } else {
- Editor.deleteBackward(editor)
- }
+ ref.current = node
+ if (typeof forwardedRef === 'function') {
+ forwardedRef(node)
+ } else if (forwardedRef) {
+ forwardedRef.current = node
+ }
+ },
+ [
+ onDOMSelectionChange,
+ scheduleOnDOMSelectionChange,
+ editor,
+ onDOMBeforeInput,
+ forwardedRef,
+ ]
+ )
+
+ useIsomorphicLayoutEffect(() => {
+ const window = ReactEditor.getWindow(editor)
+
+ // COMPAT: In Chrome, `selectionchange` events can fire when and
+ //
+
+
+
+
+ )
+ }
+)
/**
- * A default memoized decorate function.
+ * The props that get passed to renderPlaceholder
*/
-
-const defaultDecorate = () => []
+export type RenderPlaceholderProps = {
+ children: any
+ attributes: {
+ 'data-slate-placeholder': boolean
+ dir?: 'rtl'
+ contentEditable: boolean
+ ref: React.RefCallback
+ style: React.CSSProperties
+ }
+}
/**
- * Check if two DOM range objects are equal.
+ * The default placeholder element
*/
-const isRangeEqual = (a: DOMRange, b: DOMRange) => {
- return (
- (a.startContainer === b.startContainer &&
- a.startOffset === b.startOffset &&
- a.endContainer === b.endContainer &&
- a.endOffset === b.endOffset) ||
- (a.startContainer === b.endContainer &&
- a.startOffset === b.endOffset &&
- a.endContainer === b.startContainer &&
- a.endOffset === b.startOffset)
- )
-}
+export const DefaultPlaceholder = ({
+ attributes,
+ children,
+}: RenderPlaceholderProps) => (
+ // 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
+
+ {children}
+ {IS_ANDROID && }
+
+)
/**
- * Check if the target is in the editor.
+ * A default memoized decorate function.
*/
-const hasTarget = (
- editor: ReactEditor,
- target: EventTarget | null
-): target is DOMNode => {
- return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target)
-}
+export const defaultDecorate: (entry: NodeEntry) => DecoratedRange[] = () => []
/**
- * Check if the target is editable and in the editor.
+ * A default implement to scroll dom range into view.
*/
-const hasEditableTarget = (
+const defaultScrollSelectionIntoView = (
editor: ReactEditor,
- target: EventTarget | null
-): target is DOMNode => {
- return (
- isDOMNode(target) &&
- ReactEditor.hasDOMNode(editor, target, { editable: true })
- )
+ domRange: DOMRange
+) => {
+ // This was affecting the selection of multiple blocks and dragging behavior,
+ // so enabled only if the selection has been collapsed.
+ if (
+ domRange.getBoundingClientRect &&
+ (!editor.selection ||
+ (editor.selection && Range.isCollapsed(editor.selection)))
+ ) {
+ const leafEl = domRange.startContainer.parentElement!
+
+ // COMPAT: In Chrome, domRange.getBoundingClientRect() can return zero dimensions for valid ranges (e.g. line breaks).
+ // When this happens, do not scroll like most editors do.
+ const domRect = domRange.getBoundingClientRect()
+ const isZeroDimensionRect =
+ domRect.width === 0 &&
+ domRect.height === 0 &&
+ domRect.x === 0 &&
+ domRect.y === 0
+
+ if (isZeroDimensionRect) {
+ const leafRect = leafEl.getBoundingClientRect()
+ const leafHasDimensions = leafRect.width > 0 || leafRect.height > 0
+
+ if (leafHasDimensions) {
+ return
+ }
+ }
+
+ // Default behavior: use domRange's getBoundingClientRect
+ leafEl.getBoundingClientRect = domRange.getBoundingClientRect.bind(domRange)
+ scrollIntoView(leafEl, {
+ scrollMode: 'if-needed',
+ })
+
+ // @ts-expect-error an unorthodox delete D:
+ delete leafEl.getBoundingClientRect
+ }
}
/**
* Check if an event is overrided by a handler.
*/
-const isEventHandled = <
- EventType extends React.SyntheticEvent
+export const isEventHandled = <
+ EventType extends React.SyntheticEvent,
>(
event: EventType,
- handler?: (event: EventType) => void
+ handler?: (event: EventType) => void | boolean
) => {
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.
+ const shouldTreatEventAsHandled = handler(event)
+
+ if (shouldTreatEventAsHandled != null) {
+ return shouldTreatEventAsHandled
+ }
- handler(event)
return event.isDefaultPrevented() || event.isPropagationStopped()
}
/**
- * Check if a DOM event is overrided by a handler.
+ * Check if the event's target is an input element
*/
-
-const isDOMEventHandled = (event: Event, handler?: (event: Event) => void) => {
- if (!handler) {
- return false
- }
-
- handler(event)
- return event.defaultPrevented
+export const isDOMEventTargetInput = <
+ EventType extends React.SyntheticEvent,
+>(
+ event: EventType
+) => {
+ return (
+ isDOMNode(event.target) &&
+ (event.target instanceof HTMLInputElement ||
+ event.target instanceof HTMLTextAreaElement)
+ )
}
/**
- * Set the currently selected fragment to the clipboard.
+ * Check if a DOM event is overrided by a handler.
*/
-const setFragmentData = (
- dataTransfer: DataTransfer,
- editor: ReactEditor
-): void => {
- const { selection } = editor
-
- if (!selection) {
- return
- }
-
- const [start, end] = Range.edges(selection)
- const startVoid = Editor.void(editor, { at: start.path })
- const endVoid = Editor.void(editor, { at: end.path })
-
- if (Range.isCollapsed(selection) && !startVoid) {
- return
- }
-
- // Create a fake selection so that we can add a Base64-encoded copy of the
- // fragment to the HTML, to decode on future pastes.
- const domRange = ReactEditor.toDOMRange(editor, selection)
- let contents = domRange.cloneContents()
- let attach = contents.childNodes[0] as HTMLElement
-
- // Make sure attach is non-empty, since empty nodes will not get copied.
- contents.childNodes.forEach(node => {
- if (node.textContent && node.textContent.trim() !== '') {
- attach = node as HTMLElement
- }
- })
-
- // COMPAT: If the end node is a void node, we need to move the end of the
- // range from the void node's spacer span, to the end of the void node's
- // content, since the spacer is before void's content in the DOM.
- if (endVoid) {
- const [voidNode] = endVoid
- const r = domRange.cloneRange()
- const domNode = ReactEditor.toDOMNode(editor, voidNode)
- r.setEndAfter(domNode)
- contents = r.cloneContents()
- }
-
- // COMPAT: If the start node is a void node, we need to attach the encoded
- // fragment to the void node's content node instead of the spacer, because
- // attaching it to empty `
/` nodes will end up having it erased by
- // most browsers. (2018/04/27)
- if (startVoid) {
- attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement
+export const isDOMEventHandled = (
+ event: E,
+ handler?: (event: E) => void | boolean
+) => {
+ if (!handler) {
+ return false
}
- // Remove any zero-width space spans from the cloned DOM so that they don't
- // show up elsewhere when pasted.
- Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(
- zw => {
- const isNewline = zw.getAttribute('data-slate-zero-width') === 'n'
- zw.textContent = isNewline ? '\n' : ''
- }
- )
+ // The custom event handler may return a boolean to specify whether the event
+ // shall be treated as being handled or not.
+ const shouldTreatEventAsHandled = handler(event)
- // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
- // in the HTML, and can be used for intra-Slate pasting. If it's a text
- // node, wrap it in a `` so we have something to set an attribute on.
- if (isDOMText(attach)) {
- const span = document.createElement('span')
- // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
- // then leading and trailing spaces will be ignored. (2017/09/21)
- span.style.whiteSpace = 'pre'
- span.appendChild(attach)
- contents.appendChild(span)
- attach = span
+ if (shouldTreatEventAsHandled != null) {
+ return shouldTreatEventAsHandled
}
- const fragment = Node.fragment(editor, selection)
- const string = JSON.stringify(fragment)
- const encoded = window.btoa(encodeURIComponent(string))
- attach.setAttribute('data-slate-fragment', encoded)
+ return event.defaultPrevented
+}
- // Overwriting the default functionality
- const { getFormattedSelection, getHTMLFormattedSelection } = editor
+const handleNativeHistoryEvents = (editor: Editor, event: InputEvent) => {
+ const maybeHistoryEditor: any = editor
if (
- typeof getFormattedSelection === 'function' &&
- typeof getHTMLFormattedSelection === 'function'
+ event.inputType === 'historyUndo' &&
+ typeof maybeHistoryEditor.undo === 'function'
) {
- try {
- const plainText = getFormattedSelection()
- const htmlText = getHTMLFormattedSelection()
- dataTransfer.setData('text/plain', plainText)
- dataTransfer.setData('text/html', htmlText)
- return
- } catch (e) {
- // eslint-disable-next-line no-console
- console.log('Error in slate-react/src/components/editable.tsx: ', e)
- // Only setData application/x-slate-fragment as a fallback because
- // we don't want to copy the timestamps of words
- dataTransfer.setData('application/x-slate-fragment', encoded)
- }
- }
-
- // Add the content to a
so that we can get its inner HTML.
- const div = document.createElement('div')
- div.appendChild(contents)
- dataTransfer.setData('text/html', div.innerHTML)
- dataTransfer.setData('text/plain', getPlainText(div))
-}
-
-/**
- * Get a plaintext representation of the content of a node, accounting for block
- * elements which get a newline appended.
- */
-
-const getPlainText = (domNode: DOMNode) => {
- let text = ''
-
- if (isDOMText(domNode) && domNode.nodeValue) {
- return domNode.nodeValue
+ maybeHistoryEditor.undo()
+ return
}
-
- if (isDOMElement(domNode)) {
- for (const childNode of Array.from(domNode.childNodes)) {
- text += getPlainText(childNode)
- }
-
- const display = getComputedStyle(domNode).getPropertyValue('display')
-
- if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
- text += '\n'
- }
+ if (
+ event.inputType === 'historyRedo' &&
+ typeof maybeHistoryEditor.redo === 'function'
+ ) {
+ maybeHistoryEditor.redo()
+ return
}
-
- return text
}
diff --git a/src/components/element.tsx b/src/components/element.tsx
index c6c9474..1c553ed 100644
--- a/src/components/element.tsx
+++ b/src/components/element.tsx
@@ -1,59 +1,83 @@
-import React, { useRef } from 'react'
import getDirection from 'direction'
-import { Editor, Node, Range, NodeEntry, Element as SlateElement } from 'slate'
-
-import Text from './text'
-import Children from './children'
-import { ReactEditor, useEditor, useReadOnly } from '..'
-import { SelectedContext } from '../hooks/use-selected'
-import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
+import React, { useCallback } from 'react'
+import { JSX } from 'react'
+import { Editor, Element as SlateElement, Node, DecoratedRange } from 'slate'
+import { ReactEditor, useReadOnly, useSlateStatic } from '..'
+import useChildren from '../hooks/use-children'
+import { isElementDecorationsEqual } from 'slate-dom'
import {
- NODE_TO_ELEMENT,
+ EDITOR_TO_KEY_TO_ELEMENT,
ELEMENT_TO_NODE,
- NODE_TO_PARENT,
+ NODE_TO_ELEMENT,
NODE_TO_INDEX,
- KEY_TO_ELEMENT,
-} from '../utils/weak-maps'
-import { RenderElementProps, RenderLeafProps } from './editable'
+ NODE_TO_PARENT,
+} from 'slate-dom'
+import {
+ RenderChunkProps,
+ RenderElementProps,
+ RenderLeafProps,
+ RenderPlaceholderProps,
+ RenderTextProps,
+} from './editable'
+
+import Text from './text'
+import { useDecorations } from '../hooks/use-decorations'
+
+const defaultRenderElement = (props: RenderElementProps) => (
+
+)
/**
* Element.
*/
const Element = (props: {
- decorate: (entry: NodeEntry) => Range[]
- decorations: Range[]
+ decorations: DecoratedRange[]
element: SlateElement
renderElement?: (props: RenderElementProps) => JSX.Element
+ renderChunk?: (props: RenderChunkProps) => JSX.Element
+ renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
+ renderText?: (props: RenderTextProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
- selection: Range | null
- elementIndex: Number
}) => {
const {
- decorate,
- decorations,
+ decorations: parentDecorations,
element,
- renderElement = (p: RenderElementProps) => ,
+ renderElement = defaultRenderElement,
+ renderChunk,
+ renderPlaceholder,
renderLeaf,
- selection,
- elementIndex,
+ renderText,
} = props
- const ref = useRef(null)
- const editor = useEditor()
+ const editor = useSlateStatic()
const readOnly = useReadOnly()
const isInline = editor.isInline(element)
+ const decorations = useDecorations(element, parentDecorations)
const key = ReactEditor.findKey(editor, element)
-
- let children: JSX.Element | null = (
-
+ const ref = useCallback(
+ (ref: HTMLElement | null) => {
+ // Update element-related weak maps with the DOM element ref.
+ const KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor)
+ if (ref) {
+ KEY_TO_ELEMENT?.set(key, ref)
+ NODE_TO_ELEMENT.set(element, ref)
+ ELEMENT_TO_NODE.set(ref, element)
+ } else {
+ KEY_TO_ELEMENT?.delete(key)
+ NODE_TO_ELEMENT.delete(element)
+ }
+ },
+ [editor, key, element]
)
+ let children: React.ReactNode = useChildren({
+ decorations,
+ node: element,
+ renderElement,
+ renderChunk,
+ renderPlaceholder,
+ renderLeaf,
+ renderText,
+ })
// Attributes that the developer must mix into the element in their
// custom node renderer component.
@@ -64,11 +88,9 @@ const Element = (props: {
contentEditable?: false
dir?: 'rtl'
ref: any
- elementIndex: Number
} = {
'data-slate-node': 'element',
ref,
- elementIndex,
}
if (isInline) {
@@ -97,7 +119,7 @@ const Element = (props: {
const Tag = isInline ? 'span' : 'div'
const [[text]] = Node.texts(element)
- children = readOnly ? null : (
+ children = (
-
+
)
@@ -115,36 +143,18 @@ const Element = (props: {
NODE_TO_PARENT.set(text, element)
}
- // Update element-related weak maps with the DOM element ref.
- useIsomorphicLayoutEffect(() => {
- if (ref.current) {
- KEY_TO_ELEMENT.set(key, ref.current)
- NODE_TO_ELEMENT.set(element, ref.current)
- ELEMENT_TO_NODE.set(ref.current, element)
- } else {
- KEY_TO_ELEMENT.delete(key)
- NODE_TO_ELEMENT.delete(element)
- }
- })
-
- return (
-
- {renderElement({ attributes, children, element })}
-
- )
+ return renderElement({ attributes, children, element })
}
const MemoizedElement = React.memo(Element, (prev, next) => {
return (
- prev.decorate === next.decorate &&
prev.element === next.element &&
prev.renderElement === next.renderElement &&
+ prev.renderChunk === next.renderChunk &&
+ prev.renderText === next.renderText &&
prev.renderLeaf === next.renderLeaf &&
- isRangeListEqual(prev.decorations, next.decorations) &&
- (prev.selection === next.selection ||
- (!!prev.selection &&
- !!next.selection &&
- Range.equals(prev.selection, next.selection)))
+ prev.renderPlaceholder === next.renderPlaceholder &&
+ isElementDecorationsEqual(prev.decorations, next.decorations)
)
})
@@ -154,7 +164,7 @@ const MemoizedElement = React.memo(Element, (prev, next) => {
export const DefaultElement = (props: RenderElementProps) => {
const { attributes, children, element } = props
- const editor = useEditor()
+ const editor = useSlateStatic()
const Tag = editor.isInline(element) ? 'span' : 'div'
return (
@@ -163,29 +173,4 @@ export const DefaultElement = (props: RenderElementProps) => {
)
}
-/**
- * Check if a list of ranges is equal to another.
- *
- * PERF: this requires the two lists to also have the ranges inside them in the
- * same order, but this is an okay constraint for us since decorations are
- * kept in order, and the odd case where they aren't is okay to re-render for.
- */
-
-const isRangeListEqual = (list: Range[], another: Range[]): boolean => {
- if (list.length !== another.length) {
- return false
- }
-
- for (let i = 0; i < list.length; i++) {
- const range = list[i]
- const other = another[i]
-
- if (!Range.equals(range, other)) {
- return false
- }
- }
-
- return true
-}
-
export default MemoizedElement
diff --git a/src/components/leaf.tsx b/src/components/leaf.tsx
index 8ab9fcd..10ecc6d 100644
--- a/src/components/leaf.tsx
+++ b/src/components/leaf.tsx
@@ -1,51 +1,149 @@
-import React from 'react'
-import { Text, Element } from 'slate'
-
+import React, {
+ useRef,
+ useCallback,
+ MutableRefObject,
+ useState,
+ useEffect,
+} from 'react'
+import { JSX } from 'react'
+import { Element, LeafPosition, Text } from 'slate'
+import { ResizeObserver as ResizeObserverPolyfill } from '@juggle/resize-observer'
import String from './string'
-import { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'
-import { RenderLeafProps } from './editable'
+import { PLACEHOLDER_SYMBOL, EDITOR_TO_PLACEHOLDER_ELEMENT } from 'slate-dom'
+import { RenderLeafProps, RenderPlaceholderProps } from './editable'
+import { useSlateStatic } from '../hooks/use-slate-static'
+import { IS_WEBKIT, IS_ANDROID } from 'slate-dom'
+
+// Delay the placeholder on Android to prevent the keyboard from closing.
+// (https://github.com/ianstormtaylor/slate/pull/5368)
+const PLACEHOLDER_DELAY = IS_ANDROID ? 300 : 0
+
+function disconnectPlaceholderResizeObserver(
+ placeholderResizeObserver: MutableRefObject,
+ releaseObserver: boolean
+) {
+ if (placeholderResizeObserver.current) {
+ placeholderResizeObserver.current.disconnect()
+ if (releaseObserver) {
+ placeholderResizeObserver.current = null
+ }
+ }
+}
+
+type TimerId = ReturnType | null
+
+function clearTimeoutRef(timeoutRef: MutableRefObject) {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current)
+ timeoutRef.current = null
+ }
+}
+
+const defaultRenderLeaf = (props: RenderLeafProps) =>
/**
* Individual leaves in a text node with unique formatting.
*/
-
const Leaf = (props: {
isLast: boolean
leaf: Text
parent: Element
+ renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
text: Text
+ leafPosition?: LeafPosition
}) => {
const {
leaf,
isLast,
text,
parent,
- renderLeaf = (props: RenderLeafProps) => ,
+ renderPlaceholder,
+ renderLeaf = defaultRenderLeaf,
+ leafPosition,
} = props
+ const editor = useSlateStatic()
+ const placeholderResizeObserver = useRef(null)
+ const placeholderRef = useRef(null)
+ const [showPlaceholder, setShowPlaceholder] = useState(false)
+ const showPlaceholderTimeoutRef = useRef(null)
+
+ const callbackPlaceholderRef = useCallback(
+ (placeholderEl: HTMLElement | null) => {
+ disconnectPlaceholderResizeObserver(
+ placeholderResizeObserver,
+ placeholderEl == null
+ )
+
+ if (placeholderEl == null) {
+ EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
+ leaf.onPlaceholderResize?.(null)
+ } else {
+ EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)
+
+ if (!placeholderResizeObserver.current) {
+ // Create a new observer and observe the placeholder element.
+ const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill
+ placeholderResizeObserver.current = new ResizeObserver(() => {
+ leaf.onPlaceholderResize?.(placeholderEl)
+ })
+ }
+ placeholderResizeObserver.current.observe(placeholderEl)
+ placeholderRef.current = placeholderEl
+ }
+ },
+ [placeholderRef, leaf, editor]
+ )
+
let children = (
)
- if (leaf[PLACEHOLDER_SYMBOL]) {
+ const leafIsPlaceholder = Boolean(leaf[PLACEHOLDER_SYMBOL])
+ useEffect(() => {
+ if (leafIsPlaceholder) {
+ if (!showPlaceholderTimeoutRef.current) {
+ // Delay the placeholder, so it will not render in a selection
+ showPlaceholderTimeoutRef.current = setTimeout(() => {
+ setShowPlaceholder(true)
+ showPlaceholderTimeoutRef.current = null
+ }, PLACEHOLDER_DELAY)
+ }
+ } else {
+ clearTimeoutRef(showPlaceholderTimeoutRef)
+ setShowPlaceholder(false)
+ }
+ return () => clearTimeoutRef(showPlaceholderTimeoutRef)
+ }, [leafIsPlaceholder, setShowPlaceholder])
+
+ if (leafIsPlaceholder && showPlaceholder) {
+ const placeholderProps: RenderPlaceholderProps = {
+ 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: IS_WEBKIT ? 'inherit' : undefined,
+ },
+ contentEditable: false,
+ ref: callbackPlaceholderRef,
+ },
+ }
+
children = (
-
- {leaf.placeholder}
-
{children}
+ {renderPlaceholder(placeholderProps)}
)
}
@@ -59,7 +157,13 @@ const Leaf = (props: {
'data-slate-leaf': true,
}
- return renderLeaf({ attributes, children, leaf, text })
+ return renderLeaf({
+ attributes,
+ children,
+ leaf,
+ text,
+ leafPosition,
+ })
}
const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
@@ -67,15 +171,13 @@ const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
next.parent === prev.parent &&
next.isLast === prev.isLast &&
next.renderLeaf === prev.renderLeaf &&
+ next.renderPlaceholder === prev.renderPlaceholder &&
next.text === prev.text &&
- Text.matches(next.leaf, prev.leaf)
+ Text.equals(next.leaf, prev.leaf) &&
+ next.leaf[PLACEHOLDER_SYMBOL] === prev.leaf[PLACEHOLDER_SYMBOL]
)
})
-/**
- * The default custom leaf renderer.
- */
-
export const DefaultLeaf = (props: RenderLeafProps) => {
const { attributes, children } = props
return {children}
diff --git a/src/components/restore-dom/restore-dom-manager.ts b/src/components/restore-dom/restore-dom-manager.ts
new file mode 100644
index 0000000..469512d
--- /dev/null
+++ b/src/components/restore-dom/restore-dom-manager.ts
@@ -0,0 +1,61 @@
+import { RefObject } from 'react'
+import { ReactEditor } from '../../plugin/react-editor'
+import { isTrackedMutation } from 'slate-dom'
+
+export type RestoreDOMManager = {
+ registerMutations: (mutations: MutationRecord[]) => void
+ restoreDOM: () => void
+ clear: () => void
+}
+
+export const createRestoreDomManager = (
+ editor: ReactEditor,
+ receivedUserInput: RefObject
+): RestoreDOMManager => {
+ let bufferedMutations: MutationRecord[] = []
+
+ const clear = () => {
+ bufferedMutations = []
+ }
+
+ const registerMutations = (mutations: MutationRecord[]) => {
+ if (!receivedUserInput.current) {
+ return
+ }
+
+ const trackedMutations = mutations.filter(mutation =>
+ isTrackedMutation(editor, mutation, mutations)
+ )
+
+ bufferedMutations.push(...trackedMutations)
+ }
+
+ function restoreDOM() {
+ if (bufferedMutations.length > 0) {
+ bufferedMutations.reverse().forEach(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(node => {
+ mutation.target.insertBefore(node, mutation.nextSibling)
+ })
+
+ mutation.addedNodes.forEach(node => {
+ mutation.target.removeChild(node)
+ })
+ })
+
+ // Clear buffered mutations to ensure we don't undo them twice
+ clear()
+ }
+ }
+
+ return {
+ registerMutations,
+ restoreDOM,
+ clear,
+ }
+}
diff --git a/src/components/restore-dom/restore-dom.tsx b/src/components/restore-dom/restore-dom.tsx
new file mode 100644
index 0000000..187c3a7
--- /dev/null
+++ b/src/components/restore-dom/restore-dom.tsx
@@ -0,0 +1,84 @@
+import React, {
+ Component,
+ ComponentType,
+ ContextType,
+ ReactNode,
+ RefObject,
+} from 'react'
+import { EditorContext } from '../../hooks/use-slate-static'
+import { IS_ANDROID } from 'slate-dom'
+import {
+ createRestoreDomManager,
+ RestoreDOMManager,
+} from './restore-dom-manager'
+
+const MUTATION_OBSERVER_CONFIG: MutationObserverInit = {
+ subtree: true,
+ childList: true,
+ characterData: true,
+ characterDataOldValue: true,
+}
+
+type RestoreDOMProps = {
+ children?: ReactNode
+ receivedUserInput: RefObject
+ node: RefObject
+}
+
+// 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.
+class RestoreDOMComponent extends Component {
+ static contextType = EditorContext
+ context: ContextType = null
+
+ private manager: RestoreDOMManager | null = null
+ private mutationObserver: MutationObserver | null = null
+
+ observe() {
+ const { node } = this.props
+ if (!node.current) {
+ throw new Error('Failed to attach MutationObserver, `node` is undefined')
+ }
+
+ this.mutationObserver?.observe(node.current, MUTATION_OBSERVER_CONFIG)
+ }
+
+ componentDidMount() {
+ const { receivedUserInput } = this.props
+ const editor = this.context!
+
+ this.manager = createRestoreDomManager(editor, receivedUserInput)
+ this.mutationObserver = new MutationObserver(this.manager.registerMutations)
+
+ this.observe()
+ }
+
+ getSnapshotBeforeUpdate() {
+ const pendingMutations = this.mutationObserver?.takeRecords()
+ if (pendingMutations?.length) {
+ this.manager?.registerMutations(pendingMutations)
+ }
+
+ this.mutationObserver?.disconnect()
+ this.manager?.restoreDOM()
+
+ return null
+ }
+
+ componentDidUpdate() {
+ this.manager?.clear()
+ this.observe()
+ }
+
+ componentWillUnmount() {
+ this.mutationObserver?.disconnect()
+ }
+
+ render() {
+ return this.props.children
+ }
+}
+
+export const RestoreDOM: ComponentType = IS_ANDROID
+ ? RestoreDOMComponent
+ : ({ children }) => <>{children}>
diff --git a/src/components/slate.tsx b/src/components/slate.tsx
index 7568c58..45dff30 100644
--- a/src/components/slate.tsx
+++ b/src/components/slate.tsx
@@ -1,11 +1,15 @@
-import React, { useMemo, useState, useCallback } from 'react'
-import { Node } from 'slate'
-
-import { ReactEditor } from '../plugin/react-editor'
+import React, { useCallback, useEffect, useState } from 'react'
+import { Descendant, Editor, Node, Operation, Scrubber, Selection } from 'slate'
+import { EDITOR_TO_ON_CHANGE } from 'slate-dom'
import { FocusedContext } from '../hooks/use-focused'
-import { EditorContext } from '../hooks/use-editor'
-import { SlateContext } from '../hooks/use-slate'
-import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
+import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
+import {
+ useSelectorContext,
+ SlateSelectorContext,
+} from '../hooks/use-slate-selector'
+import { EditorContext } from '../hooks/use-slate-static'
+import { ReactEditor } from '../plugin/react-editor'
+import { REACT_MAJOR_VERSION } from '../utils/environment'
/**
* A wrapper around the provider to handle `onChange` events, because the editor
@@ -14,33 +18,107 @@ import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
export const Slate = (props: {
editor: ReactEditor
- value: Node[]
+ initialValue: Descendant[]
children: React.ReactNode
- onChange: (value: Node[]) => void
- [key: string]: any
+ onChange?: (value: Descendant[]) => void
+ onSelectionChange?: (selection: Selection) => void
+ onValueChange?: (value: Descendant[]) => void
}) => {
- const { editor, children, onChange, value, ...rest } = props
- const [key, setKey] = useState(0)
- const context: [ReactEditor] = useMemo(() => {
- editor.children = value
+ const {
+ editor,
+ children,
+ onChange,
+ onSelectionChange,
+ onValueChange,
+ initialValue,
+ ...rest
+ } = props
+
+ // Run once on first mount, but before `useEffect` or render
+ React.useState(() => {
+ if (!Node.isNodeList(initialValue)) {
+ throw new Error(
+ `[Slate] initialValue is invalid! Expected a list of elements but got: ${Scrubber.stringify(
+ initialValue
+ )}`
+ )
+ }
+
+ if (!Editor.isEditor(editor)) {
+ throw new Error(
+ `[Slate] editor is invalid! You passed: ${Scrubber.stringify(editor)}`
+ )
+ }
+
+ editor.children = initialValue
Object.assign(editor, rest)
- return [editor]
- }, [key, value, ...Object.values(rest)])
+ })
+
+ const { selectorContext, onChange: handleSelectorChange } =
+ useSelectorContext()
+
+ const onContextChange = useCallback(
+ (options?: { operation?: Operation }) => {
+ if (onChange) {
+ onChange(editor.children)
+ }
+
+ switch (options?.operation?.type) {
+ case 'set_selection':
+ onSelectionChange?.(editor.selection)
+ break
+ default:
+ onValueChange?.(editor.children)
+ }
+
+ handleSelectorChange()
+ },
+ [editor, handleSelectorChange, onChange, onSelectionChange, onValueChange]
+ )
+
+ useEffect(() => {
+ EDITOR_TO_ON_CHANGE.set(editor, onContextChange)
+
+ return () => {
+ EDITOR_TO_ON_CHANGE.set(editor, () => {})
+ }
+ }, [editor, onContextChange])
+
+ const [isFocused, setIsFocused] = useState(ReactEditor.isFocused(editor))
- const onContextChange = useCallback(() => {
- onChange(editor.children)
- setKey(key + 1)
- }, [key, onChange])
+ useEffect(() => {
+ setIsFocused(ReactEditor.isFocused(editor))
+ }, [editor])
- EDITOR_TO_ON_CHANGE.set(editor, onContextChange)
+ useIsomorphicLayoutEffect(() => {
+ const fn = () => 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 '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 () => {
+ document.removeEventListener('focusin', fn)
+ document.removeEventListener('focusout', fn)
+ }
+ } else {
+ document.addEventListener('focus', fn, true)
+ document.addEventListener('blur', fn, true)
+ return () => {
+ document.removeEventListener('focus', fn, true)
+ document.removeEventListener('blur', fn, true)
+ }
+ }
+ }, [])
return (
-
+
-
+
{children}
-
+
)
}
diff --git a/src/components/string.tsx b/src/components/string.tsx
index 307ec39..5f1dce9 100644
--- a/src/components/string.tsx
+++ b/src/components/string.tsx
@@ -1,7 +1,10 @@
-import React from 'react'
+import React, { forwardRef, memo, useRef, useState } from 'react'
import { Editor, Text, Path, Element, Node } from 'slate'
-import { ReactEditor, useEditor } from '..'
+import { ReactEditor, useSlateStatic } from '..'
+import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
+import { IS_ANDROID } from 'slate-dom'
+import { MARK_PLACEHOLDER_SYMBOL } from 'slate-dom'
/**
* Leaf content strings.
@@ -14,9 +17,10 @@ const String = (props: {
text: Text
}) => {
const { isLast, leaf, parent, text } = props
- const editor = useEditor()
+ const editor = useSlateStatic()
const path = ReactEditor.findPath(editor, text)
const parentPath = Path.parent(path)
+ const isMarkPlaceholder = Boolean(leaf[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.
@@ -33,14 +37,14 @@ const String = (props: {
!editor.isInline(parent) &&
Editor.string(editor, parentPath) === ''
) {
- return
+ return
}
// 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
+ return
}
// COMPAT: Browsers will collapse trailing new lines at the end of blocks,
@@ -55,29 +59,85 @@ const String = (props: {
/**
* Leaf strings with text in them.
*/
-
const TextString = (props: { text: string; isTrailing?: boolean }) => {
const { text, isTrailing = false } = props
- return (
-
- {text}
- {isTrailing ? '\n' : null}
-
- )
+ const ref = useRef(null)
+ const getTextContent = () => {
+ return `${text ?? ''}${isTrailing ? '\n' : ''}`
+ }
+ const [initialText] = useState(getTextContent)
+
+ // 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 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(() => {
+ // null coalescing text to make sure we're not outputing "null" as a string in the extreme case it is nullish at runtime
+ const textWithTrailing = getTextContent()
+
+ if (ref.current && ref.current.textContent !== textWithTrailing) {
+ 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 below" on each render
+ })
+
+ // We intentionally render a memoized 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 {initialText}
}
+const MemoizedText = memo(
+ forwardRef((props, ref) => {
+ return (
+
+ {props.children}
+
+ )
+ })
+)
+
/**
* Leaf strings without text, render as zero-width strings.
*/
-const ZeroWidthString = (props: { length?: number; isLineBreak?: boolean }) => {
- const { length = 0, isLineBreak = false } = props
+export const ZeroWidthString = (props: {
+ length?: number
+ isLineBreak?: boolean
+ isMarkPlaceholder?: boolean
+}) => {
+ const { length = 0, isLineBreak = false, isMarkPlaceholder = false } = props
+
+ const attributes: {
+ 'data-slate-zero-width': string
+ 'data-slate-length': number
+ 'data-slate-mark-placeholder'?: boolean
+ } = {
+ '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)
return (
-
- {'\uFEFF'}
+
+ {!IS_ANDROID || !isLineBreak ? '\uFEFF' : null}
{isLineBreak ? : null}
)
diff --git a/src/components/text.tsx b/src/components/text.tsx
index 4778ab7..d9d436b 100644
--- a/src/components/text.tsx
+++ b/src/components/text.tsx
@@ -1,42 +1,62 @@
-import React, { useRef } from 'react'
-import { Range, Element, Text as SlateText } from 'slate'
-
-import Leaf from './leaf'
-import { ReactEditor, useEditor } from '..'
-import { RenderLeafProps } from './editable'
-import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
+import React, { useCallback, useRef } from 'react'
+import { Element, Text as SlateText, DecoratedRange } from 'slate'
+import { ReactEditor, useSlateStatic } from '..'
+import { isTextDecorationsEqual } from 'slate-dom'
import {
- KEY_TO_ELEMENT,
- NODE_TO_ELEMENT,
+ EDITOR_TO_KEY_TO_ELEMENT,
ELEMENT_TO_NODE,
-} from '../utils/weak-maps'
+ NODE_TO_ELEMENT,
+} from 'slate-dom'
+import {
+ RenderLeafProps,
+ RenderPlaceholderProps,
+ RenderTextProps,
+} from './editable'
+import Leaf from './leaf'
+import { useDecorations } from '../hooks/use-decorations'
+
+const defaultRenderText = (props: RenderTextProps) =>
/**
* Text.
*/
const Text = (props: {
- decorations: Range[]
+ decorations: DecoratedRange[]
isLast: boolean
parent: Element
+ renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
+ renderText?: (props: RenderTextProps) => JSX.Element
text: SlateText
}) => {
- const { decorations, isLast, parent, renderLeaf, text } = props
- const editor = useEditor()
- const ref = useRef(null)
- const leaves = SlateText.decorations(text, decorations)
+ const {
+ decorations: parentDecorations,
+ isLast,
+ parent,
+ renderPlaceholder,
+ renderLeaf,
+ renderText = defaultRenderText,
+ text,
+ } = props
+
+ const editor = useSlateStatic()
+ const ref = useRef(null)
+ const decorations = useDecorations(text, parentDecorations)
+ const decoratedLeaves = SlateText.decorations(text, decorations)
const key = ReactEditor.findKey(editor, text)
const children = []
- for (let i = 0; i < leaves.length; i++) {
- const leaf = leaves[i]
+ for (let i = 0; i < decoratedLeaves.length; i++) {
+ const { leaf, position } = decoratedLeaves[i]
children.push(
{
- if (ref.current) {
- KEY_TO_ELEMENT.set(key, ref.current)
- NODE_TO_ELEMENT.set(text, ref.current)
- ELEMENT_TO_NODE.set(ref.current, text)
- } else {
- KEY_TO_ELEMENT.delete(key)
- NODE_TO_ELEMENT.delete(text)
- }
- })
-
- return (
-
- {children}
-
+ const callbackRef = useCallback(
+ (span: HTMLSpanElement | null) => {
+ const KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor)
+ if (span) {
+ KEY_TO_ELEMENT?.set(key, span)
+ NODE_TO_ELEMENT.set(text, span)
+ ELEMENT_TO_NODE.set(span, text)
+ } else {
+ KEY_TO_ELEMENT?.delete(key)
+ NODE_TO_ELEMENT.delete(text)
+ if (ref.current) {
+ ELEMENT_TO_NODE.delete(ref.current)
+ }
+ }
+ ref.current = span
+ },
+ [ref, editor, key, text]
)
+
+ const attributes: {
+ 'data-slate-node': 'text'
+ ref: any
+ } = {
+ 'data-slate-node': 'text',
+ ref: callbackRef,
+ }
+
+ return renderText({
+ text,
+ children,
+ attributes,
+ })
}
const MemoizedText = React.memo(Text, (prev, next) => {
return (
next.parent === prev.parent &&
next.isLast === prev.isLast &&
+ next.renderText === prev.renderText &&
next.renderLeaf === prev.renderLeaf &&
- next.text === prev.text
+ next.renderPlaceholder === prev.renderPlaceholder &&
+ next.text === prev.text &&
+ isTextDecorationsEqual(next.decorations, prev.decorations)
)
})
+export const DefaultText = (props: RenderTextProps) => {
+ const { attributes, children } = props
+ return {children}
+}
+
export default MemoizedText
diff --git a/src/custom-types.ts b/src/custom-types.ts
new file mode 100644
index 0000000..2ab5ae4
--- /dev/null
+++ b/src/custom-types.ts
@@ -0,0 +1,45 @@
+import { BaseRange, BaseText } from 'slate'
+import { ReactEditor } from './plugin/react-editor'
+
+declare module 'slate' {
+ interface CustomTypes {
+ Editor: ReactEditor
+ Text: BaseText & {
+ placeholder?: string
+ onPlaceholderResize?: (node: HTMLElement | null) => void
+ // FIXME: is unknown correct here?
+ [key: string]: unknown
+ }
+ Range: BaseRange & {
+ placeholder?: string
+ onPlaceholderResize?: (node: HTMLElement | null) => void
+ // FIXME: is unknown correct here?
+ [key: string]: unknown
+ }
+ }
+}
+
+declare global {
+ interface Window {
+ MSStream: boolean
+ }
+ interface DocumentOrShadowRoot {
+ getSelection(): Selection | null
+ }
+
+ interface CaretPosition {
+ readonly offsetNode: Node
+ readonly offset: number
+ getClientRect(): DOMRect | null
+ }
+
+ interface Document {
+ caretPositionFromPoint(x: number, y: number): CaretPosition | null
+ }
+
+ interface Node {
+ getRootNode(options?: GetRootNodeOptions): Document | ShadowRoot
+ }
+}
+
+export {}
diff --git a/src/hooks/android-input-manager/android-input-manager.ts b/src/hooks/android-input-manager/android-input-manager.ts
new file mode 100644
index 0000000..79a886d
--- /dev/null
+++ b/src/hooks/android-input-manager/android-input-manager.ts
@@ -0,0 +1,827 @@
+import { DebouncedFunc } from 'lodash'
+import { Editor, Node, Path, Point, Range, Text, Transforms } from 'slate'
+import { ReactEditor } from '../../plugin/react-editor'
+import {
+ applyStringDiff,
+ mergeStringDiffs,
+ normalizePoint,
+ normalizeRange,
+ normalizeStringDiff,
+ StringDiff,
+ targetRange,
+ TextDiff,
+ verifyDiffState,
+} from 'slate-dom'
+import { isDOMSelection, isTrackedMutation } from 'slate-dom'
+import {
+ EDITOR_TO_FORCE_RENDER,
+ EDITOR_TO_PENDING_ACTION,
+ EDITOR_TO_PENDING_DIFFS,
+ EDITOR_TO_PENDING_INSERTION_MARKS,
+ EDITOR_TO_PENDING_SELECTION,
+ EDITOR_TO_PLACEHOLDER_ELEMENT,
+ EDITOR_TO_USER_MARKS,
+ IS_COMPOSING,
+ IS_NODE_MAP_DIRTY,
+} from 'slate-dom'
+
+export type Action = { at?: Point | Range; run: () => void }
+
+// 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.
+const RESOLVE_DELAY = 25
+
+// Time with no user interaction before the current user action is considered as done.
+const FLUSH_DELAY = 200
+
+// Replace with `const debug = console.log` to debug
+const debug = (..._: unknown[]) => {}
+
+// Type guard to check if a value is a DataTransfer
+const isDataTransfer = (value: any): value is DataTransfer =>
+ value?.constructor.name === 'DataTransfer'
+
+export type CreateAndroidInputManagerOptions = {
+ editor: ReactEditor
+
+ scheduleOnDOMSelectionChange: DebouncedFunc<() => void>
+ onDOMSelectionChange: DebouncedFunc<() => void>
+}
+
+export type AndroidInputManager = {
+ flush: () => void
+ scheduleFlush: () => void
+
+ hasPendingDiffs: () => boolean
+ hasPendingAction: () => boolean
+ hasPendingChanges: () => boolean
+ isFlushing: () => boolean | 'action'
+
+ handleUserSelect: (range: Range | null) => void
+ handleCompositionEnd: (event: React.CompositionEvent) => void
+ handleCompositionStart: (
+ event: React.CompositionEvent
+ ) => void
+ handleDOMBeforeInput: (event: InputEvent) => void
+ handleKeyDown: (event: React.KeyboardEvent) => void
+
+ handleDomMutations: (mutations: MutationRecord[]) => void
+ handleInput: () => void
+}
+
+export function createAndroidInputManager({
+ editor,
+ scheduleOnDOMSelectionChange,
+ onDOMSelectionChange,
+}: CreateAndroidInputManagerOptions): AndroidInputManager {
+ let flushing: 'action' | boolean = false
+ let compositionEndTimeoutId: ReturnType | null = null
+ let flushTimeoutId: ReturnType | null = null
+ let actionTimeoutId: ReturnType | null = null
+
+ let idCounter = 0
+ let insertPositionHint: StringDiff | null | false = false
+
+ const applyPendingSelection = () => {
+ const pendingSelection = EDITOR_TO_PENDING_SELECTION.get(editor)
+ EDITOR_TO_PENDING_SELECTION.delete(editor)
+
+ if (pendingSelection) {
+ const { selection } = editor
+ const normalized = normalizeRange(editor, pendingSelection)
+
+ debug('apply pending selection', pendingSelection, normalized)
+
+ if (normalized && (!selection || !Range.equals(normalized, selection))) {
+ Transforms.select(editor, normalized)
+ }
+ }
+ }
+
+ const performAction = () => {
+ const action = EDITOR_TO_PENDING_ACTION.get(editor)
+ EDITOR_TO_PENDING_ACTION.delete(editor)
+ if (!action) {
+ return
+ }
+
+ if (action.at) {
+ const target = Point.isPoint(action.at)
+ ? normalizePoint(editor, action.at)
+ : normalizeRange(editor, action.at)
+
+ if (!target) {
+ return
+ }
+
+ const targetRange = Editor.range(editor, target)
+ if (!editor.selection || !Range.equals(editor.selection, targetRange)) {
+ Transforms.select(editor, target)
+ }
+ }
+
+ action.run()
+ }
+
+ const flush = () => {
+ if (flushTimeoutId) {
+ clearTimeout(flushTimeoutId)
+ flushTimeoutId = null
+ }
+
+ if (actionTimeoutId) {
+ clearTimeout(actionTimeoutId)
+ actionTimeoutId = null
+ }
+
+ if (!hasPendingDiffs() && !hasPendingAction()) {
+ applyPendingSelection()
+ return
+ }
+
+ if (!flushing) {
+ flushing = true
+ setTimeout(() => (flushing = false))
+ }
+
+ if (hasPendingAction()) {
+ flushing = 'action'
+ }
+
+ const selectionRef =
+ editor.selection &&
+ Editor.rangeRef(editor, editor.selection, { affinity: 'forward' })
+ EDITOR_TO_USER_MARKS.set(editor, editor.marks)
+
+ debug(
+ 'flush',
+ EDITOR_TO_PENDING_ACTION.get(editor),
+ EDITOR_TO_PENDING_DIFFS.get(editor)
+ )
+
+ let scheduleSelectionChange = hasPendingDiffs()
+
+ let diff: TextDiff | undefined
+ while ((diff = EDITOR_TO_PENDING_DIFFS.get(editor)?.[0])) {
+ const pendingMarks = EDITOR_TO_PENDING_INSERTION_MARKS.get(editor)
+
+ if (pendingMarks !== undefined) {
+ EDITOR_TO_PENDING_INSERTION_MARKS.delete(editor)
+ editor.marks = pendingMarks
+ }
+
+ if (pendingMarks && insertPositionHint === false) {
+ insertPositionHint = null
+ debug('insert after mark placeholder')
+ }
+
+ const range = targetRange(diff)
+ if (!editor.selection || !Range.equals(editor.selection, range)) {
+ Transforms.select(editor, range)
+ }
+
+ if (diff.diff.text) {
+ Editor.insertText(editor, diff.diff.text)
+ } else {
+ Editor.deleteFragment(editor)
+ }
+
+ // Remove diff only after we have applied it to account for it when transforming
+ // pending ranges.
+ EDITOR_TO_PENDING_DIFFS.set(
+ editor,
+ EDITOR_TO_PENDING_DIFFS.get(editor)?.filter(
+ ({ id }) => id !== diff!.id
+ )!
+ )
+
+ if (!verifyDiffState(editor, diff)) {
+ debug('invalid diff state')
+ scheduleSelectionChange = false
+ EDITOR_TO_PENDING_ACTION.delete(editor)
+ 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.
+ EDITOR_TO_PENDING_SELECTION.delete(editor)
+ scheduleOnDOMSelectionChange.cancel()
+ onDOMSelectionChange.cancel()
+ selectionRef?.unref()
+ }
+ }
+
+ const selection = selectionRef?.unref()
+ if (
+ selection &&
+ !EDITOR_TO_PENDING_SELECTION.get(editor) &&
+ (!editor.selection || !Range.equals(selection, editor.selection))
+ ) {
+ 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) {
+ debug('scheduleOnDOMSelectionChange pending changes')
+ scheduleOnDOMSelectionChange()
+ }
+
+ scheduleOnDOMSelectionChange.flush()
+ onDOMSelectionChange.flush()
+
+ applyPendingSelection()
+
+ const userMarks = EDITOR_TO_USER_MARKS.get(editor)
+ EDITOR_TO_USER_MARKS.delete(editor)
+ if (userMarks !== undefined) {
+ editor.marks = userMarks
+ editor.onChange()
+ }
+ }
+
+ const handleCompositionEnd = (
+ _event: React.CompositionEvent
+ ) => {
+ if (compositionEndTimeoutId) {
+ clearTimeout(compositionEndTimeoutId)
+ }
+
+ compositionEndTimeoutId = setTimeout(() => {
+ IS_COMPOSING.set(editor, false)
+ flush()
+ }, RESOLVE_DELAY)
+ }
+
+ const handleCompositionStart = (
+ _event: React.CompositionEvent
+ ) => {
+ debug('composition start')
+
+ IS_COMPOSING.set(editor, true)
+
+ if (compositionEndTimeoutId) {
+ clearTimeout(compositionEndTimeoutId)
+ compositionEndTimeoutId = null
+ }
+ }
+
+ const updatePlaceholderVisibility = (forceHide = false) => {
+ const placeholderElement = EDITOR_TO_PLACEHOLDER_ELEMENT.get(editor)
+ if (!placeholderElement) {
+ return
+ }
+
+ if (hasPendingDiffs() || forceHide) {
+ placeholderElement.style.display = 'none'
+ return
+ }
+
+ placeholderElement.style.removeProperty('display')
+ }
+
+ const storeDiff = (path: Path, diff: StringDiff) => {
+ debug('storeDiff', path, diff)
+
+ const pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(editor) ?? []
+ EDITOR_TO_PENDING_DIFFS.set(editor, pendingDiffs)
+
+ const target = Node.leaf(editor, path)
+ const idx = pendingDiffs.findIndex(change => Path.equals(change.path, path))
+ if (idx < 0) {
+ const normalized = normalizeStringDiff(target.text, diff)
+ if (normalized) {
+ pendingDiffs.push({ path, diff, id: idCounter++ })
+ }
+
+ updatePlaceholderVisibility()
+ return
+ }
+
+ const merged = mergeStringDiffs(target.text, pendingDiffs[idx].diff, diff)
+ if (!merged) {
+ pendingDiffs.splice(idx, 1)
+ updatePlaceholderVisibility()
+ return
+ }
+
+ pendingDiffs[idx] = {
+ ...pendingDiffs[idx],
+ diff: merged,
+ }
+ }
+
+ const scheduleAction = (
+ run: () => void,
+ { at }: { at?: Point | Range } = {}
+ ): void => {
+ insertPositionHint = false
+ debug('scheduleAction', { at, run })
+
+ EDITOR_TO_PENDING_SELECTION.delete(editor)
+ scheduleOnDOMSelectionChange.cancel()
+ onDOMSelectionChange.cancel()
+
+ if (hasPendingAction()) {
+ flush()
+ }
+
+ EDITOR_TO_PENDING_ACTION.set(editor, { at, 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)
+ }
+
+ const handleDOMBeforeInput = (event: InputEvent): void => {
+ if (flushTimeoutId) {
+ clearTimeout(flushTimeoutId)
+ flushTimeoutId = null
+ }
+
+ if (IS_NODE_MAP_DIRTY.get(editor)) {
+ return
+ }
+
+ const { inputType: type } = event
+ let targetRange: Range | null = null
+ const data: DataTransfer | string | undefined =
+ (event as any).dataTransfer || event.data || undefined
+
+ if (
+ insertPositionHint !== false &&
+ type !== 'insertText' &&
+ type !== 'insertCompositionText'
+ ) {
+ insertPositionHint = false
+ }
+
+ let [nativeTargetRange] = (event as any).getTargetRanges()
+ 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.
+ const window = ReactEditor.getWindow(editor)
+ const domSelection = window.getSelection()
+ if (!targetRange && domSelection) {
+ nativeTargetRange = domSelection
+ targetRange = ReactEditor.toSlateRange(editor, domSelection, {
+ exactMatch: false,
+ suppressThrow: true,
+ })
+ }
+
+ targetRange = targetRange ?? 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.
+ let canStoreDiff = true
+
+ if (type.startsWith('delete')) {
+ const direction = type.endsWith('Backward') ? 'backward' : 'forward'
+ let [start, end] = Range.edges(targetRange)
+ let [leaf, path] = Editor.leaf(editor, start.path)
+
+ if (Range.isExpanded(targetRange)) {
+ if (leaf.text.length === start.offset && end.offset === 0) {
+ const next = Editor.next(editor, {
+ at: start.path,
+ match: Text.isText,
+ })
+ if (next && 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
+ ;[leaf, path] = next
+ } else {
+ targetRange = { anchor: start, focus: start }
+ end = start
+ }
+ }
+ }
+ }
+
+ const diff = {
+ text: '',
+ start: start.offset,
+ end: end.offset,
+ }
+ const pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(editor)
+ const relevantPendingDiffs = pendingDiffs?.find(change =>
+ Path.equals(change.path, path)
+ )
+ const diffs = relevantPendingDiffs
+ ? [relevantPendingDiffs.diff, diff]
+ : [diff]
+ const text = applyStringDiff(leaf.text, ...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 (Range.isExpanded(targetRange)) {
+ if (
+ canStoreDiff &&
+ Path.equals(targetRange.anchor.path, targetRange.focus.path)
+ ) {
+ const point = { path: targetRange.anchor.path, offset: start.offset }
+ const range = Editor.range(editor, point, point)
+ handleUserSelect(range)
+
+ return storeDiff(targetRange.anchor.path, {
+ text: '',
+ end: end.offset,
+ start: start.offset,
+ })
+ }
+
+ return scheduleAction(
+ () => Editor.deleteFragment(editor, { direction }),
+ { at: targetRange }
+ )
+ }
+ }
+
+ switch (type) {
+ case 'deleteByComposition':
+ case 'deleteByCut':
+ case 'deleteByDrag': {
+ return scheduleAction(() => Editor.deleteFragment(editor), {
+ at: targetRange,
+ })
+ }
+
+ case 'deleteContent':
+ case 'deleteContentForward': {
+ const { anchor } = targetRange
+ if (canStoreDiff && Range.isCollapsed(targetRange)) {
+ const targetNode = 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(() => Editor.deleteForward(editor), {
+ at: targetRange,
+ })
+ }
+
+ case 'deleteContentBackward': {
+ const { anchor } = targetRange
+
+ // 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)
+ const nativeCollapsed = isDOMSelection(nativeTargetRange)
+ ? nativeTargetRange.isCollapsed
+ : !!nativeTargetRange?.collapsed
+
+ if (
+ canStoreDiff &&
+ nativeCollapsed &&
+ Range.isCollapsed(targetRange) &&
+ anchor.offset > 0
+ ) {
+ return storeDiff(anchor.path, {
+ text: '',
+ start: anchor.offset - 1,
+ end: anchor.offset,
+ })
+ }
+
+ return scheduleAction(() => Editor.deleteBackward(editor), {
+ at: targetRange,
+ })
+ }
+
+ case 'deleteEntireSoftLine': {
+ return scheduleAction(
+ () => {
+ Editor.deleteBackward(editor, { unit: 'line' })
+ Editor.deleteForward(editor, { unit: 'line' })
+ },
+ { at: targetRange }
+ )
+ }
+
+ case 'deleteHardLineBackward': {
+ return scheduleAction(
+ () => Editor.deleteBackward(editor, { unit: 'block' }),
+ { at: targetRange }
+ )
+ }
+
+ case 'deleteSoftLineBackward': {
+ return scheduleAction(
+ () => Editor.deleteBackward(editor, { unit: 'line' }),
+ { at: targetRange }
+ )
+ }
+
+ case 'deleteHardLineForward': {
+ return scheduleAction(
+ () => Editor.deleteForward(editor, { unit: 'block' }),
+ { at: targetRange }
+ )
+ }
+
+ case 'deleteSoftLineForward': {
+ return scheduleAction(
+ () => Editor.deleteForward(editor, { unit: 'line' }),
+ { at: targetRange }
+ )
+ }
+
+ case 'deleteWordBackward': {
+ return scheduleAction(
+ () => Editor.deleteBackward(editor, { unit: 'word' }),
+ { at: targetRange }
+ )
+ }
+
+ case 'deleteWordForward': {
+ return scheduleAction(
+ () => Editor.deleteForward(editor, { unit: 'word' }),
+ { at: targetRange }
+ )
+ }
+
+ case 'insertLineBreak': {
+ return scheduleAction(() => Editor.insertSoftBreak(editor), {
+ at: targetRange,
+ })
+ }
+
+ case 'insertParagraph': {
+ return scheduleAction(() => 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(() => ReactEditor.insertData(editor, data), {
+ at: targetRange,
+ })
+ }
+
+ let text = 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 (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(
+ () => {
+ const parts = text.split('\n')
+ parts.forEach((line, i) => {
+ if (line) {
+ Editor.insertText(editor, line)
+ }
+ if (i !== parts.length - 1) {
+ Editor.insertSoftBreak(editor)
+ }
+ })
+ },
+ {
+ at: targetRange,
+ }
+ )
+ }
+
+ if (Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
+ const [start, end] = Range.edges(targetRange)
+
+ const diff = {
+ start: start.offset,
+ end: end.offset,
+ 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') {
+ const hintPosition =
+ insertPositionHint.start + insertPositionHint.text.search(/\S|$/)
+ const diffPosition = diff.start + diff.text.search(/\S|$/)
+
+ if (
+ diffPosition === hintPosition + 1 &&
+ diff.end ===
+ insertPositionHint.start + insertPositionHint.text.length
+ ) {
+ debug('adjusting swiftKey insert position using hint')
+ diff.start -= 1
+ insertPositionHint = null
+ scheduleFlush()
+ } else {
+ insertPositionHint = false
+ }
+ } else if (type === 'insertText') {
+ if (insertPositionHint === null) {
+ insertPositionHint = diff
+ } else if (
+ insertPositionHint &&
+ Range.isCollapsed(targetRange) &&
+ insertPositionHint.end + insertPositionHint.text.length ===
+ start.offset
+ ) {
+ insertPositionHint = {
+ ...insertPositionHint,
+ text: insertPositionHint.text + text,
+ }
+ } else {
+ insertPositionHint = false
+ }
+ } else {
+ insertPositionHint = false
+ }
+
+ if (canStoreDiff) {
+ const currentSelection = editor.selection
+ storeDiff(start.path, diff)
+
+ if (currentSelection) {
+ const newPoint = {
+ path: start.path,
+ offset: start.offset + text.length,
+ }
+
+ scheduleAction(
+ () => {
+ Transforms.select(editor, {
+ anchor: newPoint,
+ focus: newPoint,
+ })
+ },
+ { at: newPoint }
+ )
+ }
+ return
+ }
+ }
+
+ return scheduleAction(() => Editor.insertText(editor, text), {
+ at: targetRange,
+ })
+ }
+ }
+ }
+
+ const hasPendingAction = () => {
+ return !!EDITOR_TO_PENDING_ACTION.get(editor)
+ }
+
+ const hasPendingDiffs = () => {
+ return !!EDITOR_TO_PENDING_DIFFS.get(editor)?.length
+ }
+
+ const hasPendingChanges = () => {
+ return hasPendingAction() || hasPendingDiffs()
+ }
+
+ const isFlushing = () => {
+ return flushing
+ }
+
+ const handleUserSelect = (range: Range | null) => {
+ EDITOR_TO_PENDING_SELECTION.set(editor, range)
+
+ if (flushTimeoutId) {
+ clearTimeout(flushTimeoutId)
+ flushTimeoutId = null
+ }
+
+ const { selection } = editor
+ if (!range) {
+ return
+ }
+
+ const pathChanged =
+ !selection || !Path.equals(selection.anchor.path, range.anchor.path)
+ const parentPathChanged =
+ !selection ||
+ !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)
+ }
+ }
+
+ const handleInput = () => {
+ if (hasPendingAction() || !hasPendingDiffs()) {
+ debug('flush input')
+ flush()
+ }
+ }
+
+ const handleKeyDown = (_: React.KeyboardEvent) => {
+ // 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)
+ }
+ }
+
+ const scheduleFlush = () => {
+ if (!hasPendingAction()) {
+ actionTimeoutId = setTimeout(flush)
+ }
+ }
+
+ const handleDomMutations = (mutations: MutationRecord[]) => {
+ if (hasPendingDiffs() || hasPendingAction()) {
+ return
+ }
+
+ if (
+ mutations.some(mutation => isTrackedMutation(editor, mutation, mutations))
+ ) {
+ // Cause a re-render to restore the dom state if we encounter tracked mutations without
+ // a corresponding pending action.
+ EDITOR_TO_FORCE_RENDER.get(editor)?.()
+ }
+ }
+
+ return {
+ flush,
+ scheduleFlush,
+
+ hasPendingDiffs,
+ hasPendingAction,
+ hasPendingChanges,
+
+ isFlushing,
+
+ handleUserSelect,
+ handleCompositionEnd,
+ handleCompositionStart,
+ handleDOMBeforeInput,
+ handleKeyDown,
+
+ handleDomMutations,
+ handleInput,
+ }
+}
diff --git a/src/hooks/android-input-manager/use-android-input-manager.ts b/src/hooks/android-input-manager/use-android-input-manager.ts
new file mode 100644
index 0000000..db4a08a
--- /dev/null
+++ b/src/hooks/android-input-manager/use-android-input-manager.ts
@@ -0,0 +1,54 @@
+import { RefObject, useState } from 'react'
+import { useSlateStatic } from '../use-slate-static'
+import { IS_ANDROID } from 'slate-dom'
+import { EDITOR_TO_SCHEDULE_FLUSH } from 'slate-dom'
+import {
+ createAndroidInputManager,
+ CreateAndroidInputManagerOptions,
+} from './android-input-manager'
+import { useIsMounted } from '../use-is-mounted'
+import { useMutationObserver } from '../use-mutation-observer'
+
+type UseAndroidInputManagerOptions = {
+ node: RefObject
+} & Omit<
+ CreateAndroidInputManagerOptions,
+ 'editor' | 'onUserInput' | 'receivedUserInput'
+>
+
+const MUTATION_OBSERVER_CONFIG: MutationObserverInit = {
+ subtree: true,
+ childList: true,
+ characterData: true,
+}
+
+export const useAndroidInputManager = !IS_ANDROID
+ ? () => null
+ : ({ node, ...options }: UseAndroidInputManagerOptions) => {
+ if (!IS_ANDROID) {
+ return null
+ }
+
+ const editor = useSlateStatic()
+ const isMounted = useIsMounted()
+
+ const [inputManager] = useState(() =>
+ createAndroidInputManager({
+ editor,
+ ...options,
+ })
+ )
+
+ useMutationObserver(
+ node,
+ inputManager.handleDomMutations,
+ MUTATION_OBSERVER_CONFIG
+ )
+
+ EDITOR_TO_SCHEDULE_FLUSH.set(editor, inputManager.scheduleFlush)
+ if (isMounted) {
+ inputManager.flush()
+ }
+
+ return inputManager
+ }
diff --git a/src/hooks/use-children.tsx b/src/hooks/use-children.tsx
new file mode 100644
index 0000000..66fecf3
--- /dev/null
+++ b/src/hooks/use-children.tsx
@@ -0,0 +1,190 @@
+import React, { useCallback, useRef } from 'react'
+import { Ancestor, Editor, Element, DecoratedRange, Text } from 'slate'
+import { Key, isElementDecorationsEqual } from 'slate-dom'
+import {
+ RenderChunkProps,
+ RenderElementProps,
+ RenderLeafProps,
+ RenderPlaceholderProps,
+ RenderTextProps,
+} from '../components/editable'
+
+import ElementComponent from '../components/element'
+import TextComponent from '../components/text'
+import { ReactEditor } from '../plugin/react-editor'
+import {
+ IS_NODE_MAP_DIRTY,
+ NODE_TO_INDEX,
+ NODE_TO_PARENT,
+ splitDecorationsByChild,
+} from 'slate-dom'
+import { useSlateStatic } from './use-slate-static'
+import { getChunkTreeForNode } from '../chunking'
+import ChunkTree from '../components/chunk-tree'
+import { ElementContext } from './use-element'
+
+/**
+ * Children.
+ */
+
+const useChildren = (props: {
+ decorations: DecoratedRange[]
+ node: Ancestor
+ renderElement?: (props: RenderElementProps) => JSX.Element
+ renderChunk?: (props: RenderChunkProps) => JSX.Element
+ renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
+ renderText?: (props: RenderTextProps) => JSX.Element
+ renderLeaf?: (props: RenderLeafProps) => JSX.Element
+}) => {
+ const {
+ decorations,
+ node,
+ renderElement,
+ renderChunk,
+ renderPlaceholder,
+ renderText,
+ renderLeaf,
+ } = props
+ const editor = useSlateStatic()
+ IS_NODE_MAP_DIRTY.set(editor as ReactEditor, false)
+
+ const isEditor = Editor.isEditor(node)
+ const isBlock = !isEditor && Element.isElement(node) && !editor.isInline(node)
+ const isLeafBlock = isBlock && Editor.hasInlines(editor, node)
+ const chunkSize = isLeafBlock ? null : editor.getChunkSize(node)
+ const chunking = !!chunkSize
+
+ const { decorationsByChild, childrenToRedecorate } = useDecorationsByChild(
+ editor,
+ node,
+ decorations
+ )
+
+ // 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((n, i) => {
+ NODE_TO_INDEX.set(n, i)
+ NODE_TO_PARENT.set(n, node)
+ })
+ }
+
+ const renderElementComponent = useCallback(
+ (n: Element, i: number, cachedKey?: Key) => {
+ const key = cachedKey ?? ReactEditor.findKey(editor, n)
+
+ return (
+
+
+
+ )
+ },
+ [
+ editor,
+ decorationsByChild,
+ renderElement,
+ renderChunk,
+ renderPlaceholder,
+ renderLeaf,
+ renderText,
+ ]
+ )
+
+ const renderTextComponent = (n: Text, i: number) => {
+ const key = ReactEditor.findKey(editor, n)
+
+ return (
+
+ )
+ }
+
+ if (!chunking) {
+ return node.children.map((n, i) =>
+ Text.isText(n) ? renderTextComponent(n, i) : renderElementComponent(n, i)
+ )
+ }
+
+ const chunkTree = getChunkTreeForNode(editor, node, {
+ reconcile: {
+ chunkSize,
+ rerenderChildren: childrenToRedecorate,
+ onInsert: (n, i) => {
+ NODE_TO_INDEX.set(n, i)
+ NODE_TO_PARENT.set(n, node)
+ },
+ onUpdate: (n, i) => {
+ NODE_TO_INDEX.set(n, i)
+ NODE_TO_PARENT.set(n, node)
+ },
+ onIndexChange: (n, i) => {
+ NODE_TO_INDEX.set(n, i)
+ },
+ },
+ })
+
+ return (
+
+ )
+}
+
+const useDecorationsByChild = (
+ editor: Editor,
+ node: Ancestor,
+ decorations: DecoratedRange[]
+) => {
+ const decorationsByChild = 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.
+ const mutableDecorationsByChild = 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.
+ const childrenToRedecorate: number[] = []
+
+ // Resize the mutable array to match the latest result
+ mutableDecorationsByChild.length = decorationsByChild.length
+
+ for (let i = 0; i < decorationsByChild.length; i++) {
+ const decorations = decorationsByChild[i]
+
+ const previousDecorations: DecoratedRange[] | null =
+ mutableDecorationsByChild[i] ?? null
+
+ if (!isElementDecorationsEqual(previousDecorations, decorations)) {
+ mutableDecorationsByChild[i] = decorations
+ childrenToRedecorate.push(i)
+ }
+ }
+
+ return { decorationsByChild: mutableDecorationsByChild, childrenToRedecorate }
+}
+
+export default useChildren
diff --git a/src/hooks/use-composing.ts b/src/hooks/use-composing.ts
new file mode 100644
index 0000000..5407121
--- /dev/null
+++ b/src/hooks/use-composing.ts
@@ -0,0 +1,15 @@
+import { createContext, useContext } from 'react'
+
+/**
+ * A React context for sharing the `composing` state of the editor.
+ */
+
+export const ComposingContext = createContext(false)
+
+/**
+ * Get the current `composing` state of the editor.
+ */
+
+export const useComposing = (): boolean => {
+ return useContext(ComposingContext)
+}
diff --git a/src/hooks/use-decorations.ts b/src/hooks/use-decorations.ts
new file mode 100644
index 0000000..b10b88a
--- /dev/null
+++ b/src/hooks/use-decorations.ts
@@ -0,0 +1,81 @@
+import { createContext, useCallback, useContext, useMemo, useRef } from 'react'
+import { DecoratedRange, Descendant, NodeEntry, Text } from 'slate'
+import { isTextDecorationsEqual, isElementDecorationsEqual } from 'slate-dom'
+import { useSlateStatic } from './use-slate-static'
+import { ReactEditor } from '../plugin/react-editor'
+import { useGenericSelector } from './use-generic-selector'
+import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'
+
+type Callback = () => void
+
+/**
+ * A React context for sharing the `decorate` prop of the editable and
+ * subscribing to changes on this prop.
+ */
+
+export const DecorateContext = createContext<{
+ decorate: (entry: NodeEntry) => DecoratedRange[]
+ addEventListener: (callback: Callback) => () => void
+}>({} as any)
+
+export const useDecorations = (
+ node: Descendant,
+ parentDecorations: DecoratedRange[]
+): DecoratedRange[] => {
+ const editor = useSlateStatic()
+ const { decorate, addEventListener } = useContext(DecorateContext)
+
+ // Not memoized since we want nodes to be decorated on each render
+ const selector = () => {
+ const path = ReactEditor.findPath(editor, node)
+ return decorate([node, path])
+ }
+
+ const equalityFn = Text.isText(node)
+ ? isTextDecorationsEqual
+ : isElementDecorationsEqual
+
+ const [decorations, update] = useGenericSelector(selector, equalityFn)
+
+ useIsomorphicLayoutEffect(() => {
+ const unsubscribe = addEventListener(update)
+ update()
+ return unsubscribe
+ }, [addEventListener, update])
+
+ return useMemo(
+ () => [...decorations, ...parentDecorations],
+ [decorations, parentDecorations]
+ )
+}
+
+export const useDecorateContext = (
+ decorateProp: (entry: NodeEntry) => DecoratedRange[]
+) => {
+ const eventListeners = useRef(new Set())
+
+ const latestDecorate = useRef(decorateProp)
+
+ useIsomorphicLayoutEffect(() => {
+ latestDecorate.current = decorateProp
+ eventListeners.current.forEach(listener => listener())
+ }, [decorateProp])
+
+ const decorate = useCallback(
+ (entry: NodeEntry) => latestDecorate.current(entry),
+ []
+ )
+
+ const addEventListener = useCallback((callback: Callback) => {
+ eventListeners.current.add(callback)
+
+ return () => {
+ eventListeners.current.delete(callback)
+ }
+ }, [])
+
+ return useMemo(
+ () => ({ decorate, addEventListener }),
+ [decorate, addEventListener]
+ )
+}
diff --git a/src/hooks/use-editor.tsx b/src/hooks/use-editor.tsx
index 5c956e2..d287da3 100644
--- a/src/hooks/use-editor.tsx
+++ b/src/hooks/use-editor.tsx
@@ -1,15 +1,10 @@
-import { createContext, useContext } from 'react'
+import { useContext } from 'react'
-import { ReactEditor } from '../plugin/react-editor'
-
-/**
- * A React context for sharing the editor object.
- */
-
-export const EditorContext = createContext(null)
+import { EditorContext } from './use-slate-static'
/**
* Get the current editor object from the React context.
+ * @deprecated Use useSlateStatic instead.
*/
export const useEditor = () => {
diff --git a/src/hooks/use-element.ts b/src/hooks/use-element.ts
new file mode 100644
index 0000000..21585ee
--- /dev/null
+++ b/src/hooks/use-element.ts
@@ -0,0 +1,25 @@
+import { createContext, useContext } from 'react'
+import { Element } from 'slate'
+
+export const ElementContext = createContext(null)
+
+/**
+ * Get the current element.
+ */
+
+export const useElement = (): Element => {
+ const context = 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`.
+ */
+export const useElementIf = () => useContext(ElementContext)
diff --git a/src/hooks/use-generic-selector.tsx b/src/hooks/use-generic-selector.tsx
new file mode 100644
index 0000000..7bb8705
--- /dev/null
+++ b/src/hooks/use-generic-selector.tsx
@@ -0,0 +1,92 @@
+import { useCallback, useReducer, useRef } from 'react'
+
+/**
+ * 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
+ */
+
+export function useGenericSelector(
+ selector: () => T,
+ equalityFn: (a: T | null, b: T) => boolean
+): [state: T, update: () => void] {
+ const [, forceRender] = useReducer(s => s + 1, 0)
+
+ const latestSubscriptionCallbackError = useRef()
+ const latestSelector = useRef<() => T>(() => null as any)
+ const latestSelectedState = useRef(null)
+ let selectedState: T
+
+ try {
+ if (
+ selector !== latestSelector.current ||
+ latestSubscriptionCallbackError.current
+ ) {
+ const selectorResult = selector()
+
+ if (equalityFn(latestSelectedState.current, selectorResult)) {
+ selectedState = latestSelectedState.current as T
+ } else {
+ selectedState = selectorResult
+ }
+ } else {
+ selectedState = latestSelectedState.current as T
+ }
+ } catch (err) {
+ if (latestSubscriptionCallbackError.current && isError(err)) {
+ err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
+ }
+
+ throw err
+ }
+
+ latestSelector.current = selector
+ latestSelectedState.current = selectedState
+ latestSubscriptionCallbackError.current = undefined
+
+ const update = useCallback(() => {
+ try {
+ const 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: any): error is Error {
+ return error instanceof Error
+}
diff --git a/src/hooks/use-is-mounted.tsx b/src/hooks/use-is-mounted.tsx
new file mode 100644
index 0000000..8c583a8
--- /dev/null
+++ b/src/hooks/use-is-mounted.tsx
@@ -0,0 +1,14 @@
+import { useEffect, useRef } from 'react'
+
+export function useIsMounted() {
+ const isMountedRef = useRef(false)
+
+ useEffect(() => {
+ isMountedRef.current = true
+ return () => {
+ isMountedRef.current = false
+ }
+ }, [])
+
+ return isMountedRef.current
+}
diff --git a/src/hooks/use-isomorphic-layout-effect.ts b/src/hooks/use-isomorphic-layout-effect.ts
index 2587f0b..663cc81 100644
--- a/src/hooks/use-isomorphic-layout-effect.ts
+++ b/src/hooks/use-isomorphic-layout-effect.ts
@@ -1,7 +1,10 @@
import { useLayoutEffect, useEffect } from 'react'
+import { CAN_USE_DOM } from 'slate-dom'
/**
- * Prevent warning on SSR by falling back to useEffect when window is not defined
+ * Prevent warning on SSR by falling back to useEffect when DOM isn't available
*/
-export const useIsomorphicLayoutEffect =
- typeof window !== 'undefined' ? useLayoutEffect : useEffect
+
+export const useIsomorphicLayoutEffect = CAN_USE_DOM
+ ? useLayoutEffect
+ : useEffect
diff --git a/src/hooks/use-mutation-observer.ts b/src/hooks/use-mutation-observer.ts
new file mode 100644
index 0000000..df282c7
--- /dev/null
+++ b/src/hooks/use-mutation-observer.ts
@@ -0,0 +1,25 @@
+import { RefObject, useEffect, useState } from 'react'
+import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'
+
+export function useMutationObserver(
+ node: RefObject,
+ callback: MutationCallback,
+ options: MutationObserverInit
+) {
+ const [mutationObserver] = useState(() => new MutationObserver(callback))
+
+ useIsomorphicLayoutEffect(() => {
+ // Discard mutations caused during render phase. This works due to react calling
+ // useLayoutEffect synchronously after the render phase before the next tick.
+ mutationObserver.takeRecords()
+ })
+
+ useEffect(() => {
+ if (!node.current) {
+ throw new Error('Failed to attach MutationObserver, `node` is undefined')
+ }
+
+ mutationObserver.observe(node.current, options)
+ return () => mutationObserver.disconnect()
+ }, [mutationObserver, node, options])
+}
diff --git a/src/hooks/use-selected.ts b/src/hooks/use-selected.ts
index fd96418..d26393f 100644
--- a/src/hooks/use-selected.ts
+++ b/src/hooks/use-selected.ts
@@ -1,15 +1,37 @@
-import { createContext, useContext } from 'react'
-
-/**
- * A React context for sharing the `selected` state of an element.
- */
-
-export const SelectedContext = createContext(false)
+import { useCallback } from 'react'
+import { Editor, Range } from 'slate'
+import { useElementIf } from './use-element'
+import { useSlateSelector } from './use-slate-selector'
+import { ReactEditor } from '../plugin/react-editor'
/**
* Get the current `selected` state of an element.
*/
export const useSelected = (): boolean => {
- return useContext(SelectedContext)
+ const 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
+ const selector = useCallback(
+ (editor: Editor) => {
+ if (!editor.selection) return false
+ const path = ReactEditor.findPath(editor, element)
+ const range = Editor.range(editor, path)
+ return !!Range.intersection(range, editor.selection)
+ },
+ [element]
+ )
+
+ // 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,
+ })
}
diff --git a/src/hooks/use-slate-selection.tsx b/src/hooks/use-slate-selection.tsx
new file mode 100644
index 0000000..1a936b9
--- /dev/null
+++ b/src/hooks/use-slate-selection.tsx
@@ -0,0 +1,17 @@
+import { BaseSelection, Range } from 'slate'
+
+import { useSlateSelector } from './use-slate-selector'
+
+/**
+ * Get the current slate selection.
+ * Only triggers a rerender when the selection actually changes
+ */
+export const useSlateSelection = () => {
+ return useSlateSelector(editor => editor.selection, isSelectionEqual)
+}
+
+const isSelectionEqual = (a: BaseSelection, b: BaseSelection) => {
+ if (!a && !b) return true
+ if (!a || !b) return false
+ return Range.equals(a, b)
+}
diff --git a/src/hooks/use-slate-selector.tsx b/src/hooks/use-slate-selector.tsx
new file mode 100644
index 0000000..c43ebd6
--- /dev/null
+++ b/src/hooks/use-slate-selector.tsx
@@ -0,0 +1,128 @@
+import { createContext, useCallback, useContext, useMemo, useRef } from 'react'
+import { Editor } from 'slate'
+import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'
+import { useSlateStatic } from './use-slate-static'
+import { useGenericSelector } from './use-generic-selector'
+
+type Callback = () => void
+
+export interface SlateSelectorOptions {
+ /**
+ * If true, defer calling the selector function until after `Editable` has
+ * finished rendering. This ensures that `ReactEditor.findPath` won't return
+ * an outdated path if called inside the selector.
+ */
+ deferred?: boolean
+}
+
+/**
+ * A React context for sharing the editor selector context in a way to control
+ * re-renders.
+ */
+
+export const SlateSelectorContext = createContext<{
+ addEventListener: (
+ callback: Callback,
+ options?: SlateSelectorOptions
+ ) => () => void
+ flushDeferred: () => void
+}>({} as any)
+
+const refEquality = (a: any, b: any) => 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))
+ */
+
+export function useSlateSelector(
+ selector: (editor: Editor) => T,
+ equalityFn: (a: T | null, b: T) => boolean = refEquality,
+ { deferred }: SlateSelectorOptions = {}
+): T {
+ const context = useContext(SlateSelectorContext)
+ if (!context) {
+ throw new Error(
+ `The \`useSlateSelector\` hook must be used inside the component's context.`
+ )
+ }
+ const { addEventListener } = context
+
+ const editor = useSlateStatic()
+ const genericSelector = useCallback(
+ () => selector(editor),
+ [editor, selector]
+ )
+ const [selectedState, update] = useGenericSelector(
+ genericSelector,
+ equalityFn
+ )
+
+ useIsomorphicLayoutEffect(() => {
+ const unsubscribe = addEventListener(update, { deferred })
+ update()
+ return unsubscribe
+ }, [addEventListener, update, deferred])
+
+ return selectedState
+}
+
+/**
+ * Create selector context with editor updating on every editor change
+ */
+export function useSelectorContext() {
+ const eventListeners = useRef(new Set())
+ const deferredEventListeners = useRef(new Set())
+
+ const onChange = useCallback(() => {
+ eventListeners.current.forEach(listener => listener())
+ }, [])
+
+ const flushDeferred = useCallback(() => {
+ deferredEventListeners.current.forEach(listener => listener())
+ deferredEventListeners.current.clear()
+ }, [])
+
+ const addEventListener = useCallback(
+ (
+ callbackProp: Callback,
+ { deferred = false }: SlateSelectorOptions = {}
+ ) => {
+ const callback = deferred
+ ? () => deferredEventListeners.current.add(callbackProp)
+ : callbackProp
+
+ eventListeners.current.add(callback)
+
+ return () => {
+ eventListeners.current.delete(callback)
+ }
+ },
+ []
+ )
+
+ const selectorContext = useMemo(
+ () => ({
+ addEventListener,
+ flushDeferred,
+ }),
+ [addEventListener, flushDeferred]
+ )
+
+ return { selectorContext, onChange }
+}
+
+export function useFlushDeferredSelectorsOnRender() {
+ const { flushDeferred } = useContext(SlateSelectorContext)
+ useIsomorphicLayoutEffect(flushDeferred)
+}
diff --git a/src/hooks/use-slate-static.tsx b/src/hooks/use-slate-static.tsx
new file mode 100644
index 0000000..c45452b
--- /dev/null
+++ b/src/hooks/use-slate-static.tsx
@@ -0,0 +1,25 @@
+import { createContext, useContext } from 'react'
+import { Editor } from 'slate'
+import { ReactEditor } from '../plugin/react-editor'
+
+/**
+ * A React context for sharing the editor object.
+ */
+
+export const EditorContext = createContext(null)
+
+/**
+ * Get the current editor object from the React context.
+ */
+
+export const useSlateStatic = (): Editor => {
+ const editor = useContext(EditorContext)
+
+ if (!editor) {
+ throw new Error(
+ `The \`useSlateStatic\` hook must be used inside the component's context.`
+ )
+ }
+
+ return editor
+}
diff --git a/src/hooks/use-slate.tsx b/src/hooks/use-slate.tsx
index 5d09a7c..bf6acf0 100644
--- a/src/hooks/use-slate.tsx
+++ b/src/hooks/use-slate.tsx
@@ -1,27 +1,64 @@
-import { createContext, useContext } from 'react'
-
-import { ReactEditor } from '../plugin/react-editor'
-
-/**
- * A React context for sharing the editor object, in a way that re-renders the
- * context whenever changes occur.
- */
-
-export const SlateContext = createContext<[ReactEditor] | null>(null)
+import { MutableRefObject, useContext, useMemo, useReducer } from 'react'
+import { Editor } from 'slate'
+import { SlateSelectorContext } from './use-slate-selector'
+import { useSlateStatic } from './use-slate-static'
+import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'
/**
- * Get the current editor object from the React context.
+ * Get the current editor object and re-render whenever it changes.
*/
-export const useSlate = () => {
- const context = useContext(SlateContext)
+export const useSlate = (): Editor => {
+ const { addEventListener } = useContext(SlateSelectorContext)
+ const [, forceRender] = useReducer(s => s + 1, 0)
- if (!context) {
+ if (!addEventListener) {
throw new Error(
- `The \`useSlate\` hook must be used inside the component's context.`
+ `The \`useSlate\` hook must be used inside the component's context.`
)
}
- const [editor] = context
- return editor
+ useIsomorphicLayoutEffect(
+ () => addEventListener(forceRender),
+ [addEventListener]
+ )
+
+ return useSlateStatic()
+}
+
+const EDITOR_TO_V = new WeakMap>()
+
+const getEditorVersionRef = (editor: Editor): MutableRefObject => {
+ let 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
+ const { onChange } = editor
+
+ editor.onChange = 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.
+ */
+
+export const useSlateWithV = (): { editor: Editor; v: number } => {
+ const editor = useSlate()
+ const vRef = useMemo(() => getEditorVersionRef(editor), [editor])
+ return { editor, v: vRef.current }
}
diff --git a/src/hooks/use-track-user-input.ts b/src/hooks/use-track-user-input.ts
new file mode 100644
index 0000000..cd66a3b
--- /dev/null
+++ b/src/hooks/use-track-user-input.ts
@@ -0,0 +1,32 @@
+import { useCallback, useEffect, useRef } from 'react'
+import { ReactEditor } from '../plugin/react-editor'
+import { useSlateStatic } from './use-slate-static'
+
+export function useTrackUserInput() {
+ const editor = useSlateStatic()
+
+ const receivedUserInput = useRef(false)
+ const animationFrameIdRef = useRef(0)
+
+ const onUserInput = useCallback(() => {
+ if (receivedUserInput.current) {
+ return
+ }
+
+ receivedUserInput.current = true
+
+ const window = ReactEditor.getWindow(editor)
+ window.cancelAnimationFrame(animationFrameIdRef.current)
+
+ animationFrameIdRef.current = window.requestAnimationFrame(() => {
+ receivedUserInput.current = false
+ })
+ }, [editor])
+
+ useEffect(() => () => cancelAnimationFrame(animationFrameIdRef.current), [])
+
+ return {
+ receivedUserInput,
+ onUserInput,
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 9b7d868..0f1ffce 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,20 +1,33 @@
// Components
export {
+ Editable,
RenderElementProps,
+ RenderChunkProps,
RenderLeafProps,
- Editable,
+ RenderPlaceholderProps,
+ DefaultPlaceholder,
} from './components/editable'
+
export { DefaultElement } from './components/element'
+export { DefaultText } from './components/text'
export { DefaultLeaf } from './components/leaf'
export { Slate } from './components/slate'
// Hooks
export { useEditor } from './hooks/use-editor'
+export { useElement, useElementIf } from './hooks/use-element'
+export { useSlateStatic } from './hooks/use-slate-static'
+export { useComposing } from './hooks/use-composing'
export { useFocused } from './hooks/use-focused'
export { useReadOnly } from './hooks/use-read-only'
export { useSelected } from './hooks/use-selected'
-export { useSlate } from './hooks/use-slate'
+export { useSlate, useSlateWithV } from './hooks/use-slate'
+export { useSlateSelector } from './hooks/use-slate-selector'
+export { useSlateSelection } from './hooks/use-slate-selection'
// Plugin
export { ReactEditor } from './plugin/react-editor'
export { withReact } from './plugin/with-react'
+
+// Utils
+export { NODE_TO_INDEX, NODE_TO_PARENT } from 'slate-dom'
diff --git a/src/plugin/react-editor.ts b/src/plugin/react-editor.ts
index 0bb5361..5ab4a99 100644
--- a/src/plugin/react-editor.ts
+++ b/src/plugin/react-editor.ts
@@ -1,491 +1,20 @@
-import { Editor, Node, Path, Point, Range, Transforms } from 'slate'
-
-import { Key } from '../utils/key'
-import {
- EDITOR_TO_ELEMENT,
- ELEMENT_TO_NODE,
- IS_FOCUSED,
- IS_READ_ONLY,
- KEY_TO_ELEMENT,
- NODE_TO_INDEX,
- NODE_TO_KEY,
- NODE_TO_PARENT,
-} from '../utils/weak-maps'
-import {
- DOMElement,
- DOMNode,
- DOMPoint,
- DOMRange,
- DOMSelection,
- DOMStaticRange,
- isDOMElement,
- normalizeDOMPoint,
-} from '../utils/dom'
+import { Ancestor } from 'slate'
+import { DOMEditor, type DOMEditorInterface } from 'slate-dom'
/**
* A React and DOM-specific version of the `Editor` interface.
*/
-export interface ReactEditor extends Editor {
- insertData: (data: DataTransfer) => void
-}
-
-export const ReactEditor = {
- /**
- * Find a key for a Slate node.
- */
-
- findKey(editor: ReactEditor, node: Node): Key {
- let key = NODE_TO_KEY.get(node)
-
- if (!key) {
- key = new Key()
- NODE_TO_KEY.set(node, key)
- }
-
- return key
- },
-
- /**
- * Find the path of Slate node.
- */
-
- findPath(editor: ReactEditor, node: Node): Path {
- const path: Path = []
- let child = node
-
- while (true) {
- const parent = NODE_TO_PARENT.get(child)
-
- if (parent == null) {
- if (Editor.isEditor(child)) {
- return path
- } else {
- break
- }
- }
-
- const i = NODE_TO_INDEX.get(child)
-
- if (i == null) {
- break
- }
-
- path.unshift(i)
- child = parent
- }
-
- throw new Error(
- `Unable to find the path for Slate node: ${JSON.stringify(node)}`
- )
- },
-
- /**
- * Check if the editor is focused.
- */
-
- isFocused(editor: ReactEditor): boolean {
- return !!IS_FOCUSED.get(editor)
- },
-
- /**
- * Check if the editor is in read-only mode.
- */
-
- isReadOnly(editor: ReactEditor): boolean {
- return !!IS_READ_ONLY.get(editor)
- },
-
- /**
- * Blur the editor.
- */
-
- blur(editor: ReactEditor): void {
- const el = ReactEditor.toDOMNode(editor, editor)
- IS_FOCUSED.set(editor, false)
-
- if (window.document.activeElement === el) {
- el.blur()
- }
- },
-
- /**
- * Focus the editor.
- */
-
- focus(editor: ReactEditor): void {
- const el = ReactEditor.toDOMNode(editor, editor)
- IS_FOCUSED.set(editor, true)
-
- if (window.document.activeElement !== el) {
- el.focus({ preventScroll: true })
- }
- },
-
- /**
- * Deselect the editor.
- */
-
- deselect(editor: ReactEditor): void {
- const { selection } = editor
- const domSelection = window.getSelection()
-
- if (domSelection && domSelection.rangeCount > 0) {
- domSelection.removeAllRanges()
- }
-
- if (selection) {
- Transforms.deselect(editor)
- }
- },
-
- /**
- * Check if a DOM node is within the editor.
- */
-
- hasDOMNode(
- editor: ReactEditor,
- target: DOMNode,
- options: { editable?: boolean } = {}
- ): boolean {
- const { editable = false } = options
- const el = ReactEditor.toDOMNode(editor, editor)
- let element
-
- // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
- // target is originating from an internal "restricted" element (e.g. a
- // stepper arrow on a number input). (2018/05/04)
- // https://github.com/ianstormtaylor/slate/issues/1819
- try {
- element = isDOMElement(target) ? target : target.parentElement
- } catch (err) {
- if (
- !err.message.includes('Permission denied to access property "nodeType"')
- ) {
- throw err
- }
- }
-
- if (!element) {
- return false
- }
-
- return (
- element.closest(`[data-slate-editor]`) === el &&
- (!editable || el.isContentEditable)
- )
- },
-
- /**
- * Insert data from a `DataTransfer` into the editor.
- */
-
- insertData(editor: ReactEditor, data: DataTransfer): void {
- editor.insertData(data)
- },
-
- /**
- * Find the native DOM element from a Slate node.
- */
-
- toDOMNode(editor: ReactEditor, node: Node): HTMLElement {
- const domNode = Editor.isEditor(node)
- ? EDITOR_TO_ELEMENT.get(editor)
- : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node))
-
- if (!domNode) {
- throw new Error(
- `Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`
- )
- }
-
- return domNode
- },
-
- /**
- * Find a native DOM selection point from a Slate point.
- */
-
- toDOMPoint(editor: ReactEditor, point: Point): DOMPoint {
- const [node] = Editor.node(editor, point.path)
- const el = ReactEditor.toDOMNode(editor, node)
- let domPoint: DOMPoint | undefined
-
- // If we're inside a void node, force the offset to 0, otherwise the zero
- // width spacing character will result in an incorrect offset of 1
- if (Editor.void(editor, { at: point })) {
- point = { path: point.path, offset: 0 }
- }
-
- // For each leaf, we need to isolate its content, which means filtering
- // to its direct text and zero-width spans. (We have to filter out any
- // other siblings that may have been rendered alongside them.)
- const selector = `[data-slate-string], [data-slate-zero-width]`
- const texts = Array.from(el.querySelectorAll(selector))
- let start = 0
-
- for (const text of texts) {
- const domNode = text.childNodes[0] as HTMLElement
-
- if (domNode == null || domNode.textContent == null) {
- continue
- }
-
- const { length } = domNode.textContent
- const attr = text.getAttribute('data-slate-length')
- const trueLength = attr == null ? length : parseInt(attr, 10)
- const end = start + trueLength
-
- if (point.offset <= end) {
- const offset = Math.min(length, Math.max(0, point.offset - start))
- domPoint = [domNode, offset]
- break
- }
-
- start = end
- }
-
- if (!domPoint) {
- throw new Error(
- `Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`
- )
- }
-
- return domPoint
- },
-
- /**
- * Find a native DOM range from a Slate `range`.
- */
-
- toDOMRange(editor: ReactEditor, range: Range): DOMRange {
- const { anchor, focus } = range
- const domAnchor = ReactEditor.toDOMPoint(editor, anchor)
- const domFocus = Range.isCollapsed(range)
- ? domAnchor
- : ReactEditor.toDOMPoint(editor, focus)
-
- const domRange = window.document.createRange()
- const start = Range.isBackward(range) ? domFocus : domAnchor
- const end = Range.isBackward(range) ? domAnchor : domFocus
- domRange.setStart(start[0], start[1])
- domRange.setEnd(end[0], end[1])
- return domRange
- },
-
+export interface ReactEditor extends DOMEditor {
/**
- * Find a Slate node from a native DOM `element`.
+ * Determines the chunk size used by the children chunking optimization. If
+ * null is returned (which is the default), the chunking optimization is
+ * disabled.
*/
+ getChunkSize: (node: Ancestor) => number | null
+}
- toSlateNode(editor: ReactEditor, domNode: DOMNode): Node {
- let domEl = isDOMElement(domNode) ? domNode : domNode.parentElement
-
- if (domEl && !domEl.hasAttribute('data-slate-node')) {
- domEl = domEl.closest(`[data-slate-node]`)
- }
-
- const node = domEl ? ELEMENT_TO_NODE.get(domEl as HTMLElement) : null
-
- if (!node) {
- throw new Error(`Cannot resolve a Slate node from DOM node: ${domEl}`)
- }
-
- return node
- },
-
- /**
- * Get the target range from a DOM `event`.
- */
-
- findEventRange(editor: ReactEditor, event: any): Range {
- if ('nativeEvent' in event) {
- event = event.nativeEvent
- }
-
- const { clientX: x, clientY: y, target } = event
-
- if (x == null || y == null) {
- throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)
- }
-
- const node = ReactEditor.toSlateNode(editor, event.target)
- const path = ReactEditor.findPath(editor, node)
-
- // If the drop target is inside a void node, move it into either the
- // next or previous node, depending on which side the `x` and `y`
- // coordinates are closest to.
- if (Editor.isVoid(editor, node)) {
- const rect = target.getBoundingClientRect()
- const isPrev = editor.isInline(node)
- ? x - rect.left < rect.left + rect.width - x
- : y - rect.top < rect.top + rect.height - y
-
- const edge = Editor.point(editor, path, {
- edge: isPrev ? 'start' : 'end',
- })
- const point = isPrev
- ? Editor.before(editor, edge)
- : Editor.after(editor, edge)
-
- if (point) {
- const range = Editor.range(editor, point)
- return range
- }
- }
-
- // Else resolve a range from the caret position where the drop occured.
- let domRange
- const { document } = window
-
- // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
- if (document.caretRangeFromPoint) {
- domRange = document.caretRangeFromPoint(x, y)
- } else {
- const position = document.caretPositionFromPoint(x, y)
-
- if (position) {
- domRange = document.createRange()
- domRange.setStart(position.offsetNode, position.offset)
- domRange.setEnd(position.offsetNode, position.offset)
- }
- }
-
- if (!domRange) {
- throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)
- }
-
- // Resolve a Slate range from the DOM range.
- const range = ReactEditor.toSlateRange(editor, domRange)
- return range
- },
-
- /**
- * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
- */
-
- toSlatePoint(editor: ReactEditor, domPoint: DOMPoint): Point {
- const [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint)
- const parentNode = nearestNode.parentNode as DOMElement
- let textNode: DOMElement | null = null
- let offset = 0
-
- if (parentNode) {
- const voidNode = parentNode.closest('[data-slate-void="true"]')
- let leafNode = parentNode.closest('[data-slate-leaf]')
- let domNode: DOMElement | null = null
-
- // Calculate how far into the text node the `nearestNode` is, so that we
- // can determine what the offset relative to the text node is.
- if (leafNode) {
- textNode = leafNode.closest('[data-slate-node="text"]')!
- const range = window.document.createRange()
- range.setStart(textNode, 0)
- range.setEnd(nearestNode, nearestOffset)
- const contents = range.cloneContents()
- const removals = [
- ...contents.querySelectorAll('[data-slate-zero-width]'),
- ...contents.querySelectorAll('[contenteditable=false]'),
- ]
-
- removals.forEach(el => {
- el!.parentNode!.removeChild(el)
- })
-
- // COMPAT: Edge has a bug where Range.prototype.toString() will
- // convert \n into \r\n. The bug causes a loop when slate-react
- // attempts to reposition its cursor to match the native position. Use
- // textContent.length instead.
- // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
- offset = contents.textContent!.length
- domNode = textNode
- } else if (voidNode) {
- // For void nodes, the element with the offset key will be a cousin, not an
- // ancestor, so find it by going down from the nearest void parent.
-
- leafNode = voidNode.querySelector('[data-slate-leaf]')!
- textNode = leafNode.closest('[data-slate-node="text"]')!
- domNode = leafNode
- offset = domNode.textContent!.length
- }
-
- // COMPAT: If the parent node is a Slate zero-width space, editor is
- // because the text node should have no characters. However, during IME
- // composition the ASCII characters will be prepended to the zero-width
- // space, so subtract 1 from the offset to account for the zero-width
- // space character.
- if (
- domNode &&
- offset === domNode.textContent!.length &&
- parentNode.hasAttribute('data-slate-zero-width')
- ) {
- offset--
- }
- }
-
- if (!textNode) {
- throw new Error(
- `Cannot resolve a Slate point from DOM point: ${domPoint}`
- )
- }
-
- // COMPAT: If someone is clicking from one Slate editor into another,
- // the select event fires twice, once for the old editor's `element`
- // first, and then afterwards for the correct `element`. (2017/03/03)
- const slateNode = ReactEditor.toSlateNode(editor, textNode!)
- const path = ReactEditor.findPath(editor, slateNode)
- return { path, offset }
- },
-
- /**
- * Find a Slate range from a DOM range or selection.
- */
-
- toSlateRange(
- editor: ReactEditor,
- domRange: DOMRange | DOMStaticRange | DOMSelection
- ): Range {
- const el =
- domRange instanceof Selection
- ? domRange.anchorNode
- : domRange.startContainer
- let anchorNode
- let anchorOffset
- let focusNode
- let focusOffset
- let isCollapsed
-
- if (el) {
- if (domRange instanceof Selection) {
- anchorNode = domRange.anchorNode
- anchorOffset = domRange.anchorOffset
- focusNode = domRange.focusNode
- focusOffset = domRange.focusOffset
- isCollapsed = domRange.isCollapsed
- } else {
- anchorNode = domRange.startContainer
- anchorOffset = domRange.startOffset
- focusNode = domRange.endContainer
- focusOffset = domRange.endOffset
- isCollapsed = domRange.collapsed
- }
- }
-
- if (
- anchorNode == null ||
- focusNode == null ||
- anchorOffset == null ||
- focusOffset == null
- ) {
- throw new Error(
- `Cannot resolve a Slate range from DOM range: ${domRange}`
- )
- }
-
- const anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset])
- const focus = isCollapsed
- ? anchor
- : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset])
+export interface ReactEditorInterface extends DOMEditorInterface {}
- return { anchor, focus }
- },
-}
+// eslint-disable-next-line no-redeclare
+export const ReactEditor: ReactEditorInterface = DOMEditor
diff --git a/src/plugin/with-react.ts b/src/plugin/with-react.ts
index 1441aa6..361930e 100644
--- a/src/plugin/with-react.ts
+++ b/src/plugin/with-react.ts
@@ -1,102 +1,77 @@
import ReactDOM from 'react-dom'
-import { Editor, Node, Path, Operation, Transforms } from 'slate'
-
+import { BaseEditor, Node } from 'slate'
+import { withDOM, IS_ANDROID, EDITOR_TO_PENDING_SELECTION } from 'slate-dom'
import { ReactEditor } from './react-editor'
-import { Key } from '../utils/key'
-import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'
+import { REACT_MAJOR_VERSION } from '../utils/environment'
+import { getChunkTreeForNode } from '../chunking'
/**
* `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.
*/
+export const withReact = (
+ editor: T,
+ clipboardFormatKey = 'x-slate-fragment'
+): T & ReactEditor => {
+ let e = editor as T & ReactEditor
-export const withReact = (editor: T) => {
- const e = editor as T & ReactEditor
- const { apply, onChange } = e
-
- e.apply = (op: Operation) => {
- const matches: [Path, Key][] = []
-
- switch (op.type) {
- case 'insert_text':
- case 'remove_text':
- case 'set_node': {
- for (const [node, path] of Editor.levels(e, { at: op.path })) {
- const key = ReactEditor.findKey(e, node)
- matches.push([path, key])
- }
+ e = withDOM(e, clipboardFormatKey)
- break
- }
+ const { onChange, apply, insertText } = e
- case 'insert_node':
- case 'remove_node':
- case 'merge_node':
- case 'split_node': {
- for (const [node, path] of Editor.levels(e, {
- at: Path.parent(op.path),
- })) {
- const key = ReactEditor.findKey(e, node)
- matches.push([path, key])
- }
+ e.getChunkSize = () => null
- break
- }
+ if (IS_ANDROID) {
+ e.insertText = (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.
+ EDITOR_TO_PENDING_SELECTION.delete(e)
- case 'move_node': {
- // TODO
- break
- }
- }
-
- apply(op)
-
- for (const [path, key] of matches) {
- const [node] = Editor.node(e, path)
- NODE_TO_KEY.set(node, key)
+ return insertText(text, options)
}
}
- e.insertData = (data: DataTransfer) => {
- const fragment = data.getData('application/x-slate-fragment')
-
- if (fragment) {
- const decoded = decodeURIComponent(window.atob(fragment))
- const parsed = JSON.parse(decoded) as Node[]
- Transforms.insertFragment(e, parsed)
- return
- }
-
- const text = data.getData('text/plain')
-
- if (text) {
- const lines = text.split('\n')
- let split = false
-
- for (const line of lines) {
- if (split) {
- Transforms.splitNodes(e)
- }
+ e.onChange = 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
+ const maybeBatchUpdates =
+ REACT_MAJOR_VERSION < 18
+ ? ReactDOM.unstable_batchedUpdates
+ : (callback: () => void) => callback()
- Transforms.insertText(e, line)
- split = true
- }
- }
+ maybeBatchUpdates(() => {
+ onChange(options)
+ })
}
- e.onChange = () => {
- // COMPAT: React 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
- ReactDOM.unstable_batchedUpdates(() => {
- const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
-
- if (onContextChange) {
- onContextChange()
+ // 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 = operation => {
+ if (operation.type === 'move_node') {
+ const parent = Node.parent(e, operation.path)
+ const chunking = !!e.getChunkSize(parent)
+
+ if (chunking) {
+ const node = Node.get(e, operation.path)
+ const chunkTree = getChunkTreeForNode(e, parent)
+ const key = ReactEditor.findKey(e, node)
+ chunkTree.movedNodeKeys.add(key)
}
+ }
- onChange()
- })
+ apply(operation)
}
return e
diff --git a/src/utils/dom.ts b/src/utils/dom.ts
deleted file mode 100644
index 862deb4..0000000
--- a/src/utils/dom.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * Types.
- */
-
-// COMPAT: This is required to prevent TypeScript aliases from doing some very
-// weird things for Slate's types with the same name as globals. (2019/11/27)
-// https://github.com/microsoft/TypeScript/issues/35002
-import DOMNode = globalThis.Node
-import DOMComment = globalThis.Comment
-import DOMElement = globalThis.Element
-import DOMText = globalThis.Text
-import DOMRange = globalThis.Range
-import DOMSelection = globalThis.Selection
-import DOMStaticRange = globalThis.StaticRange
-export {
- DOMNode,
- DOMComment,
- DOMElement,
- DOMText,
- DOMRange,
- DOMSelection,
- DOMStaticRange,
-}
-
-export type DOMPoint = [Node, number]
-
-/**
- * Check if a DOM node is a comment node.
- */
-
-export const isDOMComment = (value: any): value is DOMComment => {
- return isDOMNode(value) && value.nodeType === 8
-}
-
-/**
- * Check if a DOM node is an element node.
- */
-
-export const isDOMElement = (value: any): value is DOMElement => {
- return isDOMNode(value) && value.nodeType === 1
-}
-
-/**
- * Check if a value is a DOM node.
- */
-
-export const isDOMNode = (value: any): value is DOMNode => {
- return value instanceof Node
-}
-
-/**
- * Check if a DOM node is an element node.
- */
-
-export const isDOMText = (value: any): value is DOMText => {
- return isDOMNode(value) && value.nodeType === 3
-}
-
-/**
- * Normalize a DOM point so that it always refers to a text node.
- */
-
-export const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => {
- let [node, offset] = domPoint
-
- // If it's an element node, its offset refers to the index of its children
- // including comment nodes, so try to find the right text child node.
- if (isDOMElement(node) && node.childNodes.length) {
- const isLast = offset === node.childNodes.length
- const direction = isLast ? 'backward' : 'forward'
- const index = isLast ? offset - 1 : offset
- node = getEditableChild(node, index, direction)
-
- // If the node has children, traverse until we have a leaf node. Leaf nodes
- // can be either text nodes, or other void DOM nodes.
- while (isDOMElement(node) && node.childNodes.length) {
- const i = isLast ? node.childNodes.length - 1 : 0
- node = getEditableChild(node, i, direction)
- }
-
- // Determine the new offset inside the text node.
- offset = isLast && node.textContent != null ? node.textContent.length : 0
- }
-
- // Return the node and offset.
- return [node, offset]
-}
-
-/**
- * Get the nearest editable child at `index` in a `parent`, preferring
- * `direction`.
- */
-
-export const getEditableChild = (
- parent: DOMElement,
- index: number,
- direction: 'forward' | 'backward'
-): DOMNode => {
- const { childNodes } = parent
- let child = childNodes[index]
- let i = index
- let triedForward = false
- let triedBackward = false
-
- // While the child is a comment node, or an element node with no children,
- // keep iterating to find a sibling non-void, non-comment node.
- while (
- isDOMComment(child) ||
- (isDOMElement(child) && child.childNodes.length === 0) ||
- (isDOMElement(child) && child.getAttribute('contenteditable') === 'false')
- ) {
- if (triedForward && triedBackward) {
- break
- }
-
- if (i >= childNodes.length) {
- triedForward = true
- i = index - 1
- direction = 'backward'
- continue
- }
-
- if (i < 0) {
- triedBackward = true
- i = index + 1
- direction = 'forward'
- continue
- }
-
- child = childNodes[i]
- i += direction === 'forward' ? 1 : -1
- }
-
- return child
-}
diff --git a/src/utils/environment.ts b/src/utils/environment.ts
index 95d096c..6b070a6 100644
--- a/src/utils/environment.ts
+++ b/src/utils/environment.ts
@@ -1,16 +1,3 @@
-export const IS_IOS =
- typeof navigator !== 'undefined' &&
- typeof window !== 'undefined' &&
- /iPad|iPhone|iPod/.test(navigator.userAgent) &&
- !window.MSStream
+import React from 'react'
-export const IS_APPLE =
- typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)
-
-export const IS_FIREFOX =
- typeof navigator !== 'undefined' &&
- /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)
-
-export const IS_SAFARI =
- typeof navigator !== 'undefined' &&
- /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
+export const REACT_MAJOR_VERSION = parseInt(React.version.split('.')[0], 10)
diff --git a/src/utils/hotkeys.ts b/src/utils/hotkeys.ts
deleted file mode 100644
index 33937d4..0000000
--- a/src/utils/hotkeys.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { isKeyHotkey } from 'is-hotkey'
-import { IS_APPLE } from './environment'
-
-/**
- * Hotkey mappings for each platform.
- */
-
-const HOTKEYS = {
- bold: 'mod+b',
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
- moveBackward: 'left',
- moveForward: 'right',
- moveWordBackward: 'ctrl+left',
- moveWordForward: 'ctrl+right',
- deleteBackward: 'shift?+backspace',
- deleteForward: 'shift?+delete',
- extendBackward: 'shift+left',
- extendForward: 'shift+right',
- italic: 'mod+i',
- splitBlock: 'shift?+enter',
- undo: 'mod+z',
-}
-
-const APPLE_HOTKEYS = {
- moveLineBackward: 'opt+up',
- moveLineForward: 'opt+down',
- moveWordBackward: 'opt+left',
- moveWordForward: 'opt+right',
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
- deleteForward: ['ctrl+delete', 'ctrl+d'],
- deleteLineBackward: 'cmd+shift?+backspace',
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
- deleteWordBackward: 'opt+shift?+backspace',
- deleteWordForward: 'opt+shift?+delete',
- extendLineBackward: 'opt+shift+up',
- extendLineForward: 'opt+shift+down',
- redo: 'cmd+shift+z',
- transposeCharacter: 'ctrl+t',
-}
-
-const WINDOWS_HOTKEYS = {
- deleteWordBackward: 'ctrl+shift?+backspace',
- deleteWordForward: 'ctrl+shift?+delete',
- redo: ['ctrl+y', 'ctrl+shift+z'],
-}
-
-/**
- * Create a platform-aware hotkey checker.
- */
-
-const create = (key: string) => {
- const generic = HOTKEYS[key]
- const apple = APPLE_HOTKEYS[key]
- const windows = WINDOWS_HOTKEYS[key]
- const isGeneric = generic && isKeyHotkey(generic)
- const isApple = apple && isKeyHotkey(apple)
- const isWindows = windows && isKeyHotkey(windows)
-
- return (event: KeyboardEvent) => {
- if (isGeneric && isGeneric(event)) return true
- if (IS_APPLE && isApple && isApple(event)) return true
- if (!IS_APPLE && isWindows && isWindows(event)) return true
- return false
- }
-}
-
-/**
- * Hotkeys.
- */
-
-export default {
- isBold: create('bold'),
- isCompose: create('compose'),
- isMoveBackward: create('moveBackward'),
- isMoveForward: create('moveForward'),
- isDeleteBackward: create('deleteBackward'),
- isDeleteForward: create('deleteForward'),
- isDeleteLineBackward: create('deleteLineBackward'),
- isDeleteLineForward: create('deleteLineForward'),
- isDeleteWordBackward: create('deleteWordBackward'),
- isDeleteWordForward: create('deleteWordForward'),
- isExtendBackward: create('extendBackward'),
- isExtendForward: create('extendForward'),
- isExtendLineBackward: create('extendLineBackward'),
- isExtendLineForward: create('extendLineForward'),
- isItalic: create('italic'),
- isMoveLineBackward: create('moveLineBackward'),
- isMoveLineForward: create('moveLineForward'),
- isMoveWordBackward: create('moveWordBackward'),
- isMoveWordForward: create('moveWordForward'),
- isRedo: create('redo'),
- isSplitBlock: create('splitBlock'),
- isTransposeCharacter: create('transposeCharacter'),
- isUndo: create('undo'),
-}
diff --git a/src/utils/key.ts b/src/utils/key.ts
deleted file mode 100644
index 0c87733..0000000
--- a/src/utils/key.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * An auto-incrementing identifier for keys.
- */
-
-let n = 0
-
-/**
- * A class that keeps track of a key string. We use a full class here because we
- * want to be able to use them as keys in `WeakMap` objects.
- */
-
-export class Key {
- id: string
-
- constructor() {
- this.id = `${n++}`
- }
-}
diff --git a/src/utils/weak-maps.ts b/src/utils/weak-maps.ts
deleted file mode 100644
index 05f4ec9..0000000
--- a/src/utils/weak-maps.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Node, Ancestor, Editor, Range } from 'slate'
-
-import { Key } from './key'
-
-/**
- * Two weak maps that allow us rebuild a path given a node. They are populated
- * at render time such that after a render occurs we can always backtrack.
- */
-
-export const NODE_TO_INDEX: WeakMap = new WeakMap()
-export const NODE_TO_PARENT: WeakMap = new WeakMap()
-
-/**
- * Weak maps that allow us to go between Slate nodes and DOM nodes. These
- * are used to resolve DOM event-related logic into Slate actions.
- */
-
-export const EDITOR_TO_ELEMENT: WeakMap = new WeakMap()
-export const EDITOR_TO_PLACEHOLDER: WeakMap = new WeakMap()
-export const ELEMENT_TO_NODE: WeakMap = new WeakMap()
-export const KEY_TO_ELEMENT: WeakMap = new WeakMap()
-export const NODE_TO_ELEMENT: WeakMap = new WeakMap()
-export const NODE_TO_KEY: WeakMap = new WeakMap()
-
-/**
- * Weak maps for storing editor-related state.
- */
-
-export const IS_READ_ONLY: WeakMap = new WeakMap()
-export const IS_FOCUSED: WeakMap = new WeakMap()
-export const IS_DRAGGING: WeakMap = new WeakMap()
-export const IS_CLICKING: WeakMap = new WeakMap()
-
-/**
- * Weak map for associating the context `onChange` context with the plugin.
- */
-
-export const EDITOR_TO_ON_CHANGE = new WeakMap void>()
-
-/**
- * Symbols.
- */
-
-export const PLACEHOLDER_SYMBOL = (Symbol('placeholder') as unknown) as string
diff --git a/test/chunking.spec.ts b/test/chunking.spec.ts
new file mode 100644
index 0000000..c8c2ed1
--- /dev/null
+++ b/test/chunking.spec.ts
@@ -0,0 +1,953 @@
+import {
+ Descendant,
+ Editor,
+ Element,
+ Node,
+ Transforms,
+ createEditor,
+} from 'slate'
+import { Key } from 'slate-dom'
+import { ReactEditor, withReact } from '../src'
+import {
+ Chunk,
+ ChunkAncestor,
+ ChunkDescendant,
+ ChunkLeaf,
+ ChunkNode,
+ ChunkTree,
+ KEY_TO_CHUNK_TREE,
+ getChunkTreeForNode,
+} from '../src/chunking'
+import { ReconcileOptions } from '../src/chunking/reconcile-children'
+
+const block = (text: string): Element => ({ children: [{ text }] })
+
+const blocks = (count: number) =>
+ Array.from(
+ {
+ length: count,
+ },
+ (_, i) => block(i.toString())
+ )
+
+const reconcileEditor = (
+ editor: ReactEditor,
+ options: Omit = {}
+) =>
+ getChunkTreeForNode(editor, editor, {
+ reconcile: {
+ chunkSize: 3,
+ debug: true,
+ ...options,
+ },
+ })
+
+type TreeShape = string | TreeShape[]
+
+const getTreeShape = (chunkNode: ChunkNode): TreeShape => {
+ if (chunkNode.type === 'leaf') {
+ return Node.string(chunkNode.node)
+ }
+
+ return chunkNode.children.map(getTreeShape)
+}
+
+const getChildrenAndTreeForShape = (
+ editor: ReactEditor,
+ treeShape: TreeShape[]
+): { children: Descendant[]; chunkTree: ChunkTree } => {
+ const children: Descendant[] = []
+
+ const shapeToNode = (
+ ts: TreeShape,
+ parent: ChunkAncestor
+ ): ChunkDescendant => {
+ if (Array.isArray(ts)) {
+ const chunk: Chunk = {
+ type: 'chunk',
+ key: new Key(),
+ parent,
+ children: [],
+ }
+
+ chunk.children = ts.map(child => shapeToNode(child, chunk))
+
+ return chunk
+ }
+
+ const node = block(ts)
+ const index = children.length
+ children.push(node)
+
+ return {
+ type: 'leaf',
+ key: ReactEditor.findKey(editor, node),
+ node,
+ index,
+ }
+ }
+
+ const chunkTree: ChunkTree = {
+ type: 'root',
+ modifiedChunks: new Set(),
+ movedNodeKeys: new Set(),
+ children: [],
+ }
+
+ chunkTree.children = treeShape.map(child => shapeToNode(child, chunkTree))
+
+ return { children, chunkTree }
+}
+
+const withChunking = (editor: ReactEditor) => {
+ editor.getChunkSize = node => (Editor.isEditor(node) ? 3 : null)
+ return editor
+}
+
+const createEditorWithShape = (treeShape: TreeShape[]) => {
+ const editor = withChunking(withReact(createEditor()))
+ const { children, chunkTree } = getChildrenAndTreeForShape(editor, treeShape)
+ editor.children = children
+ const key = ReactEditor.findKey(editor, editor)
+ KEY_TO_CHUNK_TREE.set(key, chunkTree)
+ return editor
+}
+
+// https://stackoverflow.com/a/29450606
+const createPRNG = (seed: number) => {
+ const mask = 0xffffffff
+ let m_w = (123456789 + seed) & mask
+ let m_z = (987654321 - seed) & mask
+
+ return () => {
+ m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask
+ m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask
+
+ let result = ((m_z << 16) + (m_w & 65535)) >>> 0
+ result /= 4294967296
+ return result
+ }
+}
+
+describe('getChunkTreeForNode', () => {
+ describe('chunking initial value', () => {
+ const getShapeForInitialCount = (count: number) => {
+ const editor = withChunking(withReact(createEditor()))
+ editor.children = blocks(count)
+ const chunkTree = reconcileEditor(editor)
+ return getTreeShape(chunkTree)
+ }
+
+ it('returns empty tree for 0 children', () => {
+ expect(getShapeForInitialCount(0)).toEqual([])
+ })
+
+ it('returns flat tree for 1 child', () => {
+ expect(getShapeForInitialCount(1)).toEqual(['0'])
+ })
+
+ it('returns flat tree for 3 children', () => {
+ expect(getShapeForInitialCount(3)).toEqual(['0', '1', '2'])
+ })
+
+ it('returns 1 layer of chunking for 4 children', () => {
+ expect(getShapeForInitialCount(4)).toEqual([['0', '1', '2'], ['3']])
+ })
+
+ it('returns 1 layer of chunking for 9 children', () => {
+ expect(getShapeForInitialCount(9)).toEqual([
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ])
+ })
+
+ it('returns 2 layers of chunking for 10 children', () => {
+ expect(getShapeForInitialCount(10)).toEqual([
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [['9']],
+ ])
+ })
+
+ it('returns 2 layers of chunking for 27 children', () => {
+ expect(getShapeForInitialCount(27)).toEqual([
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ],
+ [
+ ['18', '19', '20'],
+ ['21', '22', '23'],
+ ['24', '25', '26'],
+ ],
+ ])
+ })
+
+ it('returns 3 layers of chunking for 28 children', () => {
+ expect(getShapeForInitialCount(28)).toEqual([
+ [
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ],
+ [
+ ['18', '19', '20'],
+ ['21', '22', '23'],
+ ['24', '25', '26'],
+ ],
+ ],
+ [[['27']]],
+ ])
+ })
+
+ it('calls onInsert for initial children', () => {
+ const editor = withChunking(withReact(createEditor()))
+ editor.children = blocks(3)
+
+ const onInsert = jest.fn()
+ reconcileEditor(editor, { onInsert })
+
+ expect(onInsert.mock.calls).toEqual([
+ [editor.children[0], 0],
+ [editor.children[1], 1],
+ [editor.children[2], 2],
+ ])
+ })
+
+ it('sets the index of each chunk leaf', () => {
+ const editor = withChunking(withReact(createEditor()))
+ editor.children = blocks(9)
+
+ const chunkTree = reconcileEditor(editor)
+ const chunks = chunkTree.children as Chunk[]
+ const leaves = chunks.map(chunk => chunk.children)
+
+ expect(leaves).toMatchObject([
+ [{ index: 0 }, { index: 1 }, { index: 2 }],
+ [{ index: 3 }, { index: 4 }, { index: 5 }],
+ [{ index: 6 }, { index: 7 }, { index: 8 }],
+ ])
+ })
+ })
+
+ describe('inserting nodes', () => {
+ describe('in empty editor', () => {
+ it('inserts a single node', () => {
+ const editor = createEditorWithShape([])
+ Transforms.insertNodes(editor, block('x'), { at: [0] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['x'])
+ })
+
+ it('inserts 27 nodes with 2 layers of chunking', () => {
+ const editor = createEditorWithShape([])
+ Transforms.insertNodes(editor, blocks(27), { at: [0] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ],
+ [
+ ['18', '19', '20'],
+ ['21', '22', '23'],
+ ['24', '25', '26'],
+ ],
+ ])
+ })
+
+ it('inserts 28 nodes with 3 layers of chunking', () => {
+ const editor = createEditorWithShape([])
+ Transforms.insertNodes(editor, blocks(28), { at: [0] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ [
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ],
+ [
+ ['18', '19', '20'],
+ ['21', '22', '23'],
+ ['24', '25', '26'],
+ ],
+ ],
+ [[['27']]],
+ ])
+ })
+
+ it('inserts nodes one by one', () => {
+ const editor = createEditorWithShape([])
+ let chunkTree: ChunkTree
+
+ blocks(31).forEach((node, i) => {
+ Transforms.insertNodes(editor, node, { at: [i] })
+ chunkTree = reconcileEditor(editor)
+ })
+
+ expect(getTreeShape(chunkTree!)).toEqual([
+ '0',
+ '1',
+ '2',
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ['18', '19', '20'],
+ [
+ ['21', '22', '23'],
+ ['24', '25', '26'],
+ ['27', '28', '29'],
+ ],
+ [['30']],
+ ])
+ })
+
+ it('inserts nodes one by one in reverse order', () => {
+ const editor = createEditorWithShape([])
+ let chunkTree: ChunkTree
+
+ blocks(31)
+ .reverse()
+ .forEach(node => {
+ Transforms.insertNodes(editor, node, { at: [0] })
+ chunkTree = reconcileEditor(editor)
+ })
+
+ expect(getTreeShape(chunkTree!)).toEqual([
+ [['0']],
+ [
+ ['1', '2', '3'],
+ ['4', '5', '6'],
+ ['7', '8', '9'],
+ ],
+ ['10', '11', '12'],
+ ['13', '14', '15'],
+ ['16', '17', '18'],
+ ['19', '20', '21'],
+ ['22', '23', '24'],
+ ['25', '26', '27'],
+ '28',
+ '29',
+ '30',
+ ])
+ })
+ })
+
+ describe('at end of editor', () => {
+ it('inserts a single node at the top level', () => {
+ const editor = createEditorWithShape(['0', ['1', '2', ['3', '4', '5']]])
+ Transforms.insertNodes(editor, block('x'), { at: [6] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ '0',
+ ['1', '2', ['3', '4', '5']],
+ [['x']],
+ ])
+ })
+
+ it('inserts a single node into a chunk', () => {
+ const editor = createEditorWithShape(['0', ['1', ['2', '3', '4']]])
+ Transforms.insertNodes(editor, block('x'), { at: [5] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ '0',
+ ['1', ['2', '3', '4'], ['x']],
+ ])
+ })
+
+ it('inserts a single node into a nested chunk', () => {
+ const editor = createEditorWithShape(['0', ['1', '2', ['3', '4']]])
+ Transforms.insertNodes(editor, block('x'), { at: [5] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ '0',
+ ['1', '2', ['3', '4', 'x']],
+ ])
+ })
+
+ it('inserts 25 nodes after 2 nodes with 2 layers of chunking', () => {
+ const editor = createEditorWithShape(['a', 'b'])
+ Transforms.insertNodes(editor, blocks(25), { at: [2] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ 'a',
+ 'b',
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ],
+ [['18', '19', '20'], ['21', '22', '23'], ['24']],
+ ])
+ })
+
+ it('inserts 25 nodes after 3 nodes with 3 layers of chunking', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c'])
+ Transforms.insertNodes(editor, blocks(25), { at: [3] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ 'a',
+ 'b',
+ 'c',
+ [
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [
+ ['9', '10', '11'],
+ ['12', '13', '14'],
+ ['15', '16', '17'],
+ ],
+ [['18', '19', '20'], ['21', '22', '23'], ['24']],
+ ],
+ ])
+ })
+
+ it('inserts many nodes at the ends of multiple nested chunks', () => {
+ const editor = createEditorWithShape(['a', ['b', ['c']]])
+ Transforms.insertNodes(editor, blocks(12), { at: [3] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ 'a',
+ ['b', ['c', '0', '1'], ['2']],
+ [
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ['9', '10', '11'],
+ ],
+ ])
+ })
+
+ it('calls onInsert for inserted nodes', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c'])
+ Transforms.insertNodes(editor, blocks(2), { at: [3] })
+
+ const onInsert = jest.fn()
+ reconcileEditor(editor, { onInsert })
+
+ expect(onInsert.mock.calls).toEqual([
+ [editor.children[3], 3],
+ [editor.children[4], 4],
+ ])
+ })
+
+ it('sets the index of inserted leaves', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c'])
+ Transforms.insertNodes(editor, blocks(2), { at: [3] })
+
+ const chunkTree = reconcileEditor(editor)
+ const chunk = chunkTree.children[3] as Chunk
+
+ expect(chunk.children).toMatchObject([{ index: 3 }, { index: 4 }])
+ })
+ })
+
+ describe('at start of editor', () => {
+ it('inserts a single node at the top level', () => {
+ const editor = createEditorWithShape(['0', '1'])
+ Transforms.insertNodes(editor, block('x'), { at: [0] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['x', '0', '1'])
+ })
+
+ it('inserts many nodes at the starts of multiple nested chunks', () => {
+ const editor = createEditorWithShape([[['a'], 'b'], 'c'])
+ Transforms.insertNodes(editor, blocks(12), { at: [0] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7', '8'],
+ ],
+ [['9'], ['10', '11', 'a'], 'b'],
+ 'c',
+ ])
+ })
+ })
+
+ describe('in the middle of editor', () => {
+ describe('at the top level', () => {
+ it('inserts a single node', () => {
+ const editor = createEditorWithShape(['0', '1'])
+ Transforms.insertNodes(editor, block('x'), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual(['0', 'x', '1'])
+ })
+
+ it('inserts nodes at the start of subsequent sibling chunks', () => {
+ const editor = createEditorWithShape(['a', [['b', 'c'], 'd'], 'e'])
+ Transforms.insertNodes(editor, blocks(3), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ 'a',
+ [['0']],
+ [['1'], ['2', 'b', 'c'], 'd'],
+ 'e',
+ ])
+ })
+
+ it('calls onInsert for inserted nodes', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c'])
+ Transforms.insertNodes(editor, blocks(2), { at: [1] })
+
+ const onInsert = jest.fn()
+ reconcileEditor(editor, { onInsert })
+
+ expect(onInsert.mock.calls).toEqual([
+ [editor.children[1], 1],
+ [editor.children[2], 2],
+ ])
+ })
+
+ it('calls onIndexChange for subsequent nodes', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c'])
+ Transforms.insertNodes(editor, blocks(2), { at: [1] })
+
+ const onIndexChange = jest.fn()
+ reconcileEditor(editor, { onIndexChange })
+
+ expect(onIndexChange.mock.calls).toEqual([
+ [editor.children[3], 3],
+ [editor.children[4], 4],
+ ])
+ })
+
+ it('updates the index of subsequent leaves', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c'])
+ Transforms.insertNodes(editor, blocks(3), { at: [1] })
+
+ const chunkTree = reconcileEditor(editor)
+ const subsequentLeaves = chunkTree.children.slice(2)
+
+ expect(subsequentLeaves).toMatchObject([{ index: 4 }, { index: 5 }])
+ })
+ })
+
+ describe('in the middle of a chunk', () => {
+ it('inserts a single node', () => {
+ const editor = createEditorWithShape([[['0', '1']]])
+ Transforms.insertNodes(editor, block('x'), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual([[['0', 'x', '1']]])
+ })
+
+ it('inserts 8 nodes between 2 nodes', () => {
+ const editor = createEditorWithShape([[['a', 'b']]])
+ Transforms.insertNodes(editor, blocks(8), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ [
+ [
+ 'a',
+ [
+ ['0', '1', '2'],
+ ['3', '4', '5'],
+ ['6', '7'],
+ ],
+ 'b',
+ ],
+ ],
+ ])
+ })
+
+ it('inserts nodes at the start of subsequent sibling chunks', () => {
+ const editor = createEditorWithShape([['a', [['b', 'c'], 'd'], 'e']])
+ Transforms.insertNodes(editor, blocks(3), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ ['a', [['0']], [['1'], ['2', 'b', 'c'], 'd'], 'e'],
+ ])
+ })
+ })
+
+ describe('at the end of a chunk', () => {
+ it('inserts 2 nodes in 2 adjacent shallow chunks', () => {
+ const editor = createEditorWithShape([['a', 'b'], ['c']])
+ Transforms.insertNodes(editor, blocks(2), { at: [2] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ ['a', 'b', '0'],
+ ['1', 'c'],
+ ])
+ })
+
+ it('inserts nodes in many adjacent nested chunks', () => {
+ const editor = createEditorWithShape([
+ [
+ ['a', ['b', ['c']]],
+ [[['d'], 'e'], 'f'],
+ ],
+ ])
+
+ Transforms.insertNodes(editor, blocks(17), { at: [3] })
+ const chunkTree = reconcileEditor(editor)
+
+ expect(getTreeShape(chunkTree)).toEqual([
+ [
+ ['a', ['b', ['c', '0', '1'], ['2']], [['3']]],
+ [
+ [
+ ['4', '5', '6'],
+ ['7', '8', '9'],
+ ['10', '11', '12'],
+ ],
+ ],
+ [[['13']], [['14'], ['15', '16', 'd'], 'e'], 'f'],
+ ],
+ ])
+ })
+ })
+ })
+ })
+
+ describe('removing nodes', () => {
+ it('removes a node', () => {
+ const editor = createEditorWithShape(['0', [['1']], '2'])
+ Transforms.removeNodes(editor, { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', '2'])
+ })
+
+ it('removes multiple consecutive nodes', () => {
+ const editor = createEditorWithShape(['0', ['1', '2', '3'], '4'])
+ Transforms.removeNodes(editor, { at: [3] })
+ Transforms.removeNodes(editor, { at: [2] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', ['1'], '4'])
+ })
+
+ it('removes multiple non-consecutive nodes', () => {
+ const editor = createEditorWithShape(['0', ['1', '2', '3'], '4'])
+ Transforms.removeNodes(editor, { at: [3] })
+ Transforms.removeNodes(editor, { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', ['2'], '4'])
+ })
+
+ it('calls onIndexChange for subsequent nodes', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c', 'd'])
+ Transforms.removeNodes(editor, { at: [1] })
+
+ const onIndexChange = jest.fn()
+ reconcileEditor(editor, { onIndexChange })
+
+ expect(onIndexChange.mock.calls).toEqual([
+ [editor.children[1], 1],
+ [editor.children[2], 2],
+ ])
+ })
+
+ it('updates the index of subsequent leaves', () => {
+ const editor = createEditorWithShape(['a', 'b', 'c', 'd'])
+ Transforms.removeNodes(editor, { at: [1] })
+
+ const chunkTree = reconcileEditor(editor)
+ const subsequentLeaves = chunkTree.children.slice(1)
+
+ expect(subsequentLeaves).toMatchObject([{ index: 1 }, { index: 2 }])
+ })
+ })
+
+ describe('removing and inserting nodes', () => {
+ it('removes and inserts a node from the start', () => {
+ const editor = createEditorWithShape(['0', [['1']], '2'])
+ Transforms.removeNodes(editor, { at: [0] })
+ Transforms.insertNodes(editor, block('x'), { at: [0] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual([[['x', '1']], '2'])
+ })
+
+ it('removes and inserts a node from the middle', () => {
+ const editor = createEditorWithShape(['0', [['1']], '2'])
+ Transforms.removeNodes(editor, { at: [1] })
+ Transforms.insertNodes(editor, block('x'), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', 'x', '2'])
+ })
+
+ it('removes and inserts a node from the end', () => {
+ const editor = createEditorWithShape(['0', [['1']], '2'])
+ Transforms.removeNodes(editor, { at: [2] })
+ Transforms.insertNodes(editor, block('x'), { at: [2] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', [['1', 'x']]])
+ })
+
+ it('removes 2 nodes and inserts 1 node', () => {
+ const editor = createEditorWithShape(['0', ['1', '2'], '2'])
+ Transforms.removeNodes(editor, { at: [2] })
+ Transforms.removeNodes(editor, { at: [1] })
+ Transforms.insertNodes(editor, block('x'), { at: [1] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', 'x', '2'])
+ })
+
+ it('removes 1 nodes and inserts 2 node', () => {
+ const editor = createEditorWithShape(['0', ['1'], '2'])
+ Transforms.removeNodes(editor, { at: [1] })
+ Transforms.insertNodes(editor, block('x'), { at: [1] })
+ Transforms.insertNodes(editor, block('y'), { at: [2] })
+ const chunkTree = reconcileEditor(editor)
+ expect(getTreeShape(chunkTree)).toEqual(['0', ['x', 'y'], '2'])
+ })
+
+ it('calls onIndexChange for nodes until insertions equal removals', () => {
+ const editor = createEditorWithShape([
+ 'a',
+ // Insert 2 here
+ 'b',
+ 'c',
+ 'd', // Remove
+ 'e',
+ 'f',
+ 'g', // Remove
+ 'h',
+ ])
+
+ Transforms.removeNodes(editor, { at: [6] })
+ Transforms.removeNodes(editor, { at: [3] })
+ Transforms.insertNodes(editor, blocks(2), { at: [1] })
+
+ const onIndexChange = jest.fn()
+ reconcileEditor(editor, { onIndexChange })
+
+ expect(onIndexChange.mock.calls).toEqual([
+ [editor.children[3], 3],
+ [editor.children[4], 4],
+ [editor.children[5], 5],
+ [editor.children[6], 6],
+ ])
+ })
+ })
+
+ describe('updating nodes', () => {
+ it('replaces updated Slate nodes in the chunk tree', () => {
+ const editor = createEditorWithShape(['0', ['1'], '2'])
+ Transforms.setNodes(editor, { updated: true } as any, { at: [1] })
+
+ const chunkTree = reconcileEditor(editor)
+ const chunk = chunkTree.children[1] as Chunk
+ const leaf = chunk.children[0] as ChunkLeaf
+
+ expect(leaf.node).toMatchObject({ updated: true })
+ })
+
+ it('invalidates ancestor chunks of updated Slate nodes', () => {
+ const editor = createEditorWithShape(['0', [['1']], '2'])
+ Transforms.insertText(editor, 'x', { at: [1, 0] })
+
+ const chunkTree = reconcileEditor(editor)
+ const outerChunk = chunkTree.children[1] as Chunk
+ const innerChunk = outerChunk.children[0]
+
+ expect(getTreeShape(chunkTree)).toEqual(['0', [['x']], '2'])
+
+ expect(chunkTree.modifiedChunks).toEqual(
+ new Set([outerChunk, innerChunk])
+ )
+ })
+
+ it('calls onUpdate for updated Slate nodes', () => {
+ const editor = createEditorWithShape(['0', '1', '2', '3'])
+ Transforms.setNodes(editor, { updated: true } as any, { at: [1] })
+ Transforms.setNodes(editor, { updated: true } as any, { at: [2] })
+
+ const onUpdate = jest.fn()
+ reconcileEditor(editor, { onUpdate })
+
+ expect(onUpdate.mock.calls).toEqual([
+ [editor.children[1], 1],
+ [editor.children[2], 2],
+ ])
+ })
+ })
+
+ describe('moving nodes', () => {
+ it('moves a node down', () => {
+ const editor = createEditorWithShape([['0'], ['1'], ['2'], ['3'], ['4']])
+
+ // Move 1 to after 3
+ Transforms.moveNodes(editor, { at: [1], to: [3] })
+
+ const onInsert = jest.fn()
+ const onIndexChange = jest.fn()
+ const chunkTree = reconcileEditor(editor, { onInsert, onIndexChange })
+
+ expect(getTreeShape(chunkTree)).toEqual([['0'], ['2'], ['3', '1'], ['4']])
+
+ expect(onInsert.mock.calls).toEqual([[editor.children[3], 3]])
+
+ expect(onIndexChange.mock.calls).toEqual([
+ [editor.children[1], 1],
+ [editor.children[2], 2],
+ ])
+
+ expect(chunkTree.movedNodeKeys.size).toBe(0)
+ })
+
+ it('moves a node up', () => {
+ const editor = createEditorWithShape([['0'], ['1'], ['2'], ['3'], ['4']])
+
+ // Move 3 to after 0
+ Transforms.moveNodes(editor, { at: [3], to: [1] })
+
+ const onInsert = jest.fn()
+ const onIndexChange = jest.fn()
+ const chunkTree = reconcileEditor(editor, { onInsert, onIndexChange })
+
+ expect(getTreeShape(chunkTree)).toEqual([['0', '3'], ['1'], ['2'], ['4']])
+
+ expect(onInsert.mock.calls).toEqual([[editor.children[1], 1]])
+
+ expect(onIndexChange.mock.calls).toEqual([
+ [editor.children[2], 2],
+ [editor.children[3], 3],
+ ])
+
+ expect(chunkTree.movedNodeKeys.size).toBe(0)
+ })
+ })
+
+ describe('manual rerendering', () => {
+ it('invalidates specific child indices', () => {
+ const editor = createEditorWithShape([
+ ['0'],
+ ['1', ['2'], '3'],
+ ['4'],
+ '5',
+ ])
+
+ reconcileEditor(editor)
+
+ const chunkTree = reconcileEditor(editor, { rerenderChildren: [2, 4] })
+ const twoOuterChunk = chunkTree.children[1] as Chunk
+ const twoInnerChunk = twoOuterChunk.children[1]
+ const fourChunk = chunkTree.children[2]
+
+ expect(chunkTree.modifiedChunks).toEqual(
+ new Set([twoOuterChunk, twoInnerChunk, fourChunk])
+ )
+ })
+ })
+
+ describe('random testing', () => {
+ it('remains correct after random operations', () => {
+ // Hard code a value here to reproduce a test failure
+ const seed = Math.floor(10000000 * Math.random())
+ const random = createPRNG(seed)
+
+ const duration = 250
+ const startTime = performance.now()
+ const endTime = startTime + duration
+ let iteration = 0
+
+ try {
+ while (performance.now() < endTime) {
+ iteration++
+
+ const editor = withChunking(withReact(createEditor()))
+
+ const randomPosition = (includeEnd: boolean) =>
+ Math.floor(
+ random() * (editor.children.length + (includeEnd ? 1 : 0))
+ )
+
+ for (let i = 0; i < 30; i++) {
+ const randomValue = random()
+
+ if (randomValue < 0.33) {
+ reconcileEditor(editor)
+ } else if (randomValue < 0.66) {
+ Transforms.insertNodes(editor, block(i.toString()), {
+ at: [randomPosition(true)],
+ })
+ } else if (randomValue < 0.8) {
+ if (editor.children.length > 0) {
+ Transforms.removeNodes(editor, { at: [randomPosition(false)] })
+ }
+ } else {
+ if (editor.children.length > 0) {
+ Transforms.setNodes(editor, { updated: i } as any, {
+ at: [randomPosition(false)],
+ })
+ }
+ }
+ }
+
+ const chunkTree = reconcileEditor(editor)
+ const chunkTreeSlateNodes: Descendant[] = []
+
+ const flattenTree = (node: ChunkNode) => {
+ if (node.type === 'leaf') {
+ chunkTreeSlateNodes.push(node.node)
+ } else {
+ node.children.forEach(flattenTree)
+ }
+ }
+
+ flattenTree(chunkTree)
+
+ expect(chunkTreeSlateNodes).toEqual(editor.children)
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `Random testing encountered an error or test failure on iteration ${iteration}. To reproduce this failure reliably, use the random seed: ${seed}`
+ )
+ throw e
+ }
+ })
+ })
+})
diff --git a/test/decorations.spec.tsx b/test/decorations.spec.tsx
new file mode 100644
index 0000000..c68ecc4
--- /dev/null
+++ b/test/decorations.spec.tsx
@@ -0,0 +1,672 @@
+import React from 'react'
+import {
+ DecoratedRange,
+ Node,
+ NodeEntry,
+ Path,
+ createEditor as slateCreateEditor,
+ Editor,
+ Text,
+ Transforms,
+} from 'slate'
+import { act, render } from '@testing-library/react'
+import {
+ Slate,
+ withReact,
+ Editable,
+ RenderLeafProps,
+ ReactEditor,
+} from '../src'
+
+const renderLeaf = ({ leaf, attributes, children }: RenderLeafProps) => {
+ const decorations = Object.keys(Node.extractProps(leaf)).sort()
+
+ return (
+
+ {children}
+
+ )
+}
+
+interface DecorateConfig {
+ path: Path
+ decorations: (node: Node) => (DecoratedRange & Record)[]
+}
+
+const decoratePaths =
+ (editor: ReactEditor, configs: DecorateConfig[]) =>
+ ([node, path]: NodeEntry): DecoratedRange[] => {
+ // Validate that decorate was called with a node matching the path
+ if (Node.get(editor, path) !== node) {
+ throw new Error('decorate was called with an incorrect node entry')
+ }
+
+ const matchingConfig = configs.find(({ path: p }) => Path.equals(path, p))
+ if (!matchingConfig) return []
+
+ return matchingConfig.decorations(node)
+ }
+
+const getDecoratedLeaves = (
+ editor: ReactEditor,
+ path: Path
+): { text: string; decorations: string[] }[] => {
+ const text = ReactEditor.toDOMNode(editor, Node.leaf(editor, path))
+ const leaves = Array.from(text.children) as HTMLElement[]
+
+ return leaves.map(leaf => ({
+ text: leaf.textContent!,
+ decorations: JSON.parse(leaf.dataset.decorations!),
+ }))
+}
+
+// Pad children arrays with additional nodes to test whether decorations work
+// correctly on chunked children
+const otherNodes = () =>
+ Array.from({ length: 7 }, () => ({ children: [{ text: '' }] }))
+
+describe('decorations', () => {
+ const withChunking = (chunking: boolean) => {
+ const createEditor = () => {
+ const editor = withReact(slateCreateEditor())
+
+ if (chunking) {
+ editor.getChunkSize = () => 2
+ }
+
+ return editor
+ }
+
+ describe('decorating initial value', () => {
+ it('decorates part of a single text node', () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ { children: [{ text: 'Hello world!' }] },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0, 0],
+ decorations: () => [
+ {
+ anchor: { path: [0, 0], offset: 6 },
+ focus: { path: [0, 0], offset: 11 },
+ bold: true,
+ },
+ ],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'Hello ', decorations: [] },
+ { text: 'world', decorations: ['bold'] },
+ { text: '!', decorations: [] },
+ ])
+ })
+
+ it('decorates an entire text node', () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ {
+ children: [{ text: 'before' }, { text: 'bold' }, { text: 'after' }],
+ },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0, 1],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [0, 1]),
+ bold: true,
+ },
+ ],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'before', decorations: [] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 1])).toEqual([
+ { text: 'bold', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 2])).toEqual([
+ { text: 'after', decorations: [] },
+ ])
+ })
+
+ it('applies multiple overlapping decorations in a single text node', () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ { children: [{ text: 'Hello world!' }] },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0, 0],
+ decorations: () => [
+ {
+ anchor: { path: [0, 0], offset: 0 },
+ focus: { path: [0, 0], offset: 11 },
+ bold: true,
+ },
+ {
+ anchor: { path: [0, 0], offset: 6 },
+ focus: { path: [0, 0], offset: 12 },
+ italic: true,
+ },
+ ],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'Hello ', decorations: ['bold'] },
+ { text: 'world', decorations: ['bold', 'italic'] },
+ { text: '!', decorations: ['italic'] },
+ ])
+ })
+
+ it('passes down decorations from the parent element', () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ {
+ children: [
+ { text: 'before' },
+ { text: 'middle' },
+ { text: 'after' },
+ ],
+ },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0],
+ decorations: () => [
+ {
+ anchor: { path: [0, 0], offset: 2 },
+ focus: { path: [0, 2], offset: 2 },
+ bold: true,
+ },
+ ],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'be', decorations: [] },
+ { text: 'fore', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 1])).toEqual([
+ { text: 'middle', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 2])).toEqual([
+ { text: 'af', decorations: ['bold'] },
+ { text: 'ter', decorations: [] },
+ ])
+ })
+
+ it('passes decorations down from the editor', () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ {
+ children: [{ text: '0.0' }, { text: '0.1' }, { text: '0.2' }],
+ },
+ {
+ children: [{ text: '1.0' }],
+ },
+ {
+ children: [{ text: '2.0' }],
+ },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [],
+ decorations: () => [
+ {
+ anchor: { path: [0, 1], offset: 0 },
+ focus: { path: [1, 0], offset: 3 },
+ bold: true,
+ },
+ ],
+ },
+ {
+ path: [0],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [0, 2]),
+ italic: true,
+ },
+ ],
+ },
+ {
+ path: [1, 0],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [1, 0]),
+ underline: true,
+ },
+ ],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: '0.0', decorations: [] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 1])).toEqual([
+ { text: '0.1', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 2])).toEqual([
+ { text: '0.2', decorations: ['bold', 'italic'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 0])).toEqual([
+ { text: '1.0', decorations: ['bold', 'underline'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [2, 0])).toEqual([
+ { text: '2.0', decorations: [] },
+ ])
+ })
+ })
+
+ describe('redecorating', () => {
+ it('redecorates all nodes when the decorate function changes', () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ {
+ children: [{ text: '0.0' }, { text: '0.1' }, { text: '0.2' }],
+ },
+ {
+ children: [{ text: '1.0' }, { text: '1.1' }, { text: '1.2' }],
+ },
+ ...otherNodes(),
+ ]
+
+ const decorate1 = decoratePaths(editor, [
+ {
+ path: [],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [0, 0]),
+ bold: true,
+ },
+ {
+ ...Editor.range(editor, [0, 1]),
+ italic: true,
+ },
+ ],
+ },
+ {
+ path: [1, 0],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [1, 0]),
+ bold: true,
+ },
+ ],
+ },
+ {
+ path: [1, 1],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [1, 1]),
+ italic: true,
+ },
+ ],
+ },
+ ])
+
+ const decorate2 = decoratePaths(editor, [
+ {
+ path: [0],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [0, 1]),
+ underline: true,
+ },
+ {
+ ...Editor.range(editor, [0, 2]),
+ bold: true,
+ },
+ ],
+ },
+ {
+ path: [1, 1],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [1, 1]),
+ underline: true,
+ },
+ ],
+ },
+ {
+ path: [1, 2],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [1, 2]),
+ bold: true,
+ },
+ ],
+ },
+ ])
+
+ const { rerender } = render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: '0.0', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 1])).toEqual([
+ { text: '0.1', decorations: ['italic'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 2])).toEqual([
+ { text: '0.2', decorations: [] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 0])).toEqual([
+ { text: '1.0', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 1])).toEqual([
+ { text: '1.1', decorations: ['italic'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 2])).toEqual([
+ { text: '1.2', decorations: [] },
+ ])
+
+ rerender(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: '0.0', decorations: [] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 1])).toEqual([
+ { text: '0.1', decorations: ['underline'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [0, 2])).toEqual([
+ { text: '0.2', decorations: ['bold'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 0])).toEqual([
+ { text: '1.0', decorations: [] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 1])).toEqual([
+ { text: '1.1', decorations: ['underline'] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 2])).toEqual([
+ { text: '1.2', decorations: ['bold'] },
+ ])
+ })
+
+ it('redecorates undecorated nodes when they change', async () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ { children: [{ text: 'The quick brown fox' }] },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0, 0],
+ decorations: node =>
+ Text.isText(node) && node.text.includes('box')
+ ? [
+ {
+ ...Editor.range(editor, [0, 0]),
+ bold: true,
+ },
+ ]
+ : [],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'The quick brown fox', decorations: [] },
+ ])
+
+ await act(async () => {
+ Transforms.insertText(editor, 'b', {
+ at: {
+ anchor: { path: [0, 0], offset: 16 },
+ focus: { path: [0, 0], offset: 17 },
+ },
+ })
+ })
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'The quick brown box', decorations: ['bold'] },
+ ])
+ })
+
+ it('redecorates decorated nodes when they change', async () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ { children: [{ text: 'The quick brown box' }] },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0, 0],
+ decorations: node =>
+ Text.isText(node) && node.text.includes('box')
+ ? [
+ {
+ ...Editor.range(editor, [0, 0]),
+ bold: true,
+ },
+ ]
+ : [],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'The quick brown box', decorations: ['bold'] },
+ ])
+
+ await act(async () => {
+ Transforms.insertText(editor, 'f', {
+ at: {
+ anchor: { path: [0, 0], offset: 16 },
+ focus: { path: [0, 0], offset: 17 },
+ },
+ })
+ })
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'The quick brown fox', decorations: [] },
+ ])
+ })
+
+ it('passes down new decorations from changed ancestors', async () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ {
+ children: [
+ { children: [{ text: 'Hello world!' }] },
+ ...otherNodes(),
+ ],
+ },
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [0],
+ decorations: node =>
+ 'bold' in node
+ ? [
+ {
+ ...Editor.range(editor, [0, 0, 0]),
+ bold: true,
+ },
+ ]
+ : [],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0, 0])).toEqual([
+ { text: 'Hello world!', decorations: [] },
+ ])
+
+ await act(async () => {
+ Transforms.setNodes(editor, { bold: true } as any, {
+ at: [0],
+ })
+ })
+
+ expect(getDecoratedLeaves(editor, [0, 0, 0])).toEqual([
+ { text: 'Hello world!', decorations: ['bold'] },
+ ])
+ })
+
+ it('does not redecorate unchanged nodes when their paths change', async () => {
+ const editor = createEditor()
+
+ const initialValue = [
+ { children: [{ text: 'A' }] },
+ { children: [{ text: 'B' }] },
+ ...otherNodes(),
+ ]
+
+ const decorate = decoratePaths(editor, [
+ {
+ path: [1, 0],
+ decorations: () => [
+ {
+ ...Editor.range(editor, [1, 0]),
+ bold: true,
+ },
+ ],
+ },
+ ])
+
+ render(
+
+
+
+ )
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: 'A', decorations: [] },
+ ])
+
+ expect(getDecoratedLeaves(editor, [1, 0])).toEqual([
+ { text: 'B', decorations: ['bold'] },
+ ])
+
+ await act(async () => {
+ Transforms.insertNodes(
+ editor,
+ { children: [{ text: '0' }] },
+ {
+ at: [0],
+ }
+ )
+ })
+
+ expect(getDecoratedLeaves(editor, [0, 0])).toEqual([
+ { text: '0', decorations: [] },
+ ])
+
+ // A does not become bold even though it now matches the decoration
+ expect(getDecoratedLeaves(editor, [1, 0])).toEqual([
+ { text: 'A', decorations: [] },
+ ])
+
+ // B remains bold even though it no longer matches the decoration
+ expect(getDecoratedLeaves(editor, [2, 0])).toEqual([
+ { text: 'B', decorations: ['bold'] },
+ ])
+ })
+ })
+ }
+
+ describe('without chunking', () => {
+ withChunking(false)
+ })
+
+ describe('with chunking', () => {
+ withChunking(true)
+ })
+})
diff --git a/test/editable.spec.tsx b/test/editable.spec.tsx
new file mode 100644
index 0000000..d907cc1
--- /dev/null
+++ b/test/editable.spec.tsx
@@ -0,0 +1,204 @@
+import React, { useEffect } from 'react'
+import { createEditor, Text, Transforms } from 'slate'
+import { act, render } from '@testing-library/react'
+import { Slate, withReact, Editable } from '../src'
+
+describe('slate-react', () => {
+ describe('Editable', () => {
+ describe('NODE_TO_KEY logic', () => {
+ test('should not unmount the node that gets split on a split_node operation', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const mounts = jest.fn()
+
+ act(() => {
+ render(
+ {}}
+ >
+ {
+ useEffect(() => mounts(), [])
+
+ return children
+ }}
+ />
+
+ )
+ })
+
+ // slate updates at next tick, so we need this to be async
+ await act(async () =>
+ Transforms.splitNodes(editor, { at: { path: [0, 0], offset: 2 } })
+ )
+
+ // 2 renders, one for the main element and one for the split element
+ expect(mounts).toHaveBeenCalledTimes(2)
+ })
+
+ test('should not unmount the node that gets merged into on a merge_node operation', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [
+ { type: 'block', children: [{ text: 'te' }] },
+ { type: 'block', children: [{ text: 'st' }] },
+ ]
+ const mounts = jest.fn()
+
+ act(() => {
+ render(
+ {}}
+ >
+ {
+ useEffect(() => mounts(), [])
+
+ return children
+ }}
+ />
+
+ )
+ })
+
+ // slate updates at next tick, so we need this to be async
+ await act(async () =>
+ Transforms.mergeNodes(editor, { at: { path: [0, 0], offset: 0 } })
+ )
+
+ // only 2 renders for the initial render
+ expect(mounts).toHaveBeenCalledTimes(2)
+ })
+ })
+ test('calls onSelectionChange when editor select change', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [
+ { type: 'block', children: [{ text: 'te' }] },
+ { type: 'block', children: [{ text: 'st' }] },
+ ]
+ const onChange = jest.fn()
+ const onValueChange = jest.fn()
+ const onSelectionChange = jest.fn()
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ await act(async () =>
+ Transforms.select(editor, { path: [0, 0], offset: 2 })
+ )
+
+ expect(onSelectionChange).toHaveBeenCalled()
+ expect(onChange).toHaveBeenCalled()
+ expect(onValueChange).not.toHaveBeenCalled()
+ })
+
+ test('calls onValueChange when editor children change', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const onChange = jest.fn()
+ const onValueChange = jest.fn()
+ const onSelectionChange = jest.fn()
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ await act(async () => Transforms.insertText(editor, 'Hello word!'))
+
+ expect(onValueChange).toHaveBeenCalled()
+ expect(onChange).toHaveBeenCalled()
+ expect(onSelectionChange).not.toHaveBeenCalled()
+ })
+
+ test('calls onValueChange when editor setNodes', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const onChange = jest.fn()
+ const onValueChange = jest.fn()
+ const onSelectionChange = jest.fn()
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ await act(async () =>
+ Transforms.setNodes(
+ editor,
+ // @ts-ignore
+ { bold: true },
+ {
+ at: { path: [0, 0], offset: 2 },
+ match: Text.isText,
+ split: true,
+ }
+ )
+ )
+
+ expect(onChange).toHaveBeenCalled()
+ expect(onValueChange).toHaveBeenCalled()
+ expect(onSelectionChange).not.toHaveBeenCalled()
+ })
+
+ test('calls onValueChange when editor children change', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const onChange = jest.fn()
+ const onValueChange = jest.fn()
+ const onSelectionChange = jest.fn()
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ await act(async () => Transforms.insertText(editor, 'Hello word!'))
+
+ expect(onValueChange).toHaveBeenCalled()
+ expect(onChange).toHaveBeenCalled()
+ expect(onSelectionChange).not.toHaveBeenCalled()
+ })
+ })
+})
diff --git a/test/index.js b/test/index.js
deleted file mode 100644
index 2510a9b..0000000
--- a/test/index.js
+++ /dev/null
@@ -1 +0,0 @@
-describe('slate-react', () => {})
diff --git a/test/react-editor.spec.tsx b/test/react-editor.spec.tsx
new file mode 100644
index 0000000..8ffe5ab
--- /dev/null
+++ b/test/react-editor.spec.tsx
@@ -0,0 +1,127 @@
+import React from 'react'
+import { createEditor, Transforms } from 'slate'
+import { act, render } from '@testing-library/react'
+import { Slate, withReact, Editable, ReactEditor } from '../src'
+
+describe('slate-react', () => {
+ describe('ReactEditor', () => {
+ describe('.focus', () => {
+ test('should set focus in top of document with no editor selection', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const testSelection = {
+ anchor: { path: [0, 0], offset: 0 },
+ focus: { path: [0, 0], offset: 0 },
+ }
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ expect(editor.selection).toBe(null)
+
+ await act(async () => {
+ ReactEditor.focus(editor)
+ })
+
+ expect(editor.selection).toEqual(testSelection)
+
+ await act(async () => {
+ const windowSelection = ReactEditor.getWindow(editor).getSelection()
+ expect(windowSelection?.focusNode?.textContent).toBe('test')
+ expect(windowSelection?.anchorNode?.textContent).toBe('test')
+ expect(windowSelection?.anchorOffset).toBe(
+ testSelection.anchor.offset
+ )
+ expect(windowSelection?.focusOffset).toBe(testSelection.focus.offset)
+ })
+ })
+
+ test('should be able to call .focus without getting toDOMNode errors', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const propagatedValue = [
+ { type: 'block', children: [{ text: 'foo' }] },
+ { type: 'block', children: [{ text: 'bar' }] },
+ ]
+
+ const testSelection = {
+ anchor: { path: [1, 0], offset: 0 },
+ focus: { path: [1, 0], offset: 3 },
+ }
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ await act(async () => {
+ Transforms.removeNodes(editor, { at: [0] })
+ Transforms.insertNodes(editor, propagatedValue)
+ ReactEditor.focus(editor) // Note: calling focus in the middle of these transformations.
+ Transforms.select(editor, testSelection)
+ })
+
+ expect(editor.selection).toEqual(testSelection)
+
+ await act(async () => {
+ ReactEditor.focus(editor)
+ })
+
+ await act(async () => {
+ const windowSelection = ReactEditor.getWindow(editor).getSelection()
+ expect(windowSelection?.focusNode?.textContent).toBe('bar')
+ expect(windowSelection?.anchorNode?.textContent).toBe('bar')
+ expect(windowSelection?.anchorOffset).toBe(
+ testSelection.anchor.offset
+ )
+ expect(windowSelection?.focusOffset).toBe(testSelection.focus.offset)
+ })
+ })
+
+ test('should not trigger onValueChange when focus is called', async () => {
+ const editor = withReact(createEditor())
+ const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
+ const onChange = jest.fn()
+ const onValueChange = jest.fn()
+ const onSlectionChange = jest.fn()
+
+ act(() => {
+ render(
+
+
+
+ )
+ })
+
+ expect(editor.selection).toBe(null)
+
+ await act(async () => {
+ ReactEditor.focus(editor)
+ })
+
+ expect(editor.selection).toEqual({
+ anchor: { path: [0, 0], offset: 0 },
+ focus: { path: [0, 0], offset: 0 },
+ })
+
+ expect(onChange).toHaveBeenCalled()
+ expect(onSlectionChange).toHaveBeenCalled()
+ expect(onValueChange).not.toHaveBeenCalled()
+ })
+ })
+ })
+})
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 0000000..a9b230f
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../config/typescript/tsconfig.json",
+ "compilerOptions": {
+ "types": ["@testing-library/jest-dom"]
+ },
+ "references": [{ "path": "../" }]
+}
diff --git a/test/use-selected.spec.tsx b/test/use-selected.spec.tsx
new file mode 100644
index 0000000..b59569e
--- /dev/null
+++ b/test/use-selected.spec.tsx
@@ -0,0 +1,183 @@
+import React from 'react'
+import { createEditor, Transforms } from 'slate'
+import { render, act } from '@testing-library/react'
+import {
+ Slate,
+ withReact,
+ Editable,
+ useSelected,
+ RenderElementProps,
+ ReactEditor,
+} from '../src'
+
+let editor: ReactEditor
+let elementSelectedRenders: Record
+
+const clearRenders = () =>
+ Object.values(elementSelectedRenders).forEach(selectedRenders => {
+ if (selectedRenders) {
+ selectedRenders.length = 0
+ }
+ })
+
+const initialValue = () => [
+ {
+ id: '0',
+ children: [
+ { id: '0.0', children: [{ text: '' }] },
+ { id: '0.1', children: [{ text: '' }] },
+ { id: '0.2', children: [{ text: '' }] },
+ ],
+ },
+ { id: '1', children: [{ text: '' }] },
+ { id: '2', children: [{ text: '' }] },
+]
+
+describe('useSelected', () => {
+ const withChunking = (chunking: boolean) => {
+ beforeEach(() => {
+ editor = withReact(createEditor())
+
+ if (chunking) {
+ editor.getChunkSize = () => 3
+ }
+
+ elementSelectedRenders = {}
+
+ const renderElement = ({
+ element,
+ attributes,
+ children,
+ }: RenderElementProps) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const selected = useSelected()
+ const { id } = element as any
+
+ let selectedRenders = elementSelectedRenders[id]
+
+ if (!selectedRenders) {
+ selectedRenders = []
+ elementSelectedRenders[id] = selectedRenders
+ }
+
+ selectedRenders.push(selected)
+
+ return