edytor 5.0.0 → 7.0.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.
Files changed (137) hide show
  1. package/README.md +3 -3
  2. package/dist/cloudflare/DocumentRoom.d.ts +77 -2
  3. package/dist/cloudflare/DocumentRoom.js +37 -1
  4. package/dist/cloudflare/index.d.ts +1 -1
  5. package/dist/cloudflare/index.js +1 -1
  6. package/dist/cloudflare/room/access.d.ts +6 -1
  7. package/dist/cloudflare/room/access.js +30 -4
  8. package/dist/cloudflare/room/admission.d.ts +11 -0
  9. package/dist/cloudflare/room/admission.js +43 -10
  10. package/dist/cloudflare/room/context.d.ts +73 -76
  11. package/dist/cloudflare/room/context.js +17 -6
  12. package/dist/cloudflare/room/forged.d.ts +1 -1
  13. package/dist/cloudflare/room/forged.js +11 -9
  14. package/dist/cloudflare/room/frames.d.ts +2 -0
  15. package/dist/cloudflare/room/frames.js +2 -0
  16. package/dist/cloudflare/room/presence.js +1 -4
  17. package/dist/cloudflare/room/reseed.d.ts +93 -0
  18. package/dist/cloudflare/room/reseed.js +500 -0
  19. package/dist/cloudflare/room/scheduler.d.ts +1 -1
  20. package/dist/cloudflare/room/scheduler.js +9 -1
  21. package/dist/cloudflare/room/shared.d.ts +10 -0
  22. package/dist/cloudflare/room/shared.js +10 -0
  23. package/dist/cloudflare/room/storage.d.ts +7 -0
  24. package/dist/cloudflare/room/storage.js +33 -1
  25. package/dist/components/Edytor.svelte +18 -3
  26. package/dist/components/Edytor.svelte.d.ts +10 -1
  27. package/dist/components/EdytorRoot.svelte +17 -0
  28. package/dist/components/EdytorRoot.svelte.d.ts +4 -0
  29. package/dist/components/Suggestion.svelte +1 -1
  30. package/dist/crdt/attribution/block.d.ts +3 -0
  31. package/dist/crdt/attribution/block.js +3 -2
  32. package/dist/crdt/doc/history.js +2 -3
  33. package/dist/crdt/doc/ops.d.ts +81 -0
  34. package/dist/crdt/doc/ops.js +1898 -0
  35. package/dist/crdt/doc/suggest.d.ts +6 -2
  36. package/dist/crdt/doc/suggest.js +6 -5
  37. package/dist/crdt/edytor-doc.d.ts +157 -157
  38. package/dist/crdt/edytor-doc.js +15 -43
  39. package/dist/crdt/engine-room.d.ts +4 -0
  40. package/dist/crdt/engine-room.js +6 -0
  41. package/dist/crdt/flow.d.ts +4 -46
  42. package/dist/crdt/flow.js +2 -262
  43. package/dist/crdt/index.d.ts +64 -75
  44. package/dist/crdt/operations.d.ts +1 -1
  45. package/dist/crdt/placement/resolve.d.ts +1 -1
  46. package/dist/crdt/protocol.d.ts +2 -2
  47. package/dist/crdt/protocol.js +2 -2
  48. package/dist/crdt/protocols/auth.d.ts +8 -1
  49. package/dist/crdt/protocols/auth.js +14 -1
  50. package/dist/crdt/protocols/awareness.js +46 -81
  51. package/dist/crdt/providers/index.d.ts +11 -22
  52. package/dist/crdt/providers/index.js +22 -3
  53. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  54. package/dist/crdt/providers/indexeddb.js +10 -16
  55. package/dist/crdt/providers/room.d.ts +26 -0
  56. package/dist/crdt/providers/room.js +41 -12
  57. package/dist/crdt/providers/websocket.d.ts +10 -14
  58. package/dist/crdt/providers/websocket.js +43 -23
  59. package/dist/crdt/rangeDelete.d.ts +2 -47
  60. package/dist/crdt/rangeDelete.js +1 -169
  61. package/dist/crdt/reseed.d.ts +127 -0
  62. package/dist/crdt/reseed.js +533 -0
  63. package/dist/crdt/restore.d.ts +13 -1
  64. package/dist/crdt/restore.js +8 -3
  65. package/dist/crdt/schema.d.ts +6 -0
  66. package/dist/crdt/schema.js +6 -0
  67. package/dist/crdt/structs.d.ts +8 -11
  68. package/dist/crdt/structs.js +25 -44
  69. package/dist/crdt/text/index/build.d.ts +119 -0
  70. package/dist/crdt/text/index/build.js +2801 -0
  71. package/dist/crdt/text/index/shared.d.ts +2 -6
  72. package/dist/crdt/text/index/shared.js +2 -5
  73. package/dist/crdt/text/model.js +2 -3
  74. package/dist/crdt/text/runs.d.ts +9 -12
  75. package/dist/crdt/text/runs.js +2 -165
  76. package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
  77. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  78. package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
  79. package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
  80. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  81. package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
  82. package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
  83. package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
  84. package/dist/events/domTextOffset.d.ts +2 -0
  85. package/dist/events/domTextOffset.js +8 -0
  86. package/dist/export/forms.d.ts +50 -50
  87. package/dist/index.d.ts +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  90. package/dist/plugins/richtext/richTextOperations.js +26 -32
  91. package/dist/selection/selection.svelte.d.ts +1 -1
  92. package/dist/selection/selection.svelte.js +87 -106
  93. package/dist/selection/selection.utils.js +5 -8
  94. package/dist/session/commands.js +49 -51
  95. package/dist/session/handles.js +3 -7
  96. package/dist/surface/attributes.d.ts +3 -3
  97. package/dist/surface/attributes.js +2 -2
  98. package/dist/surface/observer.svelte.d.ts +7 -7
  99. package/dist/surface/observer.svelte.js +225 -336
  100. package/dist/surface/projector.svelte.js +2 -2
  101. package/dist/utils.d.ts +5 -0
  102. package/dist/utils.js +7 -0
  103. package/package.json +1 -1
  104. package/dist/crdt/doc/capability.d.ts +0 -56
  105. package/dist/crdt/doc/capability.js +0 -207
  106. package/dist/crdt/doc/content.d.ts +0 -16
  107. package/dist/crdt/doc/content.js +0 -77
  108. package/dist/crdt/doc/delete.d.ts +0 -15
  109. package/dist/crdt/doc/delete.js +0 -84
  110. package/dist/crdt/doc/layout.d.ts +0 -9
  111. package/dist/crdt/doc/layout.js +0 -178
  112. package/dist/crdt/doc/meta.d.ts +0 -19
  113. package/dist/crdt/doc/meta.js +0 -198
  114. package/dist/crdt/doc/moves.d.ts +0 -21
  115. package/dist/crdt/doc/moves.js +0 -274
  116. package/dist/crdt/doc/split-merge.d.ts +0 -10
  117. package/dist/crdt/doc/split-merge.js +0 -179
  118. package/dist/crdt/doc/steps.d.ts +0 -65
  119. package/dist/crdt/doc/steps.js +0 -311
  120. package/dist/crdt/text/index/cache.d.ts +0 -16
  121. package/dist/crdt/text/index/cache.js +0 -175
  122. package/dist/crdt/text/index/claims.d.ts +0 -18
  123. package/dist/crdt/text/index/claims.js +0 -203
  124. package/dist/crdt/text/index/fold.d.ts +0 -21
  125. package/dist/crdt/text/index/fold.js +0 -656
  126. package/dist/crdt/text/index/layout.d.ts +0 -22
  127. package/dist/crdt/text/index/layout.js +0 -258
  128. package/dist/crdt/text/index/placement.d.ts +0 -47
  129. package/dist/crdt/text/index/placement.js +0 -738
  130. package/dist/crdt/text/index/records.d.ts +0 -13
  131. package/dist/crdt/text/index/records.js +0 -139
  132. package/dist/crdt/text/index/report.d.ts +0 -18
  133. package/dist/crdt/text/index/report.js +0 -337
  134. package/dist/crdt/text/index/state.d.ts +0 -197
  135. package/dist/crdt/text/index/state.js +0 -116
  136. package/dist/crdt/text/index/streams.d.ts +0 -25
  137. package/dist/crdt/text/index/streams.js +0 -248
@@ -1,6 +1,11 @@
1
1
  <script module lang="ts">
2
2
  import './richtext.css';
3
- import type { Plugin, BlockSnippetPayload, KindPreset, PlaceholderView } from '../../plugins.js';
3
+ import type {
4
+ Plugin,
5
+ BlockDefinition,
6
+ BlockSnippetPayload,
7
+ PlaceholderView
8
+ } from '../../plugins.js';
4
9
  import { branded } from '../brand.js';
5
10
  import type { Edytor } from '../../edytor.svelte.js';
6
11
  import { presetId } from '../../kinds.js';
@@ -191,11 +196,15 @@
191
196
  let retyping: string | null = null;
192
197
  /** The disclosures this view created, opened once drawn (`onBlockAttached`). */
193
198
  const opening = new Set<string>();
194
- const presets = (type: string, rows: KindPreset[]) =>
195
- rows.map((row, index) => {
196
- const own = keywordsOf(edytor, presetId(type, index, rows.length), row.keywords);
197
- return own ? { ...row, keywords: own } : row;
198
- });
199
+ /** Each kind's presets, their keywords the view's (`labels`) where it names some. */
200
+ const named = (blocks: Record<string, BlockDefinition>) => {
201
+ for (const [type, kind] of Object.entries(blocks))
202
+ kind.presets = kind.presets?.map((row, index, rows) => {
203
+ const own = keywordsOf(edytor, presetId(type, index, rows.length), row.keywords);
204
+ return own ? { ...row, keywords: own } : row;
205
+ });
206
+ return blocks;
207
+ };
199
208
  const operations = richTextOperations(edytor);
200
209
  /** The space an autolink types itself: its own insertion is not looked at again. */
201
210
  let autolinking = false;
@@ -395,62 +404,57 @@
395
404
  ])
396
405
  )
397
406
  },
398
- blocks: {
407
+ blocks: named({
399
408
  paragraph: {
400
409
  snippet: paragraph,
401
- presets: presets('paragraph', [
402
- { label: kinds.paragraph, icon: 'T', keywords: ['paragraph', 'plain'] }
403
- ])
410
+ presets: [{ label: kinds.paragraph, icon: 'T', keywords: ['paragraph', 'plain'] }]
404
411
  },
405
412
  heading: {
406
413
  ...headingRead,
407
414
  ...headingWrite,
408
415
  snippet: textThenChildren,
409
- presets: presets(
410
- 'heading',
411
- HEADING_LEVELS.map((level, index) => ({
412
- label: [kinds.heading1, kinds.heading2, kinds.heading3][index]!,
413
- icon: `H${['₁', '₂', '₃'][index]}`,
414
- keywords: [level, ...[['title'], ['subtitle'], []][index]!],
415
- data: { level },
416
- markdown: [`${'#'.repeat(index + 1)} `]
417
- }))
418
- )
416
+ presets: HEADING_LEVELS.map((level, index) => ({
417
+ label: [kinds.heading1, kinds.heading2, kinds.heading3][index]!,
418
+ icon: `H${['₁', '₂', '₃'][index]}`,
419
+ keywords: [level, ...[['title'], ['subtitle'], []][index]!],
420
+ data: { level },
421
+ markdown: [`${'#'.repeat(index + 1)} `]
422
+ }))
419
423
  },
420
424
  'bulleted-list-item': {
421
425
  ...itemRead,
422
426
  ...itemWrite,
423
427
  continues: true,
424
428
  snippet: listItem,
425
- presets: presets('bulleted-list-item', [
429
+ presets: [
426
430
  {
427
431
  label: kinds.bulletedList,
428
432
  icon: '•',
429
433
  keywords: ['bullet', 'ul'],
430
434
  markdown: ['- ', '* ', '+ ']
431
435
  }
432
- ])
436
+ ]
433
437
  },
434
438
  'numbered-list-item': {
435
439
  ...numberedRead,
436
440
  ...itemWrite,
437
441
  continues: true,
438
442
  snippet: listItem,
439
- presets: presets('numbered-list-item', [
443
+ presets: [
440
444
  {
441
445
  label: kinds.numberedList,
442
446
  icon: '1.',
443
447
  keywords: ['number', 'ol'],
444
448
  markdown: ['1. ', 'a. ', 'i. ']
445
449
  }
446
- ])
450
+ ]
447
451
  },
448
452
  'todo-item': {
449
453
  ...todoWrite,
450
454
  continues: true,
451
455
  // The app's control markup, if any, through its own snippet: the default costs no branch.
452
456
  snippet: checkbox ? todoItemOwn : todoItem,
453
- presets: presets('todo-item', [
457
+ presets: [
454
458
  {
455
459
  label: kinds.todoList,
456
460
  icon: '☐',
@@ -458,7 +462,7 @@
458
462
  data: { checked: false },
459
463
  markdown: ['[ ] ', '[] ']
460
464
  }
461
- ])
465
+ ]
462
466
  },
463
467
  toggle: {
464
468
  ...toggleRead,
@@ -466,14 +470,14 @@
466
470
  container: true,
467
471
  snippet: emptyBody ? detailsOwn : details,
468
472
  ...disclosure,
469
- presets: presets('toggle', [
473
+ presets: [
470
474
  {
471
475
  label: kinds.toggleList,
472
476
  icon: '▸',
473
477
  keywords: ['details', 'expand'],
474
478
  markdown: ['> ']
475
479
  }
476
- ])
480
+ ]
477
481
  },
478
482
  // Notion's toggle headings: a heading's text as a toggle's header over its
479
483
  // children. From a heading, `>` + space at its start makes it one (its level
@@ -485,23 +489,20 @@
485
489
  container: true,
486
490
  snippet: emptyBody ? detailsOwn : details,
487
491
  ...disclosure,
488
- presets: presets(
489
- 'toggle-heading',
490
- HEADING_LEVELS.map((level, index) => {
491
- const hashes = '#'.repeat(index + 1);
492
- return {
493
- label: [kinds.toggleHeading1, kinds.toggleHeading2, kinds.toggleHeading3][index]!,
494
- icon: `▸H${['₁', '₂', '₃'][index]}`,
495
- keywords: ['toggle', 'heading', 'collapse'],
496
- data: { level },
497
- markdownFrom: {
498
- heading: ['> '],
499
- toggle: [`${hashes} `],
500
- 'toggle-heading': [`${hashes} `]
501
- }
502
- };
503
- })
504
- )
492
+ presets: HEADING_LEVELS.map((level, index) => {
493
+ const hashes = '#'.repeat(index + 1);
494
+ return {
495
+ label: [kinds.toggleHeading1, kinds.toggleHeading2, kinds.toggleHeading3][index]!,
496
+ icon: `▸H${['₁', '₂', '₃'][index]}`,
497
+ keywords: ['toggle', 'heading', 'collapse'],
498
+ data: { level },
499
+ markdownFrom: {
500
+ heading: ['> '],
501
+ toggle: [`${hashes} `],
502
+ 'toggle-heading': [`${hashes} `]
503
+ }
504
+ };
505
+ })
505
506
  },
506
507
  // Notion's callout: an icon (`data.icon`, the picker's), a title (its own
507
508
  // text) and a content (its children, any blocks), which shows even empty
@@ -511,9 +512,7 @@
511
512
  container: true,
512
513
  body: true,
513
514
  snippet: calloutIcon || emptyBody ? calloutOwn : callout,
514
- presets: presets('callout', [
515
- { label: kinds.callout, icon: '✦', keywords: ['note', 'tip'], data: { icon } }
516
- ]),
515
+ presets: [{ label: kinds.callout, icon: '✦', keywords: ['note', 'tip'], data: { icon } }],
517
516
  // A callout without its own icon exports the view's.
518
517
  ...calloutWrite((data) => iconOf(data, edytor)),
519
518
  // The block menu's Change icon, while the view lists a picker: the keyboard's way to it.
@@ -537,15 +536,15 @@
537
536
  container: true,
538
537
  snippet: textThenChildren,
539
538
  // Notion: `"` + space is a quote; `>` + space is a toggle.
540
- presets: presets('quote', [{ label: kinds.quote, icon: '❝', markdown: ['" '] }])
539
+ presets: [{ label: kinds.quote, icon: '❝', markdown: ['" '] }]
541
540
  },
542
541
  divider: {
543
542
  ...richTextKinds.divider,
544
543
  ...dividerRead,
545
544
  ...dividerWrite,
546
- presets: presets('divider', [
545
+ presets: [
547
546
  { label: kinds.divider, icon: '—', keywords: ['hr', 'separator'], markdown: ['---'] }
548
- ]),
547
+ ],
549
548
  empty: { content: [], children: [] }
550
549
  },
551
550
  'ordered-list': {
@@ -561,7 +560,7 @@
561
560
  snippet: list
562
561
  },
563
562
  'list-item': { ...itemRead, ...itemWrite, snippet: listItem }
564
- }
563
+ })
565
564
  };
566
565
  };
567
566
  </script>
@@ -575,10 +574,13 @@
575
574
  nested snippet or a text node between tags is one more node per block for
576
575
  the browser to walk (`render.markers`).
577
576
  -->
577
+ <!-- prettier-ignore -->
578
+ {#snippet nested(children: Snippet)}<div data-edytor-children>{@render children()}</div>{/snippet}
579
+
578
580
  <!-- prettier-ignore -->
579
581
  {#snippet paragraph({ content, children }: BlockSnippetPayload)}<!--
580
582
  --><p>{@render content()}</p><!--
581
- -->{#if children}<div data-edytor-children>{@render children()}</div>{/if}<!--
583
+ -->{#if children}{@render nested(children)}{/if}<!--
582
584
  -->{/snippet}
583
585
 
584
586
  <!--
@@ -588,7 +590,7 @@
588
590
  <!-- prettier-ignore -->
589
591
  {#snippet details({ block, content, children }: BlockSnippetPayload)}<!--
590
592
  --><summary>{@render content()}</summary><!--
591
- -->{#if children}<div data-edytor-children>{@render children()}</div><!--
593
+ -->{#if children}{@render nested(children)}<!--
592
594
  -->{:else}<EmptyBody {block} kind="toggle" />{/if}<!--
593
595
  -->{/snippet}
594
596
 
@@ -600,7 +602,7 @@
600
602
  <!-- prettier-ignore -->
601
603
  {#snippet textThenChildren({ content, children }: BlockSnippetPayload)}<!--
602
604
  -->{@render content()}<!--
603
- -->{#if children}<div data-edytor-children>{@render children()}</div>{/if}<!--
605
+ -->{#if children}{@render nested(children)}{/if}<!--
604
606
  -->{/snippet}
605
607
 
606
608
  <!--
@@ -611,7 +613,7 @@
611
613
  {#snippet callout({ block, content, children }: BlockSnippetPayload<{ icon?: string }>)}<!--
612
614
  --><CalloutIcon {block} /><!--
613
615
  --><div data-edytor-callout-title>{@render content()}</div><!--
614
- -->{#if children}<div data-edytor-children>{@render children()}</div><!--
616
+ -->{#if children}{@render nested(children)}<!--
615
617
  -->{:else}<EmptyBody {block} kind="callout" />{/if}<!--
616
618
  -->{/snippet}
617
619
 
@@ -619,7 +621,7 @@
619
621
  {#snippet todoItem({ block, content, children }: BlockSnippetPayload<{ checked?: boolean }>)}<!--
620
622
  --><TodoCheckbox {block} /><!--
621
623
  --><div>{@render content()}</div><!--
622
- -->{#if children}<div data-edytor-children>{@render children()}</div>{/if}<!--
624
+ -->{#if children}{@render nested(children)}{/if}<!--
623
625
  -->{/snippet}
624
626
 
625
627
  <!--
@@ -630,13 +632,13 @@
630
632
  {#snippet todoItemOwn({ block, content, children }: BlockSnippetPayload<{ checked?: boolean }>)}<!--
631
633
  --><RichTextControl {block} control="checkbox" /><!--
632
634
  --><div>{@render content()}</div><!--
633
- -->{#if children}<div data-edytor-children>{@render children()}</div>{/if}<!--
635
+ -->{#if children}{@render nested(children)}{/if}<!--
634
636
  -->{/snippet}
635
637
 
636
638
  <!-- prettier-ignore -->
637
639
  {#snippet detailsOwn({ block, content, children }: BlockSnippetPayload)}<!--
638
640
  --><summary>{@render content()}</summary><!--
639
- -->{#if children}<div data-edytor-children>{@render children()}</div><!--
641
+ -->{#if children}{@render nested(children)}<!--
640
642
  -->{:else}<RichTextControl {block} control="toggle" />{/if}<!--
641
643
  -->{/snippet}
642
644
 
@@ -644,14 +646,14 @@
644
646
  {#snippet calloutOwn({ block, content, children }: BlockSnippetPayload<{ icon?: string }>)}<!--
645
647
  --><RichTextControl {block} control="calloutIcon" /><!--
646
648
  --><div data-edytor-callout-title>{@render content()}</div><!--
647
- -->{#if children}<div data-edytor-children>{@render children()}</div><!--
649
+ -->{#if children}{@render nested(children)}<!--
648
650
  -->{:else}<RichTextControl {block} control="callout" />{/if}<!--
649
651
  -->{/snippet}
650
652
 
651
653
  <!-- prettier-ignore -->
652
654
  {#snippet listItem({ content, children }: BlockSnippetPayload)}<!--
653
655
  --><div>{@render content()}</div><!--
654
- -->{#if children}<div data-edytor-children>{@render children()}</div>{/if}<!--
656
+ -->{#if children}{@render nested(children)}{/if}<!--
655
657
  -->{/snippet}
656
658
 
657
659
  <!--
@@ -74,9 +74,11 @@ export const openLink = (href, view = globalThis.window ?? null) => {
74
74
  if (safe)
75
75
  view?.open(safe, '_blank', 'noopener,noreferrer');
76
76
  };
77
+ /** A mark's value as written: `true` when none is given. */
78
+ const markValue = (value) => value === undefined ? true : value;
77
79
  /** Set `mark` to `value` (`null`: remove it) over `start`–`end` of `text`: the view's `formatRange`. */
78
80
  const markText = (edytor, text, start, end, mark, value) => edytor.formatRange(text.parent.id, text.segStart + start, end - start, {
79
- [mark]: value === undefined ? true : value
81
+ [mark]: markValue(value)
80
82
  });
81
83
  const formatSelectedTextRange = (edytor, mark, value, toggle) => {
82
84
  const { yStart, yEnd, startText, endText } = edytor.selection.textRange();
@@ -87,7 +89,7 @@ const formatSelectedTextRange = (edytor, mark, value, toggle) => {
87
89
  // One decision for the whole range (Notion): a toggle removes the mark only
88
90
  // when every selected character has it, else it marks all of them.
89
91
  const on = spans.every(({ text, start, end }) => text.getMarksAtRange(start, end).every(({ marks }) => marks && mark in marks));
90
- const next = toggle && on ? null : value === undefined ? true : value;
92
+ const next = toggle && on ? null : markValue(value);
91
93
  // One command per span: a vetoed one keeps its marks (`dispatcher.each`).
92
94
  edytor.dispatcher.each('format', spans, ({ text, start, end }) => edytor.formatRange(text.parent.id, text.segStart + start, end - start, { [mark]: next }));
93
95
  // Selected blocks stay selected (Notion; a divider's phantom text is no range).
@@ -99,6 +101,23 @@ const formatSelectedTextRange = (edytor, mark, value, toggle) => {
99
101
  * mark at (a lone selected divider's text is a phantom).
100
102
  */
101
103
  const selectsBlocks = (edytor) => edytor.selection.value.kind === 'blocks';
104
+ /**
105
+ * At a caret (no block selection: a lone divider's text is a phantom),
106
+ * stage what `next` makes of the full set the next insertion carries
107
+ * (values kept), as a `format` command, and keep the caret; `false` over a
108
+ * range.
109
+ */
110
+ const stageAtCaret = (edytor, next) => {
111
+ const { startText, yStart } = edytor.selection.textRange();
112
+ const { selection } = edytor;
113
+ if (!selection.projection.isCollapsed)
114
+ return false;
115
+ if (startText && !selectsBlocks(edytor)) {
116
+ edytor.dispatcher.run('format', () => selection.stage(next(marksForInsertion(startText, yStart, { pending: selection.pending }))));
117
+ selection.selectText(startText, yStart);
118
+ }
119
+ return true;
120
+ };
102
121
  export const richTextOperations = (edytor) => ({
103
122
  /**
104
123
  * Insert a `divider` void block at the caret — the native
@@ -210,20 +229,8 @@ export const richTextOperations = (edytor) => ({
210
229
  const safeValue = mark === 'color' || mark === 'highlight' ? sanitizeCssColorValue(value) : value;
211
230
  if (safeValue === null)
212
231
  return;
213
- const { startText, yStart } = edytor.selection.textRange();
214
- const { isCollapsed } = edytor.selection.projection;
215
- if (isCollapsed) {
216
- if (startText && !selectsBlocks(edytor)) {
217
- // Stage the full set the next insertion carries, values kept.
218
- edytor.dispatcher.run('format', () => edytor.selection.stage({
219
- ...marksForInsertion(startText, yStart, { pending: edytor.selection.pending }),
220
- [mark]: safeValue
221
- }));
222
- edytor.selection.selectText(startText, yStart);
223
- }
224
- return;
225
- }
226
- formatSelectedTextRange(edytor, mark, safeValue, false);
232
+ if (!stageAtCaret(edytor, (marks) => ({ ...marks, [mark]: safeValue })))
233
+ formatSelectedTextRange(edytor, mark, safeValue, false);
227
234
  },
228
235
  /**
229
236
  * Toggle `mark` (any mark the editor defines: the rich text ones are
@@ -231,21 +238,8 @@ export const richTextOperations = (edytor) => ({
231
238
  * stage it for the next character typed.
232
239
  */
233
240
  setMarkAtRange: (mark, value) => {
234
- const { yStart, yEnd, startText, endText } = edytor.selection.textRange();
235
- const { selection } = edytor;
236
- if (selection.projection.isCollapsed) {
237
- if (startText && !selectsBlocks(edytor)) {
238
- // A caret stages the full set the next insertion carries (values kept).
239
- edytor.dispatcher.run('format', () => {
240
- const { [mark]: current = null, ...rest } = marksForInsertion(startText, yStart, {
241
- pending: selection.pending
242
- });
243
- selection.stage(current !== null ? rest : { ...rest, [mark]: value === undefined ? true : value });
244
- });
245
- selection.selectText(startText, yStart, endText, yEnd);
246
- }
247
- return;
248
- }
249
- formatSelectedTextRange(edytor, mark, value, true);
241
+ const toggled = ({ [mark]: current = null, ...rest }) => current !== null ? rest : { ...rest, [mark]: markValue(value) };
242
+ if (!stageAtCaret(edytor, toggled))
243
+ formatSelectedTextRange(edytor, mark, value, true);
250
244
  }
251
245
  });
@@ -34,7 +34,7 @@ export type Caret = {
34
34
  * order), comparing boundary points — a focus on an ancestor element of the
35
35
  * anchor (`(textElement, childCount)`) is after it.
36
36
  */
37
- export declare const isBackward: (selection: {
37
+ export declare const isBackward: ({ anchorNode, anchorOffset, focusNode, focusOffset }: {
38
38
  anchorNode: Node | null;
39
39
  anchorOffset: number;
40
40
  focusNode: Node | null;