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