@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.
- package/dist/cake/engine/cake-engine.d.ts +2 -5
- package/dist/index.cjs +74 -227
- package/dist/index.js +74 -227
- package/package.json +1 -1
|
@@ -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
|
|
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.
|
|
2874
|
-
|
|
2875
|
-
|
|
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
|
|
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
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
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.
|
|
8071
|
-
this.container.
|
|
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
|
|
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.
|
|
2872
|
-
|
|
2873
|
-
|
|
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
|
|
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
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
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.
|
|
8069
|
-
this.container.
|
|
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
|
|
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",
|