@portabletext/editor 1.48.14 → 1.49.0

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 (48) hide show
  1. package/lib/_chunks-cjs/editor-provider.cjs +117 -34
  2. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  3. package/lib/_chunks-cjs/util.slice-blocks.cjs +2 -0
  4. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  5. package/lib/_chunks-es/editor-provider.js +117 -34
  6. package/lib/_chunks-es/editor-provider.js.map +1 -1
  7. package/lib/_chunks-es/util.slice-blocks.js +2 -0
  8. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  9. package/lib/behaviors/index.d.cts +222 -208
  10. package/lib/behaviors/index.d.ts +222 -208
  11. package/lib/index.cjs +346 -257
  12. package/lib/index.cjs.map +1 -1
  13. package/lib/index.d.cts +222 -216
  14. package/lib/index.d.ts +222 -216
  15. package/lib/index.js +353 -264
  16. package/lib/index.js.map +1 -1
  17. package/lib/plugins/index.d.cts +222 -216
  18. package/lib/plugins/index.d.ts +222 -216
  19. package/lib/selectors/index.d.cts +222 -208
  20. package/lib/selectors/index.d.ts +222 -208
  21. package/lib/utils/index.d.cts +222 -208
  22. package/lib/utils/index.d.ts +222 -208
  23. package/package.json +1 -1
  24. package/src/behaviors/behavior.config.ts +7 -0
  25. package/src/behaviors/behavior.core.block-element.ts +108 -0
  26. package/src/behaviors/behavior.core.ts +6 -2
  27. package/src/converters/converter.portable-text.ts +4 -1
  28. package/src/converters/converter.text-html.ts +4 -1
  29. package/src/converters/converter.text-plain.ts +4 -1
  30. package/src/editor/Editable.tsx +2 -4
  31. package/src/editor/__tests__/PortableTextEditor.test.tsx +6 -0
  32. package/src/editor/components/Leaf.tsx +8 -1
  33. package/src/editor/components/render-block-object.tsx +90 -0
  34. package/src/editor/components/render-default-object.tsx +21 -0
  35. package/src/editor/components/render-element.tsx +140 -0
  36. package/src/editor/components/render-inline-object.tsx +93 -0
  37. package/src/editor/components/render-text-block.tsx +148 -0
  38. package/src/editor/components/use-core-block-element-behaviors.ts +39 -0
  39. package/src/editor/create-editor.ts +17 -5
  40. package/src/editor/editor-machine.ts +21 -18
  41. package/src/internal-utils/parse-blocks.ts +2 -2
  42. package/src/internal-utils/slate-utils.ts +1 -1
  43. package/src/priority/priority.core.ts +3 -0
  44. package/src/priority/priority.sort.test.ts +319 -0
  45. package/src/priority/priority.sort.ts +121 -0
  46. package/src/priority/priority.types.ts +24 -0
  47. package/src/editor/components/DefaultObject.tsx +0 -21
  48. package/src/editor/components/Element.tsx +0 -435
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.48.14",
3
+ "version": "1.49.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -0,0 +1,7 @@
1
+ import type {EditorPriority} from '../priority/priority.types'
2
+ import type {Behavior} from './behavior.types.behavior'
3
+
4
+ export type BehaviorConfig = {
5
+ behavior: Behavior
6
+ priority: EditorPriority
7
+ }
@@ -0,0 +1,108 @@
1
+ import type {EventPositionBlock} from '../internal-utils/event-position'
2
+ import {corePriority} from '../priority/priority.core'
3
+ import {createEditorPriority} from '../priority/priority.types'
4
+ import * as selectors from '../selectors'
5
+ import {forward} from './behavior.types.action'
6
+ import {defineBehavior} from './behavior.types.behavior'
7
+
8
+ export function createCoreBlockElementBehaviorsConfig({
9
+ key,
10
+ onSetDragPositionBlock,
11
+ }: {
12
+ key: string
13
+ onSetDragPositionBlock: (
14
+ eventPositionBlock: EventPositionBlock | undefined,
15
+ ) => void
16
+ }) {
17
+ return [
18
+ {
19
+ behavior: defineBehavior({
20
+ on: 'drag.dragover',
21
+ guard: ({snapshot, event}) => {
22
+ const dropFocusBlock = selectors.getFocusBlock({
23
+ ...snapshot,
24
+ context: {
25
+ ...snapshot.context,
26
+ selection: event.position.selection,
27
+ },
28
+ })
29
+
30
+ if (!dropFocusBlock || dropFocusBlock.node._key !== key) {
31
+ return false
32
+ }
33
+
34
+ const dragOrigin = snapshot.beta.internalDrag?.origin
35
+
36
+ if (!dragOrigin) {
37
+ return false
38
+ }
39
+
40
+ const draggedBlocks = selectors.getSelectedBlocks({
41
+ ...snapshot,
42
+ context: {
43
+ ...snapshot.context,
44
+ selection: dragOrigin.selection,
45
+ },
46
+ })
47
+
48
+ if (
49
+ draggedBlocks.some((draggedBlock) => draggedBlock.node._key === key)
50
+ ) {
51
+ return false
52
+ }
53
+
54
+ const draggingEntireBlocks = selectors.isSelectingEntireBlocks({
55
+ ...snapshot,
56
+ context: {
57
+ ...snapshot.context,
58
+ selection: dragOrigin.selection,
59
+ },
60
+ })
61
+
62
+ return draggingEntireBlocks
63
+ },
64
+ actions: [
65
+ ({event}) => [
66
+ {
67
+ type: 'effect',
68
+ effect: () => {
69
+ onSetDragPositionBlock(event.position.block)
70
+ },
71
+ },
72
+ ],
73
+ ],
74
+ }),
75
+ priority: createEditorPriority({
76
+ reference: {
77
+ priority: corePriority,
78
+ importance: 'lower',
79
+ },
80
+ }),
81
+ },
82
+ {
83
+ behavior: defineBehavior({
84
+ on: 'drag.*',
85
+ guard: ({event}) => {
86
+ return event.type !== 'drag.dragover'
87
+ },
88
+ actions: [
89
+ ({event}) => [
90
+ {
91
+ type: 'effect',
92
+ effect: () => {
93
+ onSetDragPositionBlock(undefined)
94
+ },
95
+ },
96
+ forward(event),
97
+ ],
98
+ ],
99
+ }),
100
+ priority: createEditorPriority({
101
+ reference: {
102
+ priority: corePriority,
103
+ importance: 'lower',
104
+ },
105
+ }),
106
+ },
107
+ ]
108
+ }
@@ -1,3 +1,4 @@
1
+ import {corePriority} from '../priority/priority.core'
1
2
  import {coreAnnotationBehaviors} from './behavior.core.annotations'
2
3
  import {coreBlockObjectBehaviors} from './behavior.core.block-objects'
3
4
  import {coreDecoratorBehaviors} from './behavior.core.decorators'
@@ -5,7 +6,7 @@ import {coreDndBehaviors} from './behavior.core.dnd'
5
6
  import {coreInsertBreakBehaviors} from './behavior.core.insert-break'
6
7
  import {coreListBehaviors} from './behavior.core.lists'
7
8
 
8
- export const coreBehaviors = [
9
+ export const coreBehaviorsConfig = [
9
10
  coreAnnotationBehaviors.addAnnotationOnCollapsedSelection,
10
11
  coreDecoratorBehaviors.strongShortcut,
11
12
  coreDecoratorBehaviors.emShortcut,
@@ -28,4 +29,7 @@ export const coreBehaviors = [
28
29
  coreInsertBreakBehaviors.breakingAtTheStartOfTextBlock,
29
30
  coreInsertBreakBehaviors.breakingEntireDocument,
30
31
  coreInsertBreakBehaviors.breakingEntireBlocks,
31
- ]
32
+ ].map((behavior) => ({
33
+ behavior,
34
+ priority: corePriority,
35
+ }))
@@ -18,7 +18,10 @@ export const converterPortableText = defineConverter({
18
18
  }
19
19
 
20
20
  const blocks = sliceBlocks({
21
- context: snapshot.context,
21
+ context: {
22
+ selection,
23
+ schema: snapshot.context.schema,
24
+ },
22
25
  blocks: snapshot.context.value,
23
26
  })
24
27
 
@@ -26,7 +26,10 @@ export function createConverterTextHtml(
26
26
  }
27
27
 
28
28
  const blocks = sliceBlocks({
29
- context: snapshot.context,
29
+ context: {
30
+ selection,
31
+ schema: snapshot.context.schema,
32
+ },
30
33
  blocks: snapshot.context.value,
31
34
  })
32
35
 
@@ -25,7 +25,10 @@ export function createConverterTextPlain(
25
25
  }
26
26
 
27
27
  const blocks = sliceBlocks({
28
- context: snapshot.context,
28
+ context: {
29
+ selection,
30
+ schema: snapshot.context.schema,
31
+ },
29
32
  blocks: snapshot.context.value,
30
33
  })
31
34
 
@@ -53,8 +53,8 @@ import type {
53
53
  import type {HotkeyOptions} from '../types/options'
54
54
  import {isSelectionCollapsed} from '../utils'
55
55
  import {getSelectionEndPoint} from '../utils/util.get-selection-end-point'
56
- import {Element} from './components/Element'
57
56
  import {Leaf} from './components/Leaf'
57
+ import {RenderElement} from './components/render-element'
58
58
  import {EditorActorContext} from './editor-actor-context'
59
59
  import {getEditorSnapshot} from './editor-selector'
60
60
  import {usePortableTextEditor} from './hooks/usePortableTextEditor'
@@ -223,19 +223,17 @@ export const PortableTextEditable = forwardRef<
223
223
 
224
224
  const renderElement = useCallback(
225
225
  (eProps: RenderElementProps) => (
226
- <Element
226
+ <RenderElement
227
227
  {...eProps}
228
228
  readOnly={readOnly}
229
229
  renderBlock={renderBlock}
230
230
  renderChild={renderChild}
231
231
  renderListItem={renderListItem}
232
232
  renderStyle={renderStyle}
233
- schemaTypes={portableTextEditor.schemaTypes}
234
233
  spellCheck={spellCheck}
235
234
  />
236
235
  ),
237
236
  [
238
- portableTextEditor,
239
237
  spellCheck,
240
238
  readOnly,
241
239
  renderBlock,
@@ -52,6 +52,9 @@ describe('initialization', () => {
52
52
  >
53
53
  <div
54
54
  class="pt-block pt-text-block pt-text-block-style-normal"
55
+ data-block-key="k0"
56
+ data-block-name="myTestBlockType"
57
+ data-block-type="text"
55
58
  data-slate-node="element"
56
59
  >
57
60
  <div>
@@ -65,6 +68,9 @@ describe('initialization', () => {
65
68
  Jot something down here
66
69
  </span>
67
70
  <span
71
+ data-child-key="k1"
72
+ data-child-name="span"
73
+ data-child-type="span"
68
74
  data-slate-leaf="true"
69
75
  >
70
76
  <span
@@ -318,7 +318,14 @@ export const Leaf = (props: LeafProps) => {
318
318
  ])
319
319
  return useMemo(
320
320
  () => (
321
- <span key={leaf._key} {...attributes} ref={spanRef}>
321
+ <span
322
+ key={leaf._key}
323
+ {...attributes}
324
+ ref={spanRef}
325
+ data-child-key={leaf._key}
326
+ data-child-name={leaf._type}
327
+ data-child-type="span"
328
+ >
322
329
  {content}
323
330
  </span>
324
331
  ),
@@ -0,0 +1,90 @@
1
+ import type {PortableTextObject} from '@sanity/types'
2
+ import {useSelector} from '@xstate/react'
3
+ import {useContext, useRef, useState, type ReactElement} from 'react'
4
+ import {Range, type Element as SlateElement} from 'slate'
5
+ import {useSelected, useSlateStatic, type RenderElementProps} from 'slate-react'
6
+ import type {EventPositionBlock} from '../../internal-utils/event-position'
7
+ import type {RenderBlockFunction} from '../../types/editor'
8
+ import {EditorActorContext} from '../editor-actor-context'
9
+ import {DropIndicator} from './drop-indicator'
10
+ import {RenderDefaultBlockObject} from './render-default-object'
11
+ import {useCoreBlockElementBehaviors} from './use-core-block-element-behaviors'
12
+
13
+ export function RenderBlockObject(props: {
14
+ attributes: RenderElementProps['attributes']
15
+ blockObject: PortableTextObject
16
+ children: ReactElement
17
+ element: SlateElement
18
+ readOnly: boolean
19
+ renderBlock?: RenderBlockFunction
20
+ }) {
21
+ const [dragPositionBlock, setDragPositionBlock] =
22
+ useState<EventPositionBlock>()
23
+ const blockObjectRef = useRef<HTMLDivElement>(null)
24
+
25
+ const slateEditor = useSlateStatic()
26
+ const selected = useSelected()
27
+
28
+ const editorActor = useContext(EditorActorContext)
29
+
30
+ useCoreBlockElementBehaviors({
31
+ key: props.element._key,
32
+ onSetDragPositionBlock: setDragPositionBlock,
33
+ })
34
+
35
+ const legacySchemaType = useSelector(editorActor, (s) =>
36
+ s.context
37
+ .getLegacySchema()
38
+ .blockObjects.find(
39
+ (blockObject) => blockObject.name === props.element._type,
40
+ ),
41
+ )
42
+
43
+ if (!legacySchemaType) {
44
+ console.error(
45
+ `Block object type ${props.element._type} not found in Schema`,
46
+ )
47
+ }
48
+
49
+ const focused =
50
+ selected &&
51
+ slateEditor.selection !== null &&
52
+ Range.isCollapsed(slateEditor.selection)
53
+
54
+ return (
55
+ <div
56
+ key={props.element._key}
57
+ {...props.attributes}
58
+ className="pt-block pt-object-block"
59
+ data-block-key={props.element._key}
60
+ data-block-name={props.element._type}
61
+ data-block-type="object"
62
+ >
63
+ {dragPositionBlock === 'start' ? <DropIndicator /> : null}
64
+ {props.children}
65
+ <div
66
+ ref={blockObjectRef}
67
+ contentEditable={false}
68
+ draggable={!props.readOnly}
69
+ >
70
+ {props.renderBlock && legacySchemaType ? (
71
+ props.renderBlock({
72
+ children: (
73
+ <RenderDefaultBlockObject blockObject={props.blockObject} />
74
+ ),
75
+ editorElementRef: blockObjectRef,
76
+ focused,
77
+ path: [{_key: props.element._key}],
78
+ schemaType: legacySchemaType,
79
+ selected,
80
+ type: legacySchemaType,
81
+ value: props.blockObject,
82
+ })
83
+ ) : (
84
+ <RenderDefaultBlockObject blockObject={props.blockObject} />
85
+ )}
86
+ </div>
87
+ {dragPositionBlock === 'end' ? <DropIndicator /> : null}
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,21 @@
1
+ import type {PortableTextChild, PortableTextObject} from '@sanity/types'
2
+
3
+ export function RenderDefaultBlockObject(props: {
4
+ blockObject: PortableTextObject
5
+ }) {
6
+ return (
7
+ <div style={{userSelect: 'none'}}>
8
+ [{props.blockObject._type}: {props.blockObject._key}]
9
+ </div>
10
+ )
11
+ }
12
+
13
+ export function RenderDefaultInlineObject(props: {
14
+ inlineObject: PortableTextObject | PortableTextChild
15
+ }) {
16
+ return (
17
+ <span style={{userSelect: 'none'}}>
18
+ [{props.inlineObject._type}: {props.inlineObject._key}]
19
+ </span>
20
+ )
21
+ }
@@ -0,0 +1,140 @@
1
+ import {useSelector} from '@xstate/react'
2
+ import {useContext, type ReactElement} from 'react'
3
+ import type {Element as SlateElement} from 'slate'
4
+ import type {RenderElementProps} from 'slate-react'
5
+ import {
6
+ parseBlockObject,
7
+ parseInlineObject,
8
+ parseTextBlock,
9
+ } from '../../internal-utils/parse-blocks'
10
+ import type {
11
+ RenderBlockFunction,
12
+ RenderChildFunction,
13
+ RenderListItemFunction,
14
+ RenderStyleFunction,
15
+ } from '../../types/editor'
16
+ import {EditorActorContext} from '../editor-actor-context'
17
+ import {RenderBlockObject} from './render-block-object'
18
+ import {RenderInlineObject} from './render-inline-object'
19
+ import {RenderTextBlock} from './render-text-block'
20
+
21
+ export function RenderElement(props: {
22
+ attributes: RenderElementProps['attributes']
23
+ children: ReactElement
24
+ element: SlateElement
25
+ readOnly: boolean
26
+ renderBlock?: RenderBlockFunction
27
+ renderChild?: RenderChildFunction
28
+ renderListItem?: RenderListItemFunction
29
+ renderStyle?: RenderStyleFunction
30
+ spellCheck?: boolean
31
+ }) {
32
+ const editorActor = useContext(EditorActorContext)
33
+ const schema = useSelector(editorActor, (s) => s.context.schema)
34
+ const isInline =
35
+ '__inline' in props.element && props.element.__inline === true
36
+
37
+ if (isInline) {
38
+ const inlineObject = parseInlineObject({
39
+ context: {
40
+ keyGenerator: () => '',
41
+ schema,
42
+ },
43
+ options: {refreshKeys: false},
44
+ inlineObject: {
45
+ _key: props.element._key,
46
+ _type: props.element._type,
47
+ ...('value' in props.element && typeof props.element.value === 'object'
48
+ ? props.element.value
49
+ : {}),
50
+ },
51
+ })
52
+
53
+ if (!inlineObject) {
54
+ console.error(
55
+ `Unable to find Inline Object "${props.element._type}" in Schema`,
56
+ )
57
+ }
58
+
59
+ return (
60
+ <RenderInlineObject
61
+ attributes={props.attributes}
62
+ element={props.element}
63
+ inlineObject={
64
+ inlineObject ?? {
65
+ _key: props.element._key,
66
+ _type: props.element._type,
67
+ }
68
+ }
69
+ readOnly={props.readOnly}
70
+ renderChild={props.renderChild}
71
+ >
72
+ {props.children}
73
+ </RenderInlineObject>
74
+ )
75
+ }
76
+
77
+ const textBlock = parseTextBlock({
78
+ context: {
79
+ keyGenerator: () => '',
80
+ schema,
81
+ },
82
+ options: {refreshKeys: false},
83
+ block: props.element,
84
+ })
85
+
86
+ if (textBlock) {
87
+ return (
88
+ <RenderTextBlock
89
+ attributes={props.attributes}
90
+ element={props.element}
91
+ readOnly={props.readOnly}
92
+ renderBlock={props.renderBlock}
93
+ renderListItem={props.renderListItem}
94
+ renderStyle={props.renderStyle}
95
+ spellCheck={props.spellCheck}
96
+ textBlock={textBlock}
97
+ >
98
+ {props.children}
99
+ </RenderTextBlock>
100
+ )
101
+ }
102
+
103
+ const blockObject = parseBlockObject({
104
+ context: {
105
+ keyGenerator: () => '',
106
+ schema,
107
+ },
108
+ options: {refreshKeys: false},
109
+ blockObject: {
110
+ _key: props.element._key,
111
+ _type: props.element._type,
112
+ ...('value' in props.element && typeof props.element.value === 'object'
113
+ ? props.element.value
114
+ : {}),
115
+ },
116
+ })
117
+
118
+ if (!blockObject) {
119
+ console.error(
120
+ `Unable to find Block Object "${props.element._type}" in Schema`,
121
+ )
122
+ }
123
+
124
+ return (
125
+ <RenderBlockObject
126
+ attributes={props.attributes}
127
+ blockObject={
128
+ blockObject ?? {
129
+ _key: props.element._key,
130
+ _type: props.element._type,
131
+ }
132
+ }
133
+ element={props.element}
134
+ readOnly={props.readOnly}
135
+ renderBlock={props.renderBlock}
136
+ >
137
+ {props.children}
138
+ </RenderBlockObject>
139
+ )
140
+ }
@@ -0,0 +1,93 @@
1
+ import type {PortableTextObject} from '@sanity/types'
2
+ import {useSelector} from '@xstate/react'
3
+ import {useContext, useRef, type ReactElement} from 'react'
4
+ import {Range, type Element as SlateElement} from 'slate'
5
+ import {DOMEditor} from 'slate-dom'
6
+ import {useSelected, useSlateStatic, type RenderElementProps} from 'slate-react'
7
+ import {getPointBlock} from '../../internal-utils/slate-utils'
8
+ import type {RenderChildFunction} from '../../types/editor'
9
+ import {EditorActorContext} from '../editor-actor-context'
10
+ import {RenderDefaultInlineObject} from './render-default-object'
11
+
12
+ export function RenderInlineObject(props: {
13
+ attributes: RenderElementProps['attributes']
14
+ children: ReactElement
15
+ element: SlateElement
16
+ inlineObject: PortableTextObject
17
+ readOnly: boolean
18
+ renderChild?: RenderChildFunction
19
+ }) {
20
+ const inlineObjectRef = useRef<HTMLElement>(null)
21
+
22
+ const slateEditor = useSlateStatic()
23
+ const selected = useSelected()
24
+
25
+ const editorActor = useContext(EditorActorContext)
26
+ const legacySchemaType = useSelector(editorActor, (s) =>
27
+ s.context
28
+ .getLegacySchema()
29
+ .inlineObjects.find(
30
+ (inlineObject) => inlineObject.name === props.element._type,
31
+ ),
32
+ )
33
+
34
+ if (!legacySchemaType) {
35
+ console.error(
36
+ `Inline object type ${props.element._type} not found in Schema`,
37
+ )
38
+ }
39
+
40
+ const focused =
41
+ selected &&
42
+ slateEditor.selection !== null &&
43
+ Range.isCollapsed(slateEditor.selection)
44
+ const path = DOMEditor.findPath(slateEditor, props.element)
45
+ const [block] = getPointBlock({
46
+ editor: slateEditor,
47
+ point: {
48
+ path,
49
+ offset: 0,
50
+ },
51
+ })
52
+
53
+ if (!block) {
54
+ console.error(
55
+ `Unable to find parent block of inline object ${props.element._key}`,
56
+ )
57
+ }
58
+
59
+ return (
60
+ <span {...props.attributes}>
61
+ {props.children}
62
+ <span
63
+ draggable={!props.readOnly}
64
+ className="pt-inline-object"
65
+ data-testid="pt-inline-object"
66
+ ref={inlineObjectRef}
67
+ key={props.element._key}
68
+ style={{display: 'inline-block'}}
69
+ data-child-key={props.inlineObject._key}
70
+ data-child-name={props.inlineObject._type}
71
+ data-child-type="object"
72
+ >
73
+ {props.renderChild && block && legacySchemaType ? (
74
+ props.renderChild({
75
+ annotations: [],
76
+ children: (
77
+ <RenderDefaultInlineObject inlineObject={props.inlineObject} />
78
+ ),
79
+ editorElementRef: inlineObjectRef,
80
+ selected,
81
+ focused,
82
+ path: [{_key: block._key}, 'children', {_key: props.element._key}],
83
+ schemaType: legacySchemaType,
84
+ value: props.inlineObject,
85
+ type: legacySchemaType,
86
+ })
87
+ ) : (
88
+ <RenderDefaultInlineObject inlineObject={props.inlineObject} />
89
+ )}
90
+ </span>
91
+ </span>
92
+ )
93
+ }