@portabletext/editor 1.13.0 → 1.14.1

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 (74) hide show
  1. package/README.md +1 -1
  2. package/lib/_chunks-cjs/selector.get-text-before.cjs +320 -0
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -0
  4. package/lib/_chunks-es/selector.get-text-before.js +321 -0
  5. package/lib/_chunks-es/selector.get-text-before.js.map +1 -0
  6. package/lib/{index.esm.js → index.cjs} +1703 -1431
  7. package/lib/index.cjs.map +1 -0
  8. package/lib/{index.d.mts → index.d.cts} +4038 -313
  9. package/lib/index.d.ts +4038 -313
  10. package/lib/index.js +1724 -1407
  11. package/lib/index.js.map +1 -1
  12. package/lib/selectors/index.cjs +35 -0
  13. package/lib/selectors/index.cjs.map +1 -0
  14. package/lib/selectors/index.d.cts +243 -0
  15. package/lib/selectors/index.d.ts +243 -0
  16. package/lib/selectors/index.js +36 -0
  17. package/lib/selectors/index.js.map +1 -0
  18. package/package.json +21 -13
  19. package/src/editor/Editable.tsx +1 -1
  20. package/src/editor/PortableTextEditor.tsx +19 -4
  21. package/src/editor/__tests__/handleClick.test.tsx +4 -4
  22. package/src/editor/behavior/behavior.action.insert-block-object.ts +1 -1
  23. package/src/editor/behavior/behavior.action.insert-break.ts +3 -3
  24. package/src/editor/behavior/behavior.action.insert-inline-object.ts +58 -0
  25. package/src/editor/behavior/behavior.action.insert-span.ts +1 -1
  26. package/src/editor/behavior/behavior.action.list-item.ts +100 -0
  27. package/src/editor/behavior/behavior.action.style.ts +108 -0
  28. package/src/editor/behavior/behavior.action.text-block.set.ts +25 -0
  29. package/src/editor/behavior/behavior.action.text-block.unset.ts +17 -0
  30. package/src/editor/behavior/behavior.actions.ts +178 -109
  31. package/src/editor/behavior/behavior.code-editor.ts +30 -40
  32. package/src/editor/behavior/behavior.core.block-objects.ts +26 -26
  33. package/src/editor/behavior/behavior.core.decorators.ts +9 -6
  34. package/src/editor/behavior/behavior.core.lists.ts +139 -17
  35. package/src/editor/behavior/behavior.core.ts +5 -2
  36. package/src/editor/behavior/behavior.guards.ts +28 -0
  37. package/src/editor/behavior/behavior.links.ts +7 -7
  38. package/src/editor/behavior/behavior.markdown.ts +68 -79
  39. package/src/editor/behavior/behavior.types.ts +86 -60
  40. package/src/editor/{use-editor.ts → create-editor.ts} +13 -8
  41. package/src/editor/editor-event-listener.tsx +2 -2
  42. package/src/editor/editor-machine.ts +54 -15
  43. package/src/editor/editor-provider.tsx +5 -5
  44. package/src/editor/editor-selector.ts +49 -0
  45. package/src/editor/editor-snapshot.ts +22 -0
  46. package/src/editor/get-value.ts +11 -0
  47. package/src/editor/plugins/create-with-event-listeners.ts +93 -5
  48. package/src/editor/plugins/createWithEditableAPI.ts +69 -20
  49. package/src/editor/plugins/createWithHotKeys.ts +0 -54
  50. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +1 -55
  51. package/src/editor/plugins/with-plugins.ts +4 -8
  52. package/src/editor/{behavior/behavior.utils.block-offset.test.ts → utils/utils.block-offset.test.ts} +1 -1
  53. package/src/editor/{behavior/behavior.utils.block-offset.ts → utils/utils.block-offset.ts} +1 -8
  54. package/src/editor/{behavior/behavior.utils.reverse-selection.ts → utils/utils.reverse-selection.ts} +3 -5
  55. package/src/editor/utils/utils.ts +21 -0
  56. package/src/index.ts +13 -13
  57. package/src/selectors/index.ts +15 -0
  58. package/src/selectors/selector.get-active-list-item.ts +37 -0
  59. package/src/{editor/behavior/behavior.utils.get-selection-text.ts → selectors/selector.get-selection-text.ts} +10 -15
  60. package/src/selectors/selector.get-text-before.ts +41 -0
  61. package/src/selectors/selectors.ts +329 -0
  62. package/src/types/editor.ts +0 -60
  63. package/src/utils/is-hotkey.test.ts +2 -0
  64. package/src/utils/operationToPatches.ts +5 -0
  65. package/src/utils/paths.ts +4 -11
  66. package/src/utils/ranges.ts +3 -3
  67. package/lib/index.esm.js.map +0 -1
  68. package/lib/index.mjs +0 -7541
  69. package/lib/index.mjs.map +0 -1
  70. package/src/editor/behavior/behavior.utils.ts +0 -218
  71. package/src/editor/behavior/behavior.utilts.get-text-before.ts +0 -31
  72. package/src/editor/plugins/createWithPortableTextLists.ts +0 -172
  73. /package/src/editor/{behavior/behavior.utils.get-start-point.ts → utils/utils.get-start-point.ts} +0 -0
  74. /package/src/editor/{behavior/behavior.utils.is-keyed-segment.ts → utils/utils.is-keyed-segment.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import {Editor, Path, Text as SlateText, Transforms, type Node} from 'slate'
1
+ import {Editor, Path, Text as SlateText, Transforms} from 'slate'
2
2
  import type {
3
3
  PortableTextMemberSchemaTypes,
4
4
  PortableTextSlateEditor,
@@ -50,60 +50,6 @@ export function createWithPortableTextBlockStyle(
50
50
  normalizeNode(nodeEntry)
51
51
  }
52
52
 
53
- editor.pteHasBlockStyle = (style: string): boolean => {
54
- if (!editor.selection) {
55
- return false
56
- }
57
- const selectedBlocks = [
58
- ...Editor.nodes(editor, {
59
- at: editor.selection,
60
- match: (node) => editor.isTextBlock(node) && node.style === style,
61
- }),
62
- ]
63
- if (selectedBlocks.length > 0) {
64
- return true
65
- }
66
- return false
67
- }
68
-
69
- editor.pteToggleBlockStyle = (blockStyle: string): void => {
70
- if (!editor.selection) {
71
- return
72
- }
73
- const selectedBlocks = [
74
- ...Editor.nodes(editor, {
75
- at: editor.selection,
76
- match: (node) => editor.isTextBlock(node),
77
- }),
78
- ]
79
- selectedBlocks.forEach(([node, path]) => {
80
- if (editor.isTextBlock(node) && node.style === blockStyle) {
81
- debug(`Unsetting block style '${blockStyle}'`)
82
- Transforms.setNodes(
83
- editor,
84
- {...node, style: defaultStyle} as Partial<Node>,
85
- {
86
- at: path,
87
- },
88
- )
89
- } else {
90
- if (blockStyle) {
91
- debug(`Setting style '${blockStyle}'`)
92
- } else {
93
- debug('Setting default style', defaultStyle)
94
- }
95
- Transforms.setNodes(
96
- editor,
97
- {
98
- ...node,
99
- style: blockStyle || defaultStyle,
100
- } as Partial<Node>,
101
- {at: path},
102
- )
103
- }
104
- })
105
- editor.onChange()
106
- }
107
53
  return editor
108
54
  }
109
55
  }
@@ -8,7 +8,6 @@ import {createWithObjectKeys} from './createWithObjectKeys'
8
8
  import {createWithPatches} from './createWithPatches'
9
9
  import {createWithPlaceholderBlock} from './createWithPlaceholderBlock'
10
10
  import {createWithPortableTextBlockStyle} from './createWithPortableTextBlockStyle'
11
- import {createWithPortableTextLists} from './createWithPortableTextLists'
12
11
  import {createWithPortableTextMarkModel} from './createWithPortableTextMarkModel'
13
12
  import {createWithPortableTextSelections} from './createWithPortableTextSelections'
14
13
  import {createWithSchemaTypes} from './createWithSchemaTypes'
@@ -46,7 +45,6 @@ export const withPlugins = <T extends Editor>(
46
45
  subscriptions: options.subscriptions,
47
46
  })
48
47
  const withMaxBlocks = createWithMaxBlocks(editorActor)
49
- const withPortableTextLists = createWithPortableTextLists(schemaTypes)
50
48
  const withUndoRedo = createWithUndoRedo({
51
49
  editorActor,
52
50
  blockSchemaType: schemaTypes.block,
@@ -82,12 +80,10 @@ export const withPlugins = <T extends Editor>(
82
80
  withObjectKeys(
83
81
  withPortableTextMarkModel(
84
82
  withPortableTextBlockStyle(
85
- withPortableTextLists(
86
- withPlaceholderBlock(
87
- withUtils(
88
- withMaxBlocks(
89
- withUndoRedo(withPatches(withPortableTextSelections(e))),
90
- ),
83
+ withPlaceholderBlock(
84
+ withUtils(
85
+ withMaxBlocks(
86
+ withUndoRedo(withPatches(withPortableTextSelections(e))),
91
87
  ),
92
88
  ),
93
89
  ),
@@ -1,6 +1,6 @@
1
1
  import type {PortableTextBlock} from '@sanity/types'
2
2
  import {expect, test} from 'vitest'
3
- import {blockOffsetToSpanSelectionPoint} from './behavior.utils.block-offset'
3
+ import {blockOffsetToSpanSelectionPoint} from './utils.block-offset'
4
4
 
5
5
  test(blockOffsetToSpanSelectionPoint.name, () => {
6
6
  const value: Array<PortableTextBlock> = [
@@ -4,14 +4,7 @@ import {
4
4
  type KeyedSegment,
5
5
  type PortableTextBlock,
6
6
  } from '@sanity/types'
7
-
8
- /**
9
- * @alpha
10
- */
11
- export type BlockOffset = {
12
- path: [KeyedSegment]
13
- offset: number
14
- }
7
+ import type {BlockOffset} from '../behavior/behavior.types'
15
8
 
16
9
  export function blockOffsetToSpanSelectionPoint({
17
10
  value,
@@ -1,10 +1,8 @@
1
1
  import type {EditorSelection} from '../../types/editor'
2
2
 
3
- export function reverseSelection(selection: EditorSelection): EditorSelection {
4
- if (!selection) {
5
- return selection
6
- }
7
-
3
+ export function reverseSelection(
4
+ selection: NonNullable<EditorSelection>,
5
+ ): NonNullable<EditorSelection> {
8
6
  if (selection.backward) {
9
7
  return {
10
8
  anchor: selection.focus,
@@ -0,0 +1,21 @@
1
+ import {
2
+ isPortableTextSpan,
3
+ isPortableTextTextBlock,
4
+ type PortableTextBlock,
5
+ type PortableTextTextBlock,
6
+ } from '@sanity/types'
7
+
8
+ export function isEmptyTextBlock(block: PortableTextBlock) {
9
+ if (!isPortableTextTextBlock(block)) {
10
+ return false
11
+ }
12
+
13
+ const onlyText = block.children.every(isPortableTextSpan)
14
+ const blockText = getTextBlockText(block)
15
+
16
+ return onlyText && blockText === ''
17
+ }
18
+
19
+ export function getTextBlockText(block: PortableTextTextBlock) {
20
+ return block.children.map((child) => child.text ?? '').join('')
21
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export type {Patch} from '@portabletext/patches'
2
2
  export type {PortableTextBlock, PortableTextChild} from '@sanity/types'
3
+ export {
4
+ createCodeEditorBehaviors,
5
+ type CodeEditorBehaviorsConfig,
6
+ } from './editor/behavior/behavior.code-editor'
3
7
  export {coreBehavior, coreBehaviors} from './editor/behavior/behavior.core'
4
8
  export {
5
9
  createLinkBehaviors,
@@ -9,22 +13,18 @@ export {
9
13
  createMarkdownBehaviors,
10
14
  type MarkdownBehaviorsConfig,
11
15
  } from './editor/behavior/behavior.markdown'
12
- export {
13
- createCodeEditorBehaviors,
14
- type CodeEditorBehaviorsConfig,
15
- } from './editor/behavior/behavior.code-editor'
16
16
  export {
17
17
  defineBehavior,
18
18
  type Behavior,
19
19
  type BehaviorActionIntend,
20
20
  type BehaviorActionIntendSet,
21
- type BehaviorContext,
22
21
  type BehaviorEvent,
23
22
  type BehaviorGuard,
24
23
  type OmitFromUnion,
25
24
  type PickFromUnion,
25
+ type BlockOffset,
26
26
  } from './editor/behavior/behavior.types'
27
- export type {BlockOffset} from './editor/behavior/behavior.utils.block-offset'
27
+ export type {Editor, EditorConfig, EditorEvent} from './editor/create-editor'
28
28
  export type {SlateEditor} from './editor/create-slate-editor'
29
29
  export {
30
30
  defineSchema,
@@ -46,20 +46,20 @@ export {
46
46
  } from './editor/editor-machine'
47
47
  export {
48
48
  EditorProvider,
49
- useEditorContext,
49
+ useEditor,
50
50
  type EditorProviderProps,
51
51
  } from './editor/editor-provider'
52
+ export {
53
+ useEditorSelector,
54
+ type EditorSelector,
55
+ type EditorSelectorSnapshot,
56
+ } from './editor/editor-selector'
57
+ export type {EditorContext, EditorSnapshot} from './editor/editor-snapshot'
52
58
  export {usePortableTextEditor} from './editor/hooks/usePortableTextEditor'
53
59
  export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEditorSelection'
54
60
  export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
55
61
  export type {AddedAnnotationPaths} from './editor/plugins/createWithEditableAPI'
56
62
  export {PortableTextEditor} from './editor/PortableTextEditor'
57
63
  export type {PortableTextEditorProps} from './editor/PortableTextEditor'
58
- export {
59
- useEditor,
60
- type Editor,
61
- type EditorConfig,
62
- type EditorEvent,
63
- } from './editor/use-editor'
64
64
  export * from './types/editor'
65
65
  export type {HotkeyOptions} from './types/options'
@@ -0,0 +1,15 @@
1
+ export type {
2
+ EditorSelector,
3
+ EditorSelectorSnapshot,
4
+ } from '../editor/editor-selector'
5
+ export type {EditorContext, EditorSnapshot} from '../editor/editor-snapshot'
6
+ export type {
7
+ EditorSelection,
8
+ EditorSelectionPoint,
9
+ PortableTextMemberSchemaTypes,
10
+ } from '../types/editor'
11
+
12
+ export {getActiveListItem} from './selector.get-active-list-item'
13
+ export {getSelectionText} from './selector.get-selection-text'
14
+ export {getBlockTextBefore} from './selector.get-text-before'
15
+ export * from './selectors'
@@ -0,0 +1,37 @@
1
+ import type {PortableTextListBlock} from '@sanity/types'
2
+ import {createGuards} from '../editor/behavior/behavior.guards'
3
+ import type {EditorSelector} from '../editor/editor-selector'
4
+ import {getSelectedBlocks} from './selectors'
5
+
6
+ /**
7
+ * @alpha
8
+ */
9
+ export const getActiveListItem: EditorSelector<
10
+ PortableTextListBlock['listItem'] | undefined
11
+ > = ({context}) => {
12
+ if (!context.selection) {
13
+ return undefined
14
+ }
15
+
16
+ const guards = createGuards(context)
17
+ const selectedBlocks = getSelectedBlocks({context}).map((block) => block.node)
18
+ const selectedTextBlocks = selectedBlocks.filter(guards.isTextBlock)
19
+
20
+ const firstTextBlock = selectedTextBlocks.at(0)
21
+
22
+ if (!firstTextBlock) {
23
+ return undefined
24
+ }
25
+
26
+ const firstListItem = firstTextBlock.listItem
27
+
28
+ if (!firstListItem) {
29
+ return undefined
30
+ }
31
+
32
+ if (selectedTextBlocks.every((block) => block.listItem === firstListItem)) {
33
+ return firstListItem
34
+ }
35
+
36
+ return undefined
37
+ }
@@ -1,21 +1,16 @@
1
- import {
2
- isPortableTextSpan,
3
- isPortableTextTextBlock,
4
- type PortableTextBlock,
5
- } from '@sanity/types'
6
- import type {EditorSelection} from '../../types/editor'
7
- import {isKeyedSegment} from './behavior.utils.is-keyed-segment'
8
- import {reverseSelection} from './behavior.utils.reverse-selection'
1
+ import {isPortableTextSpan, isPortableTextTextBlock} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {isKeyedSegment} from '../editor/utils/utils.is-keyed-segment'
4
+ import {reverseSelection} from '../editor/utils/utils.reverse-selection'
9
5
 
10
- export function getSelectionText({
11
- value,
12
- selection,
13
- }: {
14
- value: Array<PortableTextBlock>
15
- selection: NonNullable<EditorSelection>
16
- }): string {
6
+ /**
7
+ * @alpha
8
+ */
9
+ export const getSelectionText: EditorSelector<string> = ({context}) => {
17
10
  let text = ''
18
11
 
12
+ const {value, selection} = context
13
+
19
14
  if (!value || !selection) {
20
15
  return text
21
16
  }
@@ -0,0 +1,41 @@
1
+ import type {EditorSelector} from '../editor/editor-selector'
2
+ import {getStartPoint} from '../editor/utils/utils.get-start-point'
3
+ import {isKeyedSegment} from '../editor/utils/utils.is-keyed-segment'
4
+ import {reverseSelection} from '../editor/utils/utils.reverse-selection'
5
+ import {getSelectionText} from './selector.get-selection-text'
6
+
7
+ /**
8
+ * @alpha
9
+ */
10
+ export const getBlockTextBefore: EditorSelector<string> = ({context}) => {
11
+ if (!context.selection) {
12
+ return ''
13
+ }
14
+
15
+ const selection = context.selection.backward
16
+ ? reverseSelection(context.selection)
17
+ : context.selection
18
+ const point = selection.anchor
19
+ const key = isKeyedSegment(point.path[0]) ? point.path[0]._key : undefined
20
+
21
+ const block = key
22
+ ? context.value.find((block) => block._key === key)
23
+ : undefined
24
+
25
+ if (!block) {
26
+ return ''
27
+ }
28
+
29
+ const startOfBlock = getStartPoint({node: block, path: [{_key: block._key}]})
30
+
31
+ return getSelectionText({
32
+ context: {
33
+ ...context,
34
+ value: context.value,
35
+ selection: {
36
+ anchor: startOfBlock,
37
+ focus: point,
38
+ },
39
+ },
40
+ })
41
+ }
@@ -0,0 +1,329 @@
1
+ import {
2
+ isKeySegment,
3
+ isPortableTextSpan,
4
+ isPortableTextTextBlock,
5
+ type KeyedSegment,
6
+ type PortableTextBlock,
7
+ type PortableTextListBlock,
8
+ type PortableTextObject,
9
+ type PortableTextSpan,
10
+ type PortableTextTextBlock,
11
+ } from '@sanity/types'
12
+ import {createGuards} from '../editor/behavior/behavior.guards'
13
+ import type {EditorSelector} from '../editor/editor-selector'
14
+
15
+ /**
16
+ * @alpha
17
+ */
18
+ export const selectionIsCollapsed: EditorSelector<boolean> = ({context}) => {
19
+ return (
20
+ JSON.stringify(context.selection?.anchor.path) ===
21
+ JSON.stringify(context.selection?.focus.path) &&
22
+ context.selection?.anchor.offset === context.selection?.focus.offset
23
+ )
24
+ }
25
+
26
+ /**
27
+ * @alpha
28
+ */
29
+ export const getFocusBlock: EditorSelector<
30
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
31
+ > = ({context}) => {
32
+ const key = context.selection
33
+ ? isKeySegment(context.selection.focus.path[0])
34
+ ? context.selection.focus.path[0]._key
35
+ : undefined
36
+ : undefined
37
+
38
+ const node = key
39
+ ? context.value.find((block) => block._key === key)
40
+ : undefined
41
+
42
+ return node && key ? {node, path: [{_key: key}]} : undefined
43
+ }
44
+
45
+ /**
46
+ * @alpha
47
+ */
48
+ export const getFocusListBlock: EditorSelector<
49
+ {node: PortableTextListBlock; path: [KeyedSegment]} | undefined
50
+ > = ({context}) => {
51
+ const guards = createGuards(context)
52
+ const focusBlock = getFocusBlock({context})
53
+
54
+ return focusBlock && guards.isListBlock(focusBlock.node)
55
+ ? {node: focusBlock.node, path: focusBlock.path}
56
+ : undefined
57
+ }
58
+
59
+ /**
60
+ * @alpha
61
+ */
62
+ export const getFocusTextBlock: EditorSelector<
63
+ {node: PortableTextTextBlock; path: [KeyedSegment]} | undefined
64
+ > = ({context}) => {
65
+ const focusBlock = getFocusBlock({context})
66
+
67
+ return focusBlock && isPortableTextTextBlock(focusBlock.node)
68
+ ? {node: focusBlock.node, path: focusBlock.path}
69
+ : undefined
70
+ }
71
+
72
+ /**
73
+ * @alpha
74
+ */
75
+ export const getFocusBlockObject: EditorSelector<
76
+ {node: PortableTextObject; path: [KeyedSegment]} | undefined
77
+ > = ({context}) => {
78
+ const focusBlock = getFocusBlock({context})
79
+
80
+ return focusBlock && !isPortableTextTextBlock(focusBlock.node)
81
+ ? {node: focusBlock.node, path: focusBlock.path}
82
+ : undefined
83
+ }
84
+
85
+ /**
86
+ * @alpha
87
+ */
88
+ export const getFocusChild: EditorSelector<
89
+ | {
90
+ node: PortableTextObject | PortableTextSpan
91
+ path: [KeyedSegment, 'children', KeyedSegment]
92
+ }
93
+ | undefined
94
+ > = ({context}) => {
95
+ const focusBlock = getFocusTextBlock({context})
96
+
97
+ if (!focusBlock) {
98
+ return undefined
99
+ }
100
+
101
+ const key = context.selection
102
+ ? isKeySegment(context.selection.focus.path[2])
103
+ ? context.selection.focus.path[2]._key
104
+ : undefined
105
+ : undefined
106
+
107
+ const node = key
108
+ ? focusBlock.node.children.find((span) => span._key === key)
109
+ : undefined
110
+
111
+ return node && key
112
+ ? {node, path: [...focusBlock.path, 'children', {_key: key}]}
113
+ : undefined
114
+ }
115
+
116
+ /**
117
+ * @alpha
118
+ */
119
+ export const getFocusSpan: EditorSelector<
120
+ | {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
121
+ | undefined
122
+ > = ({context}) => {
123
+ const focusChild = getFocusChild({context})
124
+
125
+ return focusChild && isPortableTextSpan(focusChild.node)
126
+ ? {node: focusChild.node, path: focusChild.path}
127
+ : undefined
128
+ }
129
+
130
+ /**
131
+ * @alpha
132
+ */
133
+ export const getFirstBlock: EditorSelector<
134
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
135
+ > = ({context}) => {
136
+ const node = context.value[0]
137
+
138
+ return node ? {node, path: [{_key: node._key}]} : undefined
139
+ }
140
+
141
+ /**
142
+ * @alpha
143
+ */
144
+ export const getLastBlock: EditorSelector<
145
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
146
+ > = ({context}) => {
147
+ const node = context.value[context.value.length - 1]
148
+ ? context.value[context.value.length - 1]
149
+ : undefined
150
+
151
+ return node ? {node, path: [{_key: node._key}]} : undefined
152
+ }
153
+
154
+ /**
155
+ * @alpha
156
+ */
157
+ export const getSelectedBlocks: EditorSelector<
158
+ Array<{node: PortableTextBlock; path: [KeyedSegment]}>
159
+ > = ({context}) => {
160
+ if (!context.selection) {
161
+ return []
162
+ }
163
+
164
+ const selectedBlocks: Array<{node: PortableTextBlock; path: [KeyedSegment]}> =
165
+ []
166
+ const startKey = context.selection.backward
167
+ ? isKeySegment(context.selection.focus.path[0])
168
+ ? context.selection.focus.path[0]._key
169
+ : undefined
170
+ : isKeySegment(context.selection.anchor.path[0])
171
+ ? context.selection.anchor.path[0]._key
172
+ : undefined
173
+ const endKey = context.selection.backward
174
+ ? isKeySegment(context.selection.anchor.path[0])
175
+ ? context.selection.anchor.path[0]._key
176
+ : undefined
177
+ : isKeySegment(context.selection.focus.path[0])
178
+ ? context.selection.focus.path[0]._key
179
+ : undefined
180
+
181
+ if (!startKey || !endKey) {
182
+ return selectedBlocks
183
+ }
184
+
185
+ for (const block of context.value) {
186
+ if (block._key === startKey) {
187
+ selectedBlocks.push({node: block, path: [{_key: block._key}]})
188
+
189
+ if (startKey === endKey) {
190
+ break
191
+ }
192
+ continue
193
+ }
194
+
195
+ if (block._key === endKey) {
196
+ selectedBlocks.push({node: block, path: [{_key: block._key}]})
197
+ break
198
+ }
199
+
200
+ if (selectedBlocks.length > 0) {
201
+ selectedBlocks.push({node: block, path: [{_key: block._key}]})
202
+ }
203
+ }
204
+
205
+ return selectedBlocks
206
+ }
207
+
208
+ /**
209
+ * @alpha
210
+ */
211
+ export const getSelectionStartBlock: EditorSelector<
212
+ | {
213
+ node: PortableTextBlock
214
+ path: [KeyedSegment]
215
+ }
216
+ | undefined
217
+ > = ({context}) => {
218
+ if (!context.selection) {
219
+ return undefined
220
+ }
221
+
222
+ const key = context.selection.backward
223
+ ? isKeySegment(context.selection.focus.path[0])
224
+ ? context.selection.focus.path[0]._key
225
+ : undefined
226
+ : isKeySegment(context.selection.anchor.path[0])
227
+ ? context.selection.anchor.path[0]._key
228
+ : undefined
229
+
230
+ const node = key
231
+ ? context.value.find((block) => block._key === key)
232
+ : undefined
233
+
234
+ return node && key ? {node, path: [{_key: key}]} : undefined
235
+ }
236
+
237
+ /**
238
+ * @alpha
239
+ */
240
+ export const getSelectionEndBlock: EditorSelector<
241
+ | {
242
+ node: PortableTextBlock
243
+ path: [KeyedSegment]
244
+ }
245
+ | undefined
246
+ > = ({context}) => {
247
+ if (!context.selection) {
248
+ return undefined
249
+ }
250
+
251
+ const key = context.selection.backward
252
+ ? isKeySegment(context.selection.anchor.path[0])
253
+ ? context.selection.anchor.path[0]._key
254
+ : undefined
255
+ : isKeySegment(context.selection.focus.path[0])
256
+ ? context.selection.focus.path[0]._key
257
+ : undefined
258
+
259
+ const node = key
260
+ ? context.value.find((block) => block._key === key)
261
+ : undefined
262
+
263
+ return node && key ? {node, path: [{_key: key}]} : undefined
264
+ }
265
+
266
+ /**
267
+ * @alpha
268
+ */
269
+ export const getPreviousBlock: EditorSelector<
270
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
271
+ > = ({context}) => {
272
+ let previousBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
273
+ const selectionStartBlock = getSelectionStartBlock({context})
274
+
275
+ if (!selectionStartBlock) {
276
+ return undefined
277
+ }
278
+
279
+ let foundSelectionStartBlock = false
280
+
281
+ for (const block of context.value) {
282
+ if (block._key === selectionStartBlock.node._key) {
283
+ foundSelectionStartBlock = true
284
+ break
285
+ }
286
+
287
+ previousBlock = {node: block, path: [{_key: block._key}]}
288
+ }
289
+
290
+ if (foundSelectionStartBlock && previousBlock) {
291
+ return previousBlock
292
+ }
293
+
294
+ return undefined
295
+ }
296
+
297
+ /**
298
+ * @alpha
299
+ */
300
+ export const getNextBlock: EditorSelector<
301
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
302
+ > = ({context}) => {
303
+ let nextBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
304
+ const selectionEndBlock = getSelectionEndBlock({context})
305
+
306
+ if (!selectionEndBlock) {
307
+ return undefined
308
+ }
309
+
310
+ let foundSelectionEndBlock = false
311
+
312
+ for (const block of context.value) {
313
+ if (block._key === selectionEndBlock.node._key) {
314
+ foundSelectionEndBlock = true
315
+ continue
316
+ }
317
+
318
+ if (foundSelectionEndBlock) {
319
+ nextBlock = {node: block, path: [{_key: block._key}]}
320
+ break
321
+ }
322
+ }
323
+
324
+ if (foundSelectionEndBlock && nextBlock) {
325
+ return nextBlock
326
+ }
327
+
328
+ return undefined
329
+ }