@portabletext/editor 1.22.0 → 1.23.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -66,14 +66,14 @@
66
66
  "get-random-values-esm": "^1.0.2",
67
67
  "lodash": "^4.17.21",
68
68
  "lodash.startcase": "^4.4.0",
69
- "react-compiler-runtime": "19.0.0-beta-55955c9-20241229",
69
+ "react-compiler-runtime": "19.0.0-beta-decd7b8-20250118",
70
70
  "slate": "0.112.0",
71
71
  "slate-dom": "^0.111.0",
72
72
  "slate-react": "0.112.0",
73
73
  "use-effect-event": "^1.0.2",
74
74
  "xstate": "^5.19.2",
75
- "@portabletext/block-tools": "1.1.0",
76
- "@portabletext/patches": "1.1.1"
75
+ "@portabletext/patches": "1.1.1",
76
+ "@portabletext/block-tools": "1.1.0"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@portabletext/toolkit": "^2.0.16",
@@ -91,11 +91,11 @@
91
91
  "@typescript-eslint/eslint-plugin": "^8.18.1",
92
92
  "@typescript-eslint/parser": "^8.18.1",
93
93
  "@vitejs/plugin-react": "^4.3.4",
94
- "@vitest/browser": "^2.1.8",
95
- "@vitest/coverage-istanbul": "^2.1.8",
96
- "babel-plugin-react-compiler": "19.0.0-beta-e552027-20250112",
94
+ "@vitest/browser": "^3.0.2",
95
+ "@vitest/coverage-istanbul": "^3.0.2",
96
+ "babel-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
97
97
  "eslint": "8.57.1",
98
- "eslint-plugin-react-compiler": "19.0.0-beta-e552027-20250112",
98
+ "eslint-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
99
99
  "eslint-plugin-react-hooks": "^5.1.0",
100
100
  "jsdom": "^26.0.0",
101
101
  "react": "^19.0.0",
@@ -103,7 +103,7 @@
103
103
  "rxjs": "^7.8.1",
104
104
  "typescript": "5.7.3",
105
105
  "vite": "^6.0.4",
106
- "vitest": "^2.1.8",
106
+ "vitest": "^3.0.2",
107
107
  "vitest-browser-react": "^0.0.4",
108
108
  "racejar": "1.1.1"
109
109
  },
@@ -129,8 +129,12 @@
129
129
  "lint:fix": "biome lint --write .",
130
130
  "test": "vitest --run",
131
131
  "test:watch": "vitest",
132
- "test:chromium": "vitest --run --project chromium",
133
- "test:chromium:watch": "vitest --project chromium",
132
+ "test:chromium": "vitest --run --project \"browser (chromium)\"",
133
+ "test:chromium:watch": "vitest --project \"browser (chromium)\"",
134
+ "test:firefox": "vitest --run --project \"browser (firefox)\"",
135
+ "test:firefox:watch": "vitest --project \"browser (firefox)\"",
136
+ "test:webkit": "vitest --run --project \"browser (webkit)\"",
137
+ "test:webkit:watch": "vitest --project \"browser (webkit)\"",
134
138
  "test:unit": "vitest --run --project unit",
135
139
  "test:unit:watch": "vitest --project unit"
136
140
  }
@@ -74,6 +74,7 @@ export type EditorEvent =
74
74
  | 'style.toggle'
75
75
  | 'patches'
76
76
  | 'update behaviors'
77
+ | 'update key generator'
77
78
  | 'update readOnly'
78
79
  | 'update value'
79
80
  >
@@ -105,6 +105,10 @@ export type InternalEditorEvent =
105
105
  type: 'update behaviors'
106
106
  behaviors: Array<Behavior>
107
107
  }
108
+ | {
109
+ type: 'update key generator'
110
+ keyGenerator: () => string
111
+ }
108
112
  | {
109
113
  type: 'update value'
110
114
  value: Array<PortableTextBlock> | undefined
@@ -493,6 +497,9 @@ export const editorMachine = setup({
493
497
  'patches': {actions: emit(({event}) => event)},
494
498
  'done loading': {actions: emit({type: 'done loading'})},
495
499
  'update behaviors': {actions: 'assign behaviors'},
500
+ 'update key generator': {
501
+ actions: assign({keyGenerator: ({event}) => event.keyGenerator}),
502
+ },
496
503
  'update schema': {actions: 'assign schema'},
497
504
  'update value': {actions: assign({value: ({event}) => event.value})},
498
505
  'update maxBlocks': {
@@ -2,43 +2,62 @@ import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
2
2
  import {describe, expect, test} from 'vitest'
3
3
  import {sliceBlocks} from './util.slice-blocks'
4
4
 
5
- const textBlock1: PortableTextTextBlock = {
5
+ const b1: PortableTextTextBlock = {
6
6
  _type: 'block',
7
7
  _key: 'b1',
8
8
  children: [
9
9
  {
10
10
  _type: 'span',
11
- _key: 's1',
11
+ _key: 'b1c1',
12
12
  text: 'foo',
13
- marks: ['strong'],
14
13
  },
15
14
  {
16
15
  _type: 'span',
17
- _key: 's2',
16
+ _key: 'b1c2',
18
17
  text: 'bar',
19
18
  },
20
19
  ],
21
20
  }
22
- const textBlock2: PortableTextTextBlock = {
21
+ const b2: PortableTextBlock = {
22
+ _type: 'image',
23
+ _key: 'b2',
24
+ src: 'https://example.com/image.jpg',
25
+ alt: 'Example',
26
+ }
27
+ const b3: PortableTextTextBlock = {
23
28
  _type: 'block',
24
29
  _key: 'b3',
25
30
  children: [
26
31
  {
27
32
  _type: 'span',
28
- _key: 's3',
33
+ _key: 'b3c1',
29
34
  text: 'baz',
30
35
  },
31
36
  ],
32
37
  }
38
+ const b4: PortableTextTextBlock = {
39
+ _type: 'block',
40
+ _key: 'b4',
41
+ children: [
42
+ {
43
+ _type: 'span',
44
+ _key: 'b4c1',
45
+ text: 'fizz',
46
+ },
47
+ {
48
+ _type: 'stock-ticker',
49
+ _key: 'b4c2',
50
+ symbol: 'AAPL',
51
+ },
52
+ {
53
+ _type: 'span',
54
+ _key: 'b4c3',
55
+ text: 'buzz',
56
+ },
57
+ ],
58
+ }
33
59
 
34
- const blocks: Array<PortableTextBlock> = [
35
- textBlock1,
36
- {
37
- _type: 'image',
38
- _key: 'b2',
39
- },
40
- textBlock2,
41
- ]
60
+ const blocks: Array<PortableTextBlock> = [b1, b2, b3, b4]
42
61
 
43
62
  describe(sliceBlocks.name, () => {
44
63
  test('sensible defaults', () => {
@@ -52,19 +71,19 @@ describe(sliceBlocks.name, () => {
52
71
  blocks,
53
72
  selection: {
54
73
  anchor: {
55
- path: [{_key: 'b1'}, 'children', {_key: 's1'}],
74
+ path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
56
75
  offset: 0,
57
76
  },
58
77
  focus: {
59
- path: [{_key: 'b1'}, 'children', {_key: 's1'}],
78
+ path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
60
79
  offset: 3,
61
80
  },
62
81
  },
63
82
  }),
64
83
  ).toEqual([
65
84
  {
66
- ...textBlock1,
67
- children: [textBlock1.children[0]],
85
+ ...b1,
86
+ children: [b1.children[0]],
68
87
  },
69
88
  ])
70
89
  })
@@ -75,21 +94,21 @@ describe(sliceBlocks.name, () => {
75
94
  blocks,
76
95
  selection: {
77
96
  anchor: {
78
- path: [{_key: 'b1'}, 'children', {_key: 's1'}],
97
+ path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
79
98
  offset: 1,
80
99
  },
81
100
  focus: {
82
- path: [{_key: 'b1'}, 'children', {_key: 's1'}],
101
+ path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
83
102
  offset: 2,
84
103
  },
85
104
  },
86
105
  }),
87
106
  ).toEqual([
88
107
  {
89
- ...textBlock1,
108
+ ...b1,
90
109
  children: [
91
110
  {
92
- ...textBlock1.children[0],
111
+ ...b1.children[0],
93
112
  text: 'o',
94
113
  },
95
114
  ],
@@ -97,64 +116,139 @@ describe(sliceBlocks.name, () => {
97
116
  ])
98
117
  })
99
118
 
119
+ test('starting and ending selection on a block object', () => {
120
+ expect(
121
+ sliceBlocks({
122
+ blocks,
123
+ selection: {
124
+ anchor: {
125
+ path: [{_key: b2._key}],
126
+ offset: 0,
127
+ },
128
+ focus: {
129
+ path: [{_key: b2._key}],
130
+ offset: 0,
131
+ },
132
+ },
133
+ }),
134
+ ).toEqual([b2])
135
+ })
136
+
100
137
  test('ending selection on a block object', () => {
101
138
  expect(
102
139
  sliceBlocks({
103
140
  blocks,
104
141
  selection: {
105
142
  anchor: {
106
- path: [{_key: 'b1'}, 'children', {_key: 's1'}],
143
+ path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
107
144
  offset: 3,
108
145
  },
109
146
  focus: {
110
- path: [{_key: 'b2'}],
147
+ path: [{_key: b2._key}],
111
148
  offset: 0,
112
149
  },
113
150
  },
114
151
  }),
115
152
  ).toEqual([
116
153
  {
117
- ...textBlock1,
154
+ ...b1,
118
155
  children: [
119
156
  {
120
- ...textBlock1.children[0],
157
+ ...b1.children[0],
121
158
  text: '',
122
159
  },
160
+ ...b1.children.slice(1),
123
161
  ],
124
162
  },
125
163
  blocks[1],
126
164
  ])
127
165
  })
128
166
 
167
+ test('slicing across block object', () => {
168
+ expect(
169
+ sliceBlocks({
170
+ blocks,
171
+ selection: {
172
+ anchor: {
173
+ path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
174
+ offset: 0,
175
+ },
176
+ focus: {
177
+ path: [{_key: b3._key}, 'children', {_key: b3.children[0]._key}],
178
+ offset: 3,
179
+ },
180
+ },
181
+ }),
182
+ ).toEqual([b1, b2, b3])
183
+ })
184
+
129
185
  test('starting and ending mid-span', () => {
130
186
  expect(
131
187
  sliceBlocks({
132
188
  blocks,
133
189
  selection: {
134
190
  anchor: {
135
- path: [{_key: 'b1'}, 'children', {_key: 's1'}],
191
+ path: [{_key: b3._key}, 'children', {_key: b3.children[0]._key}],
136
192
  offset: 2,
137
193
  },
138
- focus: {path: [{_key: 'b3'}, 'children', {_key: 's3'}], offset: 1},
194
+ focus: {
195
+ path: [{_key: b4._key}, 'children', {_key: b4.children[0]._key}],
196
+ offset: 1,
197
+ },
139
198
  },
140
199
  }),
141
200
  ).toEqual([
142
201
  {
143
- ...textBlock1,
202
+ ...b3,
144
203
  children: [
145
204
  {
146
- ...textBlock1.children[0],
147
- text: 'o',
205
+ ...b3.children[0],
206
+ text: 'z',
207
+ },
208
+ ],
209
+ },
210
+ {
211
+ ...b4,
212
+ children: [
213
+ {
214
+ ...b4.children[0],
215
+ text: 'f',
216
+ },
217
+ ],
218
+ },
219
+ ])
220
+ })
221
+
222
+ test('starting mid-span and ending end-span', () => {
223
+ expect(
224
+ sliceBlocks({
225
+ blocks,
226
+ selection: {
227
+ anchor: {
228
+ path: [{_key: b3._key}, 'children', {_key: b3.children[0]._key}],
229
+ offset: 2,
230
+ },
231
+ focus: {
232
+ path: [{_key: b4._key}, 'children', {_key: b4.children[0]._key}],
233
+ offset: 4,
234
+ },
235
+ },
236
+ }),
237
+ ).toEqual([
238
+ {
239
+ ...b3,
240
+ children: [
241
+ {
242
+ ...b3.children[0],
243
+ text: 'z',
148
244
  },
149
245
  ],
150
246
  },
151
- blocks[1],
152
247
  {
153
- ...textBlock2,
248
+ ...b4,
154
249
  children: [
155
250
  {
156
- ...textBlock2.children[0],
157
- text: 'az',
251
+ ...b4.children[0],
158
252
  },
159
253
  ],
160
254
  },
@@ -66,13 +66,16 @@ export function sliceBlocks({
66
66
  },
67
67
  ],
68
68
  }
69
- break
69
+ continue
70
70
  }
71
71
 
72
72
  startBlock = {
73
73
  ...block,
74
74
  children: [child],
75
75
  }
76
+ }
77
+
78
+ if (startChildKey === endChildKey) {
76
79
  break
77
80
  }
78
81
 
@@ -97,6 +100,10 @@ export function sliceBlocks({
97
100
  }
98
101
 
99
102
  startBlock = block
103
+
104
+ if (startBlockKey === endBlockKey) {
105
+ break
106
+ }
100
107
  }
101
108
 
102
109
  if (block._key === endBlockKey) {
@@ -111,8 +118,9 @@ export function sliceBlocks({
111
118
  if (child._key === endChildKey && isPortableTextSpan(child)) {
112
119
  endBlock.children.push({
113
120
  ...child,
114
- text: child.text.slice(endPoint.offset),
121
+ text: child.text.slice(0, endPoint.offset),
115
122
  })
123
+
116
124
  break
117
125
  }
118
126
 
@@ -124,7 +132,7 @@ export function sliceBlocks({
124
132
  }
125
133
  }
126
134
 
127
- continue
135
+ break
128
136
  }
129
137
 
130
138
  endBlock = block
@@ -132,7 +140,9 @@ export function sliceBlocks({
132
140
  break
133
141
  }
134
142
 
135
- middleBlocks.push(block)
143
+ if (startBlock) {
144
+ middleBlocks.push(block)
145
+ }
136
146
  }
137
147
 
138
148
  return [