@portabletext/editor 1.16.1 → 1.16.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.16.1",
3
+ "version": "1.16.2",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -34,18 +34,33 @@ describe('RangeDecorations', () => {
34
34
  },
35
35
  },
36
36
  ]
37
- const {rerender} = render(
38
- <PortableTextEditorTester
39
- onChange={onChange}
40
- rangeDecorations={rangeDecorations}
41
- ref={editorRef}
42
- schemaType={schemaType}
43
- value={value}
44
- />,
37
+
38
+ const {rerender} = await waitFor(() =>
39
+ render(
40
+ <PortableTextEditorTester
41
+ onChange={onChange}
42
+ rangeDecorations={rangeDecorations}
43
+ ref={editorRef}
44
+ schemaType={schemaType}
45
+ value={value}
46
+ />,
47
+ ),
45
48
  )
49
+
50
+ await waitFor(() => {
51
+ if (editorRef.current) {
52
+ expect(onChange).toHaveBeenCalledWith({
53
+ type: 'value',
54
+ value,
55
+ })
56
+ expect(onChange).toHaveBeenCalledWith({type: 'ready'})
57
+ }
58
+ })
59
+
46
60
  await waitFor(() => {
47
61
  expect([rangeDecorationIteration, 'initial']).toEqual([1, 'initial'])
48
62
  })
63
+
49
64
  // Re-render with the same range decorations
50
65
  rerender(
51
66
  <PortableTextEditorTester
@@ -48,7 +48,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
48
48
  _type: 'myTestBlockType',
49
49
  children: [
50
50
  {
51
- _key: '4',
51
+ _key: '2',
52
52
  _type: 'span',
53
53
  text: 'Hello with a new key',
54
54
  marks: [],
@@ -162,7 +162,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
162
162
  _type: 'myTestBlockType',
163
163
  children: [
164
164
  {
165
- _key: '5',
165
+ _key: '2',
166
166
  _type: 'span',
167
167
  text: '',
168
168
  marks: [],
@@ -176,7 +176,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
176
176
  _type: 'myTestBlockType',
177
177
  children: [
178
178
  {
179
- _key: '6',
179
+ _key: '4',
180
180
  _type: 'span',
181
181
  text: '',
182
182
  marks: [],
@@ -57,6 +57,7 @@ const syncValueCallback: CallbackLogicFunction<
57
57
  schema: EditorSchema
58
58
  }
59
59
  slateEditor: PortableTextSlateEditor
60
+ streamBlocks: boolean
60
61
  value: Array<PortableTextBlock> | undefined
61
62
  }
62
63
  > = ({sendBack, input}) => {
@@ -65,6 +66,7 @@ const syncValueCallback: CallbackLogicFunction<
65
66
  sendBack,
66
67
  slateEditor: input.slateEditor,
67
68
  value: input.value,
69
+ streamBlocks: input.streamBlocks,
68
70
  })
69
71
  }
70
72
 
@@ -85,6 +87,7 @@ const syncValueLogic = fromCallback(syncValueCallback)
85
87
  export const syncMachine = setup({
86
88
  types: {
87
89
  context: {} as {
90
+ initialValueSynced: boolean
88
91
  isProcessingLocalChanges: boolean
89
92
  keyGenerator: () => string
90
93
  schema: EditorSchema
@@ -122,6 +125,9 @@ export const syncMachine = setup({
122
125
  >,
123
126
  },
124
127
  actions: {
128
+ 'assign initial value synced': assign({
129
+ initialValueSynced: true,
130
+ }),
125
131
  'assign readOnly': assign({
126
132
  readOnly: ({event}) => {
127
133
  assertEvent(event, 'update readOnly')
@@ -174,6 +180,7 @@ export const syncMachine = setup({
174
180
  }).createMachine({
175
181
  id: 'sync',
176
182
  context: ({input}) => ({
183
+ initialValueSynced: false,
177
184
  isProcessingLocalChanges: false,
178
185
  keyGenerator: input.keyGenerator,
179
186
  schema: input.schema,
@@ -238,16 +245,19 @@ export const syncMachine = setup({
238
245
  invoke: {
239
246
  src: 'sync value',
240
247
  id: 'sync value',
241
- input: ({context}) => ({
242
- context: {
243
- keyGenerator: context.keyGenerator,
244
- previousValue: context.previousValue,
245
- readOnly: context.readOnly,
246
- schema: context.schema,
247
- },
248
- slateEditor: context.slateEditor,
249
- value: context.pendingValue ?? undefined,
250
- }),
248
+ input: ({context}) => {
249
+ return {
250
+ context: {
251
+ keyGenerator: context.keyGenerator,
252
+ previousValue: context.previousValue,
253
+ readOnly: context.readOnly,
254
+ schema: context.schema,
255
+ },
256
+ slateEditor: context.slateEditor,
257
+ streamBlocks: !context.initialValueSynced,
258
+ value: context.pendingValue ?? undefined,
259
+ }
260
+ },
251
261
  },
252
262
  always: {
253
263
  guard: 'pending value equals previous value',
@@ -275,7 +285,11 @@ export const syncMachine = setup({
275
285
  'done syncing': [
276
286
  {
277
287
  guard: 'value changed while syncing',
278
- actions: ['assign previous value', 'emit done syncing'],
288
+ actions: [
289
+ 'assign previous value',
290
+ 'emit done syncing',
291
+ 'assign initial value synced',
292
+ ],
279
293
  reenter: true,
280
294
  },
281
295
  {
@@ -284,6 +298,7 @@ export const syncMachine = setup({
284
298
  'clear pending value',
285
299
  'assign previous value',
286
300
  'emit done syncing',
301
+ 'assign initial value synced',
287
302
  ],
288
303
  },
289
304
  ],
@@ -294,10 +309,11 @@ export const syncMachine = setup({
294
309
 
295
310
  const debug = debugWithName('hook:useSyncValue')
296
311
 
297
- function updateValue({
312
+ async function updateValue({
298
313
  context,
299
314
  sendBack,
300
315
  slateEditor,
316
+ streamBlocks,
301
317
  value,
302
318
  }: {
303
319
  context: {
@@ -308,6 +324,7 @@ function updateValue({
308
324
  }
309
325
  sendBack: (event: SyncValueEvent) => void
310
326
  slateEditor: PortableTextSlateEditor
327
+ streamBlocks: boolean
311
328
  value: PortableTextBlock[] | undefined
312
329
  }) {
313
330
  let isChanged = false
@@ -350,41 +367,45 @@ function updateValue({
350
367
  schemaTypes: context.schema,
351
368
  })
352
369
 
353
- Editor.withoutNormalizing(slateEditor, () => {
354
- withRemoteChanges(slateEditor, () => {
355
- withoutPatching(slateEditor, () => {
356
- const childrenLength = slateEditor.children.length
370
+ await new Promise<void>((resolve) => {
371
+ Editor.withoutNormalizing(slateEditor, () => {
372
+ withRemoteChanges(slateEditor, () => {
373
+ withoutPatching(slateEditor, async () => {
374
+ const childrenLength = slateEditor.children.length
357
375
 
358
- // Remove blocks that have become superfluous
359
- if (slateValueFromProps.length < childrenLength) {
360
- for (
361
- let i = childrenLength - 1;
362
- i > slateValueFromProps.length - 1;
363
- i--
364
- ) {
365
- Transforms.removeNodes(slateEditor, {
366
- at: [i],
376
+ // Remove blocks that have become superfluous
377
+ if (slateValueFromProps.length < childrenLength) {
378
+ for (
379
+ let i = childrenLength - 1;
380
+ i > slateValueFromProps.length - 1;
381
+ i--
382
+ ) {
383
+ Transforms.removeNodes(slateEditor, {
384
+ at: [i],
385
+ })
386
+ }
387
+ isChanged = true
388
+ }
389
+
390
+ for await (const [currentBlock, currentBlockIndex] of getBlocks({
391
+ slateValue: slateValueFromProps,
392
+ streamBlocks,
393
+ })) {
394
+ // Go through all of the blocks and see if they need to be updated
395
+ const {blockChanged, blockValid} = syncBlock({
396
+ context,
397
+ sendBack,
398
+ block: currentBlock,
399
+ index: currentBlockIndex,
400
+ slateEditor,
401
+ value,
367
402
  })
403
+ isChanged = blockChanged || isChanged
404
+ isValid = isValid && blockValid
368
405
  }
369
- isChanged = true
370
- }
371
406
 
372
- for (const [
373
- currentBlockIndex,
374
- currentBlock,
375
- ] of slateValueFromProps.entries()) {
376
- // Go through all of the blocks and see if they need to be updated
377
- const {blockChanged, blockValid} = syncBlock({
378
- context,
379
- sendBack,
380
- block: currentBlock,
381
- index: currentBlockIndex,
382
- slateEditor,
383
- value,
384
- })
385
- isChanged = blockChanged || isChanged
386
- isValid = isValid && blockValid
387
- }
407
+ resolve()
408
+ })
388
409
  })
389
410
  })
390
411
  })
@@ -425,6 +446,23 @@ function updateValue({
425
446
  sendBack({type: 'done syncing', value})
426
447
  }
427
448
 
449
+ async function* getBlocks({
450
+ slateValue,
451
+ streamBlocks,
452
+ }: {
453
+ slateValue: Array<Descendant>
454
+ streamBlocks: boolean
455
+ }) {
456
+ let index = 0
457
+ for await (const block of slateValue) {
458
+ if (streamBlocks) {
459
+ await new Promise<void>((resolve) => setTimeout(resolve, 0))
460
+ }
461
+ yield [block, index] as const
462
+ index++
463
+ }
464
+ }
465
+
428
466
  function syncBlock({
429
467
  context,
430
468
  sendBack,
@@ -452,79 +490,91 @@ function syncBlock({
452
490
  const oldBlock = slateEditor.children[currentBlockIndex]
453
491
  const hasChanges = oldBlock && !isEqual(currentBlock, oldBlock)
454
492
 
455
- if (hasChanges && blockValid) {
456
- const validationValue = [value[currentBlockIndex]]
457
- const validation = validateValue(
458
- validationValue,
459
- context.schema,
460
- context.keyGenerator,
461
- )
462
- // Resolve validations that can be resolved automatically, without involving the user (but only if the value was changed)
463
- if (
464
- !validation.valid &&
465
- validation.resolution?.autoResolve &&
466
- validation.resolution?.patches.length > 0
467
- ) {
468
- // Only apply auto resolution if the value has been populated before and is different from the last one.
469
- if (
470
- !context.readOnly &&
471
- context.previousValue &&
472
- context.previousValue !== value
473
- ) {
474
- // Give a console warning about the fact that it did an auto resolution
475
- console.warn(
476
- `${validation.resolution.action} for block with _key '${validationValue[0]._key}'. ${validation.resolution?.description}`,
477
- )
478
- validation.resolution.patches.forEach((patch) => {
479
- sendBack({type: 'patch', patch})
480
- })
481
- }
482
- }
483
- if (validation.valid || validation.resolution?.autoResolve) {
484
- if (oldBlock._key === currentBlock._key) {
485
- if (debug.enabled) debug('Updating block', oldBlock, currentBlock)
486
- _updateBlock(slateEditor, currentBlock, oldBlock, currentBlockIndex)
487
- } else {
488
- if (debug.enabled) debug('Replacing block', oldBlock, currentBlock)
489
- _replaceBlock(slateEditor, currentBlock, currentBlockIndex)
490
- }
491
- blockChanged = true
492
- } else {
493
- sendBack({
494
- type: 'invalid value',
495
- resolution: validation.resolution,
496
- value,
497
- })
498
- blockValid = false
499
- }
500
- }
493
+ Editor.withoutNormalizing(slateEditor, () => {
494
+ withRemoteChanges(slateEditor, () => {
495
+ withoutPatching(slateEditor, () => {
496
+ if (hasChanges && blockValid) {
497
+ const validationValue = [value[currentBlockIndex]]
498
+ const validation = validateValue(
499
+ validationValue,
500
+ context.schema,
501
+ context.keyGenerator,
502
+ )
503
+ // Resolve validations that can be resolved automatically, without involving the user (but only if the value was changed)
504
+ if (
505
+ !validation.valid &&
506
+ validation.resolution?.autoResolve &&
507
+ validation.resolution?.patches.length > 0
508
+ ) {
509
+ // Only apply auto resolution if the value has been populated before and is different from the last one.
510
+ if (
511
+ !context.readOnly &&
512
+ context.previousValue &&
513
+ context.previousValue !== value
514
+ ) {
515
+ // Give a console warning about the fact that it did an auto resolution
516
+ console.warn(
517
+ `${validation.resolution.action} for block with _key '${validationValue[0]._key}'. ${validation.resolution?.description}`,
518
+ )
519
+ validation.resolution.patches.forEach((patch) => {
520
+ sendBack({type: 'patch', patch})
521
+ })
522
+ }
523
+ }
524
+ if (validation.valid || validation.resolution?.autoResolve) {
525
+ if (oldBlock._key === currentBlock._key) {
526
+ if (debug.enabled) debug('Updating block', oldBlock, currentBlock)
527
+ _updateBlock(
528
+ slateEditor,
529
+ currentBlock,
530
+ oldBlock,
531
+ currentBlockIndex,
532
+ )
533
+ } else {
534
+ if (debug.enabled)
535
+ debug('Replacing block', oldBlock, currentBlock)
536
+ _replaceBlock(slateEditor, currentBlock, currentBlockIndex)
537
+ }
538
+ blockChanged = true
539
+ } else {
540
+ sendBack({
541
+ type: 'invalid value',
542
+ resolution: validation.resolution,
543
+ value,
544
+ })
545
+ blockValid = false
546
+ }
547
+ }
501
548
 
502
- if (!oldBlock && blockValid) {
503
- const validationValue = [value[currentBlockIndex]]
504
- const validation = validateValue(
505
- validationValue,
506
- context.schema,
507
- context.keyGenerator,
508
- )
509
- if (debug.enabled)
510
- debug(
511
- 'Validating and inserting new block in the end of the value',
512
- currentBlock,
513
- )
514
- if (validation.valid || validation.resolution?.autoResolve) {
515
- Transforms.insertNodes(slateEditor, currentBlock, {
516
- at: [currentBlockIndex],
517
- })
518
- } else {
519
- debug('Invalid', validation)
520
- sendBack({
521
- type: 'invalid value',
522
- resolution: validation.resolution,
523
- value,
549
+ if (!oldBlock && blockValid) {
550
+ const validationValue = [value[currentBlockIndex]]
551
+ const validation = validateValue(
552
+ validationValue,
553
+ context.schema,
554
+ context.keyGenerator,
555
+ )
556
+ if (debug.enabled)
557
+ debug(
558
+ 'Validating and inserting new block in the end of the value',
559
+ currentBlock,
560
+ )
561
+ if (validation.valid || validation.resolution?.autoResolve) {
562
+ Transforms.insertNodes(slateEditor, currentBlock, {
563
+ at: [currentBlockIndex],
564
+ })
565
+ } else {
566
+ debug('Invalid', validation)
567
+ sendBack({
568
+ type: 'invalid value',
569
+ resolution: validation.resolution,
570
+ value,
571
+ })
572
+ blockValid = false
573
+ }
574
+ }
524
575
  })
525
- blockValid = false
526
- }
527
- }
576
+ })
577
+ })
528
578
 
529
579
  return {blockChanged, blockValid}
530
580
  }