@portabletext/editor 2.12.0 → 2.12.2

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.
@@ -1,5 +1,5 @@
1
1
  import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.js";
2
- import * as react22 from "react";
2
+ import * as react12 from "react";
3
3
  import React from "react";
4
4
  /**
5
5
  * @beta
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
181
181
  */
182
182
  declare function MarkdownPlugin(props: {
183
183
  config: MarkdownPluginConfig;
184
- }): react22.JSX.Element;
184
+ }): react12.JSX.Element;
185
185
  /**
186
186
  * @beta
187
187
  * Restrict the editor to one line. The plugin takes care of blocking
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
192
192
  *
193
193
  * @deprecated Install the plugin from `@portabletext/plugin-one-line`
194
194
  */
195
- declare function OneLinePlugin(): react22.JSX.Element;
195
+ declare function OneLinePlugin(): react12.JSX.Element;
196
196
  export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
@@ -1,5 +1,5 @@
1
1
  import { BlockOffset, BlockPath, ChildPath, EditorContext, EditorSelection, EditorSelectionPoint } from "../_chunks-dts/behavior.types.action.js";
2
- import * as _sanity_types9 from "@sanity/types";
2
+ import * as _sanity_types8 from "@sanity/types";
3
3
  import { KeyedSegment, PortableTextBlock, PortableTextTextBlock } from "@sanity/types";
4
4
  import { isSpan, isTextBlock } from "@portabletext/schema";
5
5
  /**
@@ -143,7 +143,7 @@ declare function mergeTextBlocks({
143
143
  context: Pick<EditorContext, 'keyGenerator' | 'schema'>;
144
144
  targetBlock: PortableTextTextBlock;
145
145
  incomingBlock: PortableTextTextBlock;
146
- }): PortableTextTextBlock<_sanity_types9.PortableTextObject | _sanity_types9.PortableTextSpan>;
146
+ }): PortableTextTextBlock<_sanity_types8.PortableTextObject | _sanity_types8.PortableTextSpan>;
147
147
  /**
148
148
  * @public
149
149
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.12.0",
3
+ "version": "2.12.2",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -51,6 +51,12 @@
51
51
  "require": "./lib/selectors/index.cjs",
52
52
  "default": "./lib/selectors/index.js"
53
53
  },
54
+ "./test": {
55
+ "default": "./src/test/_exports/index.ts"
56
+ },
57
+ "./test/vitest": {
58
+ "default": "./src/test/vitest/_exports/index.ts"
59
+ },
54
60
  "./utils": {
55
61
  "source": "./src/utils/_exports/index.ts",
56
62
  "import": "./lib/utils/index.js",
@@ -78,11 +84,11 @@
78
84
  "slate": "0.118.1",
79
85
  "slate-dom": "^0.118.1",
80
86
  "slate-react": "0.117.4",
81
- "xstate": "^5.21.0",
87
+ "xstate": "^5.22.0",
82
88
  "@portabletext/block-tools": "^3.5.6",
83
89
  "@portabletext/keyboard-shortcuts": "^1.1.1",
84
- "@portabletext/schema": "^1.2.0",
85
- "@portabletext/patches": "^1.1.8"
90
+ "@portabletext/patches": "^1.1.8",
91
+ "@portabletext/schema": "^1.2.0"
86
92
  },
87
93
  "devDependencies": {
88
94
  "@sanity/diff-match-patch": "^3.2.0",
@@ -112,7 +118,7 @@
112
118
  "vitest-browser-react": "^1.0.1",
113
119
  "@portabletext/sanity-bridge": "1.1.10",
114
120
  "@portabletext/test": "^0.0.0",
115
- "racejar": "1.2.15"
121
+ "racejar": "1.3.0"
116
122
  },
117
123
  "peerDependencies": {
118
124
  "@portabletext/sanity-bridge": "^1.1.10",
@@ -1,8 +1,29 @@
1
+ import {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'
2
+ import {isTextBlock} from '@portabletext/schema'
1
3
  import {defaultKeyboardShortcuts} from '../keyboard-shortcuts/default-keyboard-shortcuts'
2
- import * as selectors from '../selectors'
4
+ import {
5
+ getFocusBlock,
6
+ getFocusInlineObject,
7
+ getPreviousBlock,
8
+ isSelectionCollapsed,
9
+ isSelectionExpanded,
10
+ } from '../selectors'
11
+ import {getBlockEndPoint, isEmptyTextBlock} from '../utils'
3
12
  import {raise} from './behavior.types.action'
4
13
  import {defineBehavior} from './behavior.types.behavior'
5
14
 
15
+ const shiftLeft = createKeyboardShortcut({
16
+ default: [
17
+ {
18
+ key: 'ArrowLeft',
19
+ shift: true,
20
+ meta: false,
21
+ ctrl: false,
22
+ alt: false,
23
+ },
24
+ ],
25
+ })
26
+
6
27
  export const abstractKeyboardBehaviors = [
7
28
  /**
8
29
  * Allow raising an `insert.break` event when pressing Enter on an inline
@@ -12,8 +33,20 @@ export const abstractKeyboardBehaviors = [
12
33
  on: 'keyboard.keydown',
13
34
  guard: ({snapshot, event}) =>
14
35
  defaultKeyboardShortcuts.break.guard(event.originEvent) &&
15
- selectors.isSelectionCollapsed(snapshot) &&
16
- selectors.getFocusInlineObject(snapshot),
36
+ isSelectionCollapsed(snapshot) &&
37
+ getFocusInlineObject(snapshot),
38
+ actions: [() => [raise({type: 'insert.break'})]],
39
+ }),
40
+
41
+ /**
42
+ * On Firefox, Enter might collapse the selection. To mitigate this, we
43
+ * `raise` an `insert.break` event manually.
44
+ */
45
+ defineBehavior({
46
+ on: 'keyboard.keydown',
47
+ guard: ({snapshot, event}) =>
48
+ defaultKeyboardShortcuts.break.guard(event.originEvent) &&
49
+ isSelectionExpanded(snapshot),
17
50
  actions: [() => [raise({type: 'insert.break'})]],
18
51
  }),
19
52
 
@@ -48,4 +81,68 @@ export const abstractKeyboardBehaviors = [
48
81
  defaultKeyboardShortcuts.history.redo.guard(event.originEvent),
49
82
  actions: [() => [raise({type: 'history.redo'})]],
50
83
  }),
84
+
85
+ /**
86
+ * Fix edge case where Shift+ArrowLeft didn't reduce a selection hanging
87
+ * onto an empty text block.
88
+ */
89
+ defineBehavior({
90
+ on: 'keyboard.keydown',
91
+ guard: ({snapshot, event}) => {
92
+ if (!snapshot.context.selection || !shiftLeft.guard(event.originEvent)) {
93
+ return false
94
+ }
95
+
96
+ const focusBlock = getFocusBlock(snapshot)
97
+
98
+ if (!focusBlock) {
99
+ return false
100
+ }
101
+
102
+ const previousBlock = getPreviousBlock({
103
+ ...snapshot,
104
+ context: {
105
+ ...snapshot.context,
106
+ selection: {
107
+ anchor: {
108
+ path: focusBlock.path,
109
+ offset: 0,
110
+ },
111
+ focus: {
112
+ path: focusBlock.path,
113
+ offset: 0,
114
+ },
115
+ },
116
+ },
117
+ })
118
+
119
+ if (!previousBlock) {
120
+ return false
121
+ }
122
+
123
+ const hanging =
124
+ isTextBlock(snapshot.context, focusBlock.node) &&
125
+ snapshot.context.selection.focus.offset === 0
126
+
127
+ if (hanging && isEmptyTextBlock(snapshot.context, focusBlock.node)) {
128
+ return {previousBlock, selection: snapshot.context.selection}
129
+ }
130
+
131
+ return false
132
+ },
133
+ actions: [
134
+ ({snapshot}, {previousBlock, selection}) => [
135
+ raise({
136
+ type: 'select',
137
+ at: {
138
+ anchor: selection.anchor,
139
+ focus: getBlockEndPoint({
140
+ context: snapshot.context,
141
+ block: previousBlock,
142
+ }),
143
+ },
144
+ }),
145
+ ],
146
+ ],
147
+ }),
51
148
  ]
@@ -168,7 +168,15 @@ export function performEvent({
168
168
  // action prevented
169
169
  defaultBehaviorOverwritten = true
170
170
 
171
+ if (eventBehavior.actions.length === 0) {
172
+ nativeEventPrevented = true
173
+ }
174
+
175
+ let actionSetIndex = -1
176
+
171
177
  for (const actionSet of eventBehavior.actions) {
178
+ actionSetIndex++
179
+
172
180
  const actionsSnapshot = getSnapshot()
173
181
 
174
182
  let actions: Array<BehaviorAction> = []
@@ -201,7 +209,17 @@ export function performEvent({
201
209
 
202
210
  let undoStepCreated = false
203
211
 
204
- if (actions.some((action) => action.type === 'execute')) {
212
+ if (actionSetIndex > 0) {
213
+ // Since there are multiple action sets
214
+ createUndoStep(editor)
215
+
216
+ undoStepCreated = true
217
+ }
218
+
219
+ if (
220
+ !undoStepCreated &&
221
+ actions.some((action) => action.type === 'execute')
222
+ ) {
205
223
  // Since at least one action is about to `execute` changes in the editor,
206
224
  // we set up a new undo step.
207
225
  // All actions performed recursively from now will be squashed into this
@@ -3,9 +3,9 @@ import {createTestKeyGenerator} from '@portabletext/test'
3
3
  import React from 'react'
4
4
  import {describe, expect, test} from 'vitest'
5
5
  import {InternalSlateEditorRefPlugin} from '../plugins/plugin.internal.slate-editor-ref'
6
+ import {createTestEditor} from '../test/vitest'
6
7
  import type {PortableTextSlateEditor} from '../types/editor'
7
8
  import {getFocusSpan} from './slate-utils'
8
- import {createTestEditor} from './test-editor'
9
9
 
10
10
  describe(getFocusSpan.name, () => {
11
11
  const keyGenerator = createTestKeyGenerator()
@@ -1,7 +1,7 @@
1
1
  import {getTersePt} from '@portabletext/test'
2
2
  import {userEvent} from '@vitest/browser/context'
3
3
  import {describe, expect, test, vi} from 'vitest'
4
- import {createTestEditor} from '../internal-utils/test-editor'
4
+ import {createTestEditor} from '../test/vitest'
5
5
  import {AutoCloseBracketsPlugin} from './plugin.internal.auto-close-brackets'
6
6
 
7
7
  describe(AutoCloseBracketsPlugin.name, () => {
@@ -2,8 +2,8 @@ import {defineSchema} from '@portabletext/schema'
2
2
  import {getTersePt} from '@portabletext/test'
3
3
  import {userEvent} from '@vitest/browser/context'
4
4
  import {describe, expect, test, vi} from 'vitest'
5
- import {createTestEditor} from '../internal-utils/test-editor'
6
5
  import {getTextMarks} from '../internal-utils/text-marks'
6
+ import {createTestEditor} from '../test/vitest'
7
7
  import {MarkdownPlugin} from './plugin.markdown'
8
8
 
9
9
  describe(MarkdownPlugin.name, () => {
@@ -0,0 +1,76 @@
1
+ import {compileSchema, defineSchema} from '@portabletext/schema'
2
+ import {createTestKeyGenerator} from '@portabletext/test'
3
+ import {describe, expect, test} from 'vitest'
4
+ import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
5
+ import {getMarkState} from './selector.get-mark-state'
6
+
7
+ describe(getMarkState.name, () => {
8
+ test('Scenario: Caret after annotated decorator', () => {
9
+ const keyGenerator = createTestKeyGenerator()
10
+ const blockKey = keyGenerator()
11
+ const fooSpanKey = keyGenerator()
12
+ const barSpanKey = keyGenerator()
13
+ const bazSpanKey = keyGenerator()
14
+ const linkKey = keyGenerator()
15
+ const snapshot = createTestSnapshot({
16
+ context: {
17
+ keyGenerator,
18
+ value: [
19
+ {
20
+ _type: 'block',
21
+ _key: blockKey,
22
+ children: [
23
+ {
24
+ _type: 'span',
25
+ _key: fooSpanKey,
26
+ text: 'foo ',
27
+ },
28
+ {
29
+ _type: 'span',
30
+ _key: barSpanKey,
31
+ text: 'bar',
32
+ marks: [linkKey, 'strong'],
33
+ },
34
+ {
35
+ _type: 'span',
36
+ _key: bazSpanKey,
37
+ text: ' baz',
38
+ },
39
+ ],
40
+ markDefs: [
41
+ {
42
+ _type: 'link',
43
+ _key: linkKey,
44
+ href: 'https://portabletext.org',
45
+ },
46
+ ],
47
+ style: 'normal',
48
+ },
49
+ ],
50
+ selection: {
51
+ anchor: {
52
+ path: [{_key: blockKey}, 'children', {_key: bazSpanKey}],
53
+ offset: 0,
54
+ },
55
+ focus: {
56
+ path: [{_key: blockKey}, 'children', {_key: bazSpanKey}],
57
+ offset: 0,
58
+ },
59
+ backward: false,
60
+ },
61
+ schema: compileSchema(
62
+ defineSchema({
63
+ annotations: [{name: 'link'}],
64
+ decorators: [{name: 'strong'}],
65
+ }),
66
+ ),
67
+ },
68
+ })
69
+
70
+ expect(getMarkState(snapshot)).toEqual({
71
+ state: 'changed',
72
+ marks: [],
73
+ previousMarks: [linkKey, 'strong'],
74
+ })
75
+ })
76
+ })
@@ -164,7 +164,7 @@ export const getMarkState: EditorSelector<MarkState | undefined> = (
164
164
  if (previousSpanHasAnnotations) {
165
165
  return {
166
166
  state: 'changed',
167
- previousMarks: marks,
167
+ previousMarks: previousSpan.node.marks ?? [],
168
168
  marks: [],
169
169
  }
170
170
  } else {
@@ -0,0 +1 @@
1
+ export * from '../index'
@@ -0,0 +1,102 @@
1
+ import {parseTersePtString} from '@portabletext/test'
2
+ import {createParameterType, type ParameterType} from 'racejar'
3
+
4
+ /**
5
+ * @internal
6
+ */
7
+ export type Parameter = {
8
+ [K in keyof typeof parameterType]: (typeof parameterType)[K] extends ParameterType<
9
+ infer TParameterType
10
+ >
11
+ ? TParameterType
12
+ : never
13
+ }
14
+
15
+ const parameterType = {
16
+ annotation: createParameterType<'comment' | 'link'>({
17
+ name: 'annotation',
18
+ matcher: /"(comment|link)"/,
19
+ }),
20
+ blockObject: createParameterType<'image'>({
21
+ name: 'block-object',
22
+ matcher: /"(image)"/,
23
+ }),
24
+ button: createParameterType<string>({
25
+ name: 'button',
26
+ matcher: /"(([A-Z]|[a-z]|{|}|>|\/|\+)+)"/,
27
+ type: String,
28
+ }),
29
+ decorator: createParameterType<'em' | 'strong'>({
30
+ name: 'decorator',
31
+ matcher: /"(em|strong)"/,
32
+ }),
33
+ index: createParameterType({
34
+ name: 'index',
35
+ matcher: /"(\d)"/,
36
+ type: Number,
37
+ transform: (input) => Number.parseInt(input, 10),
38
+ }),
39
+ inlineObject: createParameterType<'stock-ticker'>({
40
+ name: 'inline-object',
41
+ matcher: /"(stock-ticker)"/,
42
+ }),
43
+ key: createParameterType<'key'>({
44
+ name: 'key',
45
+ matcher: /"([a-z]\d)"/,
46
+ }),
47
+ keyKeys: createParameterType<Array<string>>({
48
+ name: 'keyKeys',
49
+ matcher: /"(([a-z]\d)(,([a-z]\d))*)"/,
50
+ type: Array,
51
+ transform: (input) => input.split(','),
52
+ }),
53
+ marks: createParameterType<Array<string>>({
54
+ name: 'marks',
55
+ matcher: /"((strong|em|[a-z]\d)(,(strong|em|[a-z]\d))*)"/,
56
+ type: Array,
57
+ transform: (input) => input.split(','),
58
+ }),
59
+ placement: createParameterType<'auto' | 'after' | 'before'>({
60
+ name: 'placement',
61
+ matcher: /"(auto|after|before)"/,
62
+ }),
63
+ selectPosition: createParameterType<'start' | 'end' | 'none'>({
64
+ name: 'select-position',
65
+ matcher: /"(start|end|none)"/,
66
+ }),
67
+ style: createParameterType({
68
+ name: 'style',
69
+ matcher: /"(normal|blockquote|h\d)"/,
70
+ }),
71
+ tersePt: createParameterType<Array<string>>({
72
+ name: 'terse-pt',
73
+ matcher: /"([a-z-,#>:\\n \d|{}'"‘’“”?—]*)"/u,
74
+ type: Array,
75
+ transform: parseTersePtString,
76
+ }),
77
+ text: createParameterType<string>({
78
+ name: 'text',
79
+ matcher: /"([a-z]*)"/u,
80
+ type: String,
81
+ }),
82
+ }
83
+
84
+ /**
85
+ * @internal
86
+ */
87
+ export const parameterTypes = [
88
+ parameterType.annotation,
89
+ parameterType.blockObject,
90
+ parameterType.button,
91
+ parameterType.decorator,
92
+ parameterType.index,
93
+ parameterType.inlineObject,
94
+ parameterType.key,
95
+ parameterType.keyKeys,
96
+ parameterType.marks,
97
+ parameterType.placement,
98
+ parameterType.selectPosition,
99
+ parameterType.style,
100
+ parameterType.tersePt,
101
+ parameterType.text,
102
+ ]
@@ -0,0 +1 @@
1
+ export * from './gherkin-parameter-types'
@@ -0,0 +1 @@
1
+ export * from '../index'
@@ -0,0 +1,3 @@
1
+ export * from './step-context'
2
+ export * from './step-definitions'
3
+ export * from './test-editor'
@@ -0,0 +1,11 @@
1
+ import type {Locator} from '@vitest/browser/context'
2
+ import type {Editor} from '../../editor'
3
+
4
+ /**
5
+ * @internal
6
+ */
7
+ export type Context = {
8
+ editor: Editor
9
+ locator: Locator
10
+ keyMap?: Map<string, string>
11
+ }