@portabletext/editor 1.49.3 → 1.49.5

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.
@@ -3,10 +3,12 @@ import type {PortableTextBlock} from '@sanity/types'
3
3
  import {isEqual} from 'lodash'
4
4
  import {Editor, Text, Transforms, type Descendant, type Node} from 'slate'
5
5
  import {
6
+ and,
6
7
  assertEvent,
7
8
  assign,
8
9
  emit,
9
10
  fromCallback,
11
+ not,
10
12
  setup,
11
13
  type AnyEventObject,
12
14
  type CallbackLogicFunction,
@@ -173,6 +175,11 @@ export const syncMachine = setup({
173
175
 
174
176
  return isBusy
175
177
  },
178
+ 'is new value': ({context, event}) => {
179
+ return (
180
+ event.type === 'update value' && context.previousValue !== event.value
181
+ )
182
+ },
176
183
  'value changed while syncing': ({context, event}) => {
177
184
  assertEvent(event, 'done syncing')
178
185
  return context.pendingValue !== event.value
@@ -211,176 +218,148 @@ export const syncMachine = setup({
211
218
  actions: ['assign readOnly'],
212
219
  },
213
220
  },
214
- type: 'parallel',
221
+ initial: 'idle',
215
222
  states: {
216
- 'setting up': {
217
- initial: 'syncing initial value',
218
- states: {
219
- 'syncing initial value': {
220
- entry: [
221
- () => {
222
- debug('entry: syncing initial value')
223
- },
224
- ],
225
- exit: [
226
- () => {
227
- debug('exit: syncing initial value')
228
- },
229
- ],
230
- always: {
231
- guard: 'initial value synced',
232
- target: 'done syncing initial value',
233
- },
223
+ idle: {
224
+ entry: [
225
+ () => {
226
+ debug('entry: syncing->idle')
234
227
  },
235
- 'done syncing initial value': {
236
- entry: [
237
- 'emit done syncing value',
238
- () => {
239
- debug('entry: done syncing initial value')
240
- },
241
- ],
242
- exit: [
243
- () => {
244
- debug('exit: done syncing initial value')
245
- },
246
- ],
247
- type: 'final',
228
+ ],
229
+ exit: [
230
+ () => {
231
+ debug('exit: syncing->idle')
248
232
  },
249
- },
250
- },
251
- 'syncing': {
252
- initial: 'idle',
253
- states: {
254
- idle: {
255
- entry: [
256
- () => {
257
- debug('entry: syncing->idle')
258
- },
259
- ],
260
- exit: [
261
- () => {
262
- debug('exit: syncing->idle')
263
- },
264
- ],
265
- on: {
266
- 'update value': [
267
- {
268
- guard: 'is busy',
269
- target: 'busy',
270
- actions: ['assign pending value'],
271
- },
272
- {
273
- target: 'syncing',
274
- actions: ['assign pending value'],
233
+ ],
234
+ on: {
235
+ 'update value': [
236
+ {
237
+ guard: and(['is busy', 'is new value']),
238
+ target: 'busy',
239
+ actions: ['assign pending value'],
240
+ },
241
+ {
242
+ guard: 'is new value',
243
+ target: 'syncing',
244
+ actions: ['assign pending value'],
245
+ },
246
+ {
247
+ guard: not('initial value synced'),
248
+ actions: [
249
+ () => {
250
+ debug('no new value – setting initial value as synced')
275
251
  },
252
+ 'assign initial value synced',
253
+ 'emit done syncing value',
276
254
  ],
277
255
  },
278
- },
279
- busy: {
280
- entry: [
281
- () => {
282
- debug('entry: syncing->busy')
283
- },
284
- ],
285
- exit: [
286
- () => {
287
- debug('exit: syncing->busy')
288
- },
289
- ],
290
- after: {
291
- 1000: [
292
- {
293
- guard: 'is busy',
294
- target: '.',
295
- reenter: true,
296
- actions: [
297
- () => {
298
- debug('reenter: syncing->busy')
299
- },
300
- ],
301
- },
302
- {
303
- target: 'syncing',
256
+ {
257
+ actions: [
258
+ () => {
259
+ debug('no new value and initial value already synced')
304
260
  },
305
261
  ],
306
262
  },
307
- on: {
308
- 'update value': [
309
- {
310
- actions: ['assign pending value'],
263
+ ],
264
+ },
265
+ },
266
+ busy: {
267
+ entry: [
268
+ () => {
269
+ debug('entry: syncing->busy')
270
+ },
271
+ ],
272
+ exit: [
273
+ () => {
274
+ debug('exit: syncing->busy')
275
+ },
276
+ ],
277
+ after: {
278
+ 1000: [
279
+ {
280
+ guard: 'is busy',
281
+ target: '.',
282
+ reenter: true,
283
+ actions: [
284
+ () => {
285
+ debug('reenter: syncing->busy')
311
286
  },
312
287
  ],
313
288
  },
314
- },
315
- syncing: {
316
- entry: [
317
- () => {
318
- debug('entry: syncing->syncing')
319
- },
320
- 'emit syncing value',
321
- ],
322
- exit: [
323
- () => {
324
- debug('exit: syncing->syncing')
325
- },
326
- 'emit done syncing value',
327
- ],
328
- always: {
329
- guard: 'pending value equals previous value',
330
- target: 'idle',
331
- actions: ['clear pending value', 'assign initial value synced'],
289
+ {
290
+ target: 'syncing',
332
291
  },
333
- invoke: {
334
- src: 'sync value',
335
- id: 'sync value',
336
- input: ({context}) => {
337
- return {
338
- context: {
339
- keyGenerator: context.keyGenerator,
340
- previousValue: context.previousValue,
341
- readOnly: context.readOnly,
342
- schema: context.schema,
343
- },
344
- slateEditor: context.slateEditor,
345
- streamBlocks: !context.initialValueSynced,
346
- value: context.pendingValue,
347
- }
348
- },
292
+ ],
293
+ },
294
+ on: {
295
+ 'update value': [
296
+ {
297
+ guard: 'is new value',
298
+ actions: ['assign pending value'],
349
299
  },
350
- on: {
351
- 'update value': {
352
- actions: ['assign pending value'],
353
- },
354
- 'patch': {
355
- actions: [emit(({event}) => event)],
356
- },
357
- 'invalid value': {
358
- actions: [emit(({event}) => event)],
359
- },
360
- 'value changed': {
361
- actions: [emit(({event}) => event)],
300
+ ],
301
+ },
302
+ },
303
+ syncing: {
304
+ entry: [
305
+ () => {
306
+ debug('entry: syncing->syncing')
307
+ },
308
+ 'emit syncing value',
309
+ ],
310
+ exit: [
311
+ () => {
312
+ debug('exit: syncing->syncing')
313
+ },
314
+ 'emit done syncing value',
315
+ ],
316
+ invoke: {
317
+ src: 'sync value',
318
+ id: 'sync value',
319
+ input: ({context}) => {
320
+ return {
321
+ context: {
322
+ keyGenerator: context.keyGenerator,
323
+ previousValue: context.previousValue,
324
+ readOnly: context.readOnly,
325
+ schema: context.schema,
362
326
  },
363
- 'done syncing': [
364
- {
365
- guard: 'value changed while syncing',
366
- actions: [
367
- 'assign previous value',
368
- 'assign initial value synced',
369
- ],
370
- target: 'syncing',
371
- reenter: true,
372
- },
373
- {
374
- target: 'idle',
375
- actions: [
376
- 'clear pending value',
377
- 'assign previous value',
378
- 'assign initial value synced',
379
- ],
380
- },
327
+ slateEditor: context.slateEditor,
328
+ streamBlocks: !context.initialValueSynced,
329
+ value: context.pendingValue,
330
+ }
331
+ },
332
+ },
333
+ on: {
334
+ 'update value': {
335
+ guard: 'is new value',
336
+ actions: ['assign pending value'],
337
+ },
338
+ 'patch': {
339
+ actions: [emit(({event}) => event)],
340
+ },
341
+ 'invalid value': {
342
+ actions: [emit(({event}) => event)],
343
+ },
344
+ 'value changed': {
345
+ actions: [emit(({event}) => event)],
346
+ },
347
+ 'done syncing': [
348
+ {
349
+ guard: 'value changed while syncing',
350
+ actions: ['assign previous value', 'assign initial value synced'],
351
+ target: 'syncing',
352
+ reenter: true,
353
+ },
354
+ {
355
+ target: 'idle',
356
+ actions: [
357
+ 'clear pending value',
358
+ 'assign previous value',
359
+ 'assign initial value synced',
381
360
  ],
382
361
  },
383
- },
362
+ ],
384
363
  },
385
364
  },
386
365
  },
@@ -444,48 +423,69 @@ async function updateValue({
444
423
  schemaTypes: context.schema,
445
424
  })
446
425
 
447
- await new Promise<void>((resolve) => {
448
- Editor.withoutNormalizing(slateEditor, () => {
449
- withRemoteChanges(slateEditor, () => {
450
- withoutPatching(slateEditor, async () => {
451
- const childrenLength = slateEditor.children.length
426
+ if (streamBlocks) {
427
+ await new Promise<void>((resolve) => {
428
+ Editor.withoutNormalizing(slateEditor, () => {
429
+ withRemoteChanges(slateEditor, () => {
430
+ withoutPatching(slateEditor, async () => {
431
+ isChanged = removeExtraBlocks({
432
+ slateEditor,
433
+ slateValueFromProps,
434
+ })
452
435
 
453
- // Remove blocks that have become superfluous
454
- if (slateValueFromProps.length < childrenLength) {
455
- for (
456
- let i = childrenLength - 1;
457
- i > slateValueFromProps.length - 1;
458
- i--
459
- ) {
460
- Transforms.removeNodes(slateEditor, {
461
- at: [i],
436
+ for await (const [
437
+ currentBlock,
438
+ currentBlockIndex,
439
+ ] of getStreamedBlocks({
440
+ slateValue: slateValueFromProps,
441
+ })) {
442
+ const {blockChanged, blockValid} = syncBlock({
443
+ context,
444
+ sendBack,
445
+ block: currentBlock,
446
+ index: currentBlockIndex,
447
+ slateEditor,
448
+ value,
462
449
  })
450
+
451
+ isChanged = blockChanged || isChanged
452
+ isValid = isValid && blockValid
463
453
  }
464
- isChanged = true
465
- }
466
454
 
467
- for await (const [currentBlock, currentBlockIndex] of getBlocks({
468
- slateValue: slateValueFromProps,
469
- streamBlocks,
470
- })) {
471
- // Go through all of the blocks and see if they need to be updated
455
+ resolve()
456
+ })
457
+ })
458
+ })
459
+ })
460
+ } else {
461
+ Editor.withoutNormalizing(slateEditor, () => {
462
+ withRemoteChanges(slateEditor, () => {
463
+ withoutPatching(slateEditor, () => {
464
+ isChanged = removeExtraBlocks({
465
+ slateEditor,
466
+ slateValueFromProps,
467
+ })
468
+
469
+ let index = 0
470
+
471
+ for (const currentBlock of slateValueFromProps) {
472
472
  const {blockChanged, blockValid} = syncBlock({
473
473
  context,
474
474
  sendBack,
475
475
  block: currentBlock,
476
- index: currentBlockIndex,
476
+ index,
477
477
  slateEditor,
478
478
  value,
479
479
  })
480
+
480
481
  isChanged = blockChanged || isChanged
481
482
  isValid = isValid && blockValid
483
+ index++
482
484
  }
483
-
484
- resolve()
485
485
  })
486
486
  })
487
487
  })
488
- })
488
+ }
489
489
  }
490
490
 
491
491
  if (!isValid) {
@@ -523,16 +523,36 @@ async function updateValue({
523
523
  sendBack({type: 'done syncing', value})
524
524
  }
525
525
 
526
- async function* getBlocks({
526
+ function removeExtraBlocks({
527
+ slateEditor,
528
+ slateValueFromProps,
529
+ }: {
530
+ slateEditor: PortableTextSlateEditor
531
+ slateValueFromProps: Array<Descendant>
532
+ }) {
533
+ let isChanged = false
534
+ const childrenLength = slateEditor.children.length
535
+
536
+ // Remove blocks that have become superfluous
537
+ if (slateValueFromProps.length < childrenLength) {
538
+ for (let i = childrenLength - 1; i > slateValueFromProps.length - 1; i--) {
539
+ Transforms.removeNodes(slateEditor, {
540
+ at: [i],
541
+ })
542
+ }
543
+ isChanged = true
544
+ }
545
+ return isChanged
546
+ }
547
+
548
+ async function* getStreamedBlocks({
527
549
  slateValue,
528
- streamBlocks,
529
550
  }: {
530
551
  slateValue: Array<Descendant>
531
- streamBlocks: boolean
532
552
  }) {
533
553
  let index = 0
534
554
  for await (const block of slateValue) {
535
- if (streamBlocks && index % 10 === 0) {
555
+ if (index % 10 === 0) {
536
556
  await new Promise<void>((resolve) => setTimeout(resolve, 0))
537
557
  }
538
558
  yield [block, index] as const