@blankdotpage/cake 0.1.2 → 0.1.4

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] };
2800
+ }
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] };
2690
2804
  }
2691
- const last = runs[runs.length - 1];
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: [],
@@ -7356,6 +7662,7 @@ class CakeEngine {
7356
7662
  this.selectionDragState = null;
7357
7663
  this.pointerDownPosition = null;
7358
7664
  this.hasMovedSincePointerDown = false;
7665
+ this.lastTouchTime = 0;
7359
7666
  this.container = options.container;
7360
7667
  this.extensions = options.extensions ?? bundledExtensions;
7361
7668
  this.runtime = createRuntime(this.extensions);
@@ -7377,6 +7684,9 @@ class CakeEngine {
7377
7684
  set state(value) {
7378
7685
  this._state = value;
7379
7686
  }
7687
+ getLastRenderPerf() {
7688
+ return this.lastRenderPerf;
7689
+ }
7380
7690
  isEventTargetInContentRoot(target) {
7381
7691
  if (target === this.container) {
7382
7692
  return true;
@@ -7413,6 +7723,9 @@ class CakeEngine {
7413
7723
  getSelection() {
7414
7724
  return this.state.selection;
7415
7725
  }
7726
+ getCursorLength() {
7727
+ return this.state.map.cursorLength;
7728
+ }
7416
7729
  getFocusRect() {
7417
7730
  return this.lastFocusRect;
7418
7731
  }
@@ -7467,6 +7780,7 @@ class CakeEngine {
7467
7780
  if (!this.isComposing) {
7468
7781
  this.applySelection(this.state.selection);
7469
7782
  }
7783
+ this.scheduleScrollCaretIntoView();
7470
7784
  }
7471
7785
  setValue({ value, selection }) {
7472
7786
  const valueChanged = value !== this.state.source;
@@ -7542,7 +7856,7 @@ class CakeEngine {
7542
7856
  }
7543
7857
  executeCommand(command) {
7544
7858
  var _a;
7545
- const shouldOpenLinkPopover = command.type === "wrap-link" && command.openPopover;
7859
+ const shouldOpenLinkPopover = "openPopover" in command && command.openPopover === true;
7546
7860
  const nextState = this.runtime.applyEdit(command, this.state);
7547
7861
  if (nextState === this.state) {
7548
7862
  return false;
@@ -7717,6 +8031,14 @@ class CakeEngine {
7717
8031
  this.patchedCaretRangeFromPoint = null;
7718
8032
  }
7719
8033
  render() {
8034
+ const perfEnabled = this.container.dataset.cakePerf === "1";
8035
+ let perfStart = 0;
8036
+ let renderStart = 0;
8037
+ let renderAndMapMs = 0;
8038
+ let applySelectionMs = 0;
8039
+ if (perfEnabled) {
8040
+ perfStart = performance.now();
8041
+ }
7720
8042
  if (!this.contentRoot) {
7721
8043
  const containerPosition = window.getComputedStyle(
7722
8044
  this.container
@@ -7732,6 +8054,9 @@ class CakeEngine {
7732
8054
  this.container.replaceChildren(this.contentRoot, overlay, extensionsRoot);
7733
8055
  this.attachDragListeners();
7734
8056
  }
8057
+ if (perfEnabled) {
8058
+ renderStart = performance.now();
8059
+ }
7735
8060
  const { content, map } = renderDocContent(
7736
8061
  this.state.doc,
7737
8062
  this.extensions,
@@ -7743,9 +8068,26 @@ class CakeEngine {
7743
8068
  this.contentRoot.replaceChildren(...content);
7744
8069
  }
7745
8070
  this.domMap = map;
7746
- this.updateExtensionsOverlayPosition();
7747
- if (!this.isComposing) {
8071
+ if (perfEnabled) {
8072
+ renderAndMapMs = performance.now() - renderStart;
8073
+ }
8074
+ if (!this.isComposing && this.hasFocus()) {
8075
+ const selectionStart = perfEnabled ? performance.now() : 0;
7748
8076
  this.applySelection(this.state.selection);
8077
+ if (perfEnabled) {
8078
+ applySelectionMs = performance.now() - selectionStart;
8079
+ }
8080
+ }
8081
+ if (perfEnabled) {
8082
+ const totalMs = performance.now() - perfStart;
8083
+ this.lastRenderPerf = {
8084
+ totalMs,
8085
+ renderAndMapMs,
8086
+ applySelectionMs,
8087
+ didUpdateDom: needsUpdate,
8088
+ blockCount: this.state.doc.blocks.length,
8089
+ runCount: map.runs.length
8090
+ };
7749
8091
  }
7750
8092
  this.updatePlaceholder();
7751
8093
  this.scheduleOverlayUpdate();
@@ -7995,22 +8337,31 @@ class CakeEngine {
7995
8337
  this.suppressSelectionChange = false;
7996
8338
  return;
7997
8339
  }
7998
- const hit2 = this.pendingClickHit ?? this.hitTestFromClientPoint(event.clientX, event.clientY);
7999
- this.pendingClickHit = null;
8000
- if (hit2) {
8340
+ const pendingHit = this.pendingClickHit;
8341
+ const selection = this.state.selection;
8342
+ if (pendingHit && selection.start !== selection.end) {
8001
8343
  const newSelection = {
8002
- start: hit2.cursorOffset,
8003
- end: hit2.cursorOffset,
8004
- affinity: hit2.affinity
8344
+ start: pendingHit.cursorOffset,
8345
+ end: pendingHit.cursorOffset,
8346
+ affinity: pendingHit.affinity
8005
8347
  };
8348
+ this.pendingClickHit = null;
8349
+ this.suppressSelectionChange = true;
8006
8350
  this.state = this.runtime.updateSelection(this.state, newSelection, {
8007
8351
  kind: "dom"
8008
8352
  });
8009
8353
  this.applySelection(this.state.selection);
8010
8354
  (_b = this.onSelectionChange) == null ? void 0 : _b.call(this, this.state.selection);
8011
8355
  this.scheduleOverlayUpdate();
8356
+ setTimeout(() => {
8357
+ this.suppressSelectionChange = false;
8358
+ }, 0);
8359
+ return;
8012
8360
  }
8013
- this.suppressSelectionChange = false;
8361
+ this.pendingClickHit = null;
8362
+ setTimeout(() => {
8363
+ this.suppressSelectionChange = false;
8364
+ }, 0);
8014
8365
  return;
8015
8366
  }
8016
8367
  this.pendingClickHit = null;
@@ -8388,7 +8739,7 @@ class CakeEngine {
8388
8739
  if (!command) {
8389
8740
  continue;
8390
8741
  }
8391
- if (command.type === "insert" || command.type === "insert-line-break" || command.type === "delete-backward" || command.type === "delete-forward") {
8742
+ if (isApplyEditCommand(command)) {
8392
8743
  this.applyEdit(command);
8393
8744
  } else {
8394
8745
  this.executeCommand(command);
@@ -8908,6 +9259,39 @@ class CakeEngine {
8908
9259
  return { start: target, end: target, affinity: direction };
8909
9260
  }
8910
9261
  const currentPos = selection.start;
9262
+ const currentAffinity = selection.affinity ?? "forward";
9263
+ const measurement = this.getLayoutForNavigation();
9264
+ if (measurement) {
9265
+ const { lines, layout } = measurement;
9266
+ const { rowStart, rowEnd } = getVisualRowBoundaries({
9267
+ lines,
9268
+ layout,
9269
+ offset: currentPos,
9270
+ affinity: currentAffinity
9271
+ });
9272
+ if (direction === "backward" && currentPos === rowStart && currentAffinity === "forward" && currentPos > 0) {
9273
+ const prevBoundaries = getVisualRowBoundaries({
9274
+ lines,
9275
+ layout,
9276
+ offset: currentPos,
9277
+ affinity: "backward"
9278
+ });
9279
+ if (prevBoundaries.rowEnd !== rowEnd || prevBoundaries.rowStart !== rowStart) {
9280
+ return { start: currentPos, end: currentPos, affinity: "backward" };
9281
+ }
9282
+ }
9283
+ if (direction === "forward" && currentPos === rowEnd && currentAffinity === "backward" && currentPos < this.state.map.cursorLength) {
9284
+ const nextBoundaries = getVisualRowBoundaries({
9285
+ lines,
9286
+ layout,
9287
+ offset: currentPos,
9288
+ affinity: "forward"
9289
+ });
9290
+ if (nextBoundaries.rowEnd !== rowEnd || nextBoundaries.rowStart !== rowStart) {
9291
+ return { start: currentPos, end: currentPos, affinity: "forward" };
9292
+ }
9293
+ }
9294
+ }
8911
9295
  const nextPos = this.moveOffsetByChar(currentPos, direction);
8912
9296
  if (nextPos === null) {
8913
9297
  return null;
@@ -9597,6 +9981,7 @@ class CakeEngine {
9597
9981
  }
9598
9982
  this.overlayUpdateId = window.requestAnimationFrame(() => {
9599
9983
  this.overlayUpdateId = null;
9984
+ this.updateExtensionsOverlayPosition();
9600
9985
  this.updateSelectionOverlay();
9601
9986
  });
9602
9987
  }
@@ -9626,12 +10011,34 @@ class CakeEngine {
9626
10011
  overlay.style.zIndex = "2";
9627
10012
  const caret = document.createElement("div");
9628
10013
  caret.className = "cake-caret";
10014
+ caret.style.position = "absolute";
9629
10015
  caret.style.display = "none";
9630
10016
  overlay.append(caret);
9631
10017
  this.overlayRoot = overlay;
9632
10018
  this.caretElement = caret;
10019
+ this.selectionRectElements = [];
10020
+ this.lastSelectionRects = null;
9633
10021
  return overlay;
9634
10022
  }
10023
+ selectionRectsEqual(prev, next) {
10024
+ if (!prev) {
10025
+ return false;
10026
+ }
10027
+ if (prev.length !== next.length) {
10028
+ return false;
10029
+ }
10030
+ for (let index = 0; index < prev.length; index += 1) {
10031
+ const a = prev[index];
10032
+ const b = next[index];
10033
+ if (!a || !b) {
10034
+ return false;
10035
+ }
10036
+ if (a.top !== b.top || a.left !== b.left || a.width !== b.width || a.height !== b.height) {
10037
+ return false;
10038
+ }
10039
+ }
10040
+ return true;
10041
+ }
9635
10042
  ensureExtensionsRoot() {
9636
10043
  if (this.extensionsRoot) {
9637
10044
  return this.extensionsRoot;
@@ -9668,6 +10075,14 @@ class CakeEngine {
9668
10075
  if (!this.overlayRoot || !this.contentRoot) {
9669
10076
  return;
9670
10077
  }
10078
+ const isRecentTouch = Date.now() - this.lastTouchTime < 2e3;
10079
+ if (isRecentTouch) {
10080
+ this.contentRoot.classList.add("cake-touch-mode");
10081
+ this.updateCaret(null);
10082
+ this.syncSelectionRects([]);
10083
+ return;
10084
+ }
10085
+ this.contentRoot.classList.remove("cake-touch-mode");
9671
10086
  const lines = getDocLines(this.state.doc);
9672
10087
  const geometry = getSelectionGeometry({
9673
10088
  root: this.contentRoot,
@@ -9692,21 +10107,34 @@ class CakeEngine {
9692
10107
  if (!this.overlayRoot || !this.caretElement) {
9693
10108
  return;
9694
10109
  }
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";
10110
+ if (this.selectionRectsEqual(this.lastSelectionRects, rects)) {
10111
+ return;
10112
+ }
10113
+ this.lastSelectionRects = rects;
10114
+ while (this.selectionRectElements.length > rects.length) {
10115
+ const element = this.selectionRectElements.pop();
10116
+ element == null ? void 0 : element.remove();
10117
+ }
10118
+ if (this.selectionRectElements.length < rects.length) {
10119
+ const fragment = document.createDocumentFragment();
10120
+ while (this.selectionRectElements.length < rects.length) {
10121
+ const element = document.createElement("div");
10122
+ element.className = "cake-selection-rect";
10123
+ fragment.append(element);
10124
+ this.selectionRectElements.push(element);
10125
+ }
10126
+ this.overlayRoot.insertBefore(fragment, this.caretElement);
10127
+ }
10128
+ rects.forEach((rect, index) => {
10129
+ const element = this.selectionRectElements[index];
10130
+ if (!element) {
10131
+ return;
10132
+ }
9703
10133
  element.style.top = `${rect.top}px`;
9704
10134
  element.style.left = `${rect.left}px`;
9705
10135
  element.style.width = `${rect.width}px`;
9706
10136
  element.style.height = `${rect.height}px`;
9707
- fragment.append(element);
9708
10137
  });
9709
- this.overlayRoot.insertBefore(fragment, this.caretElement);
9710
10138
  }
9711
10139
  updateCaret(position) {
9712
10140
  if (!this.caretElement) {
@@ -9873,7 +10301,7 @@ class CakeEngine {
9873
10301
  return { cursorOffset: cursor.cursorOffset, affinity: "backward" };
9874
10302
  }
9875
10303
  handlePointerDown(event) {
9876
- var _a;
10304
+ var _a, _b, _c;
9877
10305
  if (!this.isEventTargetInContentRoot(event.target)) {
9878
10306
  return;
9879
10307
  }
@@ -9890,9 +10318,7 @@ class CakeEngine {
9890
10318
  return;
9891
10319
  }
9892
10320
  if (event.pointerType === "touch") {
9893
- this.ignoreTouchNativeSelectionUntil = performance.now() + 750;
9894
- this.suppressSelectionChangeForTick();
9895
- event.preventDefault();
10321
+ this.lastTouchTime = Date.now();
9896
10322
  return;
9897
10323
  }
9898
10324
  const selection = this.state.selection;
@@ -9938,13 +10364,51 @@ class CakeEngine {
9938
10364
  }
9939
10365
  }
9940
10366
  }
9941
- if (selection.start === selection.end && !event.shiftKey) {
9942
- this.suppressSelectionChange = true;
10367
+ if (!event.shiftKey) {
9943
10368
  const hit2 = this.hitTestFromClientPoint(event.clientX, event.clientY);
9944
- if (hit2) {
10369
+ if (!hit2) {
10370
+ return;
10371
+ }
10372
+ if (selection.start !== selection.end) {
10373
+ const selStart2 = Math.min(selection.start, selection.end);
10374
+ const selEnd2 = Math.max(selection.start, selection.end);
10375
+ const clickedInsideSelection2 = hit2.cursorOffset >= selStart2 && hit2.cursorOffset <= selEnd2;
10376
+ if (clickedInsideSelection2) {
10377
+ this.suppressSelectionChange = false;
10378
+ this.blockTrustedTextDrag = true;
10379
+ this.pendingClickHit = hit2;
10380
+ } else {
10381
+ this.suppressSelectionChange = true;
10382
+ this.pendingClickHit = hit2;
10383
+ const newSelection = {
10384
+ start: hit2.cursorOffset,
10385
+ end: hit2.cursorOffset,
10386
+ affinity: hit2.affinity
10387
+ };
10388
+ this.state = this.runtime.updateSelection(this.state, newSelection, {
10389
+ kind: "dom"
10390
+ });
10391
+ this.applySelection(this.state.selection);
10392
+ (_b = this.onSelectionChange) == null ? void 0 : _b.call(this, this.state.selection);
10393
+ this.scheduleOverlayUpdate();
10394
+ return;
10395
+ }
10396
+ } else {
10397
+ this.suppressSelectionChange = true;
9945
10398
  this.pendingClickHit = hit2;
10399
+ const newSelection = {
10400
+ start: hit2.cursorOffset,
10401
+ end: hit2.cursorOffset,
10402
+ affinity: hit2.affinity
10403
+ };
10404
+ this.state = this.runtime.updateSelection(this.state, newSelection, {
10405
+ kind: "dom"
10406
+ });
10407
+ this.applySelection(this.state.selection);
10408
+ (_c = this.onSelectionChange) == null ? void 0 : _c.call(this, this.state.selection);
10409
+ this.scheduleOverlayUpdate();
10410
+ return;
9946
10411
  }
9947
- return;
9948
10412
  }
9949
10413
  this.suppressSelectionChange = false;
9950
10414
  const selStart = Math.min(selection.start, selection.end);
@@ -10056,10 +10520,6 @@ class CakeEngine {
10056
10520
  }
10057
10521
  handlePointerUp(event) {
10058
10522
  this.blockTrustedTextDrag = false;
10059
- if (this.pendingClickHit) {
10060
- this.pendingClickHit = null;
10061
- this.suppressSelectionChange = false;
10062
- }
10063
10523
  if (this.selectionDragState) {
10064
10524
  if (event.pointerId !== this.selectionDragState.pointerId) {
10065
10525
  return;
@@ -10559,7 +11019,7 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10559
11019
  let closestDistance = Infinity;
10560
11020
  let closestYDistance = Infinity;
10561
11021
  let closestCaretX = 0;
10562
- let closestRowTop = 0;
11022
+ let selectedViaRightEdge = false;
10563
11023
  const rowInfo = /* @__PURE__ */ new Map();
10564
11024
  const range = document.createRange();
10565
11025
  let lastCharRect = null;
@@ -10628,15 +11088,28 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10628
11088
  closestDistance = xDistance;
10629
11089
  closestOffset = i;
10630
11090
  closestCaretX = caretX;
10631
- closestRowTop = bestRect.top;
11091
+ bestRect.top;
10632
11092
  closestYDistance = yDistance;
11093
+ selectedViaRightEdge = false;
11094
+ }
11095
+ if (i < text.length) {
11096
+ const rightEdgeX = bestRect.right;
11097
+ const rightEdgeDistance = Math.abs(clientX - rightEdgeX);
11098
+ if (rightEdgeDistance < closestDistance) {
11099
+ closestDistance = rightEdgeDistance;
11100
+ closestOffset = i + 1;
11101
+ closestCaretX = rightEdgeX;
11102
+ bestRect.top;
11103
+ closestYDistance = yDistance;
11104
+ selectedViaRightEdge = true;
11105
+ }
10633
11106
  }
10634
11107
  } else if (yDistance < closestYDistance || yDistance === closestYDistance && xDistance < closestDistance) {
10635
11108
  closestYDistance = yDistance;
10636
11109
  closestDistance = xDistance;
10637
11110
  closestOffset = i;
10638
11111
  closestCaretX = caretX;
10639
- closestRowTop = bestRect.top;
11112
+ bestRect.top;
10640
11113
  }
10641
11114
  }
10642
11115
  let closestRow = null;
@@ -10661,11 +11134,11 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10661
11134
  if (clientX < closestRow.left) {
10662
11135
  closestOffset = closestRow.startOffset;
10663
11136
  closestCaretX = closestRow.left;
10664
- closestRowTop = closestRow.top;
11137
+ closestRow.top;
10665
11138
  } else if (clientX > closestRow.right) {
10666
- closestOffset = closestRow.endOffset;
11139
+ closestOffset = Math.min(closestRow.endOffset + 1, text.length);
10667
11140
  closestCaretX = closestRow.right;
10668
- closestRowTop = closestRow.top;
11141
+ closestRow.top;
10669
11142
  } else if (clientY < closestRow.top || clientY > closestRow.bottom) {
10670
11143
  let bestXDistance = Infinity;
10671
11144
  const range2 = document.createRange();
@@ -10685,12 +11158,12 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10685
11158
  bestXDistance = xDist;
10686
11159
  closestOffset = i;
10687
11160
  closestCaretX = rect.left;
10688
- closestRowTop = closestRow.top;
11161
+ closestRow.top;
10689
11162
  }
10690
11163
  }
10691
11164
  }
10692
11165
  }
10693
- const pastRowEnd = closestRow !== null && clientX > closestRow.right && Math.abs(clientY - closestRowTop) < 1;
11166
+ const pastRowEnd = selectedViaRightEdge || closestRow !== null && clientX > closestRow.right;
10694
11167
  if (Math.abs(clientX - closestCaretX) <= 2 && closestOffset < text.length && text[closestOffset] === "\n") {
10695
11168
  closestOffset = Math.max(0, closestOffset - 1);
10696
11169
  }
@@ -11143,8 +11616,9 @@ const CakeEditor = require$$0.forwardRef(
11143
11616
  const lastEmittedSelectionRef = require$$0.useRef(null);
11144
11617
  const [overlayRoot, setOverlayRoot] = require$$0.useState(null);
11145
11618
  const [contentRoot, setContentRoot] = require$$0.useState(null);
11619
+ const baseExtensions = props.disableImageExtension ? bundledExtensionsWithoutImage : bundledExtensions;
11146
11620
  const allExtensionsRef = require$$0.useRef([
11147
- ...bundledExtensions,
11621
+ ...baseExtensions,
11148
11622
  ...props.extensions ?? []
11149
11623
  ]);
11150
11624
  require$$0.useEffect(() => {
@@ -11302,6 +11776,10 @@ const CakeEditor = require$$0.forwardRef(
11302
11776
  }
11303
11777
  return { start: selection.start, end: selection.end };
11304
11778
  },
11779
+ getCursorLength: () => {
11780
+ var _a;
11781
+ return ((_a = engineRef.current) == null ? void 0 : _a.getCursorLength()) ?? 0;
11782
+ },
11305
11783
  insertText: (text) => {
11306
11784
  var _a;
11307
11785
  (_a = engineRef.current) == null ? void 0 : _a.insertText(text);
@@ -11355,6 +11833,10 @@ const CakeEditor = require$$0.forwardRef(
11355
11833
  }
11356
11834
  const focus = selection.start === selection.end ? selection.start : Math.max(selection.start, selection.end);
11357
11835
  return { start: focus, end: focus };
11836
+ },
11837
+ executeCommand: (command) => {
11838
+ var _a;
11839
+ return ((_a = engineRef.current) == null ? void 0 : _a.executeCommand(command)) ?? false;
11358
11840
  }
11359
11841
  } : null;
11360
11842
  const hasOverlayExtensions = allExtensionsRef.current.some(
@@ -11386,6 +11868,7 @@ exports.CakeEngine = CakeEngine;
11386
11868
  exports.blockquoteExtension = blockquoteExtension;
11387
11869
  exports.boldExtension = boldExtension;
11388
11870
  exports.bundledExtensions = bundledExtensions;
11871
+ exports.bundledExtensionsWithoutImage = bundledExtensionsWithoutImage;
11389
11872
  exports.combinedEmphasisExtension = combinedEmphasisExtension;
11390
11873
  exports.headingExtension = headingExtension;
11391
11874
  exports.imageExtension = imageExtension;