@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,61 +1,61 @@
1
1
  import {isPortableTextTextBlock} from '@sanity/types'
2
- import {isHotkey} from '../../utils/is-hotkey'
3
- import {defineBehavior} from './behavior.types'
4
2
  import {
5
3
  getFocusBlockObject,
6
4
  getFocusTextBlock,
7
5
  getNextBlock,
8
6
  getPreviousBlock,
9
- isEmptyTextBlock,
10
7
  selectionIsCollapsed,
11
- } from './behavior.utils'
8
+ } from '../../selectors/selectors'
9
+ import {isHotkey} from '../../utils/is-hotkey'
10
+ import {isEmptyTextBlock} from '../utils/utils'
11
+ import {defineBehavior} from './behavior.types'
12
12
 
13
13
  const arrowDownOnLonelyBlockObject = defineBehavior({
14
14
  on: 'key.down',
15
15
  guard: ({context, event}) => {
16
16
  const isArrowDown = isHotkey('ArrowDown', event.keyboardEvent)
17
- const focusBlockObject = getFocusBlockObject(context)
18
- const nextBlock = getNextBlock(context)
17
+ const focusBlockObject = getFocusBlockObject({context})
18
+ const nextBlock = getNextBlock({context})
19
19
 
20
20
  return isArrowDown && focusBlockObject && !nextBlock
21
21
  },
22
- actions: [() => [{type: 'insert text block', placement: 'after'}]],
22
+ actions: [() => [{type: 'insert.text block', placement: 'after'}]],
23
23
  })
24
24
 
25
25
  const arrowUpOnLonelyBlockObject = defineBehavior({
26
26
  on: 'key.down',
27
27
  guard: ({context, event}) => {
28
28
  const isArrowUp = isHotkey('ArrowUp', event.keyboardEvent)
29
- const focusBlockObject = getFocusBlockObject(context)
30
- const previousBlock = getPreviousBlock(context)
29
+ const focusBlockObject = getFocusBlockObject({context})
30
+ const previousBlock = getPreviousBlock({context})
31
31
 
32
32
  return isArrowUp && focusBlockObject && !previousBlock
33
33
  },
34
34
  actions: [
35
35
  () => [
36
- {type: 'insert text block', placement: 'before'},
36
+ {type: 'insert.text block', placement: 'before'},
37
37
  {type: 'select previous block'},
38
38
  ],
39
39
  ],
40
40
  })
41
41
 
42
42
  const breakingBlockObject = defineBehavior({
43
- on: 'insert break',
43
+ on: 'insert.break',
44
44
  guard: ({context}) => {
45
- const focusBlockObject = getFocusBlockObject(context)
46
- const collapsedSelection = selectionIsCollapsed(context)
45
+ const focusBlockObject = getFocusBlockObject({context})
46
+ const collapsedSelection = selectionIsCollapsed({context})
47
47
 
48
48
  return collapsedSelection && focusBlockObject !== undefined
49
49
  },
50
- actions: [() => [{type: 'insert text block', placement: 'after'}]],
50
+ actions: [() => [{type: 'insert.text block', placement: 'after'}]],
51
51
  })
52
52
 
53
53
  const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
54
- on: 'delete backward',
54
+ on: 'delete.backward',
55
55
  guard: ({context}) => {
56
- const focusTextBlock = getFocusTextBlock(context)
57
- const selectionCollapsed = selectionIsCollapsed(context)
58
- const previousBlock = getPreviousBlock(context)
56
+ const focusTextBlock = getFocusTextBlock({context})
57
+ const selectionCollapsed = selectionIsCollapsed({context})
58
+ const previousBlock = getPreviousBlock({context})
59
59
 
60
60
  if (!focusTextBlock || !selectionCollapsed || !previousBlock) {
61
61
  return false
@@ -71,9 +71,9 @@ const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
71
71
  return false
72
72
  },
73
73
  actions: [
74
- (_, {focusTextBlock, previousBlock}) => [
74
+ ({focusTextBlock, previousBlock}) => [
75
75
  {
76
- type: 'delete block',
76
+ type: 'delete.block',
77
77
  blockPath: focusTextBlock.path,
78
78
  },
79
79
  {
@@ -88,11 +88,11 @@ const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
88
88
  })
89
89
 
90
90
  const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
91
- on: 'delete forward',
91
+ on: 'delete.forward',
92
92
  guard: ({context}) => {
93
- const focusTextBlock = getFocusTextBlock(context)
94
- const selectionCollapsed = selectionIsCollapsed(context)
95
- const nextBlock = getNextBlock(context)
93
+ const focusTextBlock = getFocusTextBlock({context})
94
+ const selectionCollapsed = selectionIsCollapsed({context})
95
+ const nextBlock = getNextBlock({context})
96
96
 
97
97
  if (!focusTextBlock || !selectionCollapsed || !nextBlock) {
98
98
  return false
@@ -108,9 +108,9 @@ const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
108
108
  return false
109
109
  },
110
110
  actions: [
111
- (_, {focusTextBlock, nextBlock}) => [
111
+ ({focusTextBlock, nextBlock}) => [
112
112
  {
113
- type: 'delete block',
113
+ type: 'delete.block',
114
114
  blockPath: focusTextBlock.path,
115
115
  },
116
116
  {
@@ -2,11 +2,12 @@ import {defineBehavior} from './behavior.types'
2
2
 
3
3
  const decoratorAdd = defineBehavior({
4
4
  on: 'decorator.add',
5
+ guard: ({event}) => ({decorator: event.decorator}),
5
6
  actions: [
6
- ({event}) => [
7
+ ({decorator}) => [
7
8
  {
8
9
  type: 'decorator.add',
9
- decorator: event.decorator,
10
+ decorator,
10
11
  },
11
12
  {
12
13
  type: 'reselect',
@@ -17,11 +18,12 @@ const decoratorAdd = defineBehavior({
17
18
 
18
19
  const decoratorRemove = defineBehavior({
19
20
  on: 'decorator.remove',
21
+ guard: ({event}) => ({decorator: event.decorator}),
20
22
  actions: [
21
- ({event}) => [
23
+ ({decorator}) => [
22
24
  {
23
25
  type: 'decorator.remove',
24
- decorator: event.decorator,
26
+ decorator,
25
27
  },
26
28
  {
27
29
  type: 'reselect',
@@ -32,11 +34,12 @@ const decoratorRemove = defineBehavior({
32
34
 
33
35
  const decoratorToggle = defineBehavior({
34
36
  on: 'decorator.toggle',
37
+ guard: ({event}) => ({decorator: event.decorator}),
35
38
  actions: [
36
- ({event}) => [
39
+ ({decorator}) => [
37
40
  {
38
41
  type: 'decorator.toggle',
39
- decorator: event.decorator,
42
+ decorator,
40
43
  },
41
44
  {
42
45
  type: 'reselect',
@@ -1,16 +1,23 @@
1
- import {defineBehavior} from './behavior.types'
2
1
  import {
2
+ getFocusListBlock,
3
3
  getFocusSpan,
4
4
  getFocusTextBlock,
5
+ getSelectedBlocks,
5
6
  selectionIsCollapsed,
6
- } from './behavior.utils'
7
+ } from '../../selectors/selectors'
8
+ import {isHotkey} from '../../utils/is-hotkey'
9
+ import {isEmptyTextBlock} from '../utils/utils'
10
+ import {createGuards} from './behavior.guards'
11
+ import {defineBehavior} from './behavior.types'
12
+
13
+ const MAX_LIST_LEVEL = 10
7
14
 
8
15
  const clearListOnBackspace = defineBehavior({
9
- on: 'delete backward',
16
+ on: 'delete.backward',
10
17
  guard: ({context}) => {
11
- const selectionCollapsed = selectionIsCollapsed(context)
12
- const focusTextBlock = getFocusTextBlock(context)
13
- const focusSpan = getFocusSpan(context)
18
+ const selectionCollapsed = selectionIsCollapsed({context})
19
+ const focusTextBlock = getFocusTextBlock({context})
20
+ const focusSpan = getFocusSpan({context})
14
21
 
15
22
  if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
16
23
  return false
@@ -27,22 +34,22 @@ const clearListOnBackspace = defineBehavior({
27
34
  return false
28
35
  },
29
36
  actions: [
30
- (_, {focusTextBlock}) => [
37
+ ({focusTextBlock}) => [
31
38
  {
32
- type: 'unset block',
39
+ type: 'text block.unset',
33
40
  props: ['listItem', 'level'],
34
- paths: [focusTextBlock.path],
41
+ at: focusTextBlock.path,
35
42
  },
36
43
  ],
37
44
  ],
38
45
  })
39
46
 
40
47
  const unindentListOnBackspace = defineBehavior({
41
- on: 'delete backward',
48
+ on: 'delete.backward',
42
49
  guard: ({context}) => {
43
- const selectionCollapsed = selectionIsCollapsed(context)
44
- const focusTextBlock = getFocusTextBlock(context)
45
- const focusSpan = getFocusSpan(context)
50
+ const selectionCollapsed = selectionIsCollapsed({context})
51
+ const focusTextBlock = getFocusTextBlock({context})
52
+ const focusSpan = getFocusSpan({context})
46
53
 
47
54
  if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
48
55
  return false
@@ -63,14 +70,129 @@ const unindentListOnBackspace = defineBehavior({
63
70
  return false
64
71
  },
65
72
  actions: [
66
- (_, {focusTextBlock, level}) => [
73
+ ({focusTextBlock, level}) => [
67
74
  {
68
- type: 'set block',
75
+ type: 'text block.set',
69
76
  level,
70
- paths: [focusTextBlock.path],
77
+ at: focusTextBlock.path,
71
78
  },
72
79
  ],
73
80
  ],
74
81
  })
75
82
 
76
- export const coreListBehaviors = {clearListOnBackspace, unindentListOnBackspace}
83
+ const clearListOnEnter = defineBehavior({
84
+ on: 'insert.break',
85
+ guard: ({context}) => {
86
+ const focusListBlock = getFocusListBlock({context})
87
+ const selectionCollapsed = selectionIsCollapsed({context})
88
+
89
+ if (!focusListBlock || !selectionCollapsed) {
90
+ return false
91
+ }
92
+
93
+ if (!isEmptyTextBlock(focusListBlock.node)) {
94
+ return false
95
+ }
96
+
97
+ return {focusListBlock}
98
+ },
99
+ actions: [
100
+ ({focusListBlock}) => [
101
+ {
102
+ type: 'text block.unset',
103
+ props: ['listItem', 'level'],
104
+ at: focusListBlock.path,
105
+ },
106
+ ],
107
+ ],
108
+ })
109
+
110
+ const indentListOnTab = defineBehavior({
111
+ on: 'key.down',
112
+ guard: ({context, event}) => {
113
+ const isTab = isHotkey('Tab', event.keyboardEvent)
114
+
115
+ if (!isTab) {
116
+ return false
117
+ }
118
+
119
+ const selectedBlocks = getSelectedBlocks({context})
120
+ const guards = createGuards(context)
121
+ const selectedListBlocks = selectedBlocks.flatMap((block) =>
122
+ guards.isListBlock(block.node)
123
+ ? [
124
+ {
125
+ node: block.node,
126
+ path: block.path,
127
+ },
128
+ ]
129
+ : [],
130
+ )
131
+
132
+ if (selectedListBlocks.length === selectedBlocks.length) {
133
+ return {selectedListBlocks}
134
+ }
135
+
136
+ return false
137
+ },
138
+ actions: [
139
+ ({selectedListBlocks}) =>
140
+ selectedListBlocks.map((selectedListBlock) => ({
141
+ type: 'text block.set',
142
+ level: Math.min(
143
+ MAX_LIST_LEVEL,
144
+ Math.max(1, selectedListBlock.node.level + 1),
145
+ ),
146
+ at: selectedListBlock.path,
147
+ })),
148
+ ],
149
+ })
150
+
151
+ const unindentListOnShiftTab = defineBehavior({
152
+ on: 'key.down',
153
+ guard: ({context, event}) => {
154
+ const isShiftTab = isHotkey('Shift+Tab', event.keyboardEvent)
155
+
156
+ if (!isShiftTab) {
157
+ return false
158
+ }
159
+
160
+ const selectedBlocks = getSelectedBlocks({context})
161
+ const guards = createGuards(context)
162
+ const selectedListBlocks = selectedBlocks.flatMap((block) =>
163
+ guards.isListBlock(block.node)
164
+ ? [
165
+ {
166
+ node: block.node,
167
+ path: block.path,
168
+ },
169
+ ]
170
+ : [],
171
+ )
172
+
173
+ if (selectedListBlocks.length === selectedBlocks.length) {
174
+ return {selectedListBlocks}
175
+ }
176
+
177
+ return false
178
+ },
179
+ actions: [
180
+ ({selectedListBlocks}) =>
181
+ selectedListBlocks.map((selectedListBlock) => ({
182
+ type: 'text block.set',
183
+ level: Math.min(
184
+ MAX_LIST_LEVEL,
185
+ Math.max(1, selectedListBlock.node.level - 1),
186
+ ),
187
+ at: selectedListBlock.path,
188
+ })),
189
+ ],
190
+ })
191
+
192
+ export const coreListBehaviors = {
193
+ clearListOnBackspace,
194
+ unindentListOnBackspace,
195
+ clearListOnEnter,
196
+ indentListOnTab,
197
+ unindentListOnShiftTab,
198
+ }
@@ -4,8 +4,8 @@ import {coreListBehaviors} from './behavior.core.lists'
4
4
  import {defineBehavior} from './behavior.types'
5
5
 
6
6
  const softReturn = defineBehavior({
7
- on: 'insert soft break',
8
- actions: [() => [{type: 'insert text', text: '\n'}]],
7
+ on: 'insert.soft break',
8
+ actions: [() => [{type: 'insert.text', text: '\n'}]],
9
9
  })
10
10
 
11
11
  /**
@@ -23,6 +23,9 @@ export const coreBehaviors = [
23
23
  coreBlockObjectBehaviors.deletingEmptyTextBlockBeforeBlockObject,
24
24
  coreListBehaviors.clearListOnBackspace,
25
25
  coreListBehaviors.unindentListOnBackspace,
26
+ coreListBehaviors.clearListOnEnter,
27
+ coreListBehaviors.indentListOnTab,
28
+ coreListBehaviors.unindentListOnShiftTab,
26
29
  ]
27
30
 
28
31
  /**
@@ -0,0 +1,28 @@
1
+ import {
2
+ isPortableTextListBlock,
3
+ isPortableTextTextBlock,
4
+ type PortableTextListBlock,
5
+ type PortableTextTextBlock,
6
+ } from '@sanity/types'
7
+ import type {PortableTextMemberSchemaTypes} from '../../types/editor'
8
+
9
+ /**
10
+ * @alpha
11
+ */
12
+ export type BehaviorGuards = ReturnType<typeof createGuards>
13
+
14
+ export function createGuards({
15
+ schema,
16
+ }: {
17
+ schema: PortableTextMemberSchemaTypes
18
+ }) {
19
+ function isListBlock(block: unknown): block is PortableTextListBlock {
20
+ return isPortableTextListBlock(block) && block._type === schema.block.name
21
+ }
22
+
23
+ function isTextBlock(block: unknown): block is PortableTextTextBlock {
24
+ return isPortableTextTextBlock(block) && block._type === schema.block.name
25
+ }
26
+
27
+ return {isListBlock, isTextBlock}
28
+ }
@@ -1,6 +1,6 @@
1
+ import {getFocusSpan, selectionIsCollapsed} from '../../selectors/selectors'
1
2
  import type {PortableTextMemberSchemaTypes} from '../../types/editor'
2
3
  import {defineBehavior} from './behavior.types'
3
- import {getFocusSpan, selectionIsCollapsed} from './behavior.utils'
4
4
 
5
5
  /**
6
6
  * @alpha
@@ -19,7 +19,7 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
19
19
  const pasteLinkOnSelection = defineBehavior({
20
20
  on: 'paste',
21
21
  guard: ({context, event}) => {
22
- const selectionCollapsed = selectionIsCollapsed(context)
22
+ const selectionCollapsed = selectionIsCollapsed({context})
23
23
  const text = event.data.getData('text/plain')
24
24
  const url = looksLikeUrl(text) ? text : undefined
25
25
  const annotation =
@@ -34,7 +34,7 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
34
34
  return false
35
35
  },
36
36
  actions: [
37
- (_, {annotation}) => [
37
+ ({annotation}) => [
38
38
  {
39
39
  type: 'annotation.add',
40
40
  annotation,
@@ -45,8 +45,8 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
45
45
  const pasteLinkAtCaret = defineBehavior({
46
46
  on: 'paste',
47
47
  guard: ({context, event}) => {
48
- const focusSpan = getFocusSpan(context)
49
- const selectionCollapsed = selectionIsCollapsed(context)
48
+ const focusSpan = getFocusSpan({context})
49
+ const selectionCollapsed = selectionIsCollapsed({context})
50
50
 
51
51
  if (!focusSpan || !selectionCollapsed) {
52
52
  return false
@@ -66,9 +66,9 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
66
66
  return false
67
67
  },
68
68
  actions: [
69
- (_, {annotation, url}) => [
69
+ ({annotation, url}) => [
70
70
  {
71
- type: 'insert span',
71
+ type: 'insert.span',
72
72
  text: url,
73
73
  annotations: [annotation],
74
74
  },