@portabletext/editor 2.13.2 → 2.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/lib/_chunks-cjs/{selector.is-selection-expanded.cjs → selector.get-selection-text.cjs} +25 -25
  2. package/lib/_chunks-cjs/selector.get-selection-text.cjs.map +1 -0
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs +4 -4
  4. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  5. package/lib/_chunks-cjs/{selector.is-selecting-entire-blocks.cjs → selector.is-active-style.cjs} +408 -399
  6. package/lib/_chunks-cjs/selector.is-active-style.cjs.map +1 -0
  7. package/lib/_chunks-cjs/util.child-selection-point-to-block-offset.cjs +3 -3
  8. package/lib/_chunks-cjs/util.child-selection-point-to-block-offset.cjs.map +1 -1
  9. package/lib/_chunks-cjs/{util.slice-blocks.cjs → util.get-text-block-text.cjs} +25 -26
  10. package/lib/_chunks-cjs/util.get-text-block-text.cjs.map +1 -0
  11. package/lib/_chunks-cjs/{util.is-selection-collapsed.cjs → util.is-empty-text-block.cjs} +9 -9
  12. package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +1 -0
  13. package/lib/_chunks-cjs/util.merge-text-blocks.cjs +2 -2
  14. package/lib/_chunks-cjs/util.merge-text-blocks.cjs.map +1 -1
  15. package/lib/_chunks-cjs/util.slice-text-block.cjs +5 -5
  16. package/lib/_chunks-cjs/util.slice-text-block.cjs.map +1 -1
  17. package/lib/_chunks-dts/behavior.types.action.d.cts +270 -270
  18. package/lib/_chunks-dts/behavior.types.action.d.ts +273 -273
  19. package/lib/_chunks-es/{selector.is-selection-expanded.js → selector.get-selection-text.js} +20 -20
  20. package/lib/_chunks-es/selector.get-selection-text.js.map +1 -0
  21. package/lib/_chunks-es/selector.get-text-before.js +2 -2
  22. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  23. package/lib/_chunks-es/{selector.is-selecting-entire-blocks.js → selector.is-active-style.js} +385 -376
  24. package/lib/_chunks-es/selector.is-active-style.js.map +1 -0
  25. package/lib/_chunks-es/util.child-selection-point-to-block-offset.js +1 -1
  26. package/lib/_chunks-es/util.child-selection-point-to-block-offset.js.map +1 -1
  27. package/lib/_chunks-es/{util.slice-blocks.js → util.get-text-block-text.js} +25 -26
  28. package/lib/_chunks-es/util.get-text-block-text.js.map +1 -0
  29. package/lib/_chunks-es/{util.is-selection-collapsed.js → util.is-empty-text-block.js} +8 -8
  30. package/lib/_chunks-es/util.is-empty-text-block.js.map +1 -0
  31. package/lib/_chunks-es/util.merge-text-blocks.js +1 -1
  32. package/lib/_chunks-es/util.merge-text-blocks.js.map +1 -1
  33. package/lib/_chunks-es/util.slice-text-block.js +1 -1
  34. package/lib/_chunks-es/util.slice-text-block.js.map +1 -1
  35. package/lib/index.cjs +429 -363
  36. package/lib/index.cjs.map +1 -1
  37. package/lib/index.js +225 -159
  38. package/lib/index.js.map +1 -1
  39. package/lib/plugins/index.cjs +21 -21
  40. package/lib/plugins/index.cjs.map +1 -1
  41. package/lib/plugins/index.d.cts +4 -4
  42. package/lib/plugins/index.d.ts +4 -4
  43. package/lib/plugins/index.js +3 -3
  44. package/lib/plugins/index.js.map +1 -1
  45. package/lib/selectors/index.cjs +52 -52
  46. package/lib/selectors/index.cjs.map +1 -1
  47. package/lib/selectors/index.d.cts +4 -1
  48. package/lib/selectors/index.d.ts +4 -1
  49. package/lib/selectors/index.js +5 -5
  50. package/lib/selectors/index.js.map +1 -1
  51. package/lib/utils/index.cjs +14 -14
  52. package/lib/utils/index.cjs.map +1 -1
  53. package/lib/utils/index.d.ts +2 -2
  54. package/lib/utils/index.js +3 -3
  55. package/lib/utils/index.js.map +1 -1
  56. package/package.json +12 -12
  57. package/src/behaviors/behavior.abstract.annotation.ts +3 -3
  58. package/src/behaviors/behavior.abstract.decorator.ts +2 -2
  59. package/src/behaviors/behavior.abstract.delete.ts +25 -16
  60. package/src/behaviors/behavior.abstract.deserialize.ts +4 -3
  61. package/src/behaviors/behavior.abstract.insert.ts +6 -7
  62. package/src/behaviors/behavior.abstract.keyboard.ts +7 -8
  63. package/src/behaviors/behavior.abstract.list-item.ts +2 -1
  64. package/src/behaviors/behavior.abstract.move.ts +2 -1
  65. package/src/behaviors/behavior.abstract.select.ts +4 -2
  66. package/src/behaviors/behavior.abstract.split.ts +33 -24
  67. package/src/behaviors/behavior.abstract.style.ts +2 -1
  68. package/src/behaviors/behavior.abstract.ts +8 -7
  69. package/src/behaviors/behavior.core.annotations.ts +8 -7
  70. package/src/behaviors/behavior.core.block-element.ts +7 -5
  71. package/src/behaviors/behavior.core.block-objects.ts +25 -27
  72. package/src/behaviors/behavior.core.dnd.ts +10 -8
  73. package/src/behaviors/behavior.core.insert-break.ts +45 -36
  74. package/src/behaviors/behavior.core.lists.ts +31 -25
  75. package/src/behaviors/behavior.decorator-pair.ts +26 -23
  76. package/src/behaviors/behavior.markdown.ts +26 -21
  77. package/src/converters/converter.portable-text.ts +3 -3
  78. package/src/converters/converter.text-html.serialize.test.ts +1 -1
  79. package/src/converters/converter.text-html.ts +3 -3
  80. package/src/converters/converter.text-plain.test.ts +1 -1
  81. package/src/converters/converter.text-plain.ts +3 -3
  82. package/src/editor/Editable.tsx +18 -78
  83. package/src/editor/components/render-span.tsx +3 -5
  84. package/src/editor/create-editor.ts +2 -2
  85. package/src/editor/create-slate-editor.tsx +1 -4
  86. package/src/editor/editor-dom.ts +2 -2
  87. package/src/editor/plugins/createWithEditableAPI.ts +5 -10
  88. package/src/editor/plugins/createWithMaxBlocks.ts +2 -2
  89. package/src/editor/plugins/createWithObjectKeys.ts +2 -2
  90. package/src/editor/plugins/createWithPatches.ts +3 -10
  91. package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -2
  92. package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -2
  93. package/src/editor/plugins/createWithSchemaTypes.ts +1 -1
  94. package/src/editor/plugins/createWithUndoRedo.ts +6 -6
  95. package/src/editor/plugins/slate-plugin.update-selection.ts +1 -1
  96. package/src/editor/sync-machine.ts +2 -5
  97. package/src/editor/validate-selection-machine.test.ts +47 -0
  98. package/src/editor/validate-selection-machine.ts +149 -0
  99. package/src/{internal-utils → editor}/weakMaps.ts +1 -1
  100. package/src/editor/with-undo-step.ts +1 -1
  101. package/src/index.ts +1 -1
  102. package/src/internal-utils/applyPatch.ts +2 -2
  103. package/src/internal-utils/build-index-maps.test.ts +1 -1
  104. package/src/internal-utils/create-test-snapshot.ts +1 -1
  105. package/src/internal-utils/event-position.ts +11 -9
  106. package/src/internal-utils/operation-to-patches.test.ts +1 -1
  107. package/src/internal-utils/portable-text-node.ts +1 -1
  108. package/src/internal-utils/selection-block-keys.ts +1 -1
  109. package/src/internal-utils/selection-focus-text.ts +1 -1
  110. package/src/internal-utils/to-slate-range.ts +4 -4
  111. package/src/operations/behavior.operation.annotation.add.ts +1 -1
  112. package/src/operations/behavior.operation.block.set.ts +1 -1
  113. package/src/operations/behavior.operation.block.unset.ts +2 -2
  114. package/src/operations/behavior.operation.decorator.add.ts +11 -9
  115. package/src/operations/behavior.operation.delete.ts +1 -1
  116. package/src/operations/behavior.operation.insert.block.ts +2 -2
  117. package/src/operations/behavior.operation.insert.child.ts +1 -1
  118. package/src/operations/behavior.operation.move.block.ts +1 -1
  119. package/src/plugins/plugin.behavior.tsx +1 -1
  120. package/src/plugins/plugin.decorator-shortcut.ts +3 -3
  121. package/src/plugins/plugin.internal.auto-close-brackets.ts +2 -1
  122. package/src/plugins/plugin.one-line.tsx +11 -11
  123. package/src/priority/priority.types.ts +1 -1
  124. package/src/{internal-utils → selectors}/drag-selection.test.ts +1 -1
  125. package/src/{internal-utils → selectors}/drag-selection.ts +26 -19
  126. package/src/selectors/index.ts +1 -1
  127. package/src/selectors/selector.get-anchor-block.ts +1 -1
  128. package/src/selectors/selector.get-anchor-child.ts +1 -1
  129. package/src/selectors/selector.get-block-offsets.ts +3 -3
  130. package/src/selectors/selector.get-caret-word-selection.test.ts +1 -1
  131. package/src/selectors/selector.get-focus-block.ts +1 -1
  132. package/src/selectors/selector.get-focus-child.ts +1 -1
  133. package/src/selectors/selector.get-focus-list-block.ts +1 -1
  134. package/src/selectors/selector.get-list-state.test.ts +1 -1
  135. package/src/selectors/selector.get-mark-state.ts +4 -1
  136. package/src/selectors/selector.get-next-inline-object.ts +1 -1
  137. package/src/selectors/selector.get-next-span.ts +1 -1
  138. package/src/selectors/selector.get-previous-inline-object.ts +1 -1
  139. package/src/selectors/selector.get-previous-span.ts +1 -1
  140. package/src/selectors/selector.get-selected-blocks.ts +1 -1
  141. package/src/selectors/selector.get-selected-spans.test.ts +1 -1
  142. package/src/selectors/selector.get-selected-spans.ts +2 -2
  143. package/src/selectors/selector.get-selected-text-blocks.ts +3 -2
  144. package/src/selectors/selector.get-selected-value.test.ts +87 -1
  145. package/src/selectors/selector.get-selected-value.ts +4 -6
  146. package/src/selectors/selector.get-selection-end-point.ts +1 -1
  147. package/src/selectors/selector.get-selection-start-point.ts +1 -1
  148. package/src/selectors/selector.get-selection-text.test.ts +1 -1
  149. package/src/selectors/selector.get-selection.ts +1 -1
  150. package/src/selectors/selector.get-text-before.ts +1 -1
  151. package/src/selectors/selector.get-trimmed-selection.test.ts +1 -1
  152. package/src/selectors/selector.get-trimmed-selection.ts +5 -7
  153. package/src/selectors/selector.is-active-decorator.test.ts +2 -1
  154. package/src/selectors/selector.is-at-the-end-of-block.ts +4 -3
  155. package/src/selectors/selector.is-at-the-start-of-block.ts +4 -3
  156. package/src/selectors/selector.is-overlapping-selection.test.ts +1 -1
  157. package/src/selectors/selector.is-overlapping-selection.ts +1 -1
  158. package/src/selectors/selector.is-point-after-selection.ts +3 -3
  159. package/src/selectors/selector.is-point-before-selection.ts +3 -3
  160. package/src/selectors/selector.is-selecting-entire-blocks.ts +7 -5
  161. package/src/test/gherkin-parameter-types.ts +1 -1
  162. package/src/test/vitest/step-definitions.tsx +19 -9
  163. package/src/types/paths.ts +4 -1
  164. package/src/utils/util.at-the-beginning-of-block.ts +1 -1
  165. package/src/utils/util.block-offset.ts +4 -4
  166. package/src/utils/util.block-offsets-to-selection.ts +1 -1
  167. package/src/utils/util.child-selection-point-to-block-offset.ts +3 -3
  168. package/src/utils/util.get-selection-end-point.ts +1 -1
  169. package/src/utils/util.get-selection-start-point.ts +1 -1
  170. package/src/utils/util.merge-text-blocks.ts +2 -2
  171. package/src/utils/util.selection-point-to-block-offset.ts +1 -1
  172. package/src/{selection/selection-point.ts → utils/util.selection-point.ts} +1 -1
  173. package/src/utils/util.slice-blocks.ts +6 -6
  174. package/src/utils/util.slice-text-block.test.ts +3 -1
  175. package/src/utils/util.slice-text-block.ts +3 -3
  176. package/src/utils/util.split-text-block.ts +1 -1
  177. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +0 -1
  178. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs.map +0 -1
  179. package/lib/_chunks-cjs/util.is-selection-collapsed.cjs.map +0 -1
  180. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +0 -1
  181. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +0 -1
  182. package/lib/_chunks-es/selector.is-selection-expanded.js.map +0 -1
  183. package/lib/_chunks-es/util.is-selection-collapsed.js.map +0 -1
  184. package/lib/_chunks-es/util.slice-blocks.js.map +0 -1
  185. /package/src/{internal-utils → editor}/withChanges.ts +0 -0
  186. /package/src/{internal-utils → editor}/withUndoRedo.ts +0 -0
  187. /package/src/{internal-utils → editor}/withoutPatching.ts +0 -0
  188. /package/src/{internal-utils → utils}/asserters.ts +0 -0
  189. /package/src/{editor → utils}/key-generator.ts +0 -0
  190. /package/src/{internal-utils → utils}/parse-blocks.test.ts +0 -0
  191. /package/src/{internal-utils → utils}/parse-blocks.ts +0 -0
@@ -26,18 +26,12 @@ import {
26
26
  } from '../../internal-utils/slate-utils'
27
27
  import {toSlateRange} from '../../internal-utils/to-slate-range'
28
28
  import {fromSlateValue, toSlateValue} from '../../internal-utils/values'
29
- import {
30
- KEY_TO_VALUE_ELEMENT,
31
- SLATE_TO_PORTABLE_TEXT_RANGE,
32
- } from '../../internal-utils/weakMaps'
33
- import {
34
- getFocusBlock,
35
- getFocusSpan,
36
- getSelectedValue,
37
- isActiveAnnotation,
38
- } from '../../selectors'
39
29
  import {getActiveAnnotationsMarks} from '../../selectors/selector.get-active-annotation-marks'
40
30
  import {getActiveDecorators} from '../../selectors/selector.get-active-decorators'
31
+ import {getFocusBlock} from '../../selectors/selector.get-focus-block'
32
+ import {getFocusSpan} from '../../selectors/selector.get-focus-span'
33
+ import {getSelectedValue} from '../../selectors/selector.get-selected-value'
34
+ import {isActiveAnnotation} from '../../selectors/selector.is-active-annotation'
41
35
  import type {
42
36
  EditableAPI,
43
37
  EditableAPIDeleteOptions,
@@ -46,6 +40,7 @@ import type {
46
40
  } from '../../types/editor'
47
41
  import type {EditorActor} from '../editor-machine'
48
42
  import {getEditorSnapshot} from '../editor-selector'
43
+ import {KEY_TO_VALUE_ELEMENT, SLATE_TO_PORTABLE_TEXT_RANGE} from '../weakMaps'
49
44
 
50
45
  const debug = debugWithName('API:editable')
51
46
 
@@ -1,7 +1,7 @@
1
- import {isChangingRemotely} from '../../internal-utils/withChanges'
2
- import {isRedoing, isUndoing} from '../../internal-utils/withUndoRedo'
3
1
  import type {PortableTextSlateEditor} from '../../types/editor'
4
2
  import type {EditorActor} from '../editor-machine'
3
+ import {isChangingRemotely} from '../withChanges'
4
+ import {isRedoing, isUndoing} from '../withUndoRedo'
5
5
 
6
6
  /**
7
7
  * This plugin makes sure that the PTE maxBlocks prop is respected
@@ -1,10 +1,10 @@
1
1
  import {isSpan, isTextBlock} from '@portabletext/schema'
2
2
  import {isEqual} from 'lodash'
3
3
  import {Editor, Element, Node, Path, Transforms} from 'slate'
4
- import {isChangingRemotely} from '../../internal-utils/withChanges'
5
- import {isRedoing, isUndoing} from '../../internal-utils/withUndoRedo'
6
4
  import type {PortableTextSlateEditor} from '../../types/editor'
7
5
  import type {EditorActor} from '../editor-machine'
6
+ import {isChangingRemotely} from '../withChanges'
7
+ import {isRedoing, isUndoing} from '../withUndoRedo'
8
8
 
9
9
  /**
10
10
  * This plugin makes sure that every new node in the editor get a new _key prop when created
@@ -13,20 +13,13 @@ import {
13
13
  splitNodePatch,
14
14
  } from '../../internal-utils/operation-to-patches'
15
15
  import {fromSlateValue, isEqualToEmptyEditor} from '../../internal-utils/values'
16
- import {
17
- IS_PROCESSING_REMOTE_CHANGES,
18
- KEY_TO_VALUE_ELEMENT,
19
- } from '../../internal-utils/weakMaps'
20
- import {withRemoteChanges} from '../../internal-utils/withChanges'
21
- import {
22
- isPatching,
23
- PATCHING,
24
- withoutPatching,
25
- } from '../../internal-utils/withoutPatching'
26
16
  import type {PortableTextSlateEditor} from '../../types/editor'
27
17
  import type {EditorActor} from '../editor-machine'
28
18
  import type {RelayActor} from '../relay-machine'
19
+ import {IS_PROCESSING_REMOTE_CHANGES, KEY_TO_VALUE_ELEMENT} from '../weakMaps'
29
20
  import {getCurrentUndoStepId} from '../with-undo-step'
21
+ import {withRemoteChanges} from '../withChanges'
22
+ import {isPatching, PATCHING, withoutPatching} from '../withoutPatching'
30
23
  import {withoutSaving} from './createWithUndoRedo'
31
24
 
32
25
  const debug = debugWithName('plugin:withPatches')
@@ -1,9 +1,9 @@
1
1
  import {Editor} from 'slate'
2
2
  import {debugWithName} from '../../internal-utils/debug'
3
- import {isChangingRemotely} from '../../internal-utils/withChanges'
4
- import {isRedoing, isUndoing} from '../../internal-utils/withUndoRedo'
5
3
  import type {PortableTextSlateEditor} from '../../types/editor'
6
4
  import type {EditorActor} from '../editor-machine'
5
+ import {isChangingRemotely} from '../withChanges'
6
+ import {isRedoing, isUndoing} from '../withUndoRedo'
7
7
 
8
8
  const debug = debugWithName('plugin:withPlaceholderBlock')
9
9
 
@@ -10,14 +10,14 @@ import {isEqual, uniq} from 'lodash'
10
10
  import {Editor, Element, Node, Path, Range, Text, Transforms} from 'slate'
11
11
  import {debugWithName} from '../../internal-utils/debug'
12
12
  import {getNextSpan, getPreviousSpan} from '../../internal-utils/sibling-utils'
13
- import {isChangingRemotely} from '../../internal-utils/withChanges'
14
- import {isRedoing, isUndoing} from '../../internal-utils/withUndoRedo'
15
13
  import type {BehaviorOperationImplementation} from '../../operations/behavior.operations'
16
14
  import {getActiveDecorators} from '../../selectors/selector.get-active-decorators'
17
15
  import {getMarkState} from '../../selectors/selector.get-mark-state'
18
16
  import type {PortableTextSlateEditor} from '../../types/editor'
19
17
  import type {EditorActor} from '../editor-machine'
20
18
  import {getEditorSnapshot} from '../editor-selector'
19
+ import {isChangingRemotely} from '../withChanges'
20
+ import {isRedoing, isUndoing} from '../withUndoRedo'
21
21
 
22
22
  const debug = debugWithName('plugin:withPortableTextMarkModel')
23
23
 
@@ -6,8 +6,8 @@ import type {
6
6
  } from '@sanity/types'
7
7
  import {Editor, Transforms, type Element} from 'slate'
8
8
  import {debugWithName} from '../../internal-utils/debug'
9
- import {isListBlock} from '../../internal-utils/parse-blocks'
10
9
  import type {PortableTextSlateEditor} from '../../types/editor'
10
+ import {isListBlock} from '../../utils/parse-blocks'
11
11
  import type {EditorActor} from '../editor-machine'
12
12
 
13
13
  const debug = debugWithName('plugin:withSchemaTypes')
@@ -22,7 +22,11 @@ import {
22
22
  } from 'slate'
23
23
  import {debugWithName} from '../../internal-utils/debug'
24
24
  import {fromSlateValue} from '../../internal-utils/values'
25
- import {isChangingRemotely} from '../../internal-utils/withChanges'
25
+ import type {BehaviorOperationImplementation} from '../../operations/behavior.operations'
26
+ import type {PortableTextSlateEditor} from '../../types/editor'
27
+ import type {EditorActor} from '../editor-machine'
28
+ import {getCurrentUndoStepId} from '../with-undo-step'
29
+ import {isChangingRemotely} from '../withChanges'
26
30
  import {
27
31
  isRedoing,
28
32
  isUndoing,
@@ -30,11 +34,7 @@ import {
30
34
  setIsUndoing,
31
35
  withRedoing,
32
36
  withUndoing,
33
- } from '../../internal-utils/withUndoRedo'
34
- import type {BehaviorOperationImplementation} from '../../operations/behavior.operations'
35
- import type {PortableTextSlateEditor} from '../../types/editor'
36
- import type {EditorActor} from '../editor-machine'
37
- import {getCurrentUndoStepId} from '../with-undo-step'
37
+ } from '../withUndoRedo'
38
38
 
39
39
  const debug = debugWithName('plugin:withUndoRedo')
40
40
  const debugVerbose = debug.enabled && false
@@ -1,7 +1,7 @@
1
1
  import {slateRangeToSelection} from '../../internal-utils/slate-utils'
2
- import {SLATE_TO_PORTABLE_TEXT_RANGE} from '../../internal-utils/weakMaps'
3
2
  import type {PortableTextSlateEditor} from '../../types/editor'
4
3
  import type {EditorActor} from '../editor-machine'
4
+ import {SLATE_TO_PORTABLE_TEXT_RANGE} from '../weakMaps'
5
5
 
6
6
  export function pluginUpdateSelection({
7
7
  editor,
@@ -25,11 +25,6 @@ import type {ActorRefFrom} from 'xstate'
25
25
  import {debugWithName} from '../internal-utils/debug'
26
26
  import {validateValue} from '../internal-utils/validateValue'
27
27
  import {toSlateValue, VOID_CHILD_KEY} from '../internal-utils/values'
28
- import {
29
- isChangingRemotely,
30
- withRemoteChanges,
31
- } from '../internal-utils/withChanges'
32
- import {withoutPatching} from '../internal-utils/withoutPatching'
33
28
  import type {PickFromUnion} from '../type-utils'
34
29
  import type {
35
30
  InvalidValueResolution,
@@ -37,6 +32,8 @@ import type {
37
32
  } from '../types/editor'
38
33
  import type {EditorSchema} from './editor-schema'
39
34
  import {withoutSaving} from './plugins/createWithUndoRedo'
35
+ import {isChangingRemotely, withRemoteChanges} from './withChanges'
36
+ import {withoutPatching} from './withoutPatching'
40
37
 
41
38
  const debug = debugWithName('sync machine')
42
39
 
@@ -0,0 +1,47 @@
1
+ import {getTersePt} from '@portabletext/test'
2
+ import {userEvent} from '@vitest/browser/context'
3
+ import {describe, expect, test, vi} from 'vitest'
4
+ import {getSelectionAfterText} from '../internal-utils/text-selection'
5
+ import {createTestEditor} from '../test/vitest'
6
+ import {validateSelectionMachine} from './validate-selection-machine'
7
+
8
+ describe(validateSelectionMachine.id, () => {
9
+ test('Scenario: Does not validate selection while Slate has pending operations', async () => {
10
+ const {editor, locator} = await createTestEditor()
11
+
12
+ await userEvent.click(locator)
13
+
14
+ editor.send({type: 'insert.text', text: 'foo'})
15
+
16
+ await vi.waitFor(() => {
17
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foo'])
18
+ expect(editor.getSnapshot().context.selection).toEqual(
19
+ getSelectionAfterText(editor.getSnapshot().context, 'foo'),
20
+ )
21
+ })
22
+
23
+ // This event is being sent in before "foo" has been inserted in the DOM
24
+ // This means that when the MutationObserver is finally triggered for the
25
+ // "foo" insertion, "bar" will be in the Slate state but not in the DOM.
26
+ // This causes the selection to be out of sync and this is why we need to
27
+ // make sure the selection is not validated before Slate has committed all
28
+ // pending operations.
29
+ editor.send({type: 'insert.text', text: 'bar'})
30
+
31
+ await vi.waitFor(() => {
32
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foobar'])
33
+ expect(editor.getSnapshot().context.selection).toEqual(
34
+ getSelectionAfterText(editor.getSnapshot().context, 'foobar'),
35
+ )
36
+ })
37
+
38
+ editor.send({type: 'delete.backward', unit: 'character'})
39
+
40
+ await vi.waitFor(() => {
41
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['fooba'])
42
+ expect(editor.getSnapshot().context.selection).toEqual(
43
+ getSelectionAfterText(editor.getSnapshot().context, 'fooba'),
44
+ )
45
+ })
46
+ })
47
+ })
@@ -0,0 +1,149 @@
1
+ import {Editor, Transforms} from 'slate'
2
+ import {ReactEditor} from 'slate-react'
3
+ import {setup} from 'xstate'
4
+ import {debugWithName} from '../internal-utils/debug'
5
+ import type {PortableTextSlateEditor} from '../types/editor'
6
+
7
+ const debug = debugWithName('validate selection machine')
8
+
9
+ const validateSelectionSetup = setup({
10
+ types: {
11
+ context: {} as {
12
+ slateEditor: PortableTextSlateEditor
13
+ },
14
+ input: {} as {
15
+ slateEditor: PortableTextSlateEditor
16
+ },
17
+ events: {} as {
18
+ type: 'validate selection'
19
+ editorElement: HTMLDivElement
20
+ },
21
+ },
22
+ guards: {
23
+ 'pending operations': ({context}) =>
24
+ context.slateEditor.operations.length > 0,
25
+ },
26
+ })
27
+
28
+ const validateSelectionAction = validateSelectionSetup.createAction(
29
+ ({context, event}) => {
30
+ validateSelection(context.slateEditor, event.editorElement)
31
+ },
32
+ )
33
+
34
+ export const validateSelectionMachine = validateSelectionSetup.createMachine({
35
+ id: 'validate selection',
36
+ context: ({input}) => ({
37
+ slateEditor: input.slateEditor,
38
+ }),
39
+ initial: 'idle',
40
+ states: {
41
+ idle: {
42
+ on: {
43
+ 'validate selection': [
44
+ {
45
+ guard: 'pending operations',
46
+ target: 'waiting',
47
+ },
48
+ {
49
+ actions: [validateSelectionAction],
50
+ target: 'idle',
51
+ },
52
+ ],
53
+ },
54
+ },
55
+ waiting: {
56
+ after: {
57
+ 0: [
58
+ {
59
+ guard: 'pending operations',
60
+ target: '.',
61
+ reenter: true,
62
+ },
63
+ {
64
+ target: 'idle',
65
+ actions: [validateSelectionAction],
66
+ },
67
+ ],
68
+ },
69
+ on: {
70
+ 'validate selection': {
71
+ target: '.',
72
+ reenter: true,
73
+ },
74
+ },
75
+ },
76
+ },
77
+ })
78
+
79
+ // This function will handle unexpected DOM changes inside the Editable rendering,
80
+ // and make sure that we can maintain a stable slateEditor.selection when that happens.
81
+ //
82
+ // For example, if this Editable is rendered inside something that might re-render
83
+ // this component (hidden contexts) while the user is still actively changing the
84
+ // contentEditable, this could interfere with the intermediate DOM selection,
85
+ // which again could be picked up by ReactEditor's event listeners.
86
+ // If that range is invalid at that point, the slate.editorSelection could be
87
+ // set either wrong, or invalid, to which slateEditor will throw exceptions
88
+ // that are impossible to recover properly from or result in a wrong selection.
89
+ //
90
+ // Also the other way around, when the ReactEditor will try to create a DOM Range
91
+ // from the current slateEditor.selection, it may throw unrecoverable errors
92
+ // if the current editor.selection is invalid according to the DOM.
93
+ // If this is the case, default to selecting the top of the document, if the
94
+ // user already had a selection.
95
+ function validateSelection(
96
+ slateEditor: PortableTextSlateEditor,
97
+ editorElement: HTMLDivElement,
98
+ ) {
99
+ if (!slateEditor.selection) {
100
+ return
101
+ }
102
+
103
+ let root: Document | ShadowRoot | undefined
104
+
105
+ try {
106
+ root = ReactEditor.findDocumentOrShadowRoot(slateEditor)
107
+ } catch {}
108
+
109
+ if (!root) {
110
+ // The editor has most likely been unmounted
111
+ return
112
+ }
113
+
114
+ // Return if the editor isn't the active element
115
+ if (editorElement !== root.activeElement) {
116
+ return
117
+ }
118
+ const window = ReactEditor.getWindow(slateEditor)
119
+ const domSelection = window.getSelection()
120
+ if (!domSelection || domSelection.rangeCount === 0) {
121
+ return
122
+ }
123
+ const existingDOMRange = domSelection.getRangeAt(0)
124
+ try {
125
+ const newDOMRange = ReactEditor.toDOMRange(
126
+ slateEditor,
127
+ slateEditor.selection,
128
+ )
129
+ if (
130
+ newDOMRange.startOffset !== existingDOMRange.startOffset ||
131
+ newDOMRange.endOffset !== existingDOMRange.endOffset
132
+ ) {
133
+ debug('DOM range out of sync, validating selection')
134
+ // Remove all ranges temporary
135
+ domSelection?.removeAllRanges()
136
+ // Set the correct range
137
+ domSelection.addRange(newDOMRange)
138
+ }
139
+ } catch {
140
+ debug(`Could not resolve selection, selecting top document`)
141
+ // Deselect the editor
142
+ Transforms.deselect(slateEditor)
143
+ // Select top document if there is a top block to select
144
+ if (slateEditor.children.length > 0) {
145
+ Transforms.select(slateEditor, Editor.start(slateEditor, []))
146
+ }
147
+ slateEditor.onChange()
148
+ }
149
+ }
@@ -1,5 +1,5 @@
1
1
  import type {Editor, Range} from 'slate'
2
- import type {EditorSelection} from '..'
2
+ import type {EditorSelection} from '../types/editor'
3
3
 
4
4
  // Is the editor currently receiving remote changes that are being applied to the content?
5
5
  export const IS_PROCESSING_REMOTE_CHANGES: WeakMap<Editor, boolean> =
@@ -1,5 +1,5 @@
1
1
  import type {Editor} from 'slate'
2
- import {defaultKeyGenerator} from './key-generator'
2
+ import {defaultKeyGenerator} from '../utils/key-generator'
3
3
 
4
4
  const CURRENT_UNDO_STEP: WeakMap<Editor, {undoStepId: string} | undefined> =
5
5
  new WeakMap()
package/src/index.ts CHANGED
@@ -40,7 +40,7 @@ export {useEditorSelector, type EditorSelector} from './editor/editor-selector'
40
40
  export type {EditorContext, EditorSnapshot} from './editor/editor-snapshot'
41
41
  export {usePortableTextEditor} from './editor/hooks/usePortableTextEditor'
42
42
  export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEditorSelection'
43
- export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
43
+ export {defaultKeyGenerator as keyGenerator} from './utils/key-generator'
44
44
  export {PortableTextEditor} from './editor/PortableTextEditor'
45
45
  export type {PortableTextEditorProps} from './editor/PortableTextEditor'
46
46
  export type {EditorEmittedEvent, MutationEvent} from './editor/relay-machine'
@@ -18,10 +18,10 @@ import {
18
18
  import type {Path, PortableTextBlock, PortableTextChild} from '@sanity/types'
19
19
  import {Element, Node, Text, Transforms, type Descendant} from 'slate'
20
20
  import type {EditorSchema} from '../editor/editor-schema'
21
+ import {KEY_TO_SLATE_ELEMENT} from '../editor/weakMaps'
21
22
  import type {PortableTextSlateEditor} from '../types/editor'
22
- import {isKeyedSegment} from '../utils'
23
+ import {isKeyedSegment} from '../utils/util.is-keyed-segment'
23
24
  import {isEqualToEmptyEditor, toSlateValue} from './values'
24
- import {KEY_TO_SLATE_ELEMENT} from './weakMaps'
25
25
 
26
26
  /**
27
27
  * Creates a function that can apply a patch onto a PortableTextSlateEditor.
@@ -1,7 +1,7 @@
1
1
  import {compileSchema, defineSchema} from '@portabletext/schema'
2
2
  import type {PortableTextBlock} from '@sanity/types'
3
3
  import {describe, expect, test} from 'vitest'
4
- import {defaultKeyGenerator} from '../editor/key-generator'
4
+ import {defaultKeyGenerator} from '../utils/key-generator'
5
5
  import {buildIndexMaps} from './build-index-maps'
6
6
 
7
7
  function blockObject(_key: string, name: string) {
@@ -1,6 +1,6 @@
1
1
  import {compileSchema, defineSchema} from '@portabletext/schema'
2
2
  import {createTestKeyGenerator} from '@portabletext/test'
3
- import type {EditorSnapshot} from '..'
3
+ import type {EditorSnapshot} from '../editor/editor-snapshot'
4
4
 
5
5
  export function createTestSnapshot(snapshot: {
6
6
  context?: Partial<EditorSnapshot['context']>
@@ -1,10 +1,12 @@
1
1
  import {Editor, type BaseRange, type Node} from 'slate'
2
2
  import {DOMEditor, isDOMNode} from 'slate-dom'
3
- import type {EditorSchema, EditorSelection} from '..'
4
3
  import type {EditorActor} from '../editor/editor-machine'
5
- import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
6
- import type {PortableTextSlateEditor} from '../types/editor'
7
- import * as utils from '../utils'
4
+ import type {EditorSchema} from '../editor/editor-schema'
5
+ import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
6
+ import {getBlockEndPoint} from '../utils/util.get-block-end-point'
7
+ import {getBlockStartPoint} from '../utils/util.get-block-start-point'
8
+ import {isSelectionCollapsed} from '../utils/util.is-selection-collapsed'
9
+ import {getBlockKeyFromSelectionPoint} from '../utils/util.selection-point'
8
10
  import {
9
11
  getFirstBlock,
10
12
  getLastBlock,
@@ -69,14 +71,14 @@ export function getEventPosition({
69
71
  block: eventPositionBlock,
70
72
  isEditor: false,
71
73
  selection: {
72
- anchor: utils.getBlockStartPoint({
74
+ anchor: getBlockStartPoint({
73
75
  context: editorActor.getSnapshot().context,
74
76
  block: {
75
77
  node: eventBlock,
76
78
  path: [{_key: eventBlock._key}],
77
79
  },
78
80
  }),
79
- focus: utils.getBlockEndPoint({
81
+ focus: getBlockEndPoint({
80
82
  context: editorActor.getSnapshot().context,
81
83
  block: {
82
84
  node: eventBlock,
@@ -100,7 +102,7 @@ export function getEventPosition({
100
102
  }
101
103
 
102
104
  if (
103
- utils.isSelectionCollapsed(eventSelection) &&
105
+ isSelectionCollapsed(eventSelection) &&
104
106
  eventBlock &&
105
107
  eventSelectionFocusBlockKey !== eventBlock._key
106
108
  ) {
@@ -110,14 +112,14 @@ export function getEventPosition({
110
112
  block: eventPositionBlock,
111
113
  isEditor: false,
112
114
  selection: {
113
- anchor: utils.getBlockStartPoint({
115
+ anchor: getBlockStartPoint({
114
116
  context: editorActor.getSnapshot().context,
115
117
  block: {
116
118
  node: eventBlock,
117
119
  path: [{_key: eventBlock._key}],
118
120
  },
119
121
  }),
120
- focus: utils.getBlockEndPoint({
122
+ focus: getBlockEndPoint({
121
123
  context: editorActor.getSnapshot().context,
122
124
  block: {
123
125
  node: eventBlock,
@@ -5,9 +5,9 @@ import {createEditor, type Descendant} from 'slate'
5
5
  import {beforeEach, describe, expect, it} from 'vitest'
6
6
  import {createActor} from 'xstate'
7
7
  import {editorMachine} from '../editor/editor-machine'
8
- import {defaultKeyGenerator} from '../editor/key-generator'
9
8
  import {withPlugins} from '../editor/plugins/with-plugins'
10
9
  import {relayMachine} from '../editor/relay-machine'
10
+ import {defaultKeyGenerator} from '../utils/key-generator'
11
11
  import {
12
12
  insertNodePatch,
13
13
  insertTextPatch,
@@ -1,5 +1,5 @@
1
1
  import type {EditorSchema} from '../editor/editor-schema'
2
- import {isTypedObject} from './asserters'
2
+ import {isTypedObject} from '../utils/asserters'
3
3
 
4
4
  type Path = Array<number>
5
5
 
@@ -1,5 +1,5 @@
1
- import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
2
1
  import type {EditorSelection} from '../types/editor'
2
+ import {getBlockKeyFromSelectionPoint} from '../utils/util.selection-point'
3
3
 
4
4
  export function getSelectionBlockKeys(selection: EditorSelection) {
5
5
  if (!selection) {
@@ -3,7 +3,7 @@ import type {EditorContext} from '../editor/editor-snapshot'
3
3
  import {
4
4
  getBlockKeyFromSelectionPoint,
5
5
  getChildKeyFromSelectionPoint,
6
- } from '../selection/selection-point'
6
+ } from '../utils/util.selection-point'
7
7
 
8
8
  export function getSelectionFocusText(
9
9
  context: Pick<EditorContext, 'schema' | 'value' | 'selection'>,
@@ -2,13 +2,13 @@ import {isSpan, isTextBlock} from '@portabletext/schema'
2
2
  import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
3
3
  import type {Path, Range} from 'slate'
4
4
  import type {EditorContext, EditorSnapshot} from '../editor/editor-snapshot'
5
+ import type {EditorSelectionPoint} from '../types/editor'
6
+ import {blockOffsetToSpanSelectionPoint} from '../utils/util.block-offset'
7
+ import {isEqualSelectionPoints} from '../utils/util.is-equal-selection-points'
5
8
  import {
6
9
  getBlockKeyFromSelectionPoint,
7
10
  getChildKeyFromSelectionPoint,
8
- } from '../selection/selection-point'
9
- import type {EditorSelectionPoint} from '../types/editor'
10
- import {isEqualSelectionPoints} from '../utils'
11
- import {blockOffsetToSpanSelectionPoint} from '../utils/util.block-offset'
11
+ } from '../utils/util.selection-point'
12
12
 
13
13
  export function toSlateRange(
14
14
  snapshot: {
@@ -1,5 +1,5 @@
1
1
  import {Editor, Node, Range, Text, Transforms} from 'slate'
2
- import {parseAnnotation} from '../internal-utils/parse-blocks'
2
+ import {parseAnnotation} from '../utils/parse-blocks'
3
3
  import type {BehaviorOperationImplementation} from './behavior.operations'
4
4
 
5
5
  export const addAnnotationOperationImplementation: BehaviorOperationImplementation<
@@ -1,6 +1,6 @@
1
1
  import {Transforms, type Element as SlateElement} from 'slate'
2
- import {parseBlock} from '../internal-utils/parse-blocks'
3
2
  import {toSlateValue} from '../internal-utils/values'
3
+ import {parseBlock} from '../utils/parse-blocks'
4
4
  import type {BehaviorOperationImplementation} from './behavior.operations'
5
5
 
6
6
  export const blockSetOperationImplementation: BehaviorOperationImplementation<
@@ -1,10 +1,10 @@
1
1
  import {isTextBlock} from '@portabletext/schema'
2
2
  import {omit} from 'lodash'
3
3
  import {Editor, Transforms} from 'slate'
4
- import {parseBlock} from '../internal-utils/parse-blocks'
4
+ import {KEY_TO_VALUE_ELEMENT} from '../editor/weakMaps'
5
5
  import {toSlateRange} from '../internal-utils/to-slate-range'
6
6
  import {fromSlateValue} from '../internal-utils/values'
7
- import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
7
+ import {parseBlock} from '../utils/parse-blocks'
8
8
  import type {BehaviorOperationImplementation} from './behavior.operations'
9
9
 
10
10
  export const blockUnsetOperationImplementation: BehaviorOperationImplementation<