@portabletext/editor 1.33.4 → 1.33.6

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 (34) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +14 -8
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +20 -12
  4. package/lib/_chunks-cjs/behavior.markdown.cjs.map +1 -1
  5. package/lib/_chunks-cjs/plugin.event-listener.cjs +82 -68
  6. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  7. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs +103 -46
  8. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -1
  9. package/lib/_chunks-es/behavior.core.js +14 -8
  10. package/lib/_chunks-es/behavior.core.js.map +1 -1
  11. package/lib/_chunks-es/behavior.markdown.js +20 -12
  12. package/lib/_chunks-es/behavior.markdown.js.map +1 -1
  13. package/lib/_chunks-es/plugin.event-listener.js +87 -72
  14. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  15. package/lib/_chunks-es/util.block-offsets-to-selection.js +102 -46
  16. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -1
  17. package/lib/behaviors/index.d.cts +1 -33
  18. package/lib/behaviors/index.d.ts +1 -33
  19. package/lib/index.d.cts +162 -1496
  20. package/lib/index.d.ts +162 -1496
  21. package/lib/plugins/index.d.cts +162 -1496
  22. package/lib/plugins/index.d.ts +162 -1496
  23. package/package.json +9 -9
  24. package/src/behavior-actions/behavior.action.block.set.ts +48 -5
  25. package/src/behavior-actions/behavior.action.block.unset.ts +77 -3
  26. package/src/behavior-actions/behavior.actions.ts +1 -18
  27. package/src/behaviors/behavior.core.lists.ts +18 -14
  28. package/src/behaviors/behavior.markdown.ts +14 -12
  29. package/src/behaviors/behavior.types.ts +1 -13
  30. package/src/converters/converter.portable-text.deserialize.test.ts +3 -2
  31. package/src/internal-utils/parse-blocks.test.ts +455 -0
  32. package/src/internal-utils/parse-blocks.ts +202 -87
  33. package/src/behavior-actions/behavior.action.text-block.set.ts +0 -25
  34. package/src/behavior-actions/behavior.action.text-block.unset.ts +0 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.33.4",
3
+ "version": "1.33.6",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -73,21 +73,21 @@
73
73
  "get-random-values-esm": "^1.0.2",
74
74
  "lodash": "^4.17.21",
75
75
  "lodash.startcase": "^4.4.0",
76
- "react-compiler-runtime": "19.0.0-beta-30d8a17-20250209",
76
+ "react-compiler-runtime": "19.0.0-beta-21e868a-20250216",
77
77
  "slate": "0.112.0",
78
78
  "slate-dom": "^0.112.2",
79
79
  "slate-react": "0.112.1",
80
80
  "use-effect-event": "^1.0.2",
81
81
  "xstate": "^5.19.2",
82
- "@portabletext/block-tools": "1.1.7",
82
+ "@portabletext/block-tools": "1.1.8",
83
83
  "@portabletext/patches": "1.1.3"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@portabletext/toolkit": "^2.0.17",
87
87
  "@sanity/diff-match-patch": "^3.2.0",
88
88
  "@sanity/pkg-utils": "^7.0.4",
89
- "@sanity/schema": "^3.75.0",
90
- "@sanity/types": "^3.75.0",
89
+ "@sanity/schema": "^3.75.1",
90
+ "@sanity/types": "^3.75.1",
91
91
  "@testing-library/jest-dom": "^6.6.3",
92
92
  "@testing-library/react": "^16.2.0",
93
93
  "@types/debug": "^4.1.12",
@@ -100,9 +100,9 @@
100
100
  "@vitejs/plugin-react": "^4.3.4",
101
101
  "@vitest/browser": "^3.0.5",
102
102
  "@vitest/coverage-istanbul": "^3.0.5",
103
- "babel-plugin-react-compiler": "19.0.0-beta-30d8a17-20250209",
103
+ "babel-plugin-react-compiler": "19.0.0-beta-21e868a-20250216",
104
104
  "eslint": "8.57.1",
105
- "eslint-plugin-react-compiler": "19.0.0-beta-30d8a17-20250209",
105
+ "eslint-plugin-react-compiler": "19.0.0-beta-21e868a-20250216",
106
106
  "eslint-plugin-react-hooks": "experimental",
107
107
  "jsdom": "^26.0.0",
108
108
  "react": "^19.0.0",
@@ -115,8 +115,8 @@
115
115
  "racejar": "1.2.0"
116
116
  },
117
117
  "peerDependencies": {
118
- "@sanity/schema": "^3.75.0",
119
- "@sanity/types": "^3.75.0",
118
+ "@sanity/schema": "^3.75.1",
119
+ "@sanity/types": "^3.75.1",
120
120
  "react": "^16.9 || ^17 || ^18 || ^19",
121
121
  "rxjs": "^7.8.1"
122
122
  },
@@ -1,10 +1,13 @@
1
- import {Transforms} from 'slate'
1
+ import {Editor, Transforms, type Element as SlateElement} from 'slate'
2
+ import {parseBlock} from '../internal-utils/parse-blocks'
2
3
  import {toSlateRange} from '../internal-utils/ranges'
4
+ import {fromSlateValue, toSlateValue} from '../internal-utils/values'
5
+ import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
3
6
  import type {BehaviorActionImplementation} from './behavior.actions'
4
7
 
5
8
  export const blockSetBehaviorActionImplementation: BehaviorActionImplementation<
6
9
  'block.set'
7
- > = ({action}) => {
10
+ > = ({context, action}) => {
8
11
  const location = toSlateRange(
9
12
  {
10
13
  anchor: {path: action.at, offset: 0},
@@ -14,10 +17,50 @@ export const blockSetBehaviorActionImplementation: BehaviorActionImplementation<
14
17
  )
15
18
 
16
19
  if (!location) {
17
- return
20
+ throw new Error(
21
+ `Unable to convert ${JSON.stringify(action.at)} into a Slate Range`,
22
+ )
18
23
  }
19
24
 
20
- const {at, editor, type, ...payload} = action
25
+ const blockEntry = Editor.node(action.editor, location, {depth: 1})
26
+ const block = blockEntry?.[0]
21
27
 
22
- Transforms.setNodes(action.editor, payload, {at: location})
28
+ if (!block) {
29
+ throw new Error(`Unable to find block at ${JSON.stringify(action.at)}`)
30
+ }
31
+
32
+ const parsedBlock = fromSlateValue(
33
+ [block],
34
+ context.schema.block.name,
35
+ KEY_TO_VALUE_ELEMENT.get(action.editor),
36
+ ).at(0)
37
+
38
+ if (!parsedBlock) {
39
+ throw new Error(`Unable to parse block at ${JSON.stringify(action.at)}`)
40
+ }
41
+
42
+ const {_type, ...filteredProps} = action.props
43
+
44
+ const updatedBlock = parseBlock({
45
+ context,
46
+ block: {
47
+ ...parsedBlock,
48
+ ...filteredProps,
49
+ },
50
+ options: {refreshKeys: false},
51
+ })
52
+
53
+ if (!updatedBlock) {
54
+ throw new Error(`Unable to update block at ${JSON.stringify(action.at)}`)
55
+ }
56
+
57
+ const slateBlock = toSlateValue([updatedBlock], {
58
+ schemaTypes: context.schema,
59
+ })?.at(0) as SlateElement | undefined
60
+
61
+ if (!slateBlock) {
62
+ throw new Error(`Unable to convert block to Slate value`)
63
+ }
64
+
65
+ Transforms.setNodes(action.editor, slateBlock, {at: location})
23
66
  }
@@ -1,10 +1,14 @@
1
- import {Transforms} from 'slate'
1
+ import {omit} from 'lodash'
2
+ import {Editor, Transforms} from 'slate'
3
+ import {isTextBlock, parseBlock} from '../internal-utils/parse-blocks'
2
4
  import {toSlateRange} from '../internal-utils/ranges'
5
+ import {fromSlateValue} from '../internal-utils/values'
6
+ import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
3
7
  import type {BehaviorActionImplementation} from './behavior.actions'
4
8
 
5
9
  export const blockUnsetBehaviorActionImplementation: BehaviorActionImplementation<
6
10
  'block.unset'
7
- > = ({action}) => {
11
+ > = ({context, action}) => {
8
12
  const location = toSlateRange(
9
13
  {
10
14
  anchor: {path: action.at, offset: 0},
@@ -14,8 +18,78 @@ export const blockUnsetBehaviorActionImplementation: BehaviorActionImplementatio
14
18
  )
15
19
 
16
20
  if (!location) {
21
+ throw new Error(
22
+ `Unable to convert ${JSON.stringify(action.at)} into a Slate Range`,
23
+ )
24
+ }
25
+
26
+ const blockEntry = Editor.node(action.editor, location, {depth: 1})
27
+ const block = blockEntry?.[0]
28
+
29
+ if (!block) {
30
+ throw new Error(`Unable to find block at ${JSON.stringify(action.at)}`)
31
+ }
32
+
33
+ const parsedBlock = fromSlateValue(
34
+ [block],
35
+ context.schema.block.name,
36
+ KEY_TO_VALUE_ELEMENT.get(action.editor),
37
+ ).at(0)
38
+
39
+ if (!parsedBlock) {
40
+ throw new Error(`Unable to parse block at ${JSON.stringify(action.at)}`)
41
+ }
42
+
43
+ if (isTextBlock(context.schema, parsedBlock)) {
44
+ const propsToRemove = action.props.filter((prop) => prop !== '_type')
45
+
46
+ const updatedTextBlock = parseBlock({
47
+ context,
48
+ block: omit(parsedBlock, propsToRemove),
49
+ options: {refreshKeys: false},
50
+ })
51
+
52
+ if (!updatedTextBlock) {
53
+ throw new Error(`Unable to update block at ${JSON.stringify(action.at)}`)
54
+ }
55
+
56
+ const propsToSet: Record<string, unknown> = {}
57
+
58
+ for (const prop of propsToRemove) {
59
+ if (!(prop in updatedTextBlock)) {
60
+ propsToSet[prop] = undefined
61
+ } else {
62
+ propsToSet[prop] = (updatedTextBlock as Record<string, unknown>)[prop]
63
+ }
64
+ }
65
+
66
+ Transforms.setNodes(action.editor, propsToSet, {at: location})
67
+
17
68
  return
18
69
  }
19
70
 
20
- Transforms.unsetNodes(action.editor, action.props, {at: location})
71
+ const updatedBlockObject = parseBlock({
72
+ context,
73
+ block: omit(
74
+ parsedBlock,
75
+ action.props.filter((prop) => prop !== '_type'),
76
+ ),
77
+ options: {refreshKeys: false},
78
+ })
79
+
80
+ if (!updatedBlockObject) {
81
+ throw new Error(`Unable to update block at ${JSON.stringify(action.at)}`)
82
+ }
83
+
84
+ const {_type, _key, ...props} = updatedBlockObject
85
+
86
+ Transforms.setNodes(
87
+ action.editor,
88
+ {
89
+ _type,
90
+ _key,
91
+ value: props,
92
+ },
93
+ {at: location},
94
+ )
21
95
  }
@@ -48,8 +48,6 @@ import {
48
48
  removeStyleActionImplementation,
49
49
  toggleStyleActionImplementation,
50
50
  } from './behavior.action.style'
51
- import {textBlockSetActionImplementation} from './behavior.action.text-block.set'
52
- import {textBlockUnsetActionImplementation} from './behavior.action.text-block.unset'
53
51
 
54
52
  export type BehaviorActionImplementationContext = Pick<
55
53
  EditorContext,
@@ -266,8 +264,6 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
266
264
  'style.toggle': toggleStyleActionImplementation,
267
265
  'style.add': addStyleActionImplementation,
268
266
  'style.remove': removeStyleActionImplementation,
269
- 'text block.set': textBlockSetActionImplementation,
270
- 'text block.unset': textBlockUnsetActionImplementation,
271
267
  }
272
268
 
273
269
  export function performAction({
@@ -600,25 +596,12 @@ function performDefaultAction({
600
596
  })
601
597
  break
602
598
  }
603
- case 'style.toggle': {
599
+ default: {
604
600
  behaviorActionImplementations['style.toggle']({
605
601
  context,
606
602
  action,
607
603
  })
608
604
  break
609
605
  }
610
- case 'text block.set': {
611
- behaviorActionImplementations['text block.set']({
612
- context,
613
- action,
614
- })
615
- break
616
- }
617
- default: {
618
- behaviorActionImplementations['text block.unset']({
619
- context,
620
- action,
621
- })
622
- }
623
606
  }
624
607
  }
@@ -30,7 +30,7 @@ const clearListOnBackspace = defineBehavior({
30
30
  actions: [
31
31
  (_, {focusTextBlock}) => [
32
32
  raise({
33
- type: 'text block.unset',
33
+ type: 'block.unset',
34
34
  props: ['listItem', 'level'],
35
35
  at: focusTextBlock.path,
36
36
  }),
@@ -66,8 +66,8 @@ const unindentListOnBackspace = defineBehavior({
66
66
  actions: [
67
67
  (_, {focusTextBlock, level}) => [
68
68
  raise({
69
- type: 'text block.set',
70
- level,
69
+ type: 'block.set',
70
+ props: {level},
71
71
  at: focusTextBlock.path,
72
72
  }),
73
73
  ],
@@ -93,7 +93,7 @@ const clearListOnEnter = defineBehavior({
93
93
  actions: [
94
94
  (_, {focusListBlock}) => [
95
95
  raise({
96
- type: 'text block.unset',
96
+ type: 'block.unset',
97
97
  props: ['listItem', 'level'],
98
98
  at: focusListBlock.path,
99
99
  }),
@@ -133,11 +133,13 @@ const indentListOnTab = defineBehavior({
133
133
  (_, {selectedListBlocks}) =>
134
134
  selectedListBlocks.map((selectedListBlock) =>
135
135
  raise({
136
- type: 'text block.set',
137
- level: Math.min(
138
- MAX_LIST_LEVEL,
139
- Math.max(1, selectedListBlock.node.level + 1),
140
- ),
136
+ type: 'block.set',
137
+ props: {
138
+ level: Math.min(
139
+ MAX_LIST_LEVEL,
140
+ Math.max(1, selectedListBlock.node.level + 1),
141
+ ),
142
+ },
141
143
  at: selectedListBlock.path,
142
144
  }),
143
145
  ),
@@ -176,11 +178,13 @@ const unindentListOnShiftTab = defineBehavior({
176
178
  (_, {selectedListBlocks}) =>
177
179
  selectedListBlocks.map((selectedListBlock) =>
178
180
  raise({
179
- type: 'text block.set',
180
- level: Math.min(
181
- MAX_LIST_LEVEL,
182
- Math.max(1, selectedListBlock.node.level - 1),
183
- ),
181
+ type: 'block.set',
182
+ props: {
183
+ level: Math.min(
184
+ MAX_LIST_LEVEL,
185
+ Math.max(1, selectedListBlock.node.level - 1),
186
+ ),
187
+ },
184
188
  at: selectedListBlock.path,
185
189
  }),
186
190
  ),
@@ -127,13 +127,13 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
127
127
  ],
128
128
  (_, {focusTextBlock, style}) => [
129
129
  {
130
- type: 'text block.unset',
130
+ type: 'block.unset',
131
131
  props: ['listItem', 'level'],
132
132
  at: focusTextBlock.path,
133
133
  },
134
134
  {
135
- type: 'text block.set',
136
- style,
135
+ type: 'block.set',
136
+ props: {style},
137
137
  at: focusTextBlock.path,
138
138
  },
139
139
  {
@@ -326,13 +326,13 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
326
326
  ({event}) => [event],
327
327
  (_, {focusTextBlock, style, level}) => [
328
328
  {
329
- type: 'text block.unset',
329
+ type: 'block.unset',
330
330
  props: ['listItem', 'level'],
331
331
  at: focusTextBlock.path,
332
332
  },
333
333
  {
334
- type: 'text block.set',
335
- style,
334
+ type: 'block.set',
335
+ props: {style},
336
336
  at: focusTextBlock.path,
337
337
  },
338
338
  {
@@ -379,8 +379,8 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
379
379
  actions: [
380
380
  (_, {defaultStyle, focusTextBlock}) => [
381
381
  {
382
- type: 'text block.set',
383
- style: defaultStyle,
382
+ type: 'block.set',
383
+ props: {style: defaultStyle},
384
384
  at: focusTextBlock.path,
385
385
  },
386
386
  ],
@@ -464,10 +464,12 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
464
464
  ({event}) => [event],
465
465
  (_, {focusTextBlock, style, listItem, listItemLength}) => [
466
466
  {
467
- type: 'text block.set',
468
- listItem,
469
- level: 1,
470
- style,
467
+ type: 'block.set',
468
+ props: {
469
+ listItem,
470
+ level: 1,
471
+ style,
472
+ },
471
473
  at: focusTextBlock.path,
472
474
  },
473
475
  {
@@ -38,7 +38,7 @@ export type SyntheticBehaviorEvent =
38
38
  | {
39
39
  type: 'block.set'
40
40
  at: [KeyedSegment]
41
- [props: string]: unknown
41
+ props: Record<string, unknown>
42
42
  }
43
43
  | {
44
44
  type: 'block.unset'
@@ -195,18 +195,6 @@ export type SyntheticBehaviorEvent =
195
195
  type: 'style.toggle'
196
196
  style: string
197
197
  }
198
- | {
199
- type: 'text block.set'
200
- at: [KeyedSegment]
201
- level?: number
202
- listItem?: string
203
- style?: string
204
- }
205
- | {
206
- type: 'text block.unset'
207
- at: [KeyedSegment]
208
- props: Array<'level' | 'listItem' | 'style'>
209
- }
210
198
  | (PickFromUnion<
211
199
  ConverterEvent,
212
200
  'type',
@@ -412,6 +412,7 @@ describe(converterPortableText.deserialize, () => {
412
412
  },
413
413
  ],
414
414
  markDefs: [],
415
+ level: 1,
415
416
  style: 'normal',
416
417
  },
417
418
  ])
@@ -568,7 +569,7 @@ describe(converterPortableText.deserialize, () => {
568
569
  {
569
570
  _type: 'span',
570
571
  text: 'foo',
571
- marks: ['k0'],
572
+ marks: ['k1'],
572
573
  },
573
574
  {
574
575
  _type: 'span',
@@ -578,7 +579,7 @@ describe(converterPortableText.deserialize, () => {
578
579
  ],
579
580
  markDefs: [
580
581
  {
581
- _key: 'k0',
582
+ _key: 'k1',
582
583
  _type: 'link',
583
584
  href: 'https://example.com',
584
585
  },