@portabletext/editor 1.1.0 → 1.1.2

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 (79) hide show
  1. package/README.md +3 -0
  2. package/lib/index.d.mts +1680 -12
  3. package/lib/index.d.ts +1680 -12
  4. package/lib/index.esm.js +310 -162
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +310 -163
  7. package/lib/index.js.map +1 -1
  8. package/lib/index.mjs +310 -162
  9. package/lib/index.mjs.map +1 -1
  10. package/package.json +25 -38
  11. package/src/editor/Editable.tsx +51 -50
  12. package/src/editor/PortableTextEditor.tsx +42 -26
  13. package/src/editor/__tests__/PortableTextEditor.test.tsx +11 -12
  14. package/src/editor/__tests__/PortableTextEditorTester.tsx +2 -5
  15. package/src/editor/__tests__/RangeDecorations.test.tsx +6 -7
  16. package/src/editor/__tests__/handleClick.test.tsx +27 -7
  17. package/src/editor/__tests__/insert-block.test.tsx +6 -6
  18. package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +8 -8
  19. package/src/editor/__tests__/self-solving.test.tsx +176 -0
  20. package/src/editor/components/Element.tsx +15 -17
  21. package/src/editor/components/Leaf.tsx +40 -35
  22. package/src/editor/components/SlateContainer.tsx +2 -2
  23. package/src/editor/components/Synchronizer.tsx +62 -34
  24. package/src/editor/editor-machine.ts +195 -0
  25. package/src/editor/hooks/usePortableTextEditor.ts +1 -1
  26. package/src/editor/hooks/usePortableTextEditorSelection.tsx +12 -14
  27. package/src/editor/hooks/useSyncValue.test.tsx +9 -9
  28. package/src/editor/hooks/useSyncValue.ts +16 -19
  29. package/src/editor/nodes/DefaultAnnotation.tsx +1 -2
  30. package/src/editor/nodes/DefaultObject.tsx +1 -1
  31. package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +2 -5
  32. package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +28 -28
  33. package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +17 -17
  34. package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +8 -8
  35. package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +6 -6
  36. package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +2 -2
  37. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +47 -49
  38. package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +22 -11
  39. package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +9 -9
  40. package/src/editor/plugins/createWithEditableAPI.ts +8 -8
  41. package/src/editor/plugins/createWithHotKeys.ts +8 -12
  42. package/src/editor/plugins/createWithInsertBreak.ts +4 -4
  43. package/src/editor/plugins/createWithInsertData.ts +11 -16
  44. package/src/editor/plugins/createWithMaxBlocks.ts +1 -1
  45. package/src/editor/plugins/createWithObjectKeys.ts +10 -3
  46. package/src/editor/plugins/createWithPatches.ts +9 -12
  47. package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -2
  48. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +13 -5
  49. package/src/editor/plugins/createWithPortableTextLists.ts +3 -4
  50. package/src/editor/plugins/createWithPortableTextMarkModel.ts +24 -10
  51. package/src/editor/plugins/createWithPortableTextSelections.ts +9 -10
  52. package/src/editor/plugins/createWithSchemaTypes.ts +13 -4
  53. package/src/editor/plugins/createWithUndoRedo.ts +3 -7
  54. package/src/editor/plugins/createWithUtils.ts +6 -6
  55. package/src/editor/plugins/index.ts +21 -11
  56. package/src/index.ts +9 -3
  57. package/src/types/editor.ts +33 -33
  58. package/src/types/options.ts +3 -3
  59. package/src/types/slate.ts +4 -4
  60. package/src/utils/__tests__/dmpToOperations.test.ts +4 -4
  61. package/src/utils/__tests__/operationToPatches.test.ts +62 -62
  62. package/src/utils/__tests__/patchToOperations.test.ts +40 -40
  63. package/src/utils/__tests__/ranges.test.ts +2 -2
  64. package/src/utils/__tests__/valueNormalization.test.tsx +14 -2
  65. package/src/utils/__tests__/values.test.ts +17 -17
  66. package/src/utils/applyPatch.ts +10 -12
  67. package/src/utils/getPortableTextMemberSchemaTypes.ts +8 -8
  68. package/src/utils/operationToPatches.ts +5 -9
  69. package/src/utils/paths.ts +5 -5
  70. package/src/utils/ranges.ts +4 -5
  71. package/src/utils/selection.ts +2 -2
  72. package/src/utils/ucs2Indices.ts +2 -2
  73. package/src/utils/validateValue.ts +3 -25
  74. package/src/utils/values.ts +7 -8
  75. package/src/utils/weakMaps.ts +2 -2
  76. package/src/utils/withChanges.ts +1 -1
  77. package/src/utils/withUndoRedo.ts +1 -1
  78. package/src/utils/withoutPatching.ts +1 -1
  79. package/src/editor/__tests__/utils.ts +0 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -47,66 +47,54 @@
47
47
  "is-hotkey-esm": "^1.0.0",
48
48
  "lodash": "^4.17.21",
49
49
  "slate": "0.103.0",
50
- "slate-react": "0.108.0"
50
+ "slate-react": "0.110.1",
51
+ "xstate": "^5.18.2"
51
52
  },
52
53
  "devDependencies": {
53
- "@cucumber/cucumber-expressions": "^17.1.0",
54
- "@cucumber/gherkin": "^29.0.0",
55
- "@cucumber/messages": "^26.0.0",
54
+ "@babel/preset-env": "^7.25.4",
55
+ "@babel/preset-react": "^7.24.7",
56
56
  "@jest/globals": "^29.7.0",
57
57
  "@jest/types": "^29.6.3",
58
- "@playwright/test": "1.46.1",
58
+ "@playwright/test": "1.47.2",
59
59
  "@portabletext/toolkit": "^2.0.15",
60
60
  "@sanity/block-tools": "^3.55.0",
61
61
  "@sanity/diff-match-patch": "^3.1.1",
62
- "@sanity/eslint-config-i18n": "^1.1.0",
63
- "@sanity/eslint-config-studio": "^4.0.0",
64
- "@sanity/pkg-utils": "^6.10.10",
62
+ "@sanity/pkg-utils": "^6.11.2",
65
63
  "@sanity/schema": "^3.55.0",
66
64
  "@sanity/test": "0.0.1-alpha.1",
67
65
  "@sanity/types": "^3.55.0",
68
- "@sanity/ui": "^2.8.8",
66
+ "@sanity/ui": "^2.8.9",
69
67
  "@sanity/util": "^3.55.0",
70
68
  "@testing-library/dom": "^10.4.0",
71
- "@testing-library/react": "^16.0.0",
69
+ "@testing-library/jest-dom": "^6.5.0",
70
+ "@testing-library/react": "^16.0.1",
72
71
  "@types/debug": "^4.1.5",
73
72
  "@types/express": "^4.17.21",
74
- "@types/express-ws": "^3.0.4",
73
+ "@types/express-ws": "^3.0.5",
75
74
  "@types/lodash": "^4.17.7",
76
75
  "@types/node": "^18.19.8",
77
76
  "@types/node-ipc": "^9.2.3",
78
77
  "@types/react": "^18.3.3",
79
78
  "@types/react-dom": "^18.3.0",
80
79
  "@types/ws": "~8.5.12",
81
- "@typescript-eslint/eslint-plugin": "^8.2.0",
82
- "@typescript-eslint/parser": "^8.2.0",
83
80
  "@vitejs/plugin-react": "^4.3.1",
84
81
  "dotenv": "^16.4.5",
85
- "eslint": "^8.57.0",
86
- "eslint-config-prettier": "^9.1.0",
87
- "eslint-config-sanity": "^7.1.2",
88
- "eslint-import-resolver-typescript": "^3.6.1",
89
- "eslint-plugin-import": "^2.29.1",
90
- "eslint-plugin-prettier": "^5.2.1",
91
- "eslint-plugin-react-compiler": "0.0.0-experimental-eeb1b2a-20240818",
92
- "eslint-plugin-tsdoc": "^0.3.0",
93
- "eslint-plugin-unicorn": "^55.0.0",
94
- "eslint-plugin-unused-imports": "^4.1.3",
95
82
  "express": "^4.19.2",
96
83
  "express-ws": "^5.0.2",
97
84
  "jest": "^29.7.0",
98
- "jest-dev-server": "^10.1.0",
99
- "jest-environment-jsdom": "^29.7.0",
85
+ "jest-dev-server": "^10.1.1",
100
86
  "jest-environment-node": "^29.7.0",
87
+ "jsdom": "^25.0.1",
101
88
  "node-ipc": "npm:@node-ipc/compat@9.2.5",
102
89
  "react": "^18.3.1",
103
90
  "react-dom": "^18.3.1",
104
91
  "rxjs": "^7.8.1",
105
- "styled-components": "^6.1.12",
92
+ "styled-components": "^6.1.13",
106
93
  "ts-node": "^10.9.2",
107
- "tsx": "^4.17.0",
108
- "typescript": "5.5.4",
109
- "vite": "^5.4.2"
94
+ "typescript": "5.6.2",
95
+ "vite": "^5.4.2",
96
+ "vitest": "^2.1.1",
97
+ "@sanity/gherkin-driver": "^0.0.1"
110
98
  },
111
99
  "peerDependencies": {
112
100
  "@sanity/block-tools": "^3.47.1",
@@ -114,8 +102,8 @@
114
102
  "@sanity/types": "^3.47.1",
115
103
  "@sanity/util": "^3.47.1",
116
104
  "react": "^16.9 || ^17 || ^18",
117
- "rxjs": "^7",
118
- "styled-components": "^6.1"
105
+ "rxjs": "^7.8.1",
106
+ "styled-components": "^6.1.13"
119
107
  },
120
108
  "engines": {
121
109
  "node": ">=18"
@@ -125,15 +113,14 @@
125
113
  },
126
114
  "scripts": {
127
115
  "build": "pkg-utils build --strict --check --clean",
128
- "check:lint": "eslint .",
116
+ "check:lint": "biome lint .",
129
117
  "check:types": "tsc",
130
118
  "clean": "del .turbo && del lib && del node_modules",
131
119
  "dev": "pkg-utils watch",
132
- "dev:e2e-server": "cd ./e2e-tests/ && tsx serve",
133
- "lint:fix": "eslint . --fix",
134
- "test": "jest",
120
+ "lint:fix": "biome lint --write .",
121
+ "test": "vitest --run",
122
+ "test:watch": "vitest",
135
123
  "test:e2e": "jest --config=e2e-tests/e2e.config.ts",
136
- "test:e2e:watch": "jest --config=e2e-tests/e2e.config.ts --watch",
137
- "test:watch": "jest --watch"
124
+ "test:e2e:watch": "jest --config=e2e-tests/e2e.config.ts --watch"
138
125
  }
139
126
  }
@@ -1,4 +1,4 @@
1
- import {type PortableTextBlock} from '@sanity/types'
1
+ import type {PortableTextBlock} from '@sanity/types'
2
2
  import {isEqual, noop} from 'lodash'
3
3
  import {
4
4
  forwardRef,
@@ -35,23 +35,23 @@ import {
35
35
  type RenderElementProps,
36
36
  type RenderLeafProps,
37
37
  } from 'slate-react'
38
- import {
39
- type EditorChange,
40
- type EditorSelection,
41
- type OnCopyFn,
42
- type OnPasteFn,
43
- type RangeDecoration,
44
- type RenderAnnotationFunction,
45
- type RenderBlockFunction,
46
- type RenderChildFunction,
47
- type RenderDecoratorFunction,
48
- type RenderListItemFunction,
49
- type RenderPlaceholderFunction,
50
- type RenderStyleFunction,
51
- type ScrollSelectionIntoViewFunction,
38
+ import type {
39
+ EditorChange,
40
+ EditorSelection,
41
+ OnCopyFn,
42
+ OnPasteFn,
43
+ RangeDecoration,
44
+ RenderAnnotationFunction,
45
+ RenderBlockFunction,
46
+ RenderChildFunction,
47
+ RenderDecoratorFunction,
48
+ RenderListItemFunction,
49
+ RenderPlaceholderFunction,
50
+ RenderStyleFunction,
51
+ ScrollSelectionIntoViewFunction,
52
52
  } from '../types/editor'
53
- import {type HotkeyOptions} from '../types/options'
54
- import {type SlateTextBlock, type VoidElement} from '../types/slate'
53
+ import type {HotkeyOptions} from '../types/options'
54
+ import type {SlateTextBlock, VoidElement} from '../types/slate'
55
55
  import {debugWithName} from '../utils/debug'
56
56
  import {
57
57
  moveRangeByOperation,
@@ -161,15 +161,15 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
161
161
 
162
162
  const rangeDecorationsRef = useRef(rangeDecorations)
163
163
 
164
- const {change$, schemaTypes} = portableTextEditor
164
+ const {editorActor, schemaTypes} = portableTextEditor
165
165
  const slateEditor = useSlate()
166
166
 
167
167
  const blockTypeName = schemaTypes.block.name
168
168
 
169
169
  // React/UI-specific plugins
170
170
  const withInsertData = useMemo(
171
- () => createWithInsertData(change$, schemaTypes, keyGenerator),
172
- [change$, keyGenerator, schemaTypes],
171
+ () => createWithInsertData(editorActor, schemaTypes, keyGenerator),
172
+ [editorActor, keyGenerator, schemaTypes],
173
173
  )
174
174
  const withHotKeys = useMemo(
175
175
  () => createWithHotkeys(schemaTypes, portableTextEditor, hotkeys),
@@ -278,13 +278,16 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
278
278
  // Output selection here in those cases where the editor selection was the same, and there are no set_selection operations made.
279
279
  // The selection is usually automatically emitted to change$ by the withPortableTextSelections plugin whenever there is a set_selection operation applied.
280
280
  if (!slateEditor.operations.some((o) => o.type === 'set_selection')) {
281
- change$.next({type: 'selection', selection: normalizedSelection})
281
+ editorActor.send({
282
+ type: 'selection',
283
+ selection: normalizedSelection,
284
+ })
282
285
  }
283
286
  slateEditor.onChange()
284
287
  }
285
288
  }
286
289
  }
287
- }, [propsSelection, slateEditor, blockTypeName, change$])
290
+ }, [editorActor, propsSelection, slateEditor])
288
291
 
289
292
  const syncRangeDecorations = useCallback(
290
293
  (operation?: Operation) => {
@@ -341,33 +344,31 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
341
344
  return
342
345
  }
343
346
  }
344
- setRangeDecorationsState([])
347
+ if (rangeDecorationState.length > 0) {
348
+ setRangeDecorationsState([])
349
+ }
345
350
  },
346
351
  [portableTextEditor, rangeDecorations, schemaTypes, slateEditor],
347
352
  )
348
353
 
349
- // Subscribe to change$ and restore selection from props when the editor has been initialized properly with it's value
354
+ // Restore selection from props when the editor has been initialized properly with it's value
350
355
  useEffect(() => {
351
- // debug('Subscribing to editor changes$')
352
- const sub = change$.subscribe((next: EditorChange): void => {
353
- switch (next.type) {
354
- case 'ready':
355
- restoreSelectionFromProps()
356
- break
357
- case 'invalidValue':
358
- setHasInvalidValue(true)
359
- break
360
- case 'value':
361
- setHasInvalidValue(false)
362
- break
363
- default:
364
- }
356
+ const onReady = editorActor.on('ready', () => {
357
+ restoreSelectionFromProps()
358
+ })
359
+ const onInvalidValue = editorActor.on('invalid value', () => {
360
+ setHasInvalidValue(true)
365
361
  })
362
+ const onValueChanged = editorActor.on('value changed', () => {
363
+ setHasInvalidValue(false)
364
+ })
365
+
366
366
  return () => {
367
- // debug('Unsubscribing to changes$')
368
- sub.unsubscribe()
367
+ onReady.unsubscribe()
368
+ onInvalidValue.unsubscribe()
369
+ onValueChanged.unsubscribe()
369
370
  }
370
- }, [change$, restoreSelectionFromProps])
371
+ }, [editorActor, restoreSelectionFromProps])
371
372
 
372
373
  // Restore selection from props when it changes
373
374
  useEffect(() => {
@@ -449,7 +450,7 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
449
450
  slateEditor.insertData(event.clipboardData)
450
451
  } else {
451
452
  // Resolve it as promise (can be either async promise or sync return value)
452
- change$.next({type: 'loading', isLoading: true})
453
+ editorActor.send({type: 'loading'})
453
454
  Promise.resolve(onPasteResult)
454
455
  .then((result) => {
455
456
  debug('Custom paste function from client resolved', result)
@@ -470,15 +471,15 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
470
471
  }
471
472
  })
472
473
  .catch((error) => {
473
- console.error(error) // eslint-disable-line no-console
474
+ console.error(error)
474
475
  return error
475
476
  })
476
477
  .finally(() => {
477
- change$.next({type: 'loading', isLoading: false})
478
+ editorActor.send({type: 'done loading'})
478
479
  })
479
480
  }
480
481
  },
481
- [change$, onPaste, portableTextEditor, schemaTypes, slateEditor],
482
+ [onPaste, portableTextEditor, schemaTypes, slateEditor],
482
483
  )
483
484
 
484
485
  const handleOnFocus: FocusEventHandler<HTMLDivElement> = useCallback(
@@ -493,18 +494,18 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
493
494
  Transforms.select(slateEditor, Editor.start(slateEditor, []))
494
495
  slateEditor.onChange()
495
496
  }
496
- change$.next({type: 'focus', event})
497
+ editorActor.send({type: 'focus', event})
497
498
  const newSelection = PortableTextEditor.getSelection(portableTextEditor)
498
499
  // If the selection is the same, emit it explicitly here as there is no actual onChange event triggered.
499
500
  if (selection === newSelection) {
500
- change$.next({
501
+ editorActor.send({
501
502
  type: 'selection',
502
503
  selection,
503
504
  })
504
505
  }
505
506
  }
506
507
  },
507
- [onFocus, portableTextEditor, change$, slateEditor],
508
+ [editorActor, onFocus, portableTextEditor, slateEditor],
508
509
  )
509
510
 
510
511
  const handleClick = useCallback(
@@ -540,10 +541,10 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
540
541
  onBlur(event)
541
542
  }
542
543
  if (!event.isPropagationStopped()) {
543
- change$.next({type: 'blur', event})
544
+ editorActor.send({type: 'blur', event})
544
545
  }
545
546
  },
546
- [change$, onBlur],
547
+ [editorActor, onBlur],
547
548
  )
548
549
 
549
550
  const handleOnBeforeInput = useCallback(
@@ -1,30 +1,33 @@
1
- import {
2
- type ArrayDefinition,
3
- type ArraySchemaType,
4
- type BlockSchemaType,
5
- type ObjectSchemaType,
6
- type Path,
7
- type PortableTextBlock,
8
- type PortableTextChild,
9
- type PortableTextObject,
10
- type SpanSchemaType,
1
+ import type {
2
+ ArrayDefinition,
3
+ ArraySchemaType,
4
+ BlockSchemaType,
5
+ ObjectSchemaType,
6
+ Path,
7
+ PortableTextBlock,
8
+ PortableTextChild,
9
+ PortableTextObject,
10
+ SpanSchemaType,
11
11
  } from '@sanity/types'
12
12
  import {Component, type MutableRefObject, type PropsWithChildren} from 'react'
13
13
  import {Subject} from 'rxjs'
14
- import {
15
- type EditableAPI,
16
- type EditableAPIDeleteOptions,
17
- type EditorChange,
18
- type EditorChanges,
19
- type EditorSelection,
20
- type PatchObservable,
21
- type PortableTextMemberSchemaTypes,
14
+ import {createActor, type Subscription} from 'xstate'
15
+ import type {
16
+ EditableAPI,
17
+ EditableAPIDeleteOptions,
18
+ EditorChange,
19
+ EditorChanges,
20
+ EditorSelection,
21
+ MutationChange,
22
+ PatchObservable,
23
+ PortableTextMemberSchemaTypes,
22
24
  } from '../types/editor'
23
25
  import {debugWithName} from '../utils/debug'
24
26
  import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
25
27
  import {compileType} from '../utils/schema'
26
28
  import {SlateContainer} from './components/SlateContainer'
27
29
  import {Synchronizer} from './components/Synchronizer'
30
+ import {editorMachine, type EditorActor} from './editor-machine'
28
31
  import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
29
32
  import {
30
33
  defaultKeyGenerator,
@@ -92,6 +95,11 @@ export type PortableTextEditorProps = PropsWithChildren<{
92
95
  * @public
93
96
  */
94
97
  export class PortableTextEditor extends Component<PortableTextEditorProps> {
98
+ /**
99
+ * @internal
100
+ * Don't use this API directly. It's subject to change.
101
+ */
102
+ public editorActor: EditorActor
95
103
  /**
96
104
  * An observable of all the editor changes.
97
105
  */
@@ -118,7 +126,8 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
118
126
  )
119
127
  }
120
128
 
121
- this.change$.next({type: 'loading', isLoading: true})
129
+ this.editorActor = createActor(editorMachine)
130
+ this.editorActor.start()
122
131
 
123
132
  this.schemaTypes = getPortableTextMemberSchemaTypes(
124
133
  props.schemaType.hasOwnProperty('jsonType')
@@ -154,14 +163,13 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
154
163
  }
155
164
 
156
165
  render() {
157
- const {onChange, value, children, patches$, incomingPatches$} = this.props
158
- const {change$} = this
166
+ const {value, children, patches$, incomingPatches$} = this.props
159
167
  const _patches$ = incomingPatches$ || patches$ // Backward compatibility
160
168
 
161
169
  const maxBlocks =
162
170
  typeof this.props.maxBlocks === 'undefined'
163
171
  ? undefined
164
- : parseInt(this.props.maxBlocks.toString(), 10) || undefined
172
+ : Number.parseInt(this.props.maxBlocks.toString(), 10) || undefined
165
173
 
166
174
  const readOnly = Boolean(this.props.readOnly)
167
175
  const keyGenerator = this.props.keyGenerator || defaultKeyGenerator
@@ -176,11 +184,20 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
176
184
  <PortableTextEditorKeyGeneratorContext.Provider value={keyGenerator}>
177
185
  <PortableTextEditorContext.Provider value={this}>
178
186
  <PortableTextEditorReadOnlyContext.Provider value={readOnly}>
179
- <PortableTextEditorSelectionProvider change$={change$}>
187
+ <PortableTextEditorSelectionProvider
188
+ editorActor={this.editorActor}
189
+ >
180
190
  <Synchronizer
181
- change$={change$}
191
+ editorActor={this.editorActor}
182
192
  getValue={this.getValue}
183
- onChange={onChange}
193
+ onChange={(change) => {
194
+ this.props.onChange(change)
195
+ /**
196
+ * For backwards compatibility, we relay all changes to the
197
+ * `change$` Subject as well.
198
+ */
199
+ this.change$.next(change)
200
+ }}
184
201
  value={value}
185
202
  />
186
203
  {children}
@@ -239,7 +256,6 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
239
256
  editor: PortableTextEditor,
240
257
  element: PortableTextBlock | PortableTextChild,
241
258
  ) => {
242
- // eslint-disable-next-line react/no-find-dom-node
243
259
  return editor.editable?.findDOMNode(element)
244
260
  }
245
261
  static findByPath = (editor: PortableTextEditor, path: Path) => {
@@ -1,9 +1,8 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
- /* eslint-disable no-irregular-whitespace */
3
- import {type PortableTextBlock} from '@sanity/types'
1
+ import type {PortableTextBlock} from '@sanity/types'
4
2
  import {render, waitFor} from '@testing-library/react'
5
3
  import {createRef, type RefObject} from 'react'
6
- import {type EditorSelection} from '../..'
4
+ import {describe, expect, it, vi} from 'vitest'
5
+ import type {EditorSelection} from '../..'
7
6
  import {PortableTextEditor} from '../PortableTextEditor'
8
7
  import {PortableTextEditorTester, schemaType} from './PortableTextEditorTester'
9
8
 
@@ -19,7 +18,7 @@ const renderPlaceholder = () => 'Jot something down here'
19
18
  describe('initialization', () => {
20
19
  it('receives initial onChange events and has custom placeholder', async () => {
21
20
  const editorRef: RefObject<PortableTextEditor> = createRef()
22
- const onChange = jest.fn()
21
+ const onChange = vi.fn()
23
22
  const {container} = render(
24
23
  <PortableTextEditorTester
25
24
  onChange={onChange}
@@ -89,7 +88,7 @@ describe('initialization', () => {
89
88
  })
90
89
  it('takes value from props and confirms it by emitting value change event', async () => {
91
90
  const initialValue = [helloBlock]
92
- const onChange = jest.fn()
91
+ const onChange = vi.fn()
93
92
  const editorRef = createRef<PortableTextEditor>()
94
93
  render(
95
94
  <PortableTextEditorTester
@@ -121,7 +120,7 @@ describe('initialization', () => {
121
120
  focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
122
121
  backward: false,
123
122
  }
124
- const onChange = jest.fn()
123
+ const onChange = vi.fn()
125
124
  render(
126
125
  <PortableTextEditorTester
127
126
  onChange={onChange}
@@ -165,7 +164,7 @@ describe('initialization', () => {
165
164
  focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 3},
166
165
  backward: false,
167
166
  }
168
- const onChange = jest.fn()
167
+ const onChange = vi.fn()
169
168
  const {rerender} = render(
170
169
  <PortableTextEditorTester
171
170
  onChange={onChange}
@@ -224,7 +223,7 @@ describe('initialization', () => {
224
223
  anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
225
224
  focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
226
225
  }
227
- const onChange = jest.fn()
226
+ const onChange = vi.fn()
228
227
  render(
229
228
  <PortableTextEditorTester
230
229
  onChange={onChange}
@@ -267,7 +266,7 @@ describe('initialization', () => {
267
266
  anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
268
267
  focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
269
268
  }
270
- const onChange = jest.fn()
269
+ const onChange = vi.fn()
271
270
  let _rerender: any
272
271
  await waitFor(() => {
273
272
  render(
@@ -301,7 +300,7 @@ describe('initialization', () => {
301
300
  expect(onChange).toHaveBeenCalledWith({type: 'value', value})
302
301
  })
303
302
  value = [{_type: 'banana', _key: '123'}]
304
- const newOnChange = jest.fn()
303
+ const newOnChange = vi.fn()
305
304
  _rerender(
306
305
  <PortableTextEditorTester
307
306
  onChange={newOnChange}
@@ -353,7 +352,7 @@ describe('initialization', () => {
353
352
  anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
354
353
  focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
355
354
  }
356
- const onChange = jest.fn()
355
+ const onChange = vi.fn()
357
356
  render(
358
357
  <PortableTextEditorTester
359
358
  onChange={onChange}
@@ -1,4 +1,3 @@
1
- import {jest} from '@jest/globals'
2
1
  import {Schema} from '@sanity/schema'
3
2
  import {defineArrayMember, defineField} from '@sanity/types'
4
3
  import {
@@ -8,6 +7,7 @@ import {
8
7
  useMemo,
9
8
  type ForwardedRef,
10
9
  } from 'react'
10
+ import {vi} from 'vitest'
11
11
  import {
12
12
  PortableTextEditable,
13
13
  PortableTextEditor,
@@ -100,10 +100,7 @@ export const PortableTextEditorTester = forwardRef(
100
100
  key++
101
101
  return `${key}`
102
102
  }, [])
103
- const onChange = useMemo(
104
- () => props.onChange || jest.fn(),
105
- [props.onChange],
106
- )
103
+ const onChange = useMemo(() => props.onChange || vi.fn(), [props.onChange])
107
104
  return (
108
105
  <PortableTextEditor
109
106
  schemaType={props.schemaType}
@@ -1,10 +1,9 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
- /* eslint-disable no-irregular-whitespace */
3
- import {type PortableTextBlock} from '@sanity/types'
1
+ import type {PortableTextBlock} from '@sanity/types'
4
2
  import {render, waitFor} from '@testing-library/react'
5
3
  import {createRef, type ReactNode, type RefObject} from 'react'
6
- import {type RangeDecoration} from '../..'
7
- import {type PortableTextEditor} from '../PortableTextEditor'
4
+ import {describe, expect, it, vi} from 'vitest'
5
+ import type {RangeDecoration} from '../..'
6
+ import type {PortableTextEditor} from '../PortableTextEditor'
8
7
  import {PortableTextEditorTester, schemaType} from './PortableTextEditorTester'
9
8
 
10
9
  const helloBlock: PortableTextBlock = {
@@ -22,9 +21,9 @@ const RangeDecorationTestComponent = ({children}: {children?: ReactNode}) => {
22
21
  }
23
22
 
24
23
  describe('RangeDecorations', () => {
25
- it.only('only render range decorations as necessary', async () => {
24
+ it('only render range decorations as necessary', async () => {
26
25
  const editorRef: RefObject<PortableTextEditor> = createRef()
27
- const onChange = jest.fn()
26
+ const onChange = vi.fn()
28
27
  const value = [helloBlock]
29
28
  let rangeDecorations: RangeDecoration[] = [
30
29
  {
@@ -1,9 +1,29 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
1
  import {fireEvent, render, waitFor} from '@testing-library/react'
3
- import {createRef, type RefObject} from 'react'
2
+ import {act, createRef, type RefObject} from 'react'
3
+ import {describe, expect, it, vi} from 'vitest'
4
4
  import {PortableTextEditor} from '../PortableTextEditor'
5
5
  import {PortableTextEditorTester, schemaType} from './PortableTextEditorTester'
6
- import {getEditableElement} from './utils'
6
+
7
+ async function getEditableElement(
8
+ component: ReturnType<typeof render>,
9
+ ): Promise<Element> {
10
+ await act(async () => component)
11
+ const element = component.container.querySelector(
12
+ '[data-slate-editor="true"]',
13
+ )
14
+ if (!element) {
15
+ throw new Error('Could not find element')
16
+ }
17
+ /**
18
+ * Manually add this because JSDom doesn't implement this and Slate checks for it
19
+ * internally before doing stuff.
20
+ *
21
+ * https://github.com/jsdom/jsdom/issues/1670
22
+ */
23
+ // @ts-ignore
24
+ element.isContentEditable = true
25
+ return element
26
+ }
7
27
 
8
28
  describe('adds empty text block if its needed', () => {
9
29
  const newBlock = {
@@ -34,7 +54,7 @@ describe('adds empty text block if its needed', () => {
34
54
  }
35
55
 
36
56
  const editorRef: RefObject<PortableTextEditor> = createRef()
37
- const onChange = jest.fn()
57
+ const onChange = vi.fn()
38
58
  const component = render(
39
59
  <PortableTextEditorTester
40
60
  onChange={onChange}
@@ -96,7 +116,7 @@ describe('adds empty text block if its needed', () => {
96
116
  }
97
117
 
98
118
  const editorRef: RefObject<PortableTextEditor> = createRef()
99
- const onChange = jest.fn()
119
+ const onChange = vi.fn()
100
120
  const component = render(
101
121
  <PortableTextEditorTester
102
122
  onChange={onChange}
@@ -151,7 +171,7 @@ describe('adds empty text block if its needed', () => {
151
171
  }
152
172
 
153
173
  const editorRef: RefObject<PortableTextEditor> = createRef()
154
- const onChange = jest.fn()
174
+ const onChange = vi.fn()
155
175
  const component = render(
156
176
  <PortableTextEditorTester
157
177
  onChange={onChange}
@@ -217,7 +237,7 @@ describe('adds empty text block if its needed', () => {
217
237
  }
218
238
 
219
239
  const editorRef: RefObject<PortableTextEditor> = createRef()
220
- const onChange = jest.fn()
240
+ const onChange = vi.fn()
221
241
  const component = render(
222
242
  <PortableTextEditorTester
223
243
  onChange={onChange}