@portabletext/editor 1.53.0 → 1.54.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 (94) hide show
  1. package/lib/_chunks-cjs/selection-point.cjs.map +1 -1
  2. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs +144 -6
  4. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs +6 -140
  6. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs.map +1 -1
  7. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs.map +1 -1
  8. package/lib/_chunks-es/selection-point.js.map +1 -1
  9. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  10. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +148 -10
  11. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
  12. package/lib/_chunks-es/selector.is-selection-expanded.js +6 -140
  13. package/lib/_chunks-es/selector.is-selection-expanded.js.map +1 -1
  14. package/lib/_chunks-es/util.is-equal-selection-points.js.map +1 -1
  15. package/lib/behaviors/index.cjs.map +1 -1
  16. package/lib/behaviors/index.d.cts +68 -9
  17. package/lib/behaviors/index.d.ts +68 -9
  18. package/lib/behaviors/index.js.map +1 -1
  19. package/lib/index.cjs +553 -313
  20. package/lib/index.cjs.map +1 -1
  21. package/lib/index.d.cts +69 -13
  22. package/lib/index.d.ts +69 -13
  23. package/lib/index.js +534 -294
  24. package/lib/index.js.map +1 -1
  25. package/lib/plugins/index.d.cts +69 -9
  26. package/lib/plugins/index.d.ts +69 -9
  27. package/lib/selectors/index.cjs +10 -10
  28. package/lib/selectors/index.cjs.map +1 -1
  29. package/lib/selectors/index.d.cts +42 -27
  30. package/lib/selectors/index.d.ts +42 -27
  31. package/lib/selectors/index.js +4 -4
  32. package/lib/selectors/index.js.map +1 -1
  33. package/lib/utils/index.d.cts +27 -5
  34. package/lib/utils/index.d.ts +27 -5
  35. package/package.json +14 -14
  36. package/src/behaviors/behavior.abstract.annotation.ts +51 -0
  37. package/src/behaviors/behavior.abstract.delete.ts +55 -0
  38. package/src/behaviors/behavior.perform-event.ts +6 -15
  39. package/src/behaviors/behavior.types.action.ts +1 -1
  40. package/src/behaviors/behavior.types.event.ts +32 -8
  41. package/src/behaviors/behavior.types.guard.ts +1 -1
  42. package/src/editor/Editable.tsx +3 -0
  43. package/src/editor/PortableTextEditor.tsx +1 -6
  44. package/src/editor/create-editor.ts +5 -0
  45. package/src/{internal-utils/selection-elements.ts → editor/editor-dom.ts} +29 -21
  46. package/src/editor/editor-provider.tsx +1 -6
  47. package/src/editor/hooks/usePortableTextEditorSelection.tsx +6 -46
  48. package/src/editor/plugins/createWithPatches.ts +1 -5
  49. package/src/editor.ts +2 -0
  50. package/src/index.ts +2 -0
  51. package/src/operations/behavior.operation.child.set.ts +103 -0
  52. package/src/operations/behavior.operation.child.unset.ts +89 -0
  53. package/src/operations/behavior.operations.ts +18 -0
  54. package/src/selectors/index.ts +13 -1
  55. package/src/selectors/selector.get-active-annotations.ts +2 -1
  56. package/src/selectors/selector.get-active-list-item.ts +1 -1
  57. package/src/selectors/selector.get-active-style.ts +1 -1
  58. package/src/selectors/selector.get-anchor-block.ts +3 -2
  59. package/src/selectors/selector.get-anchor-child.ts +2 -2
  60. package/src/selectors/selector.get-anchor-span.ts +2 -3
  61. package/src/selectors/selector.get-anchor-text-block.ts +3 -2
  62. package/src/selectors/selector.get-caret-word-selection.ts +1 -1
  63. package/src/selectors/selector.get-first-block.ts +14 -0
  64. package/src/selectors/selector.get-focus-block-object.ts +18 -0
  65. package/src/selectors/selector.get-focus-block.ts +23 -0
  66. package/src/selectors/selector.get-focus-child.ts +36 -0
  67. package/src/selectors/selector.get-focus-inline-object.ts +4 -8
  68. package/src/selectors/selector.get-focus-list-block.ts +18 -0
  69. package/src/selectors/selector.get-focus-span.ts +18 -0
  70. package/src/selectors/selector.get-focus-text-block.ts +18 -0
  71. package/src/selectors/selector.get-last-block.ts +16 -0
  72. package/src/selectors/selector.get-list-state.ts +2 -1
  73. package/src/selectors/selector.get-next-block.ts +38 -0
  74. package/src/selectors/selector.get-next-inline-object.ts +5 -8
  75. package/src/selectors/selector.get-previous-block.ts +35 -0
  76. package/src/selectors/selector.get-previous-inline-object.ts +5 -8
  77. package/src/selectors/selector.get-selected-blocks.ts +49 -0
  78. package/src/selectors/selector.get-selected-spans.test.ts +96 -0
  79. package/src/selectors/selector.get-selected-spans.ts +4 -3
  80. package/src/selectors/selector.get-selected-text-blocks.ts +4 -3
  81. package/src/selectors/selector.get-selection-end-block.ts +4 -1
  82. package/src/selectors/selector.get-selection-start-block.ts +4 -1
  83. package/src/selectors/selector.get-text-before.ts +1 -1
  84. package/src/selectors/selector.get-trimmed-selection.ts +1 -1
  85. package/src/selectors/selector.is-active-annotation.ts +1 -1
  86. package/src/selectors/selector.is-at-the-end-of-block.ts +3 -2
  87. package/src/selectors/selector.is-at-the-start-of-block.ts +3 -2
  88. package/src/selectors/selector.is-selecting-entire-blocks.ts +2 -1
  89. package/src/types/block-offset.ts +2 -2
  90. package/src/types/paths.ts +13 -0
  91. package/src/utils/util.block-offset.ts +2 -4
  92. package/src/utils/util.get-block-end-point.ts +3 -2
  93. package/src/utils/util.get-block-start-point.ts +3 -2
  94. package/src/selectors/selectors.ts +0 -299
@@ -1,19 +1,6 @@
1
- import {
2
- createContext,
3
- startTransition,
4
- useContext,
5
- useEffect,
6
- useState,
7
- } from 'react'
8
- import {debugWithName} from '../../internal-utils/debug'
1
+ import {startTransition, useContext, useEffect, useState} from 'react'
9
2
  import type {EditorSelection} from '../../types/editor'
10
- import type {EditorActor} from '../editor-machine'
11
-
12
- /**
13
- * A React context for sharing the editor selection.
14
- */
15
- const PortableTextEditorSelectionContext =
16
- createContext<EditorSelection | null>(null)
3
+ import {EditorActorContext} from '../editor-actor-context'
17
4
 
18
5
  /**
19
6
  * @deprecated Use `useEditorSelector` to get the current editor selection.
@@ -21,48 +8,21 @@ const PortableTextEditorSelectionContext =
21
8
  * Get the current editor selection from the React context.
22
9
  */
23
10
  export const usePortableTextEditorSelection = (): EditorSelection => {
24
- const selection = useContext(PortableTextEditorSelectionContext)
25
-
26
- if (selection === undefined) {
27
- throw new Error(
28
- `The \`usePortableTextEditorSelection\` hook must be used inside the <PortableTextEditor> component's context.`,
29
- )
30
- }
31
- return selection
32
- }
33
- const debug = debugWithName('component:PortableTextEditor:SelectionProvider')
34
- const debugVerbose = debug.enabled && false
35
-
36
- /**
37
- * @internal
38
- */
39
- export function PortableTextEditorSelectionProvider(
40
- props: React.PropsWithChildren<{
41
- editorActor: EditorActor
42
- }>,
43
- ) {
11
+ const editorActor = useContext(EditorActorContext)
44
12
  const [selection, setSelection] = useState<EditorSelection>(null)
45
13
 
46
- // Subscribe to, and handle changes from the editor
47
14
  useEffect(() => {
48
- debug('Subscribing to selection changes')
49
- const subscription = props.editorActor.on('selection', (event) => {
15
+ const subscription = editorActor.on('selection', (event) => {
50
16
  // Set the selection state in a transition, we don't need the state immediately.
51
17
  startTransition(() => {
52
- if (debugVerbose) debug('Setting selection')
53
18
  setSelection(event.selection)
54
19
  })
55
20
  })
56
21
 
57
22
  return () => {
58
- debug('Unsubscribing to selection changes')
59
23
  subscription.unsubscribe()
60
24
  }
61
- }, [props.editorActor])
25
+ }, [editorActor])
62
26
 
63
- return (
64
- <PortableTextEditorSelectionContext.Provider value={selection}>
65
- {props.children}
66
- </PortableTextEditorSelectionContext.Provider>
67
- )
27
+ return selection
68
28
  }
@@ -267,11 +267,7 @@ export function createWithPatches({
267
267
  type: 'internal.patch',
268
268
  patch: {...patch, origin: 'local'},
269
269
  operationId: getCurrentOperationId(editor),
270
- value: fromSlateValue(
271
- editor.children,
272
- editorActor.getSnapshot().context.schema.block.name,
273
- KEY_TO_VALUE_ELEMENT.get(editor),
274
- ),
270
+ value: editor.value,
275
271
  })
276
272
  }
277
273
  }
package/src/editor.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  import type {ActorRef, EventObject, Snapshot} from 'xstate'
7
7
  import type {Behavior} from './behaviors/behavior.types.behavior'
8
8
  import type {ExternalBehaviorEvent} from './behaviors/behavior.types.event'
9
+ import type {EditorDom} from './editor/editor-dom'
9
10
  import type {ExternalEditorEvent} from './editor/editor-machine'
10
11
  import type {SchemaDefinition} from './editor/editor-schema'
11
12
  import type {EditorSnapshot} from './editor/editor-snapshot'
@@ -51,6 +52,7 @@ export type EditorEvent =
51
52
  * @public
52
53
  */
53
54
  export type Editor = {
55
+ dom: EditorDom
54
56
  getSnapshot: () => EditorSnapshot
55
57
  /**
56
58
  * @beta
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export type {Patch} from '@portabletext/patches'
2
2
  export type {
3
3
  PortableTextBlock,
4
4
  PortableTextChild,
5
+ PortableTextObject,
5
6
  PortableTextSpan,
6
7
  } from '@sanity/types'
7
8
  export type {Editor, EditorConfig, EditorEvent} from './editor'
@@ -79,3 +80,4 @@ export type {
79
80
  ValueChange,
80
81
  } from './types/editor'
81
82
  export type {HotkeyOptions} from './types/options'
83
+ export type {AnnotationPath, BlockPath, ChildPath} from './types/paths'
@@ -0,0 +1,103 @@
1
+ import {Editor, Element, Transforms} from 'slate'
2
+ import {toSlateRange} from '../internal-utils/ranges'
3
+ import type {BehaviorOperationImplementation} from './behavior.operations'
4
+
5
+ export const childSetOperationImplementation: BehaviorOperationImplementation<
6
+ 'child.set'
7
+ > = ({context, operation}) => {
8
+ const location = toSlateRange(
9
+ {
10
+ anchor: {path: operation.at, offset: 0},
11
+ focus: {path: operation.at, offset: 0},
12
+ },
13
+ operation.editor,
14
+ )
15
+
16
+ if (!location) {
17
+ throw new Error(
18
+ `Unable to convert ${JSON.stringify(operation.at)} into a Slate Range`,
19
+ )
20
+ }
21
+
22
+ const childEntry = Editor.node(operation.editor, location, {depth: 2})
23
+ const child = childEntry?.[0]
24
+ const childPath = childEntry?.[1]
25
+
26
+ if (!child || !childPath) {
27
+ throw new Error(`Unable to find child at ${JSON.stringify(operation.at)}`)
28
+ }
29
+
30
+ if (operation.editor.isTextSpan(child)) {
31
+ const {_type, text, ...rest} = operation.props
32
+
33
+ Transforms.setNodes(
34
+ operation.editor,
35
+ {
36
+ ...child,
37
+ ...rest,
38
+ },
39
+ {at: childPath},
40
+ )
41
+
42
+ if (typeof text === 'string') {
43
+ if (child.text !== text) {
44
+ operation.editor.apply({
45
+ type: 'remove_text',
46
+ path: childPath,
47
+ offset: 0,
48
+ text: child.text,
49
+ })
50
+
51
+ operation.editor.apply({
52
+ type: 'insert_text',
53
+ path: childPath,
54
+ offset: 0,
55
+ text,
56
+ })
57
+ }
58
+ }
59
+
60
+ return
61
+ }
62
+
63
+ if (Element.isElement(child)) {
64
+ const definition = context.schema.inlineObjects.find(
65
+ (definition) => definition.name === child._type,
66
+ )
67
+
68
+ if (!definition) {
69
+ throw new Error(
70
+ `Unable to find schema definition for Inline Object type ${child._type}`,
71
+ )
72
+ }
73
+
74
+ const value =
75
+ 'value' in child && typeof child.value === 'object' ? child.value : {}
76
+ const {_type, _key, ...rest} = operation.props
77
+
78
+ for (const prop in rest) {
79
+ if (!definition.fields.some((field) => field.name === prop)) {
80
+ delete rest[prop]
81
+ }
82
+ }
83
+
84
+ Transforms.setNodes(
85
+ operation.editor,
86
+ {
87
+ ...child,
88
+ _key: typeof _key === 'string' ? _key : child._key,
89
+ value: {
90
+ ...value,
91
+ ...rest,
92
+ },
93
+ },
94
+ {at: childPath},
95
+ )
96
+
97
+ return
98
+ }
99
+
100
+ throw new Error(
101
+ `Unable to determine the type of child at ${JSON.stringify(operation.at)}`,
102
+ )
103
+ }
@@ -0,0 +1,89 @@
1
+ import {applyAll} from '@portabletext/patches'
2
+ import {Editor, Element, Transforms} from 'slate'
3
+ import {toSlateRange} from '../internal-utils/ranges'
4
+ import type {BehaviorOperationImplementation} from './behavior.operations'
5
+
6
+ export const childUnsetOperationImplementation: BehaviorOperationImplementation<
7
+ 'child.unset'
8
+ > = ({context, operation}) => {
9
+ const location = toSlateRange(
10
+ {
11
+ anchor: {path: operation.at, offset: 0},
12
+ focus: {path: operation.at, offset: 0},
13
+ },
14
+ operation.editor,
15
+ )
16
+
17
+ if (!location) {
18
+ throw new Error(
19
+ `Unable to convert ${JSON.stringify(operation.at)} into a Slate Range`,
20
+ )
21
+ }
22
+
23
+ const childEntry = Editor.node(operation.editor, location, {depth: 2})
24
+ const child = childEntry?.[0]
25
+ const childPath = childEntry?.[1]
26
+
27
+ if (!child || !childPath) {
28
+ throw new Error(`Unable to find child at ${JSON.stringify(operation.at)}`)
29
+ }
30
+
31
+ if (operation.editor.isTextSpan(child)) {
32
+ if (operation.props.includes('text')) {
33
+ operation.editor.apply({
34
+ type: 'remove_text',
35
+ path: childPath,
36
+ offset: 0,
37
+ text: child.text,
38
+ })
39
+ }
40
+
41
+ const newNode: Record<string, unknown> = {}
42
+
43
+ for (const prop of operation.props) {
44
+ if (prop === '_type') {
45
+ // It's not allowed to unset the _type of a span
46
+ continue
47
+ }
48
+
49
+ if (prop === '_key') {
50
+ newNode._key = context.keyGenerator()
51
+ continue
52
+ }
53
+
54
+ newNode[prop] = null
55
+ }
56
+
57
+ Transforms.setNodes(operation.editor, newNode, {at: childPath})
58
+
59
+ return
60
+ }
61
+
62
+ if (Element.isElement(child)) {
63
+ const value =
64
+ 'value' in child && typeof child.value === 'object' ? child.value : {}
65
+ const patches = operation.props.map((prop) => ({
66
+ type: 'unset' as const,
67
+ path: [prop],
68
+ }))
69
+ const newValue = applyAll(value, patches)
70
+
71
+ Transforms.setNodes(
72
+ operation.editor,
73
+ {
74
+ ...child,
75
+ _key: operation.props.includes('_key')
76
+ ? context.keyGenerator()
77
+ : child._key,
78
+ value: newValue,
79
+ },
80
+ {at: childPath},
81
+ )
82
+
83
+ return
84
+ }
85
+
86
+ throw new Error(
87
+ `Unable to determine the type of child at ${JSON.stringify(operation.at)}`,
88
+ )
89
+ }
@@ -14,6 +14,8 @@ import {addAnnotationOperationImplementation} from './behavior.operation.annotat
14
14
  import {removeAnnotationOperationImplementation} from './behavior.operation.annotation.remove'
15
15
  import {blockSetOperationImplementation} from './behavior.operation.block.set'
16
16
  import {blockUnsetOperationImplementation} from './behavior.operation.block.unset'
17
+ import {childSetOperationImplementation} from './behavior.operation.child.set'
18
+ import {childUnsetOperationImplementation} from './behavior.operation.child.unset'
17
19
  import {decoratorAddOperationImplementation} from './behavior.operation.decorator.add'
18
20
  import {deleteOperationImplementation} from './behavior.operation.delete'
19
21
  import {insertInlineObjectOperationImplementation} from './behavior.operation.insert-inline-object'
@@ -58,6 +60,8 @@ const behaviorOperationImplementations: BehaviorOperationImplementations = {
58
60
  'annotation.remove': removeAnnotationOperationImplementation,
59
61
  'block.set': blockSetOperationImplementation,
60
62
  'block.unset': blockUnsetOperationImplementation,
63
+ 'child.set': childSetOperationImplementation,
64
+ 'child.unset': childUnsetOperationImplementation,
61
65
  'decorator.add': decoratorAddOperationImplementation,
62
66
  'decorator.remove': removeDecoratorOperationImplementation,
63
67
  'delete': deleteOperationImplementation,
@@ -110,6 +114,20 @@ export function performOperation({
110
114
  })
111
115
  break
112
116
  }
117
+ case 'child.set': {
118
+ behaviorOperationImplementations['child.set']({
119
+ context,
120
+ operation: operation,
121
+ })
122
+ break
123
+ }
124
+ case 'child.unset': {
125
+ behaviorOperationImplementations['child.unset']({
126
+ context,
127
+ operation: operation,
128
+ })
129
+ break
130
+ }
113
131
  case 'decorator.add': {
114
132
  behaviorOperationImplementations['decorator.add']({
115
133
  context,
@@ -7,15 +7,28 @@ export {getAnchorSpan} from './selector.get-anchor-span'
7
7
  export {getAnchorTextBlock} from './selector.get-anchor-text-block'
8
8
  export {getBlockOffsets} from './selector.get-block-offsets'
9
9
  export {getCaretWordSelection} from './selector.get-caret-word-selection'
10
+ export {getFirstBlock} from './selector.get-first-block'
11
+ export {getFocusBlock} from './selector.get-focus-block'
12
+ export {getFocusBlockObject} from './selector.get-focus-block-object'
13
+ export {getFocusChild} from './selector.get-focus-child'
10
14
  export {getFocusInlineObject} from './selector.get-focus-inline-object'
15
+ export {getFocusListBlock} from './selector.get-focus-list-block'
16
+ export {getFocusSpan} from './selector.get-focus-span'
17
+ export {getFocusTextBlock} from './selector.get-focus-text-block'
18
+ export {getLastBlock} from './selector.get-last-block'
11
19
  export {getListIndex} from './selector.get-list-state'
20
+ export {getNextBlock} from './selector.get-next-block'
12
21
  export {getNextInlineObject} from './selector.get-next-inline-object'
22
+ export {getPreviousBlock} from './selector.get-previous-block'
13
23
  export {getPreviousInlineObject} from './selector.get-previous-inline-object'
24
+ export {getSelectedBlocks} from './selector.get-selected-blocks'
14
25
  export {getSelectedSlice} from './selector.get-selected-slice'
15
26
  export {getSelectedSpans} from './selector.get-selected-spans'
16
27
  export {getSelectedTextBlocks} from './selector.get-selected-text-blocks'
17
28
  export {getSelection} from './selector.get-selection'
29
+ export {getSelectionEndBlock} from './selector.get-selection-end-block'
18
30
  export {getSelectionEndPoint} from './selector.get-selection-end-point'
31
+ export {getSelectionStartBlock} from './selector.get-selection-start-block'
19
32
  export {getSelectionStartPoint} from './selector.get-selection-start-point'
20
33
  export {getSelectionText} from './selector.get-selection-text'
21
34
  export {getBlockTextBefore} from './selector.get-text-before'
@@ -33,4 +46,3 @@ export {isPointBeforeSelection} from './selector.is-point-before-selection'
33
46
  export {isSelectingEntireBlocks} from './selector.is-selecting-entire-blocks'
34
47
  export {isSelectionCollapsed} from './selector.is-selection-collapsed'
35
48
  export {isSelectionExpanded} from './selector.is-selection-expanded'
36
- export * from './selectors'
@@ -1,9 +1,10 @@
1
1
  import type {PortableTextObject} from '@sanity/types'
2
2
  import type {EditorSelector} from '../editor/editor-selector'
3
3
  import {isTextBlock} from '../internal-utils/parse-blocks'
4
+ import {getFocusSpan} from './selector.get-focus-span'
5
+ import {getSelectedBlocks} from './selector.get-selected-blocks'
4
6
  import {getSelectedSpans} from './selector.get-selected-spans'
5
7
  import {isSelectionCollapsed} from './selector.is-selection-collapsed'
6
- import {getFocusSpan, getSelectedBlocks} from './selectors'
7
8
 
8
9
  /**
9
10
  * @public
@@ -1,7 +1,7 @@
1
1
  import type {PortableTextListBlock} from '@sanity/types'
2
2
  import type {EditorSelector} from '../editor/editor-selector'
3
3
  import {isTextBlock} from '../internal-utils/parse-blocks'
4
- import {getSelectedBlocks} from './selectors'
4
+ import {getSelectedBlocks} from './selector.get-selected-blocks'
5
5
 
6
6
  /**
7
7
  * @public
@@ -1,7 +1,7 @@
1
1
  import type {PortableTextTextBlock} from '@sanity/types'
2
2
  import type {EditorSelector} from '../editor/editor-selector'
3
3
  import {isTextBlock} from '../internal-utils/parse-blocks'
4
- import {getSelectedBlocks} from './selectors'
4
+ import {getSelectedBlocks} from './selector.get-selected-blocks'
5
5
 
6
6
  /**
7
7
  * @public
@@ -1,12 +1,13 @@
1
- import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
1
+ import type {PortableTextBlock} from '@sanity/types'
2
2
  import type {EditorSelector} from '../editor/editor-selector'
3
3
  import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
4
+ import type {BlockPath} from '../types/paths'
4
5
 
5
6
  /**
6
7
  * @public
7
8
  */
8
9
  export const getAnchorBlock: EditorSelector<
9
- {node: PortableTextBlock; path: [KeyedSegment]} | undefined
10
+ {node: PortableTextBlock; path: BlockPath} | undefined
10
11
  > = (snapshot) => {
11
12
  if (!snapshot.context.selection) {
12
13
  return undefined
@@ -1,7 +1,7 @@
1
- import type {KeyedSegment} from '@portabletext/patches'
2
1
  import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
3
2
  import type {EditorSelector} from '../editor/editor-selector'
4
3
  import {getChildKeyFromSelectionPoint} from '../selection/selection-point'
4
+ import type {ChildPath} from '../types/paths'
5
5
  import {getAnchorTextBlock} from './selector.get-anchor-text-block'
6
6
 
7
7
  /**
@@ -10,7 +10,7 @@ import {getAnchorTextBlock} from './selector.get-anchor-text-block'
10
10
  export const getAnchorChild: EditorSelector<
11
11
  | {
12
12
  node: PortableTextObject | PortableTextSpan
13
- path: [KeyedSegment, 'children', KeyedSegment]
13
+ path: ChildPath
14
14
  }
15
15
  | undefined
16
16
  > = (snapshot) => {
@@ -1,14 +1,13 @@
1
- import type {KeyedSegment} from '@portabletext/patches'
2
1
  import {isPortableTextSpan, type PortableTextSpan} from '@sanity/types'
3
2
  import type {EditorSelector} from '../editor/editor-selector'
3
+ import type {ChildPath} from '../types/paths'
4
4
  import {getAnchorChild} from './selector.get-anchor-child'
5
5
 
6
6
  /**
7
7
  * @public
8
8
  */
9
9
  export const getAnchorSpan: EditorSelector<
10
- | {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
11
- | undefined
10
+ {node: PortableTextSpan; path: ChildPath} | undefined
12
11
  > = (snapshot) => {
13
12
  const anchorChild = getAnchorChild(snapshot)
14
13
 
@@ -1,13 +1,14 @@
1
- import type {KeyedSegment, PortableTextTextBlock} from '@sanity/types'
1
+ import type {PortableTextTextBlock} from '@sanity/types'
2
2
  import type {EditorSelector} from '../editor/editor-selector'
3
3
  import {isTextBlock} from '../internal-utils/parse-blocks'
4
+ import type {BlockPath} from '../types/paths'
4
5
  import {getAnchorBlock} from './selector.get-anchor-block'
5
6
 
6
7
  /**
7
8
  * @public
8
9
  */
9
10
  export const getAnchorTextBlock: EditorSelector<
10
- {node: PortableTextTextBlock; path: [KeyedSegment]} | undefined
11
+ {node: PortableTextTextBlock; path: BlockPath} | undefined
11
12
  > = (snapshot) => {
12
13
  const anchorBlock = getAnchorBlock(snapshot)
13
14
 
@@ -7,13 +7,13 @@ import {
7
7
  getBlockStartPoint,
8
8
  spanSelectionPointToBlockOffset,
9
9
  } from '../utils'
10
+ import {getFocusTextBlock} from './selector.get-focus-text-block'
10
11
  import {getNextInlineObject} from './selector.get-next-inline-object'
11
12
  import {getPreviousInlineObject} from './selector.get-previous-inline-object'
12
13
  import {getSelectionStartPoint} from './selector.get-selection-start-point'
13
14
  import {getSelectionText} from './selector.get-selection-text'
14
15
  import {isSelectionCollapsed} from './selector.is-selection-collapsed'
15
16
  import {isSelectionExpanded} from './selector.is-selection-expanded'
16
- import {getFocusTextBlock} from './selectors'
17
17
 
18
18
  /**
19
19
  * @public
@@ -0,0 +1,14 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import type {BlockPath} from '../types/paths'
4
+
5
+ /**
6
+ * @public
7
+ */
8
+ export const getFirstBlock: EditorSelector<
9
+ {node: PortableTextBlock; path: BlockPath} | undefined
10
+ > = (snapshot) => {
11
+ const node = snapshot.context.value[0]
12
+
13
+ return node ? {node, path: [{_key: node._key}]} : undefined
14
+ }
@@ -0,0 +1,18 @@
1
+ import type {PortableTextObject} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {isTextBlock} from '../internal-utils/parse-blocks'
4
+ import type {BlockPath} from '../types/paths'
5
+ import {getFocusBlock} from './selector.get-focus-block'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export const getFocusBlockObject: EditorSelector<
11
+ {node: PortableTextObject; path: BlockPath} | undefined
12
+ > = (snapshot) => {
13
+ const focusBlock = getFocusBlock(snapshot)
14
+
15
+ return focusBlock && !isTextBlock(snapshot.context, focusBlock.node)
16
+ ? {node: focusBlock.node, path: focusBlock.path}
17
+ : undefined
18
+ }
@@ -0,0 +1,23 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
4
+ import type {BlockPath} from '../types/paths'
5
+
6
+ /**
7
+ * @public
8
+ */
9
+ export const getFocusBlock: EditorSelector<
10
+ {node: PortableTextBlock; path: BlockPath} | undefined
11
+ > = (snapshot) => {
12
+ if (!snapshot.context.selection) {
13
+ return undefined
14
+ }
15
+
16
+ const key = getBlockKeyFromSelectionPoint(snapshot.context.selection.focus)
17
+
18
+ const node = key
19
+ ? snapshot.context.value.find((block) => block._key === key)
20
+ : undefined
21
+
22
+ return node && key ? {node, path: [{_key: key}]} : undefined
23
+ }
@@ -0,0 +1,36 @@
1
+ import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {getChildKeyFromSelectionPoint} from '../selection/selection-point'
4
+ import type {ChildPath} from '../types/paths'
5
+ import {getFocusTextBlock} from './selector.get-focus-text-block'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export const getFocusChild: EditorSelector<
11
+ | {
12
+ node: PortableTextObject | PortableTextSpan
13
+ path: ChildPath
14
+ }
15
+ | undefined
16
+ > = (snapshot) => {
17
+ if (!snapshot.context.selection) {
18
+ return undefined
19
+ }
20
+
21
+ const focusBlock = getFocusTextBlock(snapshot)
22
+
23
+ if (!focusBlock) {
24
+ return undefined
25
+ }
26
+
27
+ const key = getChildKeyFromSelectionPoint(snapshot.context.selection.focus)
28
+
29
+ const node = key
30
+ ? focusBlock.node.children.find((span) => span._key === key)
31
+ : undefined
32
+
33
+ return node && key
34
+ ? {node, path: [...focusBlock.path, 'children', {_key: key}]}
35
+ : undefined
36
+ }
@@ -1,17 +1,13 @@
1
- import {
2
- isPortableTextSpan,
3
- type KeyedSegment,
4
- type PortableTextObject,
5
- } from '@sanity/types'
1
+ import {isPortableTextSpan, type PortableTextObject} from '@sanity/types'
6
2
  import type {EditorSelector} from '../editor/editor-selector'
7
- import {getFocusChild} from './selectors'
3
+ import type {ChildPath} from '../types/paths'
4
+ import {getFocusChild} from './selector.get-focus-child'
8
5
 
9
6
  /**
10
7
  * @public
11
8
  */
12
9
  export const getFocusInlineObject: EditorSelector<
13
- | {node: PortableTextObject; path: [KeyedSegment, 'children', KeyedSegment]}
14
- | undefined
10
+ {node: PortableTextObject; path: ChildPath} | undefined
15
11
  > = (snapshot) => {
16
12
  const focusChild = getFocusChild(snapshot)
17
13
 
@@ -0,0 +1,18 @@
1
+ import type {PortableTextListBlock} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {isListBlock} from '../internal-utils/parse-blocks'
4
+ import type {BlockPath} from '../types/paths'
5
+ import {getFocusTextBlock} from './selector.get-focus-text-block'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export const getFocusListBlock: EditorSelector<
11
+ {node: PortableTextListBlock; path: BlockPath} | undefined
12
+ > = (snapshot) => {
13
+ const focusTextBlock = getFocusTextBlock(snapshot)
14
+
15
+ return focusTextBlock && isListBlock(snapshot.context, focusTextBlock.node)
16
+ ? {node: focusTextBlock.node, path: focusTextBlock.path}
17
+ : undefined
18
+ }
@@ -0,0 +1,18 @@
1
+ import type {PortableTextSpan} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {isSpan} from '../internal-utils/parse-blocks'
4
+ import type {ChildPath} from '../types/paths'
5
+ import {getFocusChild} from './selector.get-focus-child'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export const getFocusSpan: EditorSelector<
11
+ {node: PortableTextSpan; path: ChildPath} | undefined
12
+ > = (snapshot) => {
13
+ const focusChild = getFocusChild(snapshot)
14
+
15
+ return focusChild && isSpan(snapshot.context, focusChild.node)
16
+ ? {node: focusChild.node, path: focusChild.path}
17
+ : undefined
18
+ }