@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.
- package/README.md +3 -0
- package/lib/index.d.mts +1680 -12
- package/lib/index.d.ts +1680 -12
- package/lib/index.esm.js +310 -162
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +310 -163
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +310 -162
- package/lib/index.mjs.map +1 -1
- package/package.json +25 -38
- package/src/editor/Editable.tsx +51 -50
- package/src/editor/PortableTextEditor.tsx +42 -26
- package/src/editor/__tests__/PortableTextEditor.test.tsx +11 -12
- package/src/editor/__tests__/PortableTextEditorTester.tsx +2 -5
- package/src/editor/__tests__/RangeDecorations.test.tsx +6 -7
- package/src/editor/__tests__/handleClick.test.tsx +27 -7
- package/src/editor/__tests__/insert-block.test.tsx +6 -6
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +8 -8
- package/src/editor/__tests__/self-solving.test.tsx +176 -0
- package/src/editor/components/Element.tsx +15 -17
- package/src/editor/components/Leaf.tsx +40 -35
- package/src/editor/components/SlateContainer.tsx +2 -2
- package/src/editor/components/Synchronizer.tsx +62 -34
- package/src/editor/editor-machine.ts +195 -0
- package/src/editor/hooks/usePortableTextEditor.ts +1 -1
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +12 -14
- package/src/editor/hooks/useSyncValue.test.tsx +9 -9
- package/src/editor/hooks/useSyncValue.ts +16 -19
- package/src/editor/nodes/DefaultAnnotation.tsx +1 -2
- package/src/editor/nodes/DefaultObject.tsx +1 -1
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +2 -5
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +28 -28
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +17 -17
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +8 -8
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +6 -6
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +2 -2
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +47 -49
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +22 -11
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +9 -9
- package/src/editor/plugins/createWithEditableAPI.ts +8 -8
- package/src/editor/plugins/createWithHotKeys.ts +8 -12
- package/src/editor/plugins/createWithInsertBreak.ts +4 -4
- package/src/editor/plugins/createWithInsertData.ts +11 -16
- package/src/editor/plugins/createWithMaxBlocks.ts +1 -1
- package/src/editor/plugins/createWithObjectKeys.ts +10 -3
- package/src/editor/plugins/createWithPatches.ts +9 -12
- package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -2
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +13 -5
- package/src/editor/plugins/createWithPortableTextLists.ts +3 -4
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +24 -10
- package/src/editor/plugins/createWithPortableTextSelections.ts +9 -10
- package/src/editor/plugins/createWithSchemaTypes.ts +13 -4
- package/src/editor/plugins/createWithUndoRedo.ts +3 -7
- package/src/editor/plugins/createWithUtils.ts +6 -6
- package/src/editor/plugins/index.ts +21 -11
- package/src/index.ts +9 -3
- package/src/types/editor.ts +33 -33
- package/src/types/options.ts +3 -3
- package/src/types/slate.ts +4 -4
- package/src/utils/__tests__/dmpToOperations.test.ts +4 -4
- package/src/utils/__tests__/operationToPatches.test.ts +62 -62
- package/src/utils/__tests__/patchToOperations.test.ts +40 -40
- package/src/utils/__tests__/ranges.test.ts +2 -2
- package/src/utils/__tests__/valueNormalization.test.tsx +14 -2
- package/src/utils/__tests__/values.test.ts +17 -17
- package/src/utils/applyPatch.ts +10 -12
- package/src/utils/getPortableTextMemberSchemaTypes.ts +8 -8
- package/src/utils/operationToPatches.ts +5 -9
- package/src/utils/paths.ts +5 -5
- package/src/utils/ranges.ts +4 -5
- package/src/utils/selection.ts +2 -2
- package/src/utils/ucs2Indices.ts +2 -2
- package/src/utils/validateValue.ts +3 -25
- package/src/utils/values.ts +7 -8
- package/src/utils/weakMaps.ts +2 -2
- package/src/utils/withChanges.ts +1 -1
- package/src/utils/withUndoRedo.ts +1 -1
- package/src/utils/withoutPatching.ts +1 -1
- 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.
|
|
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.
|
|
50
|
+
"slate-react": "0.110.1",
|
|
51
|
+
"xstate": "^5.18.2"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@
|
|
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.
|
|
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/
|
|
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.
|
|
66
|
+
"@sanity/ui": "^2.8.9",
|
|
69
67
|
"@sanity/util": "^3.55.0",
|
|
70
68
|
"@testing-library/dom": "^10.4.0",
|
|
71
|
-
"@testing-library/
|
|
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.
|
|
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.
|
|
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.
|
|
92
|
+
"styled-components": "^6.1.13",
|
|
106
93
|
"ts-node": "^10.9.2",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
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": "
|
|
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
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"test": "
|
|
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
|
}
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
54
|
-
import
|
|
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 {
|
|
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(
|
|
172
|
-
[
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
347
|
+
if (rangeDecorationState.length > 0) {
|
|
348
|
+
setRangeDecorationsState([])
|
|
349
|
+
}
|
|
345
350
|
},
|
|
346
351
|
[portableTextEditor, rangeDecorations, schemaTypes, slateEditor],
|
|
347
352
|
)
|
|
348
353
|
|
|
349
|
-
//
|
|
354
|
+
// Restore selection from props when the editor has been initialized properly with it's value
|
|
350
355
|
useEffect(() => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
368
|
-
|
|
367
|
+
onReady.unsubscribe()
|
|
368
|
+
onInvalidValue.unsubscribe()
|
|
369
|
+
onValueChanged.unsubscribe()
|
|
369
370
|
}
|
|
370
|
-
}, [
|
|
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
|
-
|
|
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)
|
|
474
|
+
console.error(error)
|
|
474
475
|
return error
|
|
475
476
|
})
|
|
476
477
|
.finally(() => {
|
|
477
|
-
|
|
478
|
+
editorActor.send({type: 'done loading'})
|
|
478
479
|
})
|
|
479
480
|
}
|
|
480
481
|
},
|
|
481
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
501
|
+
editorActor.send({
|
|
501
502
|
type: 'selection',
|
|
502
503
|
selection,
|
|
503
504
|
})
|
|
504
505
|
}
|
|
505
506
|
}
|
|
506
507
|
},
|
|
507
|
-
[onFocus, portableTextEditor,
|
|
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
|
-
|
|
544
|
+
editorActor.send({type: 'blur', event})
|
|
544
545
|
}
|
|
545
546
|
},
|
|
546
|
-
[
|
|
547
|
+
[editorActor, onBlur],
|
|
547
548
|
)
|
|
548
549
|
|
|
549
550
|
const handleOnBeforeInput = useCallback(
|
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
|
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 {
|
|
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
|
|
187
|
+
<PortableTextEditorSelectionProvider
|
|
188
|
+
editorActor={this.editorActor}
|
|
189
|
+
>
|
|
180
190
|
<Synchronizer
|
|
181
|
-
|
|
191
|
+
editorActor={this.editorActor}
|
|
182
192
|
getValue={this.getValue}
|
|
183
|
-
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 {
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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 {
|
|
7
|
-
import
|
|
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
|
|
24
|
+
it('only render range decorations as necessary', async () => {
|
|
26
25
|
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
27
|
-
const onChange =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
240
|
+
const onChange = vi.fn()
|
|
221
241
|
const component = render(
|
|
222
242
|
<PortableTextEditorTester
|
|
223
243
|
onChange={onChange}
|