@portabletext/editor 1.12.3 → 1.14.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 (75) 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} +1954 -1513
  7. package/lib/index.cjs.map +1 -0
  8. package/lib/{index.d.mts → index.d.cts} +4249 -304
  9. package/lib/index.d.ts +4249 -304
  10. package/lib/index.js +1974 -1488
  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 +25 -17
  19. package/src/editor/Editable.tsx +61 -6
  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 +24 -27
  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 +266 -75
  31. package/src/editor/behavior/behavior.code-editor.ts +76 -0
  32. package/src/editor/behavior/behavior.core.block-objects.ts +52 -19
  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 +7 -2
  36. package/src/editor/behavior/behavior.guards.ts +28 -0
  37. package/src/editor/behavior/behavior.links.ts +9 -9
  38. package/src/editor/behavior/behavior.markdown.ts +69 -80
  39. package/src/editor/behavior/behavior.types.ts +121 -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 +57 -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 -101
  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 -9
  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 +99 -46
  64. package/src/utils/is-hotkey.ts +1 -1
  65. package/src/utils/operationToPatches.ts +5 -0
  66. package/src/utils/paths.ts +4 -11
  67. package/src/utils/ranges.ts +3 -3
  68. package/lib/index.esm.js.map +0 -1
  69. package/lib/index.mjs +0 -7372
  70. package/lib/index.mjs.map +0 -1
  71. package/src/editor/behavior/behavior.utils.ts +0 -218
  72. package/src/editor/behavior/behavior.utilts.get-text-before.ts +0 -31
  73. package/src/editor/plugins/createWithPortableTextLists.ts +0 -172
  74. /package/src/editor/{behavior/behavior.utils.get-start-point.ts → utils/utils.get-start-point.ts} +0 -0
  75. /package/src/editor/{behavior/behavior.utils.is-keyed-segment.ts → utils/utils.is-keyed-segment.ts} +0 -0
@@ -1,10 +1,7 @@
1
- import {isPortableTextSpan, isPortableTextTextBlock} from '@sanity/types'
2
1
  import type {KeyboardEvent} from 'react'
3
- import {Editor, Node, Path, Range, Transforms} from 'slate'
4
2
  import type {ReactEditor} from 'slate-react'
5
3
  import type {PortableTextSlateEditor} from '../../types/editor'
6
4
  import type {HotkeyOptions} from '../../types/options'
7
- import type {SlateTextBlock, VoidElement} from '../../types/slate'
8
5
  import {debugWithName} from '../../utils/debug'
9
6
  import {isHotkey} from '../../utils/is-hotkey'
10
7
  import type {EditorActor} from '../editor-machine'
@@ -75,104 +72,6 @@ export function createWithHotkeys(
75
72
  }
76
73
  }
77
74
  })
78
-
79
- const isEnter = isHotkey('enter', event.nativeEvent)
80
- const isTab = isHotkey('tab', event.nativeEvent)
81
- const isShiftEnter = isHotkey('shift+enter', event.nativeEvent)
82
- const isShiftTab = isHotkey('shift+tab', event.nativeEvent)
83
- const isArrowDown = isHotkey('down', event.nativeEvent)
84
- const isArrowUp = isHotkey('up', event.nativeEvent)
85
-
86
- // Check if the user is in a void block, in that case, add an empty text block below if there is no next block
87
- if (isArrowDown && editor.selection) {
88
- const focusBlock = Node.descendant(
89
- editor,
90
- editor.selection.focus.path.slice(0, 1),
91
- ) as SlateTextBlock | VoidElement
92
-
93
- if (focusBlock && Editor.isVoid(editor, focusBlock)) {
94
- const nextPath = Path.next(editor.selection.focus.path.slice(0, 1))
95
- const nextBlock = Node.has(editor, nextPath)
96
- if (!nextBlock) {
97
- Transforms.insertNodes(
98
- editor,
99
- editor.pteCreateTextBlock({decorators: []}),
100
- {
101
- at: nextPath,
102
- },
103
- )
104
- Transforms.select(editor, {path: [...nextPath, 0], offset: 0})
105
- editor.onChange()
106
- return
107
- }
108
- }
109
- }
110
- if (isArrowUp && editor.selection) {
111
- const isFirstBlock = editor.selection.focus.path[0] === 0
112
- const focusBlock = Node.descendant(
113
- editor,
114
- editor.selection.focus.path.slice(0, 1),
115
- ) as SlateTextBlock | VoidElement
116
-
117
- if (isFirstBlock && focusBlock && Editor.isVoid(editor, focusBlock)) {
118
- Transforms.insertNodes(
119
- editor,
120
- editor.pteCreateTextBlock({decorators: []}),
121
- {
122
- at: [0],
123
- },
124
- )
125
- Transforms.select(editor, {path: [0, 0], offset: 0})
126
- editor.onChange()
127
- return
128
- }
129
- }
130
-
131
- // Tab for lists
132
- // Only steal tab when we are on a plain text span or we are at the start of the line (fallback if the whole block is annotated or contains a single inline object)
133
- // Otherwise tab is reserved for accessability for buttons etc.
134
- if ((isTab || isShiftTab) && editor.selection) {
135
- const [focusChild] = Editor.node(editor, editor.selection.focus, {
136
- depth: 2,
137
- })
138
- const [focusBlock] = isPortableTextSpan(focusChild)
139
- ? Editor.node(editor, editor.selection.focus, {depth: 1})
140
- : []
141
- const hasAnnotationFocus =
142
- focusChild &&
143
- isPortableTextTextBlock(focusBlock) &&
144
- isPortableTextSpan(focusChild) &&
145
- (focusChild.marks || ([] as string[])).filter((m) =>
146
- (focusBlock.markDefs || []).map((def) => def._key).includes(m),
147
- ).length > 0
148
- const [start] = Range.edges(editor.selection)
149
- const atStartOfNode = Editor.isStart(editor, start, start.path)
150
-
151
- if (
152
- focusChild &&
153
- isPortableTextSpan(focusChild) &&
154
- (!hasAnnotationFocus || atStartOfNode) &&
155
- editor.pteIncrementBlockLevels(isShiftTab)
156
- ) {
157
- event.preventDefault()
158
- }
159
- }
160
-
161
- // Deal with enter key combos
162
- if (isEnter && !isShiftEnter && editor.selection) {
163
- const focusBlockPath = editor.selection.focus.path.slice(0, 1)
164
- const focusBlock = Node.descendant(editor, focusBlockPath) as
165
- | SlateTextBlock
166
- | VoidElement
167
-
168
- // List item enter key
169
- if (editor.isListBlock(focusBlock)) {
170
- if (editor.pteEndList()) {
171
- event.preventDefault()
172
- }
173
- return
174
- }
175
- }
176
75
  }
177
76
  return editor
178
77
  }
@@ -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,
@@ -14,13 +18,13 @@ export {
14
18
  type Behavior,
15
19
  type BehaviorActionIntend,
16
20
  type BehaviorActionIntendSet,
17
- type BehaviorContext,
18
21
  type BehaviorEvent,
19
22
  type BehaviorGuard,
20
23
  type OmitFromUnion,
21
24
  type PickFromUnion,
25
+ type BlockOffset,
22
26
  } from './editor/behavior/behavior.types'
23
- export type {BlockOffset} from './editor/behavior/behavior.utils.block-offset'
27
+ export type {Editor, EditorConfig, EditorEvent} from './editor/create-editor'
24
28
  export type {SlateEditor} from './editor/create-slate-editor'
25
29
  export {
26
30
  defineSchema,
@@ -42,20 +46,20 @@ export {
42
46
  } from './editor/editor-machine'
43
47
  export {
44
48
  EditorProvider,
45
- useEditorContext,
49
+ useEditor,
46
50
  type EditorProviderProps,
47
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'
48
58
  export {usePortableTextEditor} from './editor/hooks/usePortableTextEditor'
49
59
  export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEditorSelection'
50
60
  export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
51
61
  export type {AddedAnnotationPaths} from './editor/plugins/createWithEditableAPI'
52
62
  export {PortableTextEditor} from './editor/PortableTextEditor'
53
63
  export type {PortableTextEditorProps} from './editor/PortableTextEditor'
54
- export {
55
- useEditor,
56
- type Editor,
57
- type EditorConfig,
58
- type EditorEvent,
59
- } from './editor/use-editor'
60
64
  export * from './types/editor'
61
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
+ }