@portabletext/editor 1.34.0 → 1.35.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 (113) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +57 -118
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +27 -67
  4. package/lib/_chunks-cjs/behavior.markdown.cjs.map +1 -1
  5. package/lib/_chunks-cjs/plugin.event-listener.cjs +53 -71
  6. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  7. package/lib/_chunks-cjs/selector.get-text-before.cjs +5 -7
  8. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  9. package/lib/_chunks-cjs/selector.is-active-style.cjs +22 -36
  10. package/lib/_chunks-cjs/selector.is-active-style.cjs.map +1 -1
  11. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +68 -153
  12. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
  13. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -1
  14. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  15. package/lib/_chunks-es/behavior.core.js +57 -118
  16. package/lib/_chunks-es/behavior.core.js.map +1 -1
  17. package/lib/_chunks-es/behavior.markdown.js +27 -67
  18. package/lib/_chunks-es/behavior.markdown.js.map +1 -1
  19. package/lib/_chunks-es/plugin.event-listener.js +53 -71
  20. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  21. package/lib/_chunks-es/selector.get-text-before.js +5 -7
  22. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  23. package/lib/_chunks-es/selector.is-active-style.js +22 -36
  24. package/lib/_chunks-es/selector.is-active-style.js.map +1 -1
  25. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +68 -153
  26. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  27. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -1
  28. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  29. package/lib/behaviors/index.cjs +18 -48
  30. package/lib/behaviors/index.cjs.map +1 -1
  31. package/lib/behaviors/index.d.cts +28 -16
  32. package/lib/behaviors/index.d.ts +28 -16
  33. package/lib/behaviors/index.js +18 -48
  34. package/lib/behaviors/index.js.map +1 -1
  35. package/lib/index.d.cts +132 -71
  36. package/lib/index.d.ts +132 -71
  37. package/lib/plugins/index.cjs +182 -186
  38. package/lib/plugins/index.cjs.map +1 -1
  39. package/lib/plugins/index.d.cts +147 -82
  40. package/lib/plugins/index.d.ts +147 -82
  41. package/lib/plugins/index.js +182 -186
  42. package/lib/plugins/index.js.map +1 -1
  43. package/lib/selectors/index.cjs +22 -50
  44. package/lib/selectors/index.cjs.map +1 -1
  45. package/lib/selectors/index.d.cts +9 -200
  46. package/lib/selectors/index.d.ts +9 -200
  47. package/lib/selectors/index.js +22 -50
  48. package/lib/selectors/index.js.map +1 -1
  49. package/lib/utils/index.cjs.map +1 -1
  50. package/lib/utils/index.d.cts +15 -7
  51. package/lib/utils/index.d.ts +15 -7
  52. package/lib/utils/index.js.map +1 -1
  53. package/package.json +6 -6
  54. package/src/behaviors/behavior.code-editor.ts +6 -6
  55. package/src/behaviors/behavior.core.annotations.ts +5 -4
  56. package/src/behaviors/behavior.core.block-objects.ts +17 -17
  57. package/src/behaviors/behavior.core.decorators.ts +12 -8
  58. package/src/behaviors/behavior.core.insert-break.ts +27 -29
  59. package/src/behaviors/behavior.core.lists.ts +19 -19
  60. package/src/behaviors/behavior.decorator-pair.ts +200 -0
  61. package/src/behaviors/behavior.default.ts +35 -30
  62. package/src/behaviors/behavior.emoji-picker.ts +12 -12
  63. package/src/behaviors/behavior.links.ts +7 -7
  64. package/src/behaviors/behavior.markdown.ts +41 -42
  65. package/src/behaviors/behavior.types.ts +15 -18
  66. package/src/behaviors/index.ts +0 -1
  67. package/src/converters/converter.json.ts +6 -6
  68. package/src/converters/converter.portable-text.deserialize.test.ts +28 -26
  69. package/src/converters/converter.portable-text.ts +6 -6
  70. package/src/converters/converter.text-html.deserialize.test.ts +17 -15
  71. package/src/converters/converter.text-html.serialize.test.ts +57 -53
  72. package/src/converters/converter.text-html.ts +14 -10
  73. package/src/converters/converter.text-plain.test.ts +17 -15
  74. package/src/converters/converter.text-plain.ts +15 -11
  75. package/src/converters/converter.types.ts +8 -7
  76. package/src/editor/editor-machine.ts +6 -1
  77. package/src/editor/plugins/create-with-event-listeners.ts +0 -5
  78. package/src/index.ts +3 -3
  79. package/src/internal-utils/get-text-to-emphasize.ts +29 -7
  80. package/src/plugins/plugin.decorator-shortcut.ts +235 -0
  81. package/src/plugins/plugin.markdown.tsx +56 -8
  82. package/src/plugins/plugin.one-line.tsx +17 -17
  83. package/src/selectors/selector.get-active-list-item.ts +4 -4
  84. package/src/selectors/selector.get-active-style.ts +6 -6
  85. package/src/selectors/selector.get-anchor-block.ts +5 -5
  86. package/src/selectors/selector.get-anchor-child.ts +5 -5
  87. package/src/selectors/selector.get-anchor-span.ts +2 -2
  88. package/src/selectors/selector.get-anchor-text-block.ts +2 -2
  89. package/src/selectors/selector.get-block-offsets.ts +8 -7
  90. package/src/selectors/selector.get-caret-word-selection.ts +19 -16
  91. package/src/selectors/selector.get-next-inline-object.ts +4 -4
  92. package/src/selectors/selector.get-previous-inline-object.ts +4 -4
  93. package/src/selectors/selector.get-selected-slice.ts +7 -4
  94. package/src/selectors/selector.get-selected-spans.ts +9 -9
  95. package/src/selectors/selector.get-selection-end-point.ts +5 -5
  96. package/src/selectors/selector.get-selection-start-point.ts +5 -5
  97. package/src/selectors/selector.get-selection-text.ts +2 -2
  98. package/src/selectors/selector.get-selection.ts +2 -2
  99. package/src/selectors/selector.get-text-before.ts +8 -8
  100. package/src/selectors/selector.get-trimmed-selection.ts +15 -13
  101. package/src/selectors/selector.get-value.ts +4 -4
  102. package/src/selectors/selector.is-at-the-end-of-block.ts +6 -3
  103. package/src/selectors/selector.is-at-the-start-of-block.ts +3 -3
  104. package/src/selectors/selector.is-overlapping-selection.ts +8 -6
  105. package/src/selectors/selector.is-selection-collapsed.ts +6 -5
  106. package/src/selectors/selector.is-selection-expanded.ts +2 -2
  107. package/src/selectors/selectors.ts +59 -59
  108. package/src/types/block-offset.ts +9 -0
  109. package/src/utils/index.ts +0 -1
  110. package/src/utils/util.block-offset.ts +1 -1
  111. package/src/utils/util.block-offsets-to-selection.ts +1 -1
  112. package/src/utils/util.child-selection-point-to-block-offset.ts +1 -1
  113. package/src/behaviors/behavior.markdown-emphasis.ts +0 -437
@@ -3,17 +3,16 @@ import {defineBehavior, raise} from './behavior.types'
3
3
 
4
4
  const breakingAtTheEndOfTextBlock = defineBehavior({
5
5
  on: 'insert.break',
6
- guard: ({context}) => {
7
- const focusTextBlock = selectors.getFocusTextBlock({context})
8
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
6
+ guard: ({snapshot}) => {
7
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot)
8
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
9
9
 
10
- if (!context.selection || !focusTextBlock || !selectionCollapsed) {
10
+ if (!snapshot.context.selection || !focusTextBlock || !selectionCollapsed) {
11
11
  return false
12
12
  }
13
13
 
14
- const atTheEndOfBlock = selectors.isAtTheEndOfBlock(focusTextBlock)({
15
- context,
16
- })
14
+ const atTheEndOfBlock =
15
+ selectors.isAtTheEndOfBlock(focusTextBlock)(snapshot)
17
16
 
18
17
  const focusListItem = focusTextBlock.node.listItem
19
18
  const focusLevel = focusTextBlock.node.level
@@ -25,16 +24,16 @@ const breakingAtTheEndOfTextBlock = defineBehavior({
25
24
  return false
26
25
  },
27
26
  actions: [
28
- ({context}, {focusListItem, focusLevel}) => [
27
+ ({snapshot}, {focusListItem, focusLevel}) => [
29
28
  raise({
30
29
  type: 'insert.block',
31
30
  block: {
32
- _type: context.schema.block.name,
33
- _key: context.keyGenerator(),
31
+ _type: snapshot.context.schema.block.name,
32
+ _key: snapshot.context.keyGenerator(),
34
33
  children: [
35
34
  {
36
- _key: context.keyGenerator(),
37
- _type: context.schema.span.name,
35
+ _key: snapshot.context.keyGenerator(),
36
+ _type: snapshot.context.schema.span.name,
38
37
  text: '',
39
38
  marks: [],
40
39
  },
@@ -42,7 +41,7 @@ const breakingAtTheEndOfTextBlock = defineBehavior({
42
41
  markDefs: [],
43
42
  listItem: focusListItem,
44
43
  level: focusLevel,
45
- style: context.schema.styles[0]?.value,
44
+ style: snapshot.context.schema.styles[0]?.value,
46
45
  },
47
46
  placement: 'after',
48
47
  }),
@@ -52,35 +51,34 @@ const breakingAtTheEndOfTextBlock = defineBehavior({
52
51
 
53
52
  const breakingAtTheStartOfTextBlock = defineBehavior({
54
53
  on: 'insert.break',
55
- guard: ({context}) => {
56
- const focusTextBlock = selectors.getFocusTextBlock({context})
57
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
54
+ guard: ({snapshot}) => {
55
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot)
56
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
58
57
 
59
- if (!context.selection || !focusTextBlock || !selectionCollapsed) {
58
+ if (!snapshot.context.selection || !focusTextBlock || !selectionCollapsed) {
60
59
  return false
61
60
  }
62
61
 
63
- const focusSpan = selectors.getFocusSpan({context})
62
+ const focusSpan = selectors.getFocusSpan(snapshot)
64
63
 
65
64
  const focusDecorators = focusSpan?.node.marks?.filter(
66
65
  (mark) =>
67
- context.schema.decorators.some(
66
+ snapshot.context.schema.decorators.some(
68
67
  (decorator) => decorator.value === mark,
69
68
  ) ?? [],
70
69
  )
71
70
  const focusAnnotations =
72
71
  focusSpan?.node.marks?.filter(
73
72
  (mark) =>
74
- !context.schema.decorators.some(
73
+ !snapshot.context.schema.decorators.some(
75
74
  (decorator) => decorator.value === mark,
76
75
  ),
77
76
  ) ?? []
78
77
  const focusListItem = focusTextBlock.node.listItem
79
78
  const focusLevel = focusTextBlock.node.level
80
79
 
81
- const atTheStartOfBlock = selectors.isAtTheStartOfBlock(focusTextBlock)({
82
- context,
83
- })
80
+ const atTheStartOfBlock =
81
+ selectors.isAtTheStartOfBlock(focusTextBlock)(snapshot)
84
82
 
85
83
  if (atTheStartOfBlock) {
86
84
  return {focusAnnotations, focusDecorators, focusListItem, focusLevel}
@@ -90,25 +88,25 @@ const breakingAtTheStartOfTextBlock = defineBehavior({
90
88
  },
91
89
  actions: [
92
90
  (
93
- {context},
91
+ {snapshot},
94
92
  {focusAnnotations, focusDecorators, focusListItem, focusLevel},
95
93
  ) => [
96
94
  raise({
97
95
  type: 'insert.block',
98
96
  block: {
99
- _key: context.keyGenerator(),
100
- _type: context.schema.block.name,
97
+ _key: snapshot.context.keyGenerator(),
98
+ _type: snapshot.context.schema.block.name,
101
99
  children: [
102
100
  {
103
- _key: context.keyGenerator(),
104
- _type: context.schema.span.name,
101
+ _key: snapshot.context.keyGenerator(),
102
+ _type: snapshot.context.schema.span.name,
105
103
  marks: focusAnnotations.length === 0 ? focusDecorators : [],
106
104
  text: '',
107
105
  },
108
106
  ],
109
107
  listItem: focusListItem,
110
108
  level: focusLevel,
111
- style: context.schema.styles[0]?.value,
109
+ style: snapshot.context.schema.styles[0]?.value,
112
110
  },
113
111
  placement: 'before',
114
112
  }),
@@ -8,10 +8,10 @@ const MAX_LIST_LEVEL = 10
8
8
 
9
9
  const clearListOnBackspace = defineBehavior({
10
10
  on: 'delete.backward',
11
- guard: ({context}) => {
12
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
13
- const focusTextBlock = selectors.getFocusTextBlock({context})
14
- const focusSpan = selectors.getFocusSpan({context})
11
+ guard: ({snapshot}) => {
12
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
13
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot)
14
+ const focusSpan = selectors.getFocusSpan(snapshot)
15
15
 
16
16
  if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
17
17
  return false
@@ -19,7 +19,7 @@ const clearListOnBackspace = defineBehavior({
19
19
 
20
20
  const atTheBeginningOfBLock =
21
21
  focusTextBlock.node.children[0]._key === focusSpan.node._key &&
22
- context.selection?.focus.offset === 0
22
+ snapshot.context.selection?.focus.offset === 0
23
23
 
24
24
  if (atTheBeginningOfBLock && focusTextBlock.node.level === 1) {
25
25
  return {focusTextBlock}
@@ -40,10 +40,10 @@ const clearListOnBackspace = defineBehavior({
40
40
 
41
41
  const unindentListOnBackspace = defineBehavior({
42
42
  on: 'delete.backward',
43
- guard: ({context}) => {
44
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
45
- const focusTextBlock = selectors.getFocusTextBlock({context})
46
- const focusSpan = selectors.getFocusSpan({context})
43
+ guard: ({snapshot}) => {
44
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
45
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot)
46
+ const focusSpan = selectors.getFocusSpan(snapshot)
47
47
 
48
48
  if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
49
49
  return false
@@ -51,7 +51,7 @@ const unindentListOnBackspace = defineBehavior({
51
51
 
52
52
  const atTheBeginningOfBLock =
53
53
  focusTextBlock.node.children[0]._key === focusSpan.node._key &&
54
- context.selection?.focus.offset === 0
54
+ snapshot.context.selection?.focus.offset === 0
55
55
 
56
56
  if (
57
57
  atTheBeginningOfBLock &&
@@ -76,9 +76,9 @@ const unindentListOnBackspace = defineBehavior({
76
76
 
77
77
  const clearListOnEnter = defineBehavior({
78
78
  on: 'insert.break',
79
- guard: ({context}) => {
80
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
81
- const focusListBlock = selectors.getFocusListBlock({context})
79
+ guard: ({snapshot}) => {
80
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
81
+ const focusListBlock = selectors.getFocusListBlock(snapshot)
82
82
 
83
83
  if (
84
84
  !selectionCollapsed ||
@@ -103,15 +103,15 @@ const clearListOnEnter = defineBehavior({
103
103
 
104
104
  const indentListOnTab = defineBehavior({
105
105
  on: 'key.down',
106
- guard: ({context, event}) => {
106
+ guard: ({snapshot, event}) => {
107
107
  const isTab = isHotkey('Tab', event.keyboardEvent)
108
108
 
109
109
  if (!isTab) {
110
110
  return false
111
111
  }
112
112
 
113
- const selectedBlocks = selectors.getSelectedBlocks({context})
114
- const guards = createGuards(context)
113
+ const selectedBlocks = selectors.getSelectedBlocks(snapshot)
114
+ const guards = createGuards(snapshot.context)
115
115
  const selectedListBlocks = selectedBlocks.flatMap((block) =>
116
116
  guards.isListBlock(block.node)
117
117
  ? [
@@ -148,15 +148,15 @@ const indentListOnTab = defineBehavior({
148
148
 
149
149
  const unindentListOnShiftTab = defineBehavior({
150
150
  on: 'key.down',
151
- guard: ({context, event}) => {
151
+ guard: ({snapshot, event}) => {
152
152
  const isShiftTab = isHotkey('Shift+Tab', event.keyboardEvent)
153
153
 
154
154
  if (!isShiftTab) {
155
155
  return false
156
156
  }
157
157
 
158
- const selectedBlocks = selectors.getSelectedBlocks({context})
159
- const guards = createGuards(context)
158
+ const selectedBlocks = selectors.getSelectedBlocks(snapshot)
159
+ const guards = createGuards(snapshot.context)
160
160
  const selectedListBlocks = selectedBlocks.flatMap((block) =>
161
161
  guards.isListBlock(block.node)
162
162
  ? [
@@ -0,0 +1,200 @@
1
+ import {createPairRegex} from '../internal-utils/get-text-to-emphasize'
2
+ import * as selectors from '../selectors'
3
+ import type {BlockOffset} from '../types/block-offset'
4
+ import * as utils from '../utils'
5
+ import {defineBehavior} from './behavior.types'
6
+
7
+ export function createDecoratorPairBehavior(config: {
8
+ decorator: ({schema}: {schema: selectors.EditorSchema}) => string | undefined
9
+ pair: {char: string; amount: number}
10
+ onDecorate: (offset: BlockOffset) => void
11
+ }) {
12
+ if (config.pair.amount < 1) {
13
+ console.warn(
14
+ `The amount of characters in the pair should be greater than 0`,
15
+ )
16
+ }
17
+
18
+ const pairRegex = createPairRegex(config.pair.char, config.pair.amount)
19
+ const regEx = new RegExp(`(${pairRegex})$`)
20
+
21
+ return defineBehavior({
22
+ on: 'insert.text',
23
+ guard: ({snapshot, event}) => {
24
+ if (config.pair.amount < 1) {
25
+ return false
26
+ }
27
+
28
+ const decorator = config.decorator({schema: snapshot.context.schema})
29
+
30
+ if (decorator === undefined) {
31
+ return false
32
+ }
33
+
34
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot)
35
+ const selectionStartPoint = selectors.getSelectionStartPoint(snapshot)
36
+ const selectionStartOffset = selectionStartPoint
37
+ ? utils.spanSelectionPointToBlockOffset({
38
+ value: snapshot.context.value,
39
+ selectionPoint: selectionStartPoint,
40
+ })
41
+ : undefined
42
+
43
+ if (!focusTextBlock || !selectionStartOffset) {
44
+ return false
45
+ }
46
+
47
+ const textBefore = selectors.getBlockTextBefore(snapshot)
48
+ const newText = `${textBefore}${event.text}`
49
+ const textToDecorate = newText.match(regEx)?.at(0)
50
+
51
+ if (textToDecorate === undefined) {
52
+ return false
53
+ }
54
+
55
+ const prefixOffsets = {
56
+ anchor: {
57
+ path: focusTextBlock.path,
58
+ // Example: "foo **bar**".length - "**bar**".length = 4
59
+ offset: newText.length - textToDecorate.length,
60
+ },
61
+ focus: {
62
+ path: focusTextBlock.path,
63
+ // Example: "foo **bar**".length - "**bar**".length + "*".length * 2 = 6
64
+ offset:
65
+ newText.length -
66
+ textToDecorate.length +
67
+ config.pair.char.length * config.pair.amount,
68
+ },
69
+ }
70
+
71
+ const suffixOffsets = {
72
+ anchor: {
73
+ path: focusTextBlock.path,
74
+ // Example: "foo **bar*|" (10) + "*".length - 2 = 9
75
+ offset:
76
+ selectionStartOffset.offset +
77
+ event.text.length -
78
+ config.pair.char.length * config.pair.amount,
79
+ },
80
+ focus: {
81
+ path: focusTextBlock.path,
82
+ // Example: "foo **bar*|" (10) + "*".length = 11
83
+ offset: selectionStartOffset.offset + event.text.length,
84
+ },
85
+ }
86
+
87
+ // If the prefix is more than one character, then we need to check if
88
+ // there is an inline object inside it
89
+ if (prefixOffsets.focus.offset - prefixOffsets.anchor.offset > 1) {
90
+ const prefixSelection = utils.blockOffsetsToSelection({
91
+ value: snapshot.context.value,
92
+ offsets: prefixOffsets,
93
+ })
94
+ const inlineObjectBeforePrefixFocus = selectors.getPreviousInlineObject(
95
+ {
96
+ context: {
97
+ ...snapshot.context,
98
+ selection: prefixSelection
99
+ ? {
100
+ anchor: prefixSelection.focus,
101
+ focus: prefixSelection.focus,
102
+ }
103
+ : null,
104
+ },
105
+ },
106
+ )
107
+ const inlineObjectBeforePrefixFocusOffset =
108
+ inlineObjectBeforePrefixFocus
109
+ ? utils.childSelectionPointToBlockOffset({
110
+ value: snapshot.context.value,
111
+ selectionPoint: {
112
+ path: inlineObjectBeforePrefixFocus.path,
113
+ offset: 0,
114
+ },
115
+ })
116
+ : undefined
117
+
118
+ if (
119
+ inlineObjectBeforePrefixFocusOffset &&
120
+ inlineObjectBeforePrefixFocusOffset.offset >
121
+ prefixOffsets.anchor.offset &&
122
+ inlineObjectBeforePrefixFocusOffset.offset <
123
+ prefixOffsets.focus.offset
124
+ ) {
125
+ return false
126
+ }
127
+ }
128
+
129
+ // If the suffix is more than one character, then we need to check if
130
+ // there is an inline object inside it
131
+ if (suffixOffsets.focus.offset - suffixOffsets.anchor.offset > 1) {
132
+ const previousInlineObject = selectors.getPreviousInlineObject(snapshot)
133
+ const previousInlineObjectOffset = previousInlineObject
134
+ ? utils.childSelectionPointToBlockOffset({
135
+ value: snapshot.context.value,
136
+ selectionPoint: {
137
+ path: previousInlineObject.path,
138
+ offset: 0,
139
+ },
140
+ })
141
+ : undefined
142
+
143
+ if (
144
+ previousInlineObjectOffset &&
145
+ previousInlineObjectOffset.offset > suffixOffsets.anchor.offset &&
146
+ previousInlineObjectOffset.offset < suffixOffsets.focus.offset
147
+ ) {
148
+ return false
149
+ }
150
+ }
151
+
152
+ return {
153
+ prefixOffsets,
154
+ suffixOffsets,
155
+ decorator,
156
+ }
157
+ },
158
+ actions: [
159
+ // Insert the text as usual in its own undo step
160
+ ({event}) => [event],
161
+ (_, {prefixOffsets, suffixOffsets, decorator}) => [
162
+ // Decorate the text between the prefix and suffix
163
+ {
164
+ type: 'decorator.add',
165
+ decorator,
166
+ offsets: {
167
+ anchor: prefixOffsets.focus,
168
+ focus: suffixOffsets.anchor,
169
+ },
170
+ },
171
+ // Delete the suffix
172
+ {
173
+ type: 'delete.text',
174
+ ...suffixOffsets,
175
+ },
176
+ // Delete the prefix
177
+ {
178
+ type: 'delete.text',
179
+ ...prefixOffsets,
180
+ },
181
+ // Toggle the decorator off so the next inserted text isn't emphasized
182
+ {
183
+ type: 'decorator.remove',
184
+ decorator,
185
+ },
186
+ {
187
+ type: 'effect',
188
+ effect: () => {
189
+ config.onDecorate({
190
+ ...suffixOffsets.anchor,
191
+ offset:
192
+ suffixOffsets.anchor.offset -
193
+ (prefixOffsets.focus.offset - prefixOffsets.anchor.offset),
194
+ })
195
+ },
196
+ },
197
+ ],
198
+ ],
199
+ })
200
+ }
@@ -3,8 +3,8 @@ import {defineBehavior, raise} from './behavior.types'
3
3
 
4
4
  const toggleAnnotationOff = defineBehavior({
5
5
  on: 'annotation.toggle',
6
- guard: ({context, event}) =>
7
- selectors.isActiveAnnotation(event.annotation.name)({context}),
6
+ guard: ({snapshot, event}) =>
7
+ selectors.isActiveAnnotation(event.annotation.name)(snapshot),
8
8
  actions: [
9
9
  ({event}) => [
10
10
  raise({type: 'annotation.remove', annotation: event.annotation}),
@@ -14,8 +14,8 @@ const toggleAnnotationOff = defineBehavior({
14
14
 
15
15
  const toggleAnnotationOn = defineBehavior({
16
16
  on: 'annotation.toggle',
17
- guard: ({context, event}) =>
18
- !selectors.isActiveAnnotation(event.annotation.name)({context}),
17
+ guard: ({snapshot, event}) =>
18
+ !selectors.isActiveAnnotation(event.annotation.name)(snapshot),
19
19
  actions: [
20
20
  ({event}) => [
21
21
  raise({type: 'annotation.add', annotation: event.annotation}),
@@ -25,8 +25,8 @@ const toggleAnnotationOn = defineBehavior({
25
25
 
26
26
  const toggleDecoratorOff = defineBehavior({
27
27
  on: 'decorator.toggle',
28
- guard: ({context, event}) =>
29
- selectors.isActiveDecorator(event.decorator)({context}),
28
+ guard: ({snapshot, event}) =>
29
+ selectors.isActiveDecorator(event.decorator)(snapshot),
30
30
  actions: [
31
31
  ({event}) => [
32
32
  raise({type: 'decorator.remove', decorator: event.decorator}),
@@ -36,8 +36,8 @@ const toggleDecoratorOff = defineBehavior({
36
36
 
37
37
  const toggleDecoratorOn = defineBehavior({
38
38
  on: 'decorator.toggle',
39
- guard: ({context, event}) =>
40
- !selectors.isActiveDecorator(event.decorator)({context}),
39
+ guard: ({snapshot, event}) =>
40
+ !selectors.isActiveDecorator(event.decorator)(snapshot),
41
41
  actions: [
42
42
  ({event}) => [raise({type: 'decorator.add', decorator: event.decorator})],
43
43
  ],
@@ -45,8 +45,8 @@ const toggleDecoratorOn = defineBehavior({
45
45
 
46
46
  const toggleListItemOff = defineBehavior({
47
47
  on: 'list item.toggle',
48
- guard: ({context, event}) =>
49
- selectors.isActiveListItem(event.listItem)({context}),
48
+ guard: ({snapshot, event}) =>
49
+ selectors.isActiveListItem(event.listItem)(snapshot),
50
50
  actions: [
51
51
  ({event}) => [
52
52
  raise({
@@ -59,8 +59,8 @@ const toggleListItemOff = defineBehavior({
59
59
 
60
60
  const toggleListItemOn = defineBehavior({
61
61
  on: 'list item.toggle',
62
- guard: ({context, event}) =>
63
- !selectors.isActiveListItem(event.listItem)({context}),
62
+ guard: ({snapshot, event}) =>
63
+ !selectors.isActiveListItem(event.listItem)(snapshot),
64
64
  actions: [
65
65
  ({event}) => [
66
66
  raise({
@@ -73,30 +73,35 @@ const toggleListItemOn = defineBehavior({
73
73
 
74
74
  const toggleStyleOff = defineBehavior({
75
75
  on: 'style.toggle',
76
- guard: ({context, event}) => selectors.isActiveStyle(event.style)({context}),
76
+ guard: ({snapshot, event}) => selectors.isActiveStyle(event.style)(snapshot),
77
77
  actions: [({event}) => [raise({type: 'style.remove', style: event.style})]],
78
78
  })
79
79
 
80
80
  const toggleStyleOn = defineBehavior({
81
81
  on: 'style.toggle',
82
- guard: ({context, event}) => !selectors.isActiveStyle(event.style)({context}),
82
+ guard: ({snapshot, event}) => !selectors.isActiveStyle(event.style)(snapshot),
83
83
  actions: [({event}) => [raise({type: 'style.add', style: event.style})]],
84
84
  })
85
85
 
86
86
  const raiseDeserializationSuccessOrFailure = defineBehavior({
87
87
  on: 'deserialize',
88
- guard: ({context, event}) => {
89
- const deserializeEvents = context.converters.flatMap((converter) => {
90
- const data = event.dataTransfer.getData(converter.mimeType)
91
-
92
- if (!data) {
93
- return []
94
- }
95
-
96
- return [
97
- converter.deserialize({context, event: {type: 'deserialize', data}}),
98
- ]
99
- })
88
+ guard: ({snapshot, event}) => {
89
+ const deserializeEvents = snapshot.context.converters.flatMap(
90
+ (converter) => {
91
+ const data = event.dataTransfer.getData(converter.mimeType)
92
+
93
+ if (!data) {
94
+ return []
95
+ }
96
+
97
+ return [
98
+ converter.deserialize({
99
+ snapshot,
100
+ event: {type: 'deserialize', data},
101
+ }),
102
+ ]
103
+ },
104
+ )
100
105
 
101
106
  const firstSuccess = deserializeEvents.find(
102
107
  (deserializeEvent) => deserializeEvent.type === 'deserialization.success',
@@ -142,13 +147,13 @@ const raiseInsertBlocks = defineBehavior({
142
147
 
143
148
  const raiseSerializationSuccessOrFailure = defineBehavior({
144
149
  on: 'serialize',
145
- guard: ({context, event}) => {
146
- if (context.converters.length === 0) {
150
+ guard: ({snapshot, event}) => {
151
+ if (snapshot.context.converters.length === 0) {
147
152
  return false
148
153
  }
149
154
 
150
- const serializeEvents = context.converters.map((converter) =>
151
- converter.serialize({context, event}),
155
+ const serializeEvents = snapshot.context.converters.map((converter) =>
156
+ converter.serialize({snapshot, event}),
152
157
  )
153
158
 
154
159
  if (serializeEvents.length === 0) {
@@ -39,7 +39,7 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
39
39
  return [
40
40
  defineBehavior({
41
41
  on: 'insert.text',
42
- guard: ({context, event}) => {
42
+ guard: ({snapshot, event}) => {
43
43
  if (event.text === ':') {
44
44
  return false
45
45
  }
@@ -50,8 +50,8 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
50
50
  return {emojis: []}
51
51
  }
52
52
 
53
- const focusBlock = selectors.getFocusTextBlock({context})
54
- const textBefore = selectors.getBlockTextBefore({context})
53
+ const focusBlock = selectors.getFocusTextBlock(snapshot)
54
+ const textBefore = selectors.getBlockTextBefore(snapshot)
55
55
  const emojiKeyword = `${textBefore}${event.text}`.match(
56
56
  incompleteEmojiRegEx,
57
57
  )?.[1]
@@ -80,7 +80,7 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
80
80
  }),
81
81
  defineBehavior({
82
82
  on: 'insert.text',
83
- guard: ({context, event}) => {
83
+ guard: ({snapshot, event}) => {
84
84
  const isColon = event.text === ':'
85
85
 
86
86
  if (!isColon) {
@@ -94,8 +94,8 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
94
94
  ? config.parseMatch({match: matches[selectedIndex]})
95
95
  : undefined
96
96
 
97
- const focusBlock = selectors.getFocusTextBlock({context})
98
- const textBefore = selectors.getBlockTextBefore({context})
97
+ const focusBlock = selectors.getFocusTextBlock(snapshot)
98
+ const textBefore = selectors.getBlockTextBefore(snapshot)
99
99
  const emojiKeyword = `${textBefore}:`.match(emojiRegEx)?.[1]
100
100
 
101
101
  if (!focusBlock || emojiKeyword === undefined) {
@@ -149,7 +149,7 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
149
149
  }),
150
150
  defineBehavior({
151
151
  on: 'key.down',
152
- guard: ({context, event}) => {
152
+ guard: ({snapshot, event}) => {
153
153
  const matches = emojiPickerActor.getSnapshot().context.matches
154
154
 
155
155
  if (matches.length === 0) {
@@ -177,8 +177,8 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
177
177
  return false
178
178
  }
179
179
 
180
- const focusBlock = selectors.getFocusTextBlock({context})
181
- const textBefore = selectors.getBlockTextBefore({context})
180
+ const focusBlock = selectors.getFocusTextBlock(snapshot)
181
+ const textBefore = selectors.getBlockTextBefore(snapshot)
182
182
  const emojiKeyword = textBefore.match(incompleteEmojiRegEx)?.[1]
183
183
 
184
184
  if (!focusBlock || emojiKeyword === undefined) {
@@ -286,7 +286,7 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
286
286
  }),
287
287
  defineBehavior({
288
288
  on: 'delete.backward',
289
- guard: ({context, event}) => {
289
+ guard: ({snapshot, event}) => {
290
290
  if (event.unit !== 'character') {
291
291
  return false
292
292
  }
@@ -297,8 +297,8 @@ export function createEmojiPickerBehaviors<TEmojiMatch>(
297
297
  return false
298
298
  }
299
299
 
300
- const focusBlock = selectors.getFocusTextBlock({context})
301
- const textBefore = selectors.getBlockTextBefore({context})
300
+ const focusBlock = selectors.getFocusTextBlock(snapshot)
301
+ const textBefore = selectors.getBlockTextBefore(snapshot)
302
302
  const emojiKeyword = textBefore
303
303
  .slice(0, textBefore.length - 1)
304
304
  .match(incompleteEmojiRegEx)?.[1]
@@ -19,13 +19,13 @@ export type LinkBehaviorsConfig = {
19
19
  export function createLinkBehaviors(config: LinkBehaviorsConfig) {
20
20
  const pasteLinkOnSelection = defineBehavior({
21
21
  on: 'paste',
22
- guard: ({context, event}) => {
23
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
22
+ guard: ({snapshot, event}) => {
23
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
24
24
  const text = event.data.getData('text/plain')
25
25
  const url = looksLikeUrl(text) ? text : undefined
26
26
  const annotation =
27
27
  url !== undefined
28
- ? config.linkAnnotation?.({url, schema: context.schema})
28
+ ? config.linkAnnotation?.({url, schema: snapshot.context.schema})
29
29
  : undefined
30
30
 
31
31
  if (annotation && !selectionCollapsed) {
@@ -45,9 +45,9 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
45
45
  })
46
46
  const pasteLinkAtCaret = defineBehavior({
47
47
  on: 'paste',
48
- guard: ({context, event}) => {
49
- const focusSpan = selectors.getFocusSpan({context})
50
- const selectionCollapsed = selectors.isSelectionCollapsed({context})
48
+ guard: ({snapshot, event}) => {
49
+ const focusSpan = selectors.getFocusSpan(snapshot)
50
+ const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
51
51
 
52
52
  if (!focusSpan || !selectionCollapsed) {
53
53
  return false
@@ -57,7 +57,7 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
57
57
  const url = looksLikeUrl(text) ? text : undefined
58
58
  const annotation =
59
59
  url !== undefined
60
- ? config.linkAnnotation?.({url, schema: context.schema})
60
+ ? config.linkAnnotation?.({url, schema: snapshot.context.schema})
61
61
  : undefined
62
62
 
63
63
  if (url && annotation && selectionCollapsed) {