@blankdotpage/cake 0.1.5 → 0.1.6

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.
@@ -3,6 +3,7 @@ import { type CakeExtension, type EditCommand } from "../core/runtime";
3
3
  import type { SelectionRect } from "./selection/selection-geometry";
4
4
  type EngineOptions = {
5
5
  container: HTMLElement;
6
+ contentRoot?: HTMLElement;
6
7
  value: string;
7
8
  selection?: Selection;
8
9
  extensions?: CakeExtension[];
@@ -24,8 +25,6 @@ export declare class CakeEngine {
24
25
  private runtime;
25
26
  private extensions;
26
27
  private _state;
27
- private originalCaretRangeFromPoint;
28
- private patchedCaretRangeFromPoint;
29
28
  private get state();
30
29
  private set state(value);
31
30
  private contentRoot;
@@ -101,7 +100,7 @@ export declare class CakeEngine {
101
100
  getFocusRect(): SelectionRect | null;
102
101
  getContainer(): HTMLElement;
103
102
  getContentRoot(): HTMLElement | null;
104
- getOverlayRoot(): HTMLDivElement | null;
103
+ getOverlayRoot(): HTMLDivElement;
105
104
  syncPlaceholder(): void;
106
105
  insertText(text: string): void;
107
106
  replaceText(oldText: string, newText: string): void;
@@ -123,8 +122,6 @@ export declare class CakeEngine {
123
122
  private attachDragListeners;
124
123
  private detachDragListeners;
125
124
  private detachListeners;
126
- private installCaretRangeFromPointShim;
127
- private uninstallCaretRangeFromPointShim;
128
125
  private render;
129
126
  private isEmptyParagraphDoc;
130
127
  private updatePlaceholder;
package/dist/index.cjs CHANGED
@@ -2870,13 +2870,11 @@ function renderDocContent(doc, extensions, root) {
2870
2870
  return "unknown";
2871
2871
  }
2872
2872
  function getElementKey(element) {
2873
- if (element.hasAttribute("data-block")) {
2874
- const blockType = element.getAttribute("data-block") ?? "unknown";
2875
- const lineKind = element instanceof HTMLElement ? element.dataset.lineKind : null;
2876
- if (lineKind && lineKind !== blockType) {
2877
- return lineKind;
2873
+ if (element.classList.contains("cake-line")) {
2874
+ if (element.hasAttribute("data-block-atom")) {
2875
+ return `block-atom:${element.getAttribute("data-block-atom")}`;
2878
2876
  }
2879
- return blockType;
2877
+ return "paragraph";
2880
2878
  }
2881
2879
  if (element.hasAttribute("data-block-wrapper")) {
2882
2880
  return `block-wrapper:${element.getAttribute("data-block-wrapper")}`;
@@ -2902,11 +2900,13 @@ function renderDocContent(doc, extensions, root) {
2902
2900
  if (element.classList.contains("cake-text")) {
2903
2901
  return "text";
2904
2902
  }
2905
- if (element.hasAttribute("data-inline")) {
2906
- return `inline-wrapper:${element.getAttribute("data-inline")}`;
2907
- }
2908
- if (element.hasAttribute("data-inline-atom")) {
2909
- return `inline-atom:${element.getAttribute("data-inline-atom")}`;
2903
+ for (const cls of Array.from(element.classList)) {
2904
+ if (cls.startsWith("cake-inline--")) {
2905
+ return `inline-wrapper:${cls.slice("cake-inline--".length)}`;
2906
+ }
2907
+ if (cls.startsWith("cake-inline-atom--")) {
2908
+ return `inline-atom:${cls.slice("cake-inline-atom--".length)}`;
2909
+ }
2910
2910
  }
2911
2911
  return "unknown";
2912
2912
  }
@@ -2943,11 +2943,13 @@ function renderDocContent(doc, extensions, root) {
2943
2943
  if (inline.type === "inline-wrapper") {
2944
2944
  const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2945
2945
  if (canReuse) {
2946
+ existing.removeAttribute("data-inline");
2947
+ existing.classList.add("cake-inline", `cake-inline--${inline.kind}`);
2946
2948
  reconcileInlineChildren(existing, inline.children);
2947
2949
  return [existing];
2948
2950
  }
2949
2951
  const element = document.createElement("span");
2950
- element.setAttribute("data-inline", inline.kind);
2952
+ element.classList.add("cake-inline", `cake-inline--${inline.kind}`);
2951
2953
  for (const child of inline.children) {
2952
2954
  for (const node of reconcileInline(child, null)) {
2953
2955
  element.append(node);
@@ -2958,6 +2960,11 @@ function renderDocContent(doc, extensions, root) {
2958
2960
  if (inline.type === "inline-atom") {
2959
2961
  const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2960
2962
  if (canReuse) {
2963
+ existing.removeAttribute("data-inline-atom");
2964
+ existing.classList.add(
2965
+ "cake-inline-atom",
2966
+ `cake-inline-atom--${inline.kind}`
2967
+ );
2961
2968
  const textNode = existing.firstChild;
2962
2969
  if (textNode instanceof Text) {
2963
2970
  createTextRun$1(textNode);
@@ -2965,7 +2972,10 @@ function renderDocContent(doc, extensions, root) {
2965
2972
  }
2966
2973
  }
2967
2974
  const element = document.createElement("span");
2968
- element.setAttribute("data-inline-atom", inline.kind);
2975
+ element.classList.add(
2976
+ "cake-inline-atom",
2977
+ `cake-inline-atom--${inline.kind}`
2978
+ );
2969
2979
  const node = document.createTextNode(" ");
2970
2980
  createTextRun$1(node);
2971
2981
  element.append(node);
@@ -3008,6 +3018,13 @@ function renderDocContent(doc, extensions, root) {
3008
3018
  context.incrementLineIndex();
3009
3019
  if (canReuse) {
3010
3020
  existing.setAttribute("data-line-index", String(currentLineIndex));
3021
+ existing.removeAttribute("data-block");
3022
+ delete existing.dataset.lineKind;
3023
+ delete existing.dataset.headingLevel;
3024
+ delete existing.dataset.headingPlaceholder;
3025
+ existing.removeAttribute("aria-placeholder");
3026
+ existing.className = "cake-line";
3027
+ existing.removeAttribute("style");
3011
3028
  if (block.content.length === 0) {
3012
3029
  const firstChild = existing.firstChild;
3013
3030
  if (firstChild instanceof Text && existing.querySelector("br")) {
@@ -3028,10 +3045,8 @@ function renderDocContent(doc, extensions, root) {
3028
3045
  return [existing];
3029
3046
  }
3030
3047
  const element = document.createElement("div");
3031
- element.setAttribute("data-block", "paragraph");
3032
3048
  element.setAttribute("data-line-index", String(currentLineIndex));
3033
3049
  element.classList.add("cake-line");
3034
- element.dataset.lineKind = "paragraph";
3035
3050
  if (block.content.length === 0) {
3036
3051
  const textNode = document.createTextNode("");
3037
3052
  createTextRun$1(textNode);
@@ -4639,18 +4654,16 @@ const headingExtension = defineExtension({
4639
4654
  const level = typeof ((_a = block.data) == null ? void 0 : _a.level) === "number" ? block.data.level : 1;
4640
4655
  const normalizedLevel = Math.max(1, Math.min(3, level));
4641
4656
  const lineElement = document.createElement("div");
4642
- lineElement.setAttribute("data-block", "paragraph");
4643
4657
  lineElement.setAttribute("data-line-index", String(context.getLineIndex()));
4644
4658
  lineElement.classList.add(
4645
4659
  "cake-line",
4646
4660
  "is-heading",
4647
4661
  `is-heading-${normalizedLevel}`
4648
4662
  );
4649
- lineElement.dataset.lineKind = "heading";
4650
- lineElement.dataset.headingLevel = String(normalizedLevel);
4651
4663
  context.incrementLineIndex();
4652
4664
  const paragraph = block.blocks[0];
4653
4665
  if ((paragraph == null ? void 0 : paragraph.type) === "paragraph" && paragraph.content.length > 0) {
4666
+ lineElement.removeAttribute("aria-placeholder");
4654
4667
  const mergedContent = mergeInlineForRender(paragraph.content);
4655
4668
  for (const inline of mergedContent) {
4656
4669
  for (const node of context.renderInline(inline)) {
@@ -4658,7 +4671,10 @@ const headingExtension = defineExtension({
4658
4671
  }
4659
4672
  }
4660
4673
  } else {
4661
- lineElement.dataset.headingPlaceholder = `Heading ${normalizedLevel}`;
4674
+ lineElement.setAttribute(
4675
+ "aria-placeholder",
4676
+ `Heading ${normalizedLevel}`
4677
+ );
4662
4678
  const node = document.createTextNode("");
4663
4679
  context.createTextRun(node);
4664
4680
  lineElement.append(node);
@@ -5665,7 +5681,6 @@ const listExtension = defineExtension({
5665
5681
  return null;
5666
5682
  }
5667
5683
  const element = document.createElement("div");
5668
- element.setAttribute("data-block", "paragraph");
5669
5684
  element.setAttribute("data-line-index", String(context.getLineIndex()));
5670
5685
  element.classList.add("cake-line", "is-list");
5671
5686
  context.incrementLineIndex();
@@ -7601,8 +7616,6 @@ const HISTORY_GROUPING_INTERVAL_MS = 500;
7601
7616
  const MAX_UNDO_STACK_SIZE = 100;
7602
7617
  class CakeEngine {
7603
7618
  constructor(options) {
7604
- this.originalCaretRangeFromPoint = null;
7605
- this.patchedCaretRangeFromPoint = null;
7606
7619
  this.contentRoot = null;
7607
7620
  this.domMap = null;
7608
7621
  this.isApplyingSelection = false;
@@ -7665,6 +7678,7 @@ class CakeEngine {
7665
7678
  this.hasMovedSincePointerDown = false;
7666
7679
  this.lastTouchTime = 0;
7667
7680
  this.container = options.container;
7681
+ this.contentRoot = options.contentRoot ?? null;
7668
7682
  this.extensions = options.extensions ?? bundledExtensions;
7669
7683
  this.runtime = createRuntime(this.extensions);
7670
7684
  this.state = this.runtime.createState(
@@ -7677,7 +7691,6 @@ class CakeEngine {
7677
7691
  this.spellCheckEnabled = options.spellCheckEnabled ?? true;
7678
7692
  this.render();
7679
7693
  this.attachListeners();
7680
- this.installCaretRangeFromPointShim();
7681
7694
  }
7682
7695
  get state() {
7683
7696
  return this._state;
@@ -7704,7 +7717,6 @@ class CakeEngine {
7704
7717
  }
7705
7718
  destroy() {
7706
7719
  this.detachListeners();
7707
- this.uninstallCaretRangeFromPointShim();
7708
7720
  this.clearCaretBlinkTimer();
7709
7721
  if (this.overlayUpdateId !== null) {
7710
7722
  window.cancelAnimationFrame(this.overlayUpdateId);
@@ -7742,7 +7754,7 @@ class CakeEngine {
7742
7754
  return this.contentRoot;
7743
7755
  }
7744
7756
  getOverlayRoot() {
7745
- return this.extensionsRoot;
7757
+ return this.ensureExtensionsRoot();
7746
7758
  }
7747
7759
  // Placeholder text is provided by the caller via the container's
7748
7760
  // `data-placeholder` attribute (set by the React wrapper).
@@ -7970,80 +7982,6 @@ class CakeEngine {
7970
7982
  this.container.removeEventListener("pointerup", this.handlePointerUpBound);
7971
7983
  this.detachDragListeners();
7972
7984
  }
7973
- installCaretRangeFromPointShim() {
7974
- const doc = document;
7975
- if (typeof doc.caretRangeFromPoint !== "function") {
7976
- return;
7977
- }
7978
- if (this.patchedCaretRangeFromPoint) {
7979
- return;
7980
- }
7981
- this.originalCaretRangeFromPoint = doc.caretRangeFromPoint.bind(document);
7982
- const patched = (x, y) => {
7983
- var _a;
7984
- const original = this.originalCaretRangeFromPoint;
7985
- const range = original ? original(x, y) : null;
7986
- const startNode = (range == null ? void 0 : range.startContainer) ?? null;
7987
- const startLine = startNode instanceof HTMLElement ? startNode.closest("[data-line-index]") : (_a = startNode == null ? void 0 : startNode.parentElement) == null ? void 0 : _a.closest("[data-line-index]");
7988
- if (startLine) {
7989
- return range;
7990
- }
7991
- const rect = this.container.getBoundingClientRect();
7992
- const isInside = x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
7993
- if (!isInside || !this.domMap) {
7994
- return range;
7995
- }
7996
- const docAny = document;
7997
- let node = null;
7998
- let offset = 0;
7999
- if (typeof docAny.caretPositionFromPoint === "function") {
8000
- const pos = docAny.caretPositionFromPoint(x, y);
8001
- node = (pos == null ? void 0 : pos.offsetNode) ?? null;
8002
- offset = (pos == null ? void 0 : pos.offset) ?? 0;
8003
- } else if (original) {
8004
- const fallback = original(x, y);
8005
- node = (fallback == null ? void 0 : fallback.startContainer) ?? null;
8006
- offset = (fallback == null ? void 0 : fallback.startOffset) ?? 0;
8007
- }
8008
- let resolved = null;
8009
- if (node instanceof Text) {
8010
- resolved = { node, offset };
8011
- } else if (node instanceof Element) {
8012
- resolved = resolveTextPoint(node, offset);
8013
- }
8014
- if (!resolved) {
8015
- return range;
8016
- }
8017
- const cursor = this.domMap.cursorAtDom(resolved.node, resolved.offset);
8018
- if (!cursor) {
8019
- return range;
8020
- }
8021
- const domPoint = this.domMap.domAtCursor(
8022
- cursor.cursorOffset,
8023
- cursor.affinity
8024
- );
8025
- if (!domPoint) {
8026
- return range;
8027
- }
8028
- const fixed = document.createRange();
8029
- fixed.setStart(domPoint.node, domPoint.offset);
8030
- fixed.setEnd(domPoint.node, domPoint.offset);
8031
- return fixed;
8032
- };
8033
- this.patchedCaretRangeFromPoint = patched;
8034
- doc.caretRangeFromPoint = patched;
8035
- }
8036
- uninstallCaretRangeFromPointShim() {
8037
- if (!this.originalCaretRangeFromPoint || !this.patchedCaretRangeFromPoint) {
8038
- return;
8039
- }
8040
- const doc = document;
8041
- if (doc.caretRangeFromPoint === this.patchedCaretRangeFromPoint) {
8042
- doc.caretRangeFromPoint = this.originalCaretRangeFromPoint;
8043
- }
8044
- this.originalCaretRangeFromPoint = null;
8045
- this.patchedCaretRangeFromPoint = null;
8046
- }
8047
7985
  render() {
8048
7986
  const perfEnabled = this.container.dataset.cakePerf === "1";
8049
7987
  let perfStart = 0;
@@ -8062,13 +8000,32 @@ class CakeEngine {
8062
8000
  }
8063
8001
  this.contentRoot = document.createElement("div");
8064
8002
  this.contentRoot.className = "cake-content";
8065
- if (this.isTouchDevice()) {
8066
- this.contentRoot.classList.add("cake-touch-mode");
8067
- }
8068
- this.updateContentRootAttributes();
8003
+ }
8004
+ if (this.isTouchDevice()) {
8005
+ this.contentRoot.classList.add("cake-touch-mode");
8006
+ }
8007
+ this.updateContentRootAttributes();
8008
+ if (!this.overlayRoot) {
8069
8009
  const overlay = this.ensureOverlayRoot();
8070
- const extensionsRoot = this.ensureExtensionsRoot();
8071
- this.container.replaceChildren(this.contentRoot, overlay, extensionsRoot);
8010
+ const extensionsRoot = this.extensionsRoot;
8011
+ const existingContainerChildren = Array.from(this.container.childNodes);
8012
+ const isCakeManagedContainerChild = (node) => node instanceof Element && (node.classList.contains("cake-content") || node.classList.contains("cake-selection-overlay") || node.classList.contains("cake-extension-overlay") || node.classList.contains("cake-placeholder"));
8013
+ const preservedContainerChildren = existingContainerChildren.filter(
8014
+ (node) => !isCakeManagedContainerChild(node)
8015
+ );
8016
+ if (this.contentRoot.parentElement === this.container) {
8017
+ this.container.append(overlay);
8018
+ if (extensionsRoot && !extensionsRoot.isConnected) {
8019
+ this.container.append(extensionsRoot);
8020
+ }
8021
+ } else {
8022
+ this.container.replaceChildren(
8023
+ this.contentRoot,
8024
+ overlay,
8025
+ ...extensionsRoot ? [extensionsRoot] : [],
8026
+ ...preservedContainerChildren
8027
+ );
8028
+ }
8072
8029
  this.attachDragListeners();
8073
8030
  }
8074
8031
  if (perfEnabled) {
@@ -8080,9 +8037,12 @@ class CakeEngine {
8080
8037
  this.contentRoot
8081
8038
  );
8082
8039
  const existingChildren = Array.from(this.contentRoot.childNodes);
8083
- const needsUpdate = content.length !== existingChildren.length || content.some((node, i) => node !== existingChildren[i]);
8040
+ const isManagedChild = (node) => node instanceof Element && node.hasAttribute("data-line-index");
8041
+ const existingManagedChildren = existingChildren.filter(isManagedChild);
8042
+ const preservedChildren = existingChildren.filter((node) => !isManagedChild(node));
8043
+ const needsUpdate = content.length !== existingManagedChildren.length || content.some((node, i) => node !== existingManagedChildren[i]);
8084
8044
  if (needsUpdate) {
8085
- this.contentRoot.replaceChildren(...content);
8045
+ this.contentRoot.replaceChildren(...content, ...preservedChildren);
8086
8046
  }
8087
8047
  this.domMap = map;
8088
8048
  if (perfEnabled) {
@@ -8770,81 +8730,21 @@ class CakeEngine {
8770
8730
  }
8771
8731
  handleBeforeInput(event) {
8772
8732
  if (this.readOnly || this.isComposing || event.isComposing) {
8773
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8774
- console.log("[SPELLCHECK] beforeinput ignored (readonly/composing)", {
8775
- inputType: event.inputType,
8776
- data: event.data,
8777
- cancelable: event.cancelable,
8778
- isComposing: this.isComposing,
8779
- eventIsComposing: event.isComposing,
8780
- readOnly: this.readOnly
8781
- });
8782
- }
8783
8733
  return;
8784
8734
  }
8785
8735
  if (!this.isEventTargetInContentRoot(event.target)) {
8786
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8787
- console.log(
8788
- "[SPELLCHECK] beforeinput ignored (target outside editor)",
8789
- {
8790
- inputType: event.inputType,
8791
- data: event.data,
8792
- cancelable: event.cancelable,
8793
- target: event.target
8794
- }
8795
- );
8796
- }
8797
8736
  return;
8798
8737
  }
8799
8738
  if (this.keydownHandledBeforeInput) {
8800
8739
  this.keydownHandledBeforeInput = false;
8801
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8802
- console.log(
8803
- "[SPELLCHECK] beforeinput skipped (keydownHandledBeforeInput)",
8804
- {
8805
- inputType: event.inputType,
8806
- data: event.data,
8807
- cancelable: event.cancelable
8808
- }
8809
- );
8810
- }
8811
8740
  event.preventDefault();
8812
8741
  return;
8813
8742
  }
8814
8743
  const intent = this.resolveBeforeInputIntent(event);
8815
8744
  if (!intent) {
8816
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8817
- console.log("[SPELLCHECK] beforeinput: no intent", {
8818
- inputType: event.inputType,
8819
- data: event.data,
8820
- cancelable: event.cancelable
8821
- });
8822
- }
8823
8745
  return;
8824
8746
  }
8825
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText" && intent.type === "replace-text") {
8826
- const selection = this.state.selection;
8827
- const focus = selection.start === selection.end ? selection.start : Math.max(selection.start, selection.end);
8828
- const preview = this.state.source.slice(
8829
- Math.max(0, focus - 24),
8830
- Math.min(this.state.source.length, focus + 24)
8831
- );
8832
- console.log("[SPELLCHECK] beforeinput: resolved intent", {
8833
- inputType: event.inputType,
8834
- data: event.data,
8835
- cancelable: event.cancelable,
8836
- intent,
8837
- currentSelection: this.state.selection,
8838
- sourcePreviewAroundFocus: preview
8839
- });
8840
- }
8841
8747
  event.preventDefault();
8842
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText" && intent.type === "replace-text") {
8843
- console.log("[SPELLCHECK] beforeinput: after preventDefault", {
8844
- inputType: event.inputType,
8845
- defaultPrevented: event.defaultPrevented
8846
- });
8847
- }
8848
8748
  this.markBeforeInputHandled();
8849
8749
  this.suppressSelectionChangeForTick();
8850
8750
  this.applyInputIntent(intent);
@@ -8891,22 +8791,9 @@ class CakeEngine {
8891
8791
  return;
8892
8792
  }
8893
8793
  if (!this.isEventTargetInContentRoot(event.target)) {
8894
- if (event.inputType === "insertReplacementText") {
8895
- console.log("[SPELLCHECK] input ignored (target outside editor)", {
8896
- inputType: event.inputType,
8897
- data: event.data,
8898
- target: event.target
8899
- });
8900
- }
8901
8794
  return;
8902
8795
  }
8903
8796
  if (this.beforeInputHandled) {
8904
- if (event.inputType === "insertReplacementText") {
8905
- console.log("[SPELLCHECK] input ignored (handled via beforeinput)", {
8906
- inputType: event.inputType,
8907
- data: event.data
8908
- });
8909
- }
8910
8797
  return;
8911
8798
  }
8912
8799
  if (this.compositionCommit && event.inputType === "insertText") {
@@ -9018,57 +8905,20 @@ class CakeEngine {
9018
8905
  return null;
9019
8906
  }
9020
8907
  selectionFromTargetRangesWithStatus(event) {
9021
- const debug = event.inputType === "insertReplacementText";
9022
8908
  if (!event.getTargetRanges) {
9023
- if (debug) {
9024
- console.log("[SPELLCHECK][targetRanges] missing getTargetRanges()", {
9025
- inputType: event.inputType,
9026
- data: event.data,
9027
- cancelable: event.cancelable
9028
- });
9029
- }
9030
8909
  return { status: "none" };
9031
8910
  }
9032
8911
  const ranges = event.getTargetRanges();
9033
8912
  if (!ranges || ranges.length === 0) {
9034
- if (debug) {
9035
- console.log("[SPELLCHECK][targetRanges] no ranges", {
9036
- inputType: event.inputType,
9037
- data: event.data,
9038
- cancelable: event.cancelable
9039
- });
9040
- }
9041
8913
  return { status: "none" };
9042
8914
  }
9043
8915
  const range = ranges[0];
9044
- if (debug || event.inputType === "insertText") {
9045
- console.log("[SPELLCHECK][targetRanges] raw range", {
9046
- inputType: event.inputType,
9047
- data: event.data,
9048
- cancelable: event.cancelable,
9049
- startContainer: range.startContainer instanceof Element ? range.startContainer.tagName : range.startContainer.nodeName,
9050
- startOffset: range.startOffset,
9051
- endContainer: range.endContainer instanceof Element ? range.endContainer.tagName : range.endContainer.nodeName,
9052
- endOffset: range.endOffset,
9053
- startContained: this.container.contains(range.startContainer),
9054
- endContained: this.container.contains(range.endContainer)
9055
- });
9056
- }
9057
8916
  if (!this.container.contains(range.startContainer) || !this.container.contains(range.endContainer)) {
9058
8917
  return { status: "invalid" };
9059
8918
  }
9060
8919
  const start = this.cursorFromDom(range.startContainer, range.startOffset);
9061
8920
  const end = this.cursorFromDom(range.endContainer, range.endOffset);
9062
8921
  if (!start || !end) {
9063
- if (debug || event.inputType === "insertText") {
9064
- console.log("[SPELLCHECK][targetRanges] cursorFromDom failed", {
9065
- inputType: event.inputType,
9066
- data: event.data,
9067
- cancelable: event.cancelable,
9068
- start,
9069
- end
9070
- });
9071
- }
9072
8922
  return { status: "invalid" };
9073
8923
  }
9074
8924
  const affinity = start.cursorOffset === end.cursorOffset ? end.affinity : "forward";
@@ -9819,9 +9669,7 @@ class CakeEngine {
9819
9669
  return this.state.source;
9820
9670
  }
9821
9671
  const blocks = Array.from(
9822
- this.contentRoot.querySelectorAll(
9823
- '[data-block="paragraph"]'
9824
- )
9672
+ this.contentRoot.querySelectorAll(".cake-line")
9825
9673
  );
9826
9674
  if (blocks.length === 0) {
9827
9675
  return this.contentRoot.textContent ?? "";
@@ -10074,6 +9922,9 @@ class CakeEngine {
10074
9922
  root.style.zIndex = "50";
10075
9923
  root.style.overflow = "hidden";
10076
9924
  this.extensionsRoot = root;
9925
+ if (this.overlayRoot && !root.isConnected) {
9926
+ this.container.append(root);
9927
+ }
10077
9928
  return root;
10078
9929
  }
10079
9930
  updateExtensionsOverlayPosition() {
@@ -11639,13 +11490,15 @@ const CakeEditor = require$$0.forwardRef(
11639
11490
  const onSelectionChangeRef = require$$0.useRef(props.onSelectionChange);
11640
11491
  const lastEmittedValueRef = require$$0.useRef(null);
11641
11492
  const lastEmittedSelectionRef = require$$0.useRef(null);
11642
- const [overlayRoot, setOverlayRoot] = require$$0.useState(null);
11643
11493
  const [contentRoot, setContentRoot] = require$$0.useState(null);
11644
11494
  const baseExtensions = props.disableImageExtension ? bundledExtensionsWithoutImage : bundledExtensions;
11645
11495
  const allExtensionsRef = require$$0.useRef([
11646
11496
  ...baseExtensions,
11647
11497
  ...props.extensions ?? []
11648
11498
  ]);
11499
+ const hasOverlayExtensions = allExtensionsRef.current.some(
11500
+ (ext) => ext.renderOverlay
11501
+ );
11649
11502
  require$$0.useEffect(() => {
11650
11503
  onChangeRef.current = props.onChange;
11651
11504
  onSelectionChangeRef.current = props.onSelectionChange;
@@ -11690,12 +11543,10 @@ const CakeEditor = require$$0.forwardRef(
11690
11543
  }
11691
11544
  });
11692
11545
  engineRef.current = engine;
11693
- setOverlayRoot(engine.getOverlayRoot());
11694
11546
  setContentRoot(engine.getContentRoot());
11695
11547
  return () => {
11696
11548
  engine.destroy();
11697
11549
  engineRef.current = null;
11698
- setOverlayRoot(null);
11699
11550
  setContentRoot(null);
11700
11551
  };
11701
11552
  }, []);
@@ -11823,7 +11674,6 @@ const CakeEditor = require$$0.forwardRef(
11823
11674
  const overlayContext = containerRef.current && contentRoot ? {
11824
11675
  container: containerRef.current,
11825
11676
  contentRoot,
11826
- overlayRoot: overlayRoot ?? void 0,
11827
11677
  toOverlayRect: (rect) => {
11828
11678
  var _a;
11829
11679
  const containerRect = (_a = containerRef.current) == null ? void 0 : _a.getBoundingClientRect();
@@ -11864,9 +11714,6 @@ const CakeEditor = require$$0.forwardRef(
11864
11714
  return ((_a = engineRef.current) == null ? void 0 : _a.executeCommand(command)) ?? false;
11865
11715
  }
11866
11716
  } : null;
11867
- const hasOverlayExtensions = allExtensionsRef.current.some(
11868
- (ext) => ext.renderOverlay
11869
- );
11870
11717
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "relative", height: "100%" }, children: [
11871
11718
  /* @__PURE__ */ jsxRuntimeExports.jsx(
11872
11719
  "div",
package/dist/index.js CHANGED
@@ -2868,13 +2868,11 @@ function renderDocContent(doc, extensions, root) {
2868
2868
  return "unknown";
2869
2869
  }
2870
2870
  function getElementKey(element) {
2871
- if (element.hasAttribute("data-block")) {
2872
- const blockType = element.getAttribute("data-block") ?? "unknown";
2873
- const lineKind = element instanceof HTMLElement ? element.dataset.lineKind : null;
2874
- if (lineKind && lineKind !== blockType) {
2875
- return lineKind;
2871
+ if (element.classList.contains("cake-line")) {
2872
+ if (element.hasAttribute("data-block-atom")) {
2873
+ return `block-atom:${element.getAttribute("data-block-atom")}`;
2876
2874
  }
2877
- return blockType;
2875
+ return "paragraph";
2878
2876
  }
2879
2877
  if (element.hasAttribute("data-block-wrapper")) {
2880
2878
  return `block-wrapper:${element.getAttribute("data-block-wrapper")}`;
@@ -2900,11 +2898,13 @@ function renderDocContent(doc, extensions, root) {
2900
2898
  if (element.classList.contains("cake-text")) {
2901
2899
  return "text";
2902
2900
  }
2903
- if (element.hasAttribute("data-inline")) {
2904
- return `inline-wrapper:${element.getAttribute("data-inline")}`;
2905
- }
2906
- if (element.hasAttribute("data-inline-atom")) {
2907
- return `inline-atom:${element.getAttribute("data-inline-atom")}`;
2901
+ for (const cls of Array.from(element.classList)) {
2902
+ if (cls.startsWith("cake-inline--")) {
2903
+ return `inline-wrapper:${cls.slice("cake-inline--".length)}`;
2904
+ }
2905
+ if (cls.startsWith("cake-inline-atom--")) {
2906
+ return `inline-atom:${cls.slice("cake-inline-atom--".length)}`;
2907
+ }
2908
2908
  }
2909
2909
  return "unknown";
2910
2910
  }
@@ -2941,11 +2941,13 @@ function renderDocContent(doc, extensions, root) {
2941
2941
  if (inline.type === "inline-wrapper") {
2942
2942
  const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2943
2943
  if (canReuse) {
2944
+ existing.removeAttribute("data-inline");
2945
+ existing.classList.add("cake-inline", `cake-inline--${inline.kind}`);
2944
2946
  reconcileInlineChildren(existing, inline.children);
2945
2947
  return [existing];
2946
2948
  }
2947
2949
  const element = document.createElement("span");
2948
- element.setAttribute("data-inline", inline.kind);
2950
+ element.classList.add("cake-inline", `cake-inline--${inline.kind}`);
2949
2951
  for (const child of inline.children) {
2950
2952
  for (const node of reconcileInline(child, null)) {
2951
2953
  element.append(node);
@@ -2956,6 +2958,11 @@ function renderDocContent(doc, extensions, root) {
2956
2958
  if (inline.type === "inline-atom") {
2957
2959
  const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2958
2960
  if (canReuse) {
2961
+ existing.removeAttribute("data-inline-atom");
2962
+ existing.classList.add(
2963
+ "cake-inline-atom",
2964
+ `cake-inline-atom--${inline.kind}`
2965
+ );
2959
2966
  const textNode = existing.firstChild;
2960
2967
  if (textNode instanceof Text) {
2961
2968
  createTextRun$1(textNode);
@@ -2963,7 +2970,10 @@ function renderDocContent(doc, extensions, root) {
2963
2970
  }
2964
2971
  }
2965
2972
  const element = document.createElement("span");
2966
- element.setAttribute("data-inline-atom", inline.kind);
2973
+ element.classList.add(
2974
+ "cake-inline-atom",
2975
+ `cake-inline-atom--${inline.kind}`
2976
+ );
2967
2977
  const node = document.createTextNode(" ");
2968
2978
  createTextRun$1(node);
2969
2979
  element.append(node);
@@ -3006,6 +3016,13 @@ function renderDocContent(doc, extensions, root) {
3006
3016
  context.incrementLineIndex();
3007
3017
  if (canReuse) {
3008
3018
  existing.setAttribute("data-line-index", String(currentLineIndex));
3019
+ existing.removeAttribute("data-block");
3020
+ delete existing.dataset.lineKind;
3021
+ delete existing.dataset.headingLevel;
3022
+ delete existing.dataset.headingPlaceholder;
3023
+ existing.removeAttribute("aria-placeholder");
3024
+ existing.className = "cake-line";
3025
+ existing.removeAttribute("style");
3009
3026
  if (block.content.length === 0) {
3010
3027
  const firstChild = existing.firstChild;
3011
3028
  if (firstChild instanceof Text && existing.querySelector("br")) {
@@ -3026,10 +3043,8 @@ function renderDocContent(doc, extensions, root) {
3026
3043
  return [existing];
3027
3044
  }
3028
3045
  const element = document.createElement("div");
3029
- element.setAttribute("data-block", "paragraph");
3030
3046
  element.setAttribute("data-line-index", String(currentLineIndex));
3031
3047
  element.classList.add("cake-line");
3032
- element.dataset.lineKind = "paragraph";
3033
3048
  if (block.content.length === 0) {
3034
3049
  const textNode = document.createTextNode("");
3035
3050
  createTextRun$1(textNode);
@@ -4637,18 +4652,16 @@ const headingExtension = defineExtension({
4637
4652
  const level = typeof ((_a = block.data) == null ? void 0 : _a.level) === "number" ? block.data.level : 1;
4638
4653
  const normalizedLevel = Math.max(1, Math.min(3, level));
4639
4654
  const lineElement = document.createElement("div");
4640
- lineElement.setAttribute("data-block", "paragraph");
4641
4655
  lineElement.setAttribute("data-line-index", String(context.getLineIndex()));
4642
4656
  lineElement.classList.add(
4643
4657
  "cake-line",
4644
4658
  "is-heading",
4645
4659
  `is-heading-${normalizedLevel}`
4646
4660
  );
4647
- lineElement.dataset.lineKind = "heading";
4648
- lineElement.dataset.headingLevel = String(normalizedLevel);
4649
4661
  context.incrementLineIndex();
4650
4662
  const paragraph = block.blocks[0];
4651
4663
  if ((paragraph == null ? void 0 : paragraph.type) === "paragraph" && paragraph.content.length > 0) {
4664
+ lineElement.removeAttribute("aria-placeholder");
4652
4665
  const mergedContent = mergeInlineForRender(paragraph.content);
4653
4666
  for (const inline of mergedContent) {
4654
4667
  for (const node of context.renderInline(inline)) {
@@ -4656,7 +4669,10 @@ const headingExtension = defineExtension({
4656
4669
  }
4657
4670
  }
4658
4671
  } else {
4659
- lineElement.dataset.headingPlaceholder = `Heading ${normalizedLevel}`;
4672
+ lineElement.setAttribute(
4673
+ "aria-placeholder",
4674
+ `Heading ${normalizedLevel}`
4675
+ );
4660
4676
  const node = document.createTextNode("");
4661
4677
  context.createTextRun(node);
4662
4678
  lineElement.append(node);
@@ -5663,7 +5679,6 @@ const listExtension = defineExtension({
5663
5679
  return null;
5664
5680
  }
5665
5681
  const element = document.createElement("div");
5666
- element.setAttribute("data-block", "paragraph");
5667
5682
  element.setAttribute("data-line-index", String(context.getLineIndex()));
5668
5683
  element.classList.add("cake-line", "is-list");
5669
5684
  context.incrementLineIndex();
@@ -7599,8 +7614,6 @@ const HISTORY_GROUPING_INTERVAL_MS = 500;
7599
7614
  const MAX_UNDO_STACK_SIZE = 100;
7600
7615
  class CakeEngine {
7601
7616
  constructor(options) {
7602
- this.originalCaretRangeFromPoint = null;
7603
- this.patchedCaretRangeFromPoint = null;
7604
7617
  this.contentRoot = null;
7605
7618
  this.domMap = null;
7606
7619
  this.isApplyingSelection = false;
@@ -7663,6 +7676,7 @@ class CakeEngine {
7663
7676
  this.hasMovedSincePointerDown = false;
7664
7677
  this.lastTouchTime = 0;
7665
7678
  this.container = options.container;
7679
+ this.contentRoot = options.contentRoot ?? null;
7666
7680
  this.extensions = options.extensions ?? bundledExtensions;
7667
7681
  this.runtime = createRuntime(this.extensions);
7668
7682
  this.state = this.runtime.createState(
@@ -7675,7 +7689,6 @@ class CakeEngine {
7675
7689
  this.spellCheckEnabled = options.spellCheckEnabled ?? true;
7676
7690
  this.render();
7677
7691
  this.attachListeners();
7678
- this.installCaretRangeFromPointShim();
7679
7692
  }
7680
7693
  get state() {
7681
7694
  return this._state;
@@ -7702,7 +7715,6 @@ class CakeEngine {
7702
7715
  }
7703
7716
  destroy() {
7704
7717
  this.detachListeners();
7705
- this.uninstallCaretRangeFromPointShim();
7706
7718
  this.clearCaretBlinkTimer();
7707
7719
  if (this.overlayUpdateId !== null) {
7708
7720
  window.cancelAnimationFrame(this.overlayUpdateId);
@@ -7740,7 +7752,7 @@ class CakeEngine {
7740
7752
  return this.contentRoot;
7741
7753
  }
7742
7754
  getOverlayRoot() {
7743
- return this.extensionsRoot;
7755
+ return this.ensureExtensionsRoot();
7744
7756
  }
7745
7757
  // Placeholder text is provided by the caller via the container's
7746
7758
  // `data-placeholder` attribute (set by the React wrapper).
@@ -7968,80 +7980,6 @@ class CakeEngine {
7968
7980
  this.container.removeEventListener("pointerup", this.handlePointerUpBound);
7969
7981
  this.detachDragListeners();
7970
7982
  }
7971
- installCaretRangeFromPointShim() {
7972
- const doc = document;
7973
- if (typeof doc.caretRangeFromPoint !== "function") {
7974
- return;
7975
- }
7976
- if (this.patchedCaretRangeFromPoint) {
7977
- return;
7978
- }
7979
- this.originalCaretRangeFromPoint = doc.caretRangeFromPoint.bind(document);
7980
- const patched = (x, y) => {
7981
- var _a;
7982
- const original = this.originalCaretRangeFromPoint;
7983
- const range = original ? original(x, y) : null;
7984
- const startNode = (range == null ? void 0 : range.startContainer) ?? null;
7985
- const startLine = startNode instanceof HTMLElement ? startNode.closest("[data-line-index]") : (_a = startNode == null ? void 0 : startNode.parentElement) == null ? void 0 : _a.closest("[data-line-index]");
7986
- if (startLine) {
7987
- return range;
7988
- }
7989
- const rect = this.container.getBoundingClientRect();
7990
- const isInside = x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
7991
- if (!isInside || !this.domMap) {
7992
- return range;
7993
- }
7994
- const docAny = document;
7995
- let node = null;
7996
- let offset = 0;
7997
- if (typeof docAny.caretPositionFromPoint === "function") {
7998
- const pos = docAny.caretPositionFromPoint(x, y);
7999
- node = (pos == null ? void 0 : pos.offsetNode) ?? null;
8000
- offset = (pos == null ? void 0 : pos.offset) ?? 0;
8001
- } else if (original) {
8002
- const fallback = original(x, y);
8003
- node = (fallback == null ? void 0 : fallback.startContainer) ?? null;
8004
- offset = (fallback == null ? void 0 : fallback.startOffset) ?? 0;
8005
- }
8006
- let resolved = null;
8007
- if (node instanceof Text) {
8008
- resolved = { node, offset };
8009
- } else if (node instanceof Element) {
8010
- resolved = resolveTextPoint(node, offset);
8011
- }
8012
- if (!resolved) {
8013
- return range;
8014
- }
8015
- const cursor = this.domMap.cursorAtDom(resolved.node, resolved.offset);
8016
- if (!cursor) {
8017
- return range;
8018
- }
8019
- const domPoint = this.domMap.domAtCursor(
8020
- cursor.cursorOffset,
8021
- cursor.affinity
8022
- );
8023
- if (!domPoint) {
8024
- return range;
8025
- }
8026
- const fixed = document.createRange();
8027
- fixed.setStart(domPoint.node, domPoint.offset);
8028
- fixed.setEnd(domPoint.node, domPoint.offset);
8029
- return fixed;
8030
- };
8031
- this.patchedCaretRangeFromPoint = patched;
8032
- doc.caretRangeFromPoint = patched;
8033
- }
8034
- uninstallCaretRangeFromPointShim() {
8035
- if (!this.originalCaretRangeFromPoint || !this.patchedCaretRangeFromPoint) {
8036
- return;
8037
- }
8038
- const doc = document;
8039
- if (doc.caretRangeFromPoint === this.patchedCaretRangeFromPoint) {
8040
- doc.caretRangeFromPoint = this.originalCaretRangeFromPoint;
8041
- }
8042
- this.originalCaretRangeFromPoint = null;
8043
- this.patchedCaretRangeFromPoint = null;
8044
- }
8045
7983
  render() {
8046
7984
  const perfEnabled = this.container.dataset.cakePerf === "1";
8047
7985
  let perfStart = 0;
@@ -8060,13 +7998,32 @@ class CakeEngine {
8060
7998
  }
8061
7999
  this.contentRoot = document.createElement("div");
8062
8000
  this.contentRoot.className = "cake-content";
8063
- if (this.isTouchDevice()) {
8064
- this.contentRoot.classList.add("cake-touch-mode");
8065
- }
8066
- this.updateContentRootAttributes();
8001
+ }
8002
+ if (this.isTouchDevice()) {
8003
+ this.contentRoot.classList.add("cake-touch-mode");
8004
+ }
8005
+ this.updateContentRootAttributes();
8006
+ if (!this.overlayRoot) {
8067
8007
  const overlay = this.ensureOverlayRoot();
8068
- const extensionsRoot = this.ensureExtensionsRoot();
8069
- this.container.replaceChildren(this.contentRoot, overlay, extensionsRoot);
8008
+ const extensionsRoot = this.extensionsRoot;
8009
+ const existingContainerChildren = Array.from(this.container.childNodes);
8010
+ const isCakeManagedContainerChild = (node) => node instanceof Element && (node.classList.contains("cake-content") || node.classList.contains("cake-selection-overlay") || node.classList.contains("cake-extension-overlay") || node.classList.contains("cake-placeholder"));
8011
+ const preservedContainerChildren = existingContainerChildren.filter(
8012
+ (node) => !isCakeManagedContainerChild(node)
8013
+ );
8014
+ if (this.contentRoot.parentElement === this.container) {
8015
+ this.container.append(overlay);
8016
+ if (extensionsRoot && !extensionsRoot.isConnected) {
8017
+ this.container.append(extensionsRoot);
8018
+ }
8019
+ } else {
8020
+ this.container.replaceChildren(
8021
+ this.contentRoot,
8022
+ overlay,
8023
+ ...extensionsRoot ? [extensionsRoot] : [],
8024
+ ...preservedContainerChildren
8025
+ );
8026
+ }
8070
8027
  this.attachDragListeners();
8071
8028
  }
8072
8029
  if (perfEnabled) {
@@ -8078,9 +8035,12 @@ class CakeEngine {
8078
8035
  this.contentRoot
8079
8036
  );
8080
8037
  const existingChildren = Array.from(this.contentRoot.childNodes);
8081
- const needsUpdate = content.length !== existingChildren.length || content.some((node, i) => node !== existingChildren[i]);
8038
+ const isManagedChild = (node) => node instanceof Element && node.hasAttribute("data-line-index");
8039
+ const existingManagedChildren = existingChildren.filter(isManagedChild);
8040
+ const preservedChildren = existingChildren.filter((node) => !isManagedChild(node));
8041
+ const needsUpdate = content.length !== existingManagedChildren.length || content.some((node, i) => node !== existingManagedChildren[i]);
8082
8042
  if (needsUpdate) {
8083
- this.contentRoot.replaceChildren(...content);
8043
+ this.contentRoot.replaceChildren(...content, ...preservedChildren);
8084
8044
  }
8085
8045
  this.domMap = map;
8086
8046
  if (perfEnabled) {
@@ -8768,81 +8728,21 @@ class CakeEngine {
8768
8728
  }
8769
8729
  handleBeforeInput(event) {
8770
8730
  if (this.readOnly || this.isComposing || event.isComposing) {
8771
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8772
- console.log("[SPELLCHECK] beforeinput ignored (readonly/composing)", {
8773
- inputType: event.inputType,
8774
- data: event.data,
8775
- cancelable: event.cancelable,
8776
- isComposing: this.isComposing,
8777
- eventIsComposing: event.isComposing,
8778
- readOnly: this.readOnly
8779
- });
8780
- }
8781
8731
  return;
8782
8732
  }
8783
8733
  if (!this.isEventTargetInContentRoot(event.target)) {
8784
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8785
- console.log(
8786
- "[SPELLCHECK] beforeinput ignored (target outside editor)",
8787
- {
8788
- inputType: event.inputType,
8789
- data: event.data,
8790
- cancelable: event.cancelable,
8791
- target: event.target
8792
- }
8793
- );
8794
- }
8795
8734
  return;
8796
8735
  }
8797
8736
  if (this.keydownHandledBeforeInput) {
8798
8737
  this.keydownHandledBeforeInput = false;
8799
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8800
- console.log(
8801
- "[SPELLCHECK] beforeinput skipped (keydownHandledBeforeInput)",
8802
- {
8803
- inputType: event.inputType,
8804
- data: event.data,
8805
- cancelable: event.cancelable
8806
- }
8807
- );
8808
- }
8809
8738
  event.preventDefault();
8810
8739
  return;
8811
8740
  }
8812
8741
  const intent = this.resolveBeforeInputIntent(event);
8813
8742
  if (!intent) {
8814
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText") {
8815
- console.log("[SPELLCHECK] beforeinput: no intent", {
8816
- inputType: event.inputType,
8817
- data: event.data,
8818
- cancelable: event.cancelable
8819
- });
8820
- }
8821
8743
  return;
8822
8744
  }
8823
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText" && intent.type === "replace-text") {
8824
- const selection = this.state.selection;
8825
- const focus = selection.start === selection.end ? selection.start : Math.max(selection.start, selection.end);
8826
- const preview = this.state.source.slice(
8827
- Math.max(0, focus - 24),
8828
- Math.min(this.state.source.length, focus + 24)
8829
- );
8830
- console.log("[SPELLCHECK] beforeinput: resolved intent", {
8831
- inputType: event.inputType,
8832
- data: event.data,
8833
- cancelable: event.cancelable,
8834
- intent,
8835
- currentSelection: this.state.selection,
8836
- sourcePreviewAroundFocus: preview
8837
- });
8838
- }
8839
8745
  event.preventDefault();
8840
- if (event.inputType === "insertReplacementText" || event.inputType === "insertText" && intent.type === "replace-text") {
8841
- console.log("[SPELLCHECK] beforeinput: after preventDefault", {
8842
- inputType: event.inputType,
8843
- defaultPrevented: event.defaultPrevented
8844
- });
8845
- }
8846
8746
  this.markBeforeInputHandled();
8847
8747
  this.suppressSelectionChangeForTick();
8848
8748
  this.applyInputIntent(intent);
@@ -8889,22 +8789,9 @@ class CakeEngine {
8889
8789
  return;
8890
8790
  }
8891
8791
  if (!this.isEventTargetInContentRoot(event.target)) {
8892
- if (event.inputType === "insertReplacementText") {
8893
- console.log("[SPELLCHECK] input ignored (target outside editor)", {
8894
- inputType: event.inputType,
8895
- data: event.data,
8896
- target: event.target
8897
- });
8898
- }
8899
8792
  return;
8900
8793
  }
8901
8794
  if (this.beforeInputHandled) {
8902
- if (event.inputType === "insertReplacementText") {
8903
- console.log("[SPELLCHECK] input ignored (handled via beforeinput)", {
8904
- inputType: event.inputType,
8905
- data: event.data
8906
- });
8907
- }
8908
8795
  return;
8909
8796
  }
8910
8797
  if (this.compositionCommit && event.inputType === "insertText") {
@@ -9016,57 +8903,20 @@ class CakeEngine {
9016
8903
  return null;
9017
8904
  }
9018
8905
  selectionFromTargetRangesWithStatus(event) {
9019
- const debug = event.inputType === "insertReplacementText";
9020
8906
  if (!event.getTargetRanges) {
9021
- if (debug) {
9022
- console.log("[SPELLCHECK][targetRanges] missing getTargetRanges()", {
9023
- inputType: event.inputType,
9024
- data: event.data,
9025
- cancelable: event.cancelable
9026
- });
9027
- }
9028
8907
  return { status: "none" };
9029
8908
  }
9030
8909
  const ranges = event.getTargetRanges();
9031
8910
  if (!ranges || ranges.length === 0) {
9032
- if (debug) {
9033
- console.log("[SPELLCHECK][targetRanges] no ranges", {
9034
- inputType: event.inputType,
9035
- data: event.data,
9036
- cancelable: event.cancelable
9037
- });
9038
- }
9039
8911
  return { status: "none" };
9040
8912
  }
9041
8913
  const range = ranges[0];
9042
- if (debug || event.inputType === "insertText") {
9043
- console.log("[SPELLCHECK][targetRanges] raw range", {
9044
- inputType: event.inputType,
9045
- data: event.data,
9046
- cancelable: event.cancelable,
9047
- startContainer: range.startContainer instanceof Element ? range.startContainer.tagName : range.startContainer.nodeName,
9048
- startOffset: range.startOffset,
9049
- endContainer: range.endContainer instanceof Element ? range.endContainer.tagName : range.endContainer.nodeName,
9050
- endOffset: range.endOffset,
9051
- startContained: this.container.contains(range.startContainer),
9052
- endContained: this.container.contains(range.endContainer)
9053
- });
9054
- }
9055
8914
  if (!this.container.contains(range.startContainer) || !this.container.contains(range.endContainer)) {
9056
8915
  return { status: "invalid" };
9057
8916
  }
9058
8917
  const start = this.cursorFromDom(range.startContainer, range.startOffset);
9059
8918
  const end = this.cursorFromDom(range.endContainer, range.endOffset);
9060
8919
  if (!start || !end) {
9061
- if (debug || event.inputType === "insertText") {
9062
- console.log("[SPELLCHECK][targetRanges] cursorFromDom failed", {
9063
- inputType: event.inputType,
9064
- data: event.data,
9065
- cancelable: event.cancelable,
9066
- start,
9067
- end
9068
- });
9069
- }
9070
8920
  return { status: "invalid" };
9071
8921
  }
9072
8922
  const affinity = start.cursorOffset === end.cursorOffset ? end.affinity : "forward";
@@ -9817,9 +9667,7 @@ class CakeEngine {
9817
9667
  return this.state.source;
9818
9668
  }
9819
9669
  const blocks = Array.from(
9820
- this.contentRoot.querySelectorAll(
9821
- '[data-block="paragraph"]'
9822
- )
9670
+ this.contentRoot.querySelectorAll(".cake-line")
9823
9671
  );
9824
9672
  if (blocks.length === 0) {
9825
9673
  return this.contentRoot.textContent ?? "";
@@ -10072,6 +9920,9 @@ class CakeEngine {
10072
9920
  root.style.zIndex = "50";
10073
9921
  root.style.overflow = "hidden";
10074
9922
  this.extensionsRoot = root;
9923
+ if (this.overlayRoot && !root.isConnected) {
9924
+ this.container.append(root);
9925
+ }
10075
9926
  return root;
10076
9927
  }
10077
9928
  updateExtensionsOverlayPosition() {
@@ -11637,13 +11488,15 @@ const CakeEditor = forwardRef(
11637
11488
  const onSelectionChangeRef = useRef(props.onSelectionChange);
11638
11489
  const lastEmittedValueRef = useRef(null);
11639
11490
  const lastEmittedSelectionRef = useRef(null);
11640
- const [overlayRoot, setOverlayRoot] = useState(null);
11641
11491
  const [contentRoot, setContentRoot] = useState(null);
11642
11492
  const baseExtensions = props.disableImageExtension ? bundledExtensionsWithoutImage : bundledExtensions;
11643
11493
  const allExtensionsRef = useRef([
11644
11494
  ...baseExtensions,
11645
11495
  ...props.extensions ?? []
11646
11496
  ]);
11497
+ const hasOverlayExtensions = allExtensionsRef.current.some(
11498
+ (ext) => ext.renderOverlay
11499
+ );
11647
11500
  useEffect(() => {
11648
11501
  onChangeRef.current = props.onChange;
11649
11502
  onSelectionChangeRef.current = props.onSelectionChange;
@@ -11688,12 +11541,10 @@ const CakeEditor = forwardRef(
11688
11541
  }
11689
11542
  });
11690
11543
  engineRef.current = engine;
11691
- setOverlayRoot(engine.getOverlayRoot());
11692
11544
  setContentRoot(engine.getContentRoot());
11693
11545
  return () => {
11694
11546
  engine.destroy();
11695
11547
  engineRef.current = null;
11696
- setOverlayRoot(null);
11697
11548
  setContentRoot(null);
11698
11549
  };
11699
11550
  }, []);
@@ -11821,7 +11672,6 @@ const CakeEditor = forwardRef(
11821
11672
  const overlayContext = containerRef.current && contentRoot ? {
11822
11673
  container: containerRef.current,
11823
11674
  contentRoot,
11824
- overlayRoot: overlayRoot ?? void 0,
11825
11675
  toOverlayRect: (rect) => {
11826
11676
  var _a;
11827
11677
  const containerRect = (_a = containerRef.current) == null ? void 0 : _a.getBoundingClientRect();
@@ -11862,9 +11712,6 @@ const CakeEditor = forwardRef(
11862
11712
  return ((_a = engineRef.current) == null ? void 0 : _a.executeCommand(command)) ?? false;
11863
11713
  }
11864
11714
  } : null;
11865
- const hasOverlayExtensions = allExtensionsRef.current.some(
11866
- (ext) => ext.renderOverlay
11867
- );
11868
11715
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "relative", height: "100%" }, children: [
11869
11716
  /* @__PURE__ */ jsxRuntimeExports.jsx(
11870
11717
  "div",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blankdotpage/cake",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",