@blankdotpage/cake 0.1.2 → 0.1.3

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/dist/index.cjs CHANGED
@@ -999,15 +999,36 @@ class CursorSourceBuilder {
999
999
  if (!text) {
1000
1000
  return;
1001
1001
  }
1002
- for (const segment of graphemeSegments(text)) {
1003
- this.sourceParts.push(segment.segment);
1004
- this.sourceLengthValue += segment.segment.length;
1005
- const sourceLength = this.sourceLengthValue;
1006
- this.cursorLength += 1;
1007
- this.boundaries.push({
1008
- sourceBackward: sourceLength,
1009
- sourceForward: sourceLength
1010
- });
1002
+ let isAllAscii = true;
1003
+ for (let i = 0; i < text.length; i++) {
1004
+ if (text.charCodeAt(i) >= 128) {
1005
+ isAllAscii = false;
1006
+ break;
1007
+ }
1008
+ }
1009
+ if (isAllAscii) {
1010
+ for (let i = 0; i < text.length; i++) {
1011
+ const char = text[i];
1012
+ this.sourceParts.push(char);
1013
+ this.sourceLengthValue += 1;
1014
+ const sourceLength = this.sourceLengthValue;
1015
+ this.cursorLength += 1;
1016
+ this.boundaries.push({
1017
+ sourceBackward: sourceLength,
1018
+ sourceForward: sourceLength
1019
+ });
1020
+ }
1021
+ } else {
1022
+ for (const segment of graphemeSegments(text)) {
1023
+ this.sourceParts.push(segment.segment);
1024
+ this.sourceLengthValue += segment.segment.length;
1025
+ const sourceLength = this.sourceLengthValue;
1026
+ this.cursorLength += 1;
1027
+ this.boundaries.push({
1028
+ sourceBackward: sourceLength,
1029
+ sourceForward: sourceLength
1030
+ });
1031
+ }
1011
1032
  }
1012
1033
  }
1013
1034
  appendCursorAtom(sourceText, cursorUnits = 1) {
@@ -1057,6 +1078,15 @@ class CursorSourceBuilder {
1057
1078
  };
1058
1079
  }
1059
1080
  }
1081
+ function isStructuralEdit(command) {
1082
+ return command.type === "insert" || command.type === "delete-backward" || command.type === "delete-forward" || command.type === "insert-line-break" || command.type === "exit-block-wrapper";
1083
+ }
1084
+ function isApplyEditCommand(command) {
1085
+ return command.type === "insert" || command.type === "insert-line-break" || command.type === "delete-backward" || command.type === "delete-forward";
1086
+ }
1087
+ function defineExtension(extension) {
1088
+ return extension;
1089
+ }
1060
1090
  const defaultSelection$1 = { start: 0, end: 0, affinity: "forward" };
1061
1091
  function createRuntime(extensions) {
1062
1092
  const toggleMarkerToKind = /* @__PURE__ */ new Map();
@@ -1298,7 +1328,7 @@ function createRuntime(extensions) {
1298
1328
  }
1299
1329
  }
1300
1330
  const selection = normalizeSelection$1(state2.selection);
1301
- if (command.type === "insert" || command.type === "delete-backward" || command.type === "delete-forward" || command.type === "insert-line-break" || command.type === "exit-block-wrapper") {
1331
+ if (isStructuralEdit(command)) {
1302
1332
  const structural = applyStructuralEdit(command, state2.doc, selection);
1303
1333
  if (!structural) {
1304
1334
  if (command.type === "delete-backward" || command.type === "delete-forward") {
@@ -1340,6 +1370,39 @@ function createRuntime(extensions) {
1340
1370
  }
1341
1371
  };
1342
1372
  }
1373
+ if (command.type === "insert" || command.type === "insert-line-break") {
1374
+ const cursorLength = state2.map.cursorLength;
1375
+ const cursorStart = Math.max(
1376
+ 0,
1377
+ Math.min(cursorLength, Math.min(selection.start, selection.end))
1378
+ );
1379
+ const cursorEnd = Math.max(
1380
+ 0,
1381
+ Math.min(cursorLength, Math.max(selection.start, selection.end))
1382
+ );
1383
+ const range = { start: cursorStart, end: cursorEnd };
1384
+ const fullDocReplace = range.start === 0 && range.end === cursorLength;
1385
+ const from = fullDocReplace ? 0 : state2.map.cursorToSource(range.start, "backward");
1386
+ const to = fullDocReplace ? state2.source.length : state2.map.cursorToSource(range.end, "forward");
1387
+ const fromClamped = Math.max(0, Math.min(from, state2.source.length));
1388
+ const toClamped = Math.max(
1389
+ fromClamped,
1390
+ Math.min(to, state2.source.length)
1391
+ );
1392
+ const insertText = command.type === "insert" ? command.text : "\n";
1393
+ const nextSource = state2.source.slice(0, fromClamped) + insertText + state2.source.slice(toClamped);
1394
+ const next2 = createState(nextSource);
1395
+ const caretSource2 = fromClamped + insertText.length;
1396
+ const caretCursor2 = next2.map.sourceToCursor(caretSource2, "forward");
1397
+ return {
1398
+ ...next2,
1399
+ selection: {
1400
+ start: caretCursor2.cursorOffset,
1401
+ end: caretCursor2.cursorOffset,
1402
+ affinity: caretCursor2.affinity
1403
+ }
1404
+ };
1405
+ }
1343
1406
  return state2;
1344
1407
  }
1345
1408
  const interim = createStateFromDoc(structural.doc);
@@ -2582,7 +2645,20 @@ function parseLiteralBlock(source, start, context) {
2582
2645
  return { block: { type: "paragraph", content }, nextPos: end };
2583
2646
  }
2584
2647
  function parseLiteralInline(source, start, end) {
2585
- const segment = graphemeSegments(source.slice(start, end))[0];
2648
+ const code = source.charCodeAt(start);
2649
+ if (code < 128) {
2650
+ const text2 = source[start] ?? "";
2651
+ return { inline: { type: "text", text: text2 }, nextPos: start + 1 };
2652
+ }
2653
+ if (code >= 55296 && code <= 56319) {
2654
+ const lowCode = source.charCodeAt(start + 1);
2655
+ if (lowCode >= 56320 && lowCode <= 57343) {
2656
+ const segment2 = graphemeSegments(source.slice(start, Math.min(start + 10, end)))[0];
2657
+ const text2 = segment2 ? segment2.segment : source.slice(start, start + 2);
2658
+ return { inline: { type: "text", text: text2 }, nextPos: start + text2.length };
2659
+ }
2660
+ }
2661
+ const segment = graphemeSegments(source.slice(start, Math.min(start + 10, end)))[0];
2586
2662
  const text = segment ? segment.segment : source[start] ?? "";
2587
2663
  return { inline: { type: "text", text }, nextPos: start + text.length };
2588
2664
  }
@@ -2625,29 +2701,66 @@ function normalizeSelection$1(selection) {
2625
2701
  affinity: isRange ? "backward" : selection.affinity
2626
2702
  };
2627
2703
  }
2704
+ const graphemeCache = /* @__PURE__ */ new WeakMap();
2628
2705
  function createTextRun(node, cursorStart) {
2629
- const segments = graphemeSegments(node.data);
2706
+ const data = node.data;
2707
+ const cached = graphemeCache.get(node);
2708
+ if (cached && cached.data === data) {
2709
+ const segmentCount = Math.max(0, cached.boundaryOffsets.length - 1);
2710
+ return {
2711
+ node,
2712
+ cursorStart,
2713
+ cursorEnd: cursorStart + segmentCount,
2714
+ boundaryOffsets: cached.boundaryOffsets
2715
+ };
2716
+ }
2717
+ const segments = graphemeSegments(data);
2630
2718
  const boundaryOffsets = [0];
2631
2719
  for (const segment of segments) {
2632
2720
  boundaryOffsets.push(segment.index + segment.segment.length);
2633
2721
  }
2634
2722
  const cursorEnd = cursorStart + segments.length;
2723
+ graphemeCache.set(node, { data, boundaryOffsets });
2635
2724
  return { node, cursorStart, cursorEnd, boundaryOffsets };
2636
2725
  }
2637
2726
  function boundaryIndexForOffset(boundaryOffsets, offset) {
2638
2727
  if (offset <= 0) {
2639
2728
  return 0;
2640
2729
  }
2641
- for (let i = 1; i < boundaryOffsets.length; i += 1) {
2642
- const boundary = boundaryOffsets[i];
2643
- if (offset < boundary) {
2644
- return i - 1;
2730
+ const lastIndex = boundaryOffsets.length - 1;
2731
+ if (lastIndex <= 0) {
2732
+ return 0;
2733
+ }
2734
+ const lastBoundary = boundaryOffsets[lastIndex];
2735
+ if (offset >= lastBoundary) {
2736
+ return lastIndex;
2737
+ }
2738
+ let low = 1;
2739
+ let high = lastIndex;
2740
+ while (low < high) {
2741
+ const mid = low + high >>> 1;
2742
+ const boundary2 = boundaryOffsets[mid];
2743
+ if (boundary2 < offset) {
2744
+ low = mid + 1;
2745
+ } else {
2746
+ high = mid;
2645
2747
  }
2646
- if (offset === boundary) {
2647
- return i;
2748
+ }
2749
+ const boundary = boundaryOffsets[low];
2750
+ return boundary === offset ? low : low - 1;
2751
+ }
2752
+ function firstRunStartingAfterCursor(runs, cursorOffset) {
2753
+ let low = 0;
2754
+ let high = runs.length;
2755
+ while (low < high) {
2756
+ const mid = low + high >>> 1;
2757
+ if (runs[mid].cursorStart <= cursorOffset) {
2758
+ low = mid + 1;
2759
+ } else {
2760
+ high = mid;
2648
2761
  }
2649
2762
  }
2650
- return boundaryOffsets.length - 1;
2763
+ return low;
2651
2764
  }
2652
2765
  function createDomMap(runs) {
2653
2766
  const runForNode = /* @__PURE__ */ new Map();
@@ -2662,33 +2775,34 @@ function createDomMap(runs) {
2662
2775
  if (runs.length === 0) {
2663
2776
  return null;
2664
2777
  }
2665
- for (let i = 0; i < runs.length; i += 1) {
2666
- const run = runs[i];
2667
- const previous = i > 0 ? runs[i - 1] : null;
2668
- const next = i + 1 < runs.length ? runs[i + 1] : null;
2669
- if (cursorOffset < run.cursorStart) {
2670
- if (!previous) {
2671
- return { node: run.node, offset: run.boundaryOffsets[0] };
2672
- }
2673
- const previousOffset = previous.boundaryOffsets[previous.boundaryOffsets.length - 1];
2674
- return affinity === "backward" ? { node: previous.node, offset: previousOffset } : { node: run.node, offset: run.boundaryOffsets[0] };
2675
- }
2676
- if (cursorOffset === run.cursorStart && previous && affinity === "backward") {
2677
- return {
2678
- node: previous.node,
2679
- offset: previous.boundaryOffsets[previous.boundaryOffsets.length - 1]
2680
- };
2681
- }
2682
- if (cursorOffset <= run.cursorEnd) {
2683
- if (cursorOffset === run.cursorEnd && affinity === "forward" && next && next.cursorStart === cursorOffset) {
2684
- return { node: next.node, offset: next.boundaryOffsets[0] };
2685
- }
2686
- const index = Math.max(0, cursorOffset - run.cursorStart);
2687
- const boundedIndex = Math.min(index, run.boundaryOffsets.length - 1);
2688
- return { node: run.node, offset: run.boundaryOffsets[boundedIndex] };
2778
+ const nextIndex = firstRunStartingAfterCursor(runs, cursorOffset);
2779
+ const runIndex = nextIndex - 1;
2780
+ if (runIndex < 0) {
2781
+ const first = runs[0];
2782
+ return { node: first.node, offset: first.boundaryOffsets[0] };
2783
+ }
2784
+ const run = runs[runIndex];
2785
+ const previous = runIndex > 0 ? runs[runIndex - 1] : null;
2786
+ const next = nextIndex < runs.length ? runs[nextIndex] : null;
2787
+ if (cursorOffset === run.cursorStart && previous && affinity === "backward") {
2788
+ return {
2789
+ node: previous.node,
2790
+ offset: previous.boundaryOffsets[previous.boundaryOffsets.length - 1]
2791
+ };
2792
+ }
2793
+ if (cursorOffset <= run.cursorEnd) {
2794
+ if (cursorOffset === run.cursorEnd && affinity === "forward" && next && next.cursorStart === cursorOffset) {
2795
+ return { node: next.node, offset: next.boundaryOffsets[0] };
2689
2796
  }
2797
+ const index = Math.max(0, cursorOffset - run.cursorStart);
2798
+ const boundedIndex = Math.min(index, run.boundaryOffsets.length - 1);
2799
+ return { node: run.node, offset: run.boundaryOffsets[boundedIndex] };
2690
2800
  }
2691
- const last = runs[runs.length - 1];
2801
+ if (next) {
2802
+ const runEndOffset = run.boundaryOffsets[run.boundaryOffsets.length - 1];
2803
+ return affinity === "backward" ? { node: run.node, offset: runEndOffset } : { node: next.node, offset: next.boundaryOffsets[0] };
2804
+ }
2805
+ const last = run;
2692
2806
  return {
2693
2807
  node: last.node,
2694
2808
  offset: last.boundaryOffsets[last.boundaryOffsets.length - 1]
@@ -3076,7 +3190,9 @@ function applyDomSelection(selection, map) {
3076
3190
  if (!anchorPoint || !focusPoint) {
3077
3191
  return;
3078
3192
  }
3079
- domSelection.removeAllRanges();
3193
+ if (domSelection.rangeCount > 0 && domSelection.anchorNode === anchorPoint.node && domSelection.anchorOffset === anchorPoint.offset && domSelection.focusNode === focusPoint.node && domSelection.focusOffset === focusPoint.offset) {
3194
+ return;
3195
+ }
3080
3196
  if (isCollapsed) {
3081
3197
  domSelection.collapse(anchorPoint.node, anchorPoint.offset);
3082
3198
  return;
@@ -3096,6 +3212,7 @@ function applyDomSelection(selection, map) {
3096
3212
  domSelection.extend(focusPoint.node, focusPoint.offset);
3097
3213
  return;
3098
3214
  }
3215
+ domSelection.removeAllRanges();
3099
3216
  const range = document.createRange();
3100
3217
  const rangeStart = isForward ? anchorPoint : focusPoint;
3101
3218
  const rangeEnd = isForward ? focusPoint : anchorPoint;
@@ -3239,7 +3356,7 @@ function findTextNodeAtOrBefore$1(nodes, start) {
3239
3356
  return null;
3240
3357
  }
3241
3358
  const BOLD_KIND$1 = "bold";
3242
- const boldExtension = {
3359
+ const boldExtension = defineExtension({
3243
3360
  name: "bold",
3244
3361
  toggleInline: { kind: BOLD_KIND$1, markers: ["**"] },
3245
3362
  keybindings: [
@@ -3322,7 +3439,7 @@ const boldExtension = {
3322
3439
  }
3323
3440
  return element;
3324
3441
  }
3325
- };
3442
+ });
3326
3443
  function countSingleAsterisks(source, start, end) {
3327
3444
  let count = 0;
3328
3445
  for (let i = start; i < end; i += 1) {
@@ -3341,7 +3458,7 @@ function countSingleAsterisks(source, start, end) {
3341
3458
  const BOLD_KIND = "bold";
3342
3459
  const ITALIC_KIND$1 = "italic";
3343
3460
  const MARKERS = ["***", "___"];
3344
- const combinedEmphasisExtension = {
3461
+ const combinedEmphasisExtension = defineExtension({
3345
3462
  name: "combined-emphasis",
3346
3463
  parseInline(source, start, end, context) {
3347
3464
  const marker = MARKERS.find((m) => source.slice(start, start + 3) === m);
@@ -3375,7 +3492,7 @@ const combinedEmphasisExtension = {
3375
3492
  nextPos: close + 3
3376
3493
  };
3377
3494
  }
3378
- };
3495
+ });
3379
3496
  function ensureHttpsProtocol(url) {
3380
3497
  const trimmedUrl = url.trim();
3381
3498
  if (!trimmedUrl) {
@@ -3423,7 +3540,7 @@ function getPopoverPosition(params) {
3423
3540
  };
3424
3541
  }
3425
3542
  function CakeLinkPopover(params) {
3426
- const { container, contentRoot, toOverlayRect } = params;
3543
+ const { container, contentRoot, toOverlayRect, getSelection, executeCommand } = params;
3427
3544
  const anchorRef = require$$0.useRef(null);
3428
3545
  const popoverRef = require$$0.useRef(null);
3429
3546
  const inputRef = require$$0.useRef(null);
@@ -3587,6 +3704,12 @@ function CakeLinkPopover(params) {
3587
3704
  window.open(displayUrl, "_blank", "noopener,noreferrer");
3588
3705
  };
3589
3706
  const handleUnlink = () => {
3707
+ const selection = getSelection();
3708
+ if (!selection) {
3709
+ close();
3710
+ return;
3711
+ }
3712
+ executeCommand({ type: "unlink", start: selection.start, end: selection.end });
3590
3713
  close();
3591
3714
  };
3592
3715
  const handleInputKeyDown = (event) => {
@@ -3890,7 +4013,7 @@ function cursorOffsetToVisibleOffset(lines, cursorOffset) {
3890
4013
  return codeUnitIndex + (lastLine.cursorToCodeUnit[lastOffset] ?? lastLine.text.length);
3891
4014
  }
3892
4015
  const LINK_KIND = "link";
3893
- const linkExtension = {
4016
+ const linkExtension = defineExtension({
3894
4017
  name: "link",
3895
4018
  inlineWrapperAffinity: [{ kind: LINK_KIND, inclusive: false }],
3896
4019
  keybindings: [
@@ -3918,6 +4041,39 @@ const linkExtension = {
3918
4041
  }
3919
4042
  ],
3920
4043
  onEdit(command, state2) {
4044
+ if (command.type === "unlink") {
4045
+ const cursorPos = command.start;
4046
+ const sourcePos = state2.map.cursorToSource(cursorPos, "forward");
4047
+ const source = state2.source;
4048
+ let linkStart = sourcePos;
4049
+ while (linkStart > 0 && source[linkStart] !== "[") {
4050
+ linkStart--;
4051
+ }
4052
+ if (source[linkStart] !== "[") {
4053
+ return null;
4054
+ }
4055
+ const labelClose = source.indexOf("](", linkStart + 1);
4056
+ if (labelClose === -1) {
4057
+ return null;
4058
+ }
4059
+ const urlClose = source.indexOf(")", labelClose + 2);
4060
+ if (urlClose === -1) {
4061
+ return null;
4062
+ }
4063
+ const label2 = source.slice(linkStart + 1, labelClose);
4064
+ const nextSource2 = source.slice(0, linkStart) + label2 + source.slice(urlClose + 1);
4065
+ const newState = state2.runtime.createState(nextSource2);
4066
+ const labelEndSource = linkStart + label2.length;
4067
+ const newCursor = newState.map.sourceToCursor(labelEndSource, "forward");
4068
+ return {
4069
+ source: nextSource2,
4070
+ selection: {
4071
+ start: newCursor.cursorOffset,
4072
+ end: newCursor.cursorOffset,
4073
+ affinity: "forward"
4074
+ }
4075
+ };
4076
+ }
3921
4077
  if (command.type !== "wrap-link") {
3922
4078
  return null;
3923
4079
  }
@@ -4044,13 +4200,15 @@ const linkExtension = {
4044
4200
  {
4045
4201
  container: context.container,
4046
4202
  contentRoot: context.contentRoot,
4047
- toOverlayRect: context.toOverlayRect
4203
+ toOverlayRect: context.toOverlayRect,
4204
+ getSelection: context.getSelection,
4205
+ executeCommand: context.executeCommand
4048
4206
  }
4049
4207
  );
4050
4208
  }
4051
- };
4209
+ });
4052
4210
  const PIPE_LINK_KIND = "pipe-link";
4053
- const pipeLinkExtension = {
4211
+ const pipeLinkExtension = defineExtension({
4054
4212
  name: "pipe-link",
4055
4213
  parseInline(source, start, end) {
4056
4214
  if (source[start] !== "|") {
@@ -4121,10 +4279,10 @@ const pipeLinkExtension = {
4121
4279
  }
4122
4280
  return element;
4123
4281
  }
4124
- };
4282
+ });
4125
4283
  const BLOCKQUOTE_KIND = "blockquote";
4126
4284
  const PREFIX = "> ";
4127
- const blockquoteExtension = {
4285
+ const blockquoteExtension = defineExtension({
4128
4286
  name: "blockquote",
4129
4287
  parseBlock(source, start, context) {
4130
4288
  if (source.slice(start, start + PREFIX.length) !== PREFIX) {
@@ -4193,9 +4351,9 @@ const blockquoteExtension = {
4193
4351
  }
4194
4352
  return element;
4195
4353
  }
4196
- };
4354
+ });
4197
4355
  const ITALIC_KIND = "italic";
4198
- const italicExtension = {
4356
+ const italicExtension = defineExtension({
4199
4357
  name: "italic",
4200
4358
  toggleInline: { kind: ITALIC_KIND, markers: ["*", "_"] },
4201
4359
  keybindings: [
@@ -4265,7 +4423,7 @@ const italicExtension = {
4265
4423
  }
4266
4424
  return element;
4267
4425
  }
4268
- };
4426
+ });
4269
4427
  const HEADING_KIND = "heading";
4270
4428
  const HEADING_PATTERN = /^(#{1,3}) /;
4271
4429
  function findLineStartInSource(source, sourceOffset) {
@@ -4366,7 +4524,7 @@ function shouldExitHeadingOnLineBreak(state2) {
4366
4524
  const contentStart = lineStart + marker.length;
4367
4525
  return sourcePos >= contentStart && sourcePos <= lineEnd;
4368
4526
  }
4369
- const headingExtension = {
4527
+ const headingExtension = defineExtension({
4370
4528
  name: "heading",
4371
4529
  onEdit(command, state2) {
4372
4530
  if (command.type === "delete-backward") {
@@ -4507,7 +4665,7 @@ const headingExtension = {
4507
4665
  }
4508
4666
  return lineElement;
4509
4667
  }
4510
- };
4668
+ });
4511
4669
  const IMAGE_KIND = "image";
4512
4670
  const IMAGE_PATTERN = /^!\[([^\]]*)\]\(([^)]*)\)$/;
4513
4671
  const UPLOADING_PATTERN = /^!\[uploading:([^\]]+)\]\(\)$/;
@@ -4525,7 +4683,7 @@ function isImageData(data) {
4525
4683
  }
4526
4684
  return false;
4527
4685
  }
4528
- const imageExtension = {
4686
+ const imageExtension = defineExtension({
4529
4687
  name: "image",
4530
4688
  parseBlock(source, start) {
4531
4689
  let lineEnd = source.indexOf("\n", start);
@@ -4620,7 +4778,7 @@ const imageExtension = {
4620
4778
  }
4621
4779
  return element;
4622
4780
  }
4623
- };
4781
+ });
4624
4782
  const LIST_LINE_REGEX$2 = /^(\s*)([-*+]|\d+\.)( )(.*)$/;
4625
4783
  const INDENT_SIZE = 2;
4626
4784
  function parseMarkerType(marker) {
@@ -5439,7 +5597,7 @@ function getParagraphText(block) {
5439
5597
  }
5440
5598
  return text;
5441
5599
  }
5442
- const listExtension = {
5600
+ const listExtension = defineExtension({
5443
5601
  name: "list",
5444
5602
  keybindings: [
5445
5603
  {
@@ -5532,7 +5690,7 @@ const listExtension = {
5532
5690
  }
5533
5691
  return element;
5534
5692
  }
5535
- };
5693
+ });
5536
5694
  const THUMB_MIN_HEIGHT = 30;
5537
5695
  const SCROLL_HIDE_DELAY = 500;
5538
5696
  const TRACK_PADDING = 8;
@@ -5769,14 +5927,14 @@ function ScrollbarOverlay({ container }) {
5769
5927
  }
5770
5928
  ) });
5771
5929
  }
5772
- const scrollbarExtension = {
5930
+ const scrollbarExtension = defineExtension({
5773
5931
  name: "scrollbar",
5774
5932
  renderOverlay(context) {
5775
5933
  return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollbarOverlay, { container: context.container });
5776
5934
  }
5777
- };
5935
+ });
5778
5936
  const STRIKE_KIND = "strikethrough";
5779
- const strikethroughExtension = {
5937
+ const strikethroughExtension = defineExtension({
5780
5938
  name: "strikethrough",
5781
5939
  toggleInline: { kind: STRIKE_KIND, markers: ["~~"] },
5782
5940
  keybindings: [
@@ -5849,8 +6007,8 @@ const strikethroughExtension = {
5849
6007
  }
5850
6008
  return element;
5851
6009
  }
5852
- };
5853
- const bundledExtensions = [
6010
+ });
6011
+ const bundledExtensionsWithoutImage = [
5854
6012
  blockquoteExtension,
5855
6013
  headingExtension,
5856
6014
  listExtension,
@@ -5859,7 +6017,10 @@ const bundledExtensions = [
5859
6017
  italicExtension,
5860
6018
  strikethroughExtension,
5861
6019
  pipeLinkExtension,
5862
- linkExtension,
6020
+ linkExtension
6021
+ ];
6022
+ const bundledExtensions = [
6023
+ ...bundledExtensionsWithoutImage,
5863
6024
  imageExtension
5864
6025
  ];
5865
6026
  function toLayoutRect(params) {
@@ -5919,6 +6080,46 @@ function cursorOffsetToCodeUnit(cursorToCodeUnit, offset) {
5919
6080
  const clamped = Math.max(0, Math.min(offset, cursorToCodeUnit.length - 1));
5920
6081
  return cursorToCodeUnit[clamped] ?? 0;
5921
6082
  }
6083
+ function createDomPositionResolver(lineElement) {
6084
+ const textNodes = [];
6085
+ const cumulativeEnds = [];
6086
+ const walker = createTextWalker(lineElement);
6087
+ let current = walker.nextNode();
6088
+ let total = 0;
6089
+ while (current) {
6090
+ if (current instanceof Text) {
6091
+ const length = current.data.length;
6092
+ textNodes.push(current);
6093
+ total += length;
6094
+ cumulativeEnds.push(total);
6095
+ }
6096
+ current = walker.nextNode();
6097
+ }
6098
+ if (textNodes.length === 0) {
6099
+ return () => {
6100
+ if (!lineElement.textContent) {
6101
+ return { node: lineElement, offset: 0 };
6102
+ }
6103
+ return { node: lineElement, offset: lineElement.childNodes.length };
6104
+ };
6105
+ }
6106
+ return (offsetInLine) => {
6107
+ const clamped = Math.max(0, Math.min(offsetInLine, total));
6108
+ let low = 0;
6109
+ let high = cumulativeEnds.length - 1;
6110
+ while (low < high) {
6111
+ const mid = low + high >>> 1;
6112
+ if ((cumulativeEnds[mid] ?? 0) < clamped) {
6113
+ low = mid + 1;
6114
+ } else {
6115
+ high = mid;
6116
+ }
6117
+ }
6118
+ const node = textNodes[low] ?? lineElement;
6119
+ const prevEnd = low > 0 ? cumulativeEnds[low - 1] ?? 0 : 0;
6120
+ return { node, offset: clamped - prevEnd };
6121
+ };
6122
+ }
5922
6123
  function measureCharacterRect(params) {
5923
6124
  if (params.lineLength <= 0) {
5924
6125
  return null;
@@ -5931,101 +6132,155 @@ function measureCharacterRect(params) {
5931
6132
  params.cursorToCodeUnit,
5932
6133
  Math.min(params.offset + 1, params.lineLength)
5933
6134
  );
5934
- const startPosition = resolveDomPosition(params.lineElement, startCodeUnit);
5935
- const endPosition = resolveDomPosition(params.lineElement, endCodeUnit);
5936
- const range = document.createRange();
5937
- range.setStart(startPosition.node, startPosition.offset);
5938
- range.setEnd(endPosition.node, endPosition.offset);
5939
- const rects = range.getClientRects();
6135
+ const startPosition = params.resolveDomPosition(startCodeUnit);
6136
+ const endPosition = params.resolveDomPosition(endCodeUnit);
6137
+ params.range.setStart(startPosition.node, startPosition.offset);
6138
+ params.range.setEnd(endPosition.node, endPosition.offset);
6139
+ const rects = params.range.getClientRects();
5940
6140
  if (rects.length > 0) {
5941
6141
  return rects[0] ?? null;
5942
6142
  }
5943
- const rect = range.getBoundingClientRect();
6143
+ const rect = params.range.getBoundingClientRect();
5944
6144
  if (rect.width === 0 && rect.height === 0) {
5945
6145
  return null;
5946
6146
  }
5947
6147
  return rect;
5948
6148
  }
5949
6149
  function measureLineRows(params) {
6150
+ var _a, _b;
6151
+ const fallbackLineBox = toLayoutRect({
6152
+ rect: params.lineRect,
6153
+ containerRect: params.containerRect,
6154
+ scroll: params.scroll
6155
+ });
5950
6156
  if (params.lineLength === 0) {
5951
- const lineBox = toLayoutRect({
5952
- rect: params.lineRect,
5953
- containerRect: params.containerRect,
5954
- scroll: params.scroll
5955
- });
5956
6157
  return [
5957
6158
  {
5958
6159
  startOffset: 0,
5959
6160
  endOffset: 0,
5960
- rect: { ...lineBox, width: 0 }
6161
+ rect: { ...fallbackLineBox, width: 0 }
5961
6162
  }
5962
6163
  ];
5963
6164
  }
5964
- const fullLineRange = document.createRange();
5965
- const fullLineStart = resolveDomPosition(params.lineElement, 0);
5966
- const fullLineEnd = resolveDomPosition(
5967
- params.lineElement,
5968
- params.codeUnitLength
5969
- );
5970
- fullLineRange.setStart(fullLineStart.node, fullLineStart.offset);
5971
- fullLineRange.setEnd(fullLineEnd.node, fullLineEnd.offset);
5972
- const fullLineRects = groupDomRectsByRow(
5973
- Array.from(fullLineRange.getClientRects())
5974
- );
5975
- const fallbackLineBox = toLayoutRect({
5976
- rect: params.lineRect,
5977
- containerRect: params.containerRect,
5978
- scroll: params.scroll
5979
- });
5980
- const charRect = measureCharacterRect({
5981
- lineElement: params.lineElement,
5982
- offset: 0,
5983
- lineLength: params.lineLength,
5984
- cursorToCodeUnit: params.cursorToCodeUnit
5985
- });
5986
- const charWidth = (charRect == null ? void 0 : charRect.width) ?? 0;
5987
- if (fullLineRects.length === 0 || charWidth <= 0) {
5988
- const rows2 = [
6165
+ const WRAP_THRESHOLD_PX = 3;
6166
+ const resolvePosition = createDomPositionResolver(params.lineElement);
6167
+ const scratchRange = document.createRange();
6168
+ const topCache = /* @__PURE__ */ new Map();
6169
+ const fullLineStart = resolvePosition(0);
6170
+ const fullLineEnd = resolvePosition(params.codeUnitLength);
6171
+ scratchRange.setStart(fullLineStart.node, fullLineStart.offset);
6172
+ scratchRange.setEnd(fullLineEnd.node, fullLineEnd.offset);
6173
+ const fullLineRects = groupDomRectsByRow(Array.from(scratchRange.getClientRects()));
6174
+ if (fullLineRects.length === 0) {
6175
+ return [
5989
6176
  {
5990
6177
  startOffset: 0,
5991
6178
  endOffset: params.lineLength,
5992
6179
  rect: fallbackLineBox
5993
6180
  }
5994
6181
  ];
5995
- return rows2.length === 1 ? [
6182
+ }
6183
+ function offsetToTop(offset) {
6184
+ if (topCache.has(offset)) {
6185
+ return topCache.get(offset) ?? null;
6186
+ }
6187
+ const rect = measureCharacterRect({
6188
+ lineElement: params.lineElement,
6189
+ offset,
6190
+ lineLength: params.lineLength,
6191
+ cursorToCodeUnit: params.cursorToCodeUnit,
6192
+ resolveDomPosition: resolvePosition,
6193
+ range: scratchRange
6194
+ });
6195
+ const top = rect ? rect.top : null;
6196
+ topCache.set(offset, top);
6197
+ return top;
6198
+ }
6199
+ function findFirstMeasurableOffset(from) {
6200
+ for (let offset = Math.max(0, from); offset < params.lineLength; offset++) {
6201
+ if (offsetToTop(offset) !== null) {
6202
+ return offset;
6203
+ }
6204
+ }
6205
+ return null;
6206
+ }
6207
+ function findNextRowStartOffset(fromExclusive, rowTop) {
6208
+ const lastIndex = params.lineLength - 1;
6209
+ if (fromExclusive > lastIndex) {
6210
+ return null;
6211
+ }
6212
+ const isNewRowAt = (offset) => {
6213
+ const top = offsetToTop(offset);
6214
+ return top !== null && Math.abs(top - rowTop) > WRAP_THRESHOLD_PX;
6215
+ };
6216
+ let step = 1;
6217
+ let lastSame = fromExclusive - 1;
6218
+ let probe = fromExclusive;
6219
+ while (probe <= lastIndex) {
6220
+ if (isNewRowAt(probe)) {
6221
+ break;
6222
+ }
6223
+ lastSame = probe;
6224
+ probe += step;
6225
+ step *= 2;
6226
+ }
6227
+ if (probe > lastIndex) {
6228
+ probe = lastIndex;
6229
+ if (!isNewRowAt(probe)) {
6230
+ return null;
6231
+ }
6232
+ }
6233
+ let low = Math.max(fromExclusive, lastSame + 1);
6234
+ let high = probe;
6235
+ while (low < high) {
6236
+ const mid = low + high >>> 1;
6237
+ if (isNewRowAt(mid)) {
6238
+ high = mid;
6239
+ } else {
6240
+ low = mid + 1;
6241
+ }
6242
+ }
6243
+ return low < params.lineLength ? low : null;
6244
+ }
6245
+ const rows = [];
6246
+ const firstMeasurable = findFirstMeasurableOffset(0);
6247
+ if (firstMeasurable === null) {
6248
+ return [
5996
6249
  {
5997
- ...rows2[0],
5998
- rect: {
5999
- ...rows2[0].rect,
6000
- top: fallbackLineBox.top,
6001
- height: fallbackLineBox.height
6002
- }
6250
+ startOffset: 0,
6251
+ endOffset: params.lineLength,
6252
+ rect: fallbackLineBox
6003
6253
  }
6004
- ] : rows2;
6254
+ ];
6005
6255
  }
6006
- let startOffset = 0;
6007
- const rows = fullLineRects.map((rect, index) => {
6008
- const remaining = params.lineLength - startOffset;
6009
- const isLast = index === fullLineRects.length - 1;
6010
- const estimatedLength = Math.max(1, Math.round(rect.width / charWidth));
6011
- const rowLength = isLast ? remaining : Math.min(remaining, estimatedLength);
6012
- const row = {
6013
- startOffset,
6014
- endOffset: startOffset + rowLength,
6256
+ let currentRowStart = 0;
6257
+ let currentRowTop = ((_a = fullLineRects[0]) == null ? void 0 : _a.top) ?? params.lineRect.top;
6258
+ let searchFrom = firstMeasurable + 1;
6259
+ let rowIndex = 0;
6260
+ while (currentRowStart < params.lineLength) {
6261
+ const nextRowStart = findNextRowStartOffset(searchFrom, currentRowTop);
6262
+ const currentRowEnd = nextRowStart ?? params.lineLength;
6263
+ const domRect = fullLineRects[rowIndex] ?? params.lineRect;
6264
+ rows.push({
6265
+ startOffset: currentRowStart,
6266
+ endOffset: currentRowEnd,
6015
6267
  rect: toLayoutRect({
6016
- rect,
6268
+ rect: domRect,
6017
6269
  containerRect: params.containerRect,
6018
6270
  scroll: params.scroll
6019
6271
  })
6020
- };
6021
- startOffset += rowLength;
6022
- return row;
6023
- });
6024
- if (rows.length > 0) {
6025
- rows[rows.length - 1] = {
6026
- ...rows[rows.length - 1],
6027
- endOffset: params.lineLength
6028
- };
6272
+ });
6273
+ if (nextRowStart === null) {
6274
+ break;
6275
+ }
6276
+ currentRowStart = nextRowStart;
6277
+ rowIndex += 1;
6278
+ const nextMeasurable = findFirstMeasurableOffset(currentRowStart);
6279
+ if (nextMeasurable === null) {
6280
+ break;
6281
+ }
6282
+ currentRowTop = ((_b = fullLineRects[rowIndex]) == null ? void 0 : _b.top) ?? currentRowTop;
6283
+ searchFrom = nextMeasurable + 1;
6029
6284
  }
6030
6285
  if (rows.length === 1) {
6031
6286
  rows[0] = {
@@ -6097,6 +6352,44 @@ function measureLayoutModelFromDom(params) {
6097
6352
  }
6098
6353
  return buildLayoutModel(params.lines, measurer);
6099
6354
  }
6355
+ function measureLayoutModelRangeFromDom(params) {
6356
+ const measurer = createDomLayoutMeasurer({
6357
+ root: params.root,
6358
+ container: params.container,
6359
+ lines: params.lines
6360
+ });
6361
+ if (!measurer) {
6362
+ return null;
6363
+ }
6364
+ const clampedStart = Math.max(0, Math.min(params.startLineIndex, params.lines.length - 1));
6365
+ const clampedEnd = Math.max(clampedStart, Math.min(params.endLineIndex, params.lines.length - 1));
6366
+ const lineOffsets = getLineOffsets(params.lines);
6367
+ let lineStartOffset = lineOffsets[clampedStart] ?? 0;
6368
+ const layouts = [];
6369
+ for (let lineIndex = clampedStart; lineIndex <= clampedEnd; lineIndex += 1) {
6370
+ const lineInfo = params.lines[lineIndex];
6371
+ if (!lineInfo) {
6372
+ continue;
6373
+ }
6374
+ const measurement = measurer.measureLine({
6375
+ lineIndex: lineInfo.lineIndex,
6376
+ lineText: lineInfo.text,
6377
+ lineLength: lineInfo.cursorLength,
6378
+ lineHasNewline: lineInfo.hasNewline,
6379
+ top: 0
6380
+ });
6381
+ layouts.push({
6382
+ lineIndex: lineInfo.lineIndex,
6383
+ lineStartOffset,
6384
+ lineLength: lineInfo.cursorLength,
6385
+ lineHasNewline: lineInfo.hasNewline,
6386
+ lineBox: measurement.lineBox,
6387
+ rows: measurement.rows
6388
+ });
6389
+ lineStartOffset += lineInfo.cursorLength + (lineInfo.hasNewline ? 1 : 0);
6390
+ }
6391
+ return { container: measurer.container, lines: layouts };
6392
+ }
6100
6393
  function getLineElement(root, lineIndex) {
6101
6394
  return root.querySelector(`[data-line-index="${lineIndex}"]`);
6102
6395
  }
@@ -6312,7 +6605,17 @@ function getSelectionGeometry(params) {
6312
6605
  end: selection.start,
6313
6606
  affinity: selection.affinity
6314
6607
  };
6315
- const layout = shouldMeasureLayout(normalized) ? measureLayoutModelFromDom({ lines: docLines, root, container }) : null;
6608
+ const layout = shouldMeasureLayout(normalized) ? (() => {
6609
+ const startLine2 = resolveOffsetToLine(docLines, normalized.start);
6610
+ const endLine = resolveOffsetToLine(docLines, normalized.end);
6611
+ return measureLayoutModelRangeFromDom({
6612
+ lines: docLines,
6613
+ root,
6614
+ container,
6615
+ startLineIndex: startLine2.lineIndex,
6616
+ endLineIndex: endLine.lineIndex
6617
+ });
6618
+ })() : null;
6316
6619
  const containerRect = container.getBoundingClientRect();
6317
6620
  const scroll = { top: container.scrollTop, left: container.scrollLeft };
6318
6621
  const startLine = resolveOffsetToLine(docLines, normalized.start);
@@ -7320,10 +7623,13 @@ class CakeEngine {
7320
7623
  this.caretBlinkTimeoutId = null;
7321
7624
  this.overlayUpdateId = null;
7322
7625
  this.scrollCaretIntoViewId = null;
7626
+ this.selectionRectElements = [];
7627
+ this.lastSelectionRects = null;
7323
7628
  this.extensionsRoot = null;
7324
7629
  this.placeholderRoot = null;
7325
7630
  this.lastFocusRect = null;
7326
7631
  this.verticalNavGoalX = null;
7632
+ this.lastRenderPerf = null;
7327
7633
  this.history = {
7328
7634
  undoStack: [],
7329
7635
  redoStack: [],
@@ -7377,6 +7683,9 @@ class CakeEngine {
7377
7683
  set state(value) {
7378
7684
  this._state = value;
7379
7685
  }
7686
+ getLastRenderPerf() {
7687
+ return this.lastRenderPerf;
7688
+ }
7380
7689
  isEventTargetInContentRoot(target) {
7381
7690
  if (target === this.container) {
7382
7691
  return true;
@@ -7413,6 +7722,9 @@ class CakeEngine {
7413
7722
  getSelection() {
7414
7723
  return this.state.selection;
7415
7724
  }
7725
+ getCursorLength() {
7726
+ return this.state.map.cursorLength;
7727
+ }
7416
7728
  getFocusRect() {
7417
7729
  return this.lastFocusRect;
7418
7730
  }
@@ -7467,6 +7779,7 @@ class CakeEngine {
7467
7779
  if (!this.isComposing) {
7468
7780
  this.applySelection(this.state.selection);
7469
7781
  }
7782
+ this.scheduleScrollCaretIntoView();
7470
7783
  }
7471
7784
  setValue({ value, selection }) {
7472
7785
  const valueChanged = value !== this.state.source;
@@ -7542,7 +7855,7 @@ class CakeEngine {
7542
7855
  }
7543
7856
  executeCommand(command) {
7544
7857
  var _a;
7545
- const shouldOpenLinkPopover = command.type === "wrap-link" && command.openPopover;
7858
+ const shouldOpenLinkPopover = "openPopover" in command && command.openPopover === true;
7546
7859
  const nextState = this.runtime.applyEdit(command, this.state);
7547
7860
  if (nextState === this.state) {
7548
7861
  return false;
@@ -7717,6 +8030,14 @@ class CakeEngine {
7717
8030
  this.patchedCaretRangeFromPoint = null;
7718
8031
  }
7719
8032
  render() {
8033
+ const perfEnabled = this.container.dataset.cakePerf === "1";
8034
+ let perfStart = 0;
8035
+ let renderStart = 0;
8036
+ let renderAndMapMs = 0;
8037
+ let applySelectionMs = 0;
8038
+ if (perfEnabled) {
8039
+ perfStart = performance.now();
8040
+ }
7720
8041
  if (!this.contentRoot) {
7721
8042
  const containerPosition = window.getComputedStyle(
7722
8043
  this.container
@@ -7732,6 +8053,9 @@ class CakeEngine {
7732
8053
  this.container.replaceChildren(this.contentRoot, overlay, extensionsRoot);
7733
8054
  this.attachDragListeners();
7734
8055
  }
8056
+ if (perfEnabled) {
8057
+ renderStart = performance.now();
8058
+ }
7735
8059
  const { content, map } = renderDocContent(
7736
8060
  this.state.doc,
7737
8061
  this.extensions,
@@ -7743,9 +8067,26 @@ class CakeEngine {
7743
8067
  this.contentRoot.replaceChildren(...content);
7744
8068
  }
7745
8069
  this.domMap = map;
7746
- this.updateExtensionsOverlayPosition();
7747
- if (!this.isComposing) {
8070
+ if (perfEnabled) {
8071
+ renderAndMapMs = performance.now() - renderStart;
8072
+ }
8073
+ if (!this.isComposing && this.hasFocus()) {
8074
+ const selectionStart = perfEnabled ? performance.now() : 0;
7748
8075
  this.applySelection(this.state.selection);
8076
+ if (perfEnabled) {
8077
+ applySelectionMs = performance.now() - selectionStart;
8078
+ }
8079
+ }
8080
+ if (perfEnabled) {
8081
+ const totalMs = performance.now() - perfStart;
8082
+ this.lastRenderPerf = {
8083
+ totalMs,
8084
+ renderAndMapMs,
8085
+ applySelectionMs,
8086
+ didUpdateDom: needsUpdate,
8087
+ blockCount: this.state.doc.blocks.length,
8088
+ runCount: map.runs.length
8089
+ };
7749
8090
  }
7750
8091
  this.updatePlaceholder();
7751
8092
  this.scheduleOverlayUpdate();
@@ -7995,22 +8336,31 @@ class CakeEngine {
7995
8336
  this.suppressSelectionChange = false;
7996
8337
  return;
7997
8338
  }
7998
- const hit2 = this.pendingClickHit ?? this.hitTestFromClientPoint(event.clientX, event.clientY);
7999
- this.pendingClickHit = null;
8000
- if (hit2) {
8339
+ const pendingHit = this.pendingClickHit;
8340
+ const selection = this.state.selection;
8341
+ if (pendingHit && selection.start !== selection.end) {
8001
8342
  const newSelection = {
8002
- start: hit2.cursorOffset,
8003
- end: hit2.cursorOffset,
8004
- affinity: hit2.affinity
8343
+ start: pendingHit.cursorOffset,
8344
+ end: pendingHit.cursorOffset,
8345
+ affinity: pendingHit.affinity
8005
8346
  };
8347
+ this.pendingClickHit = null;
8348
+ this.suppressSelectionChange = true;
8006
8349
  this.state = this.runtime.updateSelection(this.state, newSelection, {
8007
8350
  kind: "dom"
8008
8351
  });
8009
8352
  this.applySelection(this.state.selection);
8010
8353
  (_b = this.onSelectionChange) == null ? void 0 : _b.call(this, this.state.selection);
8011
8354
  this.scheduleOverlayUpdate();
8355
+ setTimeout(() => {
8356
+ this.suppressSelectionChange = false;
8357
+ }, 0);
8358
+ return;
8012
8359
  }
8013
- this.suppressSelectionChange = false;
8360
+ this.pendingClickHit = null;
8361
+ setTimeout(() => {
8362
+ this.suppressSelectionChange = false;
8363
+ }, 0);
8014
8364
  return;
8015
8365
  }
8016
8366
  this.pendingClickHit = null;
@@ -8388,7 +8738,7 @@ class CakeEngine {
8388
8738
  if (!command) {
8389
8739
  continue;
8390
8740
  }
8391
- if (command.type === "insert" || command.type === "insert-line-break" || command.type === "delete-backward" || command.type === "delete-forward") {
8741
+ if (isApplyEditCommand(command)) {
8392
8742
  this.applyEdit(command);
8393
8743
  } else {
8394
8744
  this.executeCommand(command);
@@ -8908,6 +9258,39 @@ class CakeEngine {
8908
9258
  return { start: target, end: target, affinity: direction };
8909
9259
  }
8910
9260
  const currentPos = selection.start;
9261
+ const currentAffinity = selection.affinity ?? "forward";
9262
+ const measurement = this.getLayoutForNavigation();
9263
+ if (measurement) {
9264
+ const { lines, layout } = measurement;
9265
+ const { rowStart, rowEnd } = getVisualRowBoundaries({
9266
+ lines,
9267
+ layout,
9268
+ offset: currentPos,
9269
+ affinity: currentAffinity
9270
+ });
9271
+ if (direction === "backward" && currentPos === rowStart && currentAffinity === "forward" && currentPos > 0) {
9272
+ const prevBoundaries = getVisualRowBoundaries({
9273
+ lines,
9274
+ layout,
9275
+ offset: currentPos,
9276
+ affinity: "backward"
9277
+ });
9278
+ if (prevBoundaries.rowEnd !== rowEnd || prevBoundaries.rowStart !== rowStart) {
9279
+ return { start: currentPos, end: currentPos, affinity: "backward" };
9280
+ }
9281
+ }
9282
+ if (direction === "forward" && currentPos === rowEnd && currentAffinity === "backward" && currentPos < this.state.map.cursorLength) {
9283
+ const nextBoundaries = getVisualRowBoundaries({
9284
+ lines,
9285
+ layout,
9286
+ offset: currentPos,
9287
+ affinity: "forward"
9288
+ });
9289
+ if (nextBoundaries.rowEnd !== rowEnd || nextBoundaries.rowStart !== rowStart) {
9290
+ return { start: currentPos, end: currentPos, affinity: "forward" };
9291
+ }
9292
+ }
9293
+ }
8911
9294
  const nextPos = this.moveOffsetByChar(currentPos, direction);
8912
9295
  if (nextPos === null) {
8913
9296
  return null;
@@ -9597,6 +9980,7 @@ class CakeEngine {
9597
9980
  }
9598
9981
  this.overlayUpdateId = window.requestAnimationFrame(() => {
9599
9982
  this.overlayUpdateId = null;
9983
+ this.updateExtensionsOverlayPosition();
9600
9984
  this.updateSelectionOverlay();
9601
9985
  });
9602
9986
  }
@@ -9626,12 +10010,34 @@ class CakeEngine {
9626
10010
  overlay.style.zIndex = "2";
9627
10011
  const caret = document.createElement("div");
9628
10012
  caret.className = "cake-caret";
10013
+ caret.style.position = "absolute";
9629
10014
  caret.style.display = "none";
9630
10015
  overlay.append(caret);
9631
10016
  this.overlayRoot = overlay;
9632
10017
  this.caretElement = caret;
10018
+ this.selectionRectElements = [];
10019
+ this.lastSelectionRects = null;
9633
10020
  return overlay;
9634
10021
  }
10022
+ selectionRectsEqual(prev, next) {
10023
+ if (!prev) {
10024
+ return false;
10025
+ }
10026
+ if (prev.length !== next.length) {
10027
+ return false;
10028
+ }
10029
+ for (let index = 0; index < prev.length; index += 1) {
10030
+ const a = prev[index];
10031
+ const b = next[index];
10032
+ if (!a || !b) {
10033
+ return false;
10034
+ }
10035
+ if (a.top !== b.top || a.left !== b.left || a.width !== b.width || a.height !== b.height) {
10036
+ return false;
10037
+ }
10038
+ }
10039
+ return true;
10040
+ }
9635
10041
  ensureExtensionsRoot() {
9636
10042
  if (this.extensionsRoot) {
9637
10043
  return this.extensionsRoot;
@@ -9692,21 +10098,34 @@ class CakeEngine {
9692
10098
  if (!this.overlayRoot || !this.caretElement) {
9693
10099
  return;
9694
10100
  }
9695
- const existing = Array.from(
9696
- this.overlayRoot.querySelectorAll(".cake-selection-rect")
9697
- );
9698
- existing.forEach((node) => node.remove());
9699
- const fragment = document.createDocumentFragment();
9700
- rects.forEach((rect) => {
9701
- const element = document.createElement("div");
9702
- element.className = "cake-selection-rect";
10101
+ if (this.selectionRectsEqual(this.lastSelectionRects, rects)) {
10102
+ return;
10103
+ }
10104
+ this.lastSelectionRects = rects;
10105
+ while (this.selectionRectElements.length > rects.length) {
10106
+ const element = this.selectionRectElements.pop();
10107
+ element == null ? void 0 : element.remove();
10108
+ }
10109
+ if (this.selectionRectElements.length < rects.length) {
10110
+ const fragment = document.createDocumentFragment();
10111
+ while (this.selectionRectElements.length < rects.length) {
10112
+ const element = document.createElement("div");
10113
+ element.className = "cake-selection-rect";
10114
+ fragment.append(element);
10115
+ this.selectionRectElements.push(element);
10116
+ }
10117
+ this.overlayRoot.insertBefore(fragment, this.caretElement);
10118
+ }
10119
+ rects.forEach((rect, index) => {
10120
+ const element = this.selectionRectElements[index];
10121
+ if (!element) {
10122
+ return;
10123
+ }
9703
10124
  element.style.top = `${rect.top}px`;
9704
10125
  element.style.left = `${rect.left}px`;
9705
10126
  element.style.width = `${rect.width}px`;
9706
10127
  element.style.height = `${rect.height}px`;
9707
- fragment.append(element);
9708
10128
  });
9709
- this.overlayRoot.insertBefore(fragment, this.caretElement);
9710
10129
  }
9711
10130
  updateCaret(position) {
9712
10131
  if (!this.caretElement) {
@@ -9873,7 +10292,7 @@ class CakeEngine {
9873
10292
  return { cursorOffset: cursor.cursorOffset, affinity: "backward" };
9874
10293
  }
9875
10294
  handlePointerDown(event) {
9876
- var _a;
10295
+ var _a, _b, _c;
9877
10296
  if (!this.isEventTargetInContentRoot(event.target)) {
9878
10297
  return;
9879
10298
  }
@@ -9938,13 +10357,51 @@ class CakeEngine {
9938
10357
  }
9939
10358
  }
9940
10359
  }
9941
- if (selection.start === selection.end && !event.shiftKey) {
9942
- this.suppressSelectionChange = true;
10360
+ if (!event.shiftKey) {
9943
10361
  const hit2 = this.hitTestFromClientPoint(event.clientX, event.clientY);
9944
- if (hit2) {
10362
+ if (!hit2) {
10363
+ return;
10364
+ }
10365
+ if (selection.start !== selection.end) {
10366
+ const selStart2 = Math.min(selection.start, selection.end);
10367
+ const selEnd2 = Math.max(selection.start, selection.end);
10368
+ const clickedInsideSelection2 = hit2.cursorOffset >= selStart2 && hit2.cursorOffset <= selEnd2;
10369
+ if (clickedInsideSelection2) {
10370
+ this.suppressSelectionChange = false;
10371
+ this.blockTrustedTextDrag = true;
10372
+ this.pendingClickHit = hit2;
10373
+ } else {
10374
+ this.suppressSelectionChange = true;
10375
+ this.pendingClickHit = hit2;
10376
+ const newSelection = {
10377
+ start: hit2.cursorOffset,
10378
+ end: hit2.cursorOffset,
10379
+ affinity: hit2.affinity
10380
+ };
10381
+ this.state = this.runtime.updateSelection(this.state, newSelection, {
10382
+ kind: "dom"
10383
+ });
10384
+ this.applySelection(this.state.selection);
10385
+ (_b = this.onSelectionChange) == null ? void 0 : _b.call(this, this.state.selection);
10386
+ this.scheduleOverlayUpdate();
10387
+ return;
10388
+ }
10389
+ } else {
10390
+ this.suppressSelectionChange = true;
9945
10391
  this.pendingClickHit = hit2;
10392
+ const newSelection = {
10393
+ start: hit2.cursorOffset,
10394
+ end: hit2.cursorOffset,
10395
+ affinity: hit2.affinity
10396
+ };
10397
+ this.state = this.runtime.updateSelection(this.state, newSelection, {
10398
+ kind: "dom"
10399
+ });
10400
+ this.applySelection(this.state.selection);
10401
+ (_c = this.onSelectionChange) == null ? void 0 : _c.call(this, this.state.selection);
10402
+ this.scheduleOverlayUpdate();
10403
+ return;
9946
10404
  }
9947
- return;
9948
10405
  }
9949
10406
  this.suppressSelectionChange = false;
9950
10407
  const selStart = Math.min(selection.start, selection.end);
@@ -10056,10 +10513,6 @@ class CakeEngine {
10056
10513
  }
10057
10514
  handlePointerUp(event) {
10058
10515
  this.blockTrustedTextDrag = false;
10059
- if (this.pendingClickHit) {
10060
- this.pendingClickHit = null;
10061
- this.suppressSelectionChange = false;
10062
- }
10063
10516
  if (this.selectionDragState) {
10064
10517
  if (event.pointerId !== this.selectionDragState.pointerId) {
10065
10518
  return;
@@ -10559,7 +11012,7 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10559
11012
  let closestDistance = Infinity;
10560
11013
  let closestYDistance = Infinity;
10561
11014
  let closestCaretX = 0;
10562
- let closestRowTop = 0;
11015
+ let selectedViaRightEdge = false;
10563
11016
  const rowInfo = /* @__PURE__ */ new Map();
10564
11017
  const range = document.createRange();
10565
11018
  let lastCharRect = null;
@@ -10628,15 +11081,28 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10628
11081
  closestDistance = xDistance;
10629
11082
  closestOffset = i;
10630
11083
  closestCaretX = caretX;
10631
- closestRowTop = bestRect.top;
11084
+ bestRect.top;
10632
11085
  closestYDistance = yDistance;
11086
+ selectedViaRightEdge = false;
11087
+ }
11088
+ if (i < text.length) {
11089
+ const rightEdgeX = bestRect.right;
11090
+ const rightEdgeDistance = Math.abs(clientX - rightEdgeX);
11091
+ if (rightEdgeDistance < closestDistance) {
11092
+ closestDistance = rightEdgeDistance;
11093
+ closestOffset = i + 1;
11094
+ closestCaretX = rightEdgeX;
11095
+ bestRect.top;
11096
+ closestYDistance = yDistance;
11097
+ selectedViaRightEdge = true;
11098
+ }
10633
11099
  }
10634
11100
  } else if (yDistance < closestYDistance || yDistance === closestYDistance && xDistance < closestDistance) {
10635
11101
  closestYDistance = yDistance;
10636
11102
  closestDistance = xDistance;
10637
11103
  closestOffset = i;
10638
11104
  closestCaretX = caretX;
10639
- closestRowTop = bestRect.top;
11105
+ bestRect.top;
10640
11106
  }
10641
11107
  }
10642
11108
  let closestRow = null;
@@ -10661,11 +11127,11 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10661
11127
  if (clientX < closestRow.left) {
10662
11128
  closestOffset = closestRow.startOffset;
10663
11129
  closestCaretX = closestRow.left;
10664
- closestRowTop = closestRow.top;
11130
+ closestRow.top;
10665
11131
  } else if (clientX > closestRow.right) {
10666
- closestOffset = closestRow.endOffset;
11132
+ closestOffset = Math.min(closestRow.endOffset + 1, text.length);
10667
11133
  closestCaretX = closestRow.right;
10668
- closestRowTop = closestRow.top;
11134
+ closestRow.top;
10669
11135
  } else if (clientY < closestRow.top || clientY > closestRow.bottom) {
10670
11136
  let bestXDistance = Infinity;
10671
11137
  const range2 = document.createRange();
@@ -10685,12 +11151,12 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10685
11151
  bestXDistance = xDist;
10686
11152
  closestOffset = i;
10687
11153
  closestCaretX = rect.left;
10688
- closestRowTop = closestRow.top;
11154
+ closestRow.top;
10689
11155
  }
10690
11156
  }
10691
11157
  }
10692
11158
  }
10693
- const pastRowEnd = closestRow !== null && clientX > closestRow.right && Math.abs(clientY - closestRowTop) < 1;
11159
+ const pastRowEnd = selectedViaRightEdge || closestRow !== null && clientX > closestRow.right;
10694
11160
  if (Math.abs(clientX - closestCaretX) <= 2 && closestOffset < text.length && text[closestOffset] === "\n") {
10695
11161
  closestOffset = Math.max(0, closestOffset - 1);
10696
11162
  }
@@ -11143,8 +11609,9 @@ const CakeEditor = require$$0.forwardRef(
11143
11609
  const lastEmittedSelectionRef = require$$0.useRef(null);
11144
11610
  const [overlayRoot, setOverlayRoot] = require$$0.useState(null);
11145
11611
  const [contentRoot, setContentRoot] = require$$0.useState(null);
11612
+ const baseExtensions = props.disableImageExtension ? bundledExtensionsWithoutImage : bundledExtensions;
11146
11613
  const allExtensionsRef = require$$0.useRef([
11147
- ...bundledExtensions,
11614
+ ...baseExtensions,
11148
11615
  ...props.extensions ?? []
11149
11616
  ]);
11150
11617
  require$$0.useEffect(() => {
@@ -11302,6 +11769,10 @@ const CakeEditor = require$$0.forwardRef(
11302
11769
  }
11303
11770
  return { start: selection.start, end: selection.end };
11304
11771
  },
11772
+ getCursorLength: () => {
11773
+ var _a;
11774
+ return ((_a = engineRef.current) == null ? void 0 : _a.getCursorLength()) ?? 0;
11775
+ },
11305
11776
  insertText: (text) => {
11306
11777
  var _a;
11307
11778
  (_a = engineRef.current) == null ? void 0 : _a.insertText(text);
@@ -11355,6 +11826,10 @@ const CakeEditor = require$$0.forwardRef(
11355
11826
  }
11356
11827
  const focus = selection.start === selection.end ? selection.start : Math.max(selection.start, selection.end);
11357
11828
  return { start: focus, end: focus };
11829
+ },
11830
+ executeCommand: (command) => {
11831
+ var _a;
11832
+ return ((_a = engineRef.current) == null ? void 0 : _a.executeCommand(command)) ?? false;
11358
11833
  }
11359
11834
  } : null;
11360
11835
  const hasOverlayExtensions = allExtensionsRef.current.some(
@@ -11386,6 +11861,7 @@ exports.CakeEngine = CakeEngine;
11386
11861
  exports.blockquoteExtension = blockquoteExtension;
11387
11862
  exports.boldExtension = boldExtension;
11388
11863
  exports.bundledExtensions = bundledExtensions;
11864
+ exports.bundledExtensionsWithoutImage = bundledExtensionsWithoutImage;
11389
11865
  exports.combinedEmphasisExtension = combinedEmphasisExtension;
11390
11866
  exports.headingExtension = headingExtension;
11391
11867
  exports.imageExtension = imageExtension;