@markput/react 0.12.1 → 0.14.0
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/index.d.ts +163 -103
- package/index.d.ts.map +1 -1
- package/index.js +1936 -1607
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -58,13 +58,15 @@ var MarkputHandler = class {
|
|
|
58
58
|
this.store = store;
|
|
59
59
|
}
|
|
60
60
|
get container() {
|
|
61
|
-
return this.store.
|
|
61
|
+
return this.store.dom.container();
|
|
62
62
|
}
|
|
63
63
|
get overlay() {
|
|
64
64
|
return this.store.overlay.element();
|
|
65
65
|
}
|
|
66
66
|
focus() {
|
|
67
|
-
this.store.
|
|
67
|
+
const firstAddress = this.store.parsing.index().addressFor([0]);
|
|
68
|
+
if (firstAddress && this.store.dom.focusAddress(firstAddress).ok) return;
|
|
69
|
+
this.container?.focus();
|
|
68
70
|
}
|
|
69
71
|
};
|
|
70
72
|
//#endregion
|
|
@@ -79,192 +81,6 @@ var KeyGenerator = class {
|
|
|
79
81
|
}
|
|
80
82
|
};
|
|
81
83
|
//#endregion
|
|
82
|
-
//#region ../../core/src/shared/checkers/domGuards.ts
|
|
83
|
-
/** Type guard: checks if a value is an HTMLElement. */
|
|
84
|
-
function isHtmlElement(el) {
|
|
85
|
-
return typeof HTMLElement !== "undefined" && el instanceof HTMLElement;
|
|
86
|
-
}
|
|
87
|
-
/** Type guard: checks if a value is a Text node. */
|
|
88
|
-
function isTextNode(node) {
|
|
89
|
-
return node instanceof Text;
|
|
90
|
-
}
|
|
91
|
-
/** Get the i-th child of an element as HTMLElement, or undefined if out of bounds or wrong type. */
|
|
92
|
-
function childAt(parent, index) {
|
|
93
|
-
const child = parent?.children[index];
|
|
94
|
-
return child instanceof HTMLElement ? child : void 0;
|
|
95
|
-
}
|
|
96
|
-
/** Get all children of an element as HTMLElement[], filtering out non-HTML elements. */
|
|
97
|
-
function htmlChildren(parent) {
|
|
98
|
-
if (!parent) return [];
|
|
99
|
-
return Array.from(parent.children).filter((child) => child instanceof HTMLElement);
|
|
100
|
-
}
|
|
101
|
-
/** Get the first element child as HTMLElement, or null. */
|
|
102
|
-
function firstHtmlChild(parent) {
|
|
103
|
-
const child = parent?.firstElementChild;
|
|
104
|
-
return child instanceof HTMLElement ? child : null;
|
|
105
|
-
}
|
|
106
|
-
/** Get the last element child as HTMLElement, or null. */
|
|
107
|
-
function lastHtmlChild(parent) {
|
|
108
|
-
const child = parent?.lastElementChild;
|
|
109
|
-
return child instanceof HTMLElement ? child : null;
|
|
110
|
-
}
|
|
111
|
-
/** Safely narrow an event's target to Node. */
|
|
112
|
-
function nodeTarget(event) {
|
|
113
|
-
const { target } = event;
|
|
114
|
-
return target instanceof Node ? target : null;
|
|
115
|
-
}
|
|
116
|
-
/** Get the next node from a TreeWalker as Text, or null. */
|
|
117
|
-
function nextText(walker) {
|
|
118
|
-
const node = walker.nextNode();
|
|
119
|
-
return node?.nodeType === 3 ? node : null;
|
|
120
|
-
}
|
|
121
|
-
//#endregion
|
|
122
|
-
//#region ../../core/src/features/caret/Caret.ts
|
|
123
|
-
var Caret = class {
|
|
124
|
-
static get isSelectedPosition() {
|
|
125
|
-
const selection = window.getSelection();
|
|
126
|
-
if (!selection) return;
|
|
127
|
-
return selection.isCollapsed;
|
|
128
|
-
}
|
|
129
|
-
static getCurrentPosition() {
|
|
130
|
-
return window.getSelection()?.anchorOffset ?? 0;
|
|
131
|
-
}
|
|
132
|
-
static getFocusedSpan() {
|
|
133
|
-
return window.getSelection()?.anchorNode?.textContent ?? "";
|
|
134
|
-
}
|
|
135
|
-
static getSelectedNode() {
|
|
136
|
-
const node = window.getSelection()?.anchorNode;
|
|
137
|
-
if (node && document.contains(node)) return node;
|
|
138
|
-
throw new Error("Anchor node of selection is not exists!");
|
|
139
|
-
}
|
|
140
|
-
static getAbsolutePosition() {
|
|
141
|
-
const rect = window.getSelection()?.getRangeAt(0).getBoundingClientRect();
|
|
142
|
-
if (rect) return {
|
|
143
|
-
left: rect.left,
|
|
144
|
-
top: rect.top + rect.height + 1
|
|
145
|
-
};
|
|
146
|
-
return {
|
|
147
|
-
left: 0,
|
|
148
|
-
top: 0
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
/** Returns the raw DOMRect of the current caret position, or null if unavailable. */
|
|
152
|
-
static getCaretRect() {
|
|
153
|
-
try {
|
|
154
|
-
return (window.getSelection()?.getRangeAt(0))?.getBoundingClientRect() ?? null;
|
|
155
|
-
} catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Returns true if the caret is on the first visual line of the element.
|
|
161
|
-
*/
|
|
162
|
-
static isCaretOnFirstLine(element) {
|
|
163
|
-
const caretRect = this.getCaretRect();
|
|
164
|
-
if (!caretRect || caretRect.height === 0) return true;
|
|
165
|
-
const elRect = element.getBoundingClientRect();
|
|
166
|
-
return caretRect.top < elRect.top + caretRect.height + 2;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Returns true if the caret is on the last visual line of the element.
|
|
170
|
-
*/
|
|
171
|
-
static isCaretOnLastLine(element) {
|
|
172
|
-
const caretRect = this.getCaretRect();
|
|
173
|
-
if (!caretRect || caretRect.height === 0) return true;
|
|
174
|
-
const elRect = element.getBoundingClientRect();
|
|
175
|
-
return caretRect.bottom > elRect.bottom - caretRect.height - 2;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Positions the caret in `element` at the character closest to the given x coordinate.
|
|
179
|
-
* `y` defaults to the vertical center of the element.
|
|
180
|
-
*/
|
|
181
|
-
static setAtX(element, x, y) {
|
|
182
|
-
const elRect = element.getBoundingClientRect();
|
|
183
|
-
const targetY = y ?? elRect.top + elRect.height / 2;
|
|
184
|
-
const caretDoc = document;
|
|
185
|
-
const caretPos = caretDoc.caretRangeFromPoint?.(x, targetY) ?? caretDoc.caretPositionFromPoint?.(x, targetY);
|
|
186
|
-
if (!caretPos) return;
|
|
187
|
-
const sel = window.getSelection();
|
|
188
|
-
if (!sel) return;
|
|
189
|
-
let domRange;
|
|
190
|
-
if (caretPos instanceof Range) domRange = caretPos;
|
|
191
|
-
else if ("offsetNode" in caretPos) {
|
|
192
|
-
domRange = document.createRange();
|
|
193
|
-
domRange.setStart(caretPos.offsetNode, caretPos.offset);
|
|
194
|
-
domRange.collapse(true);
|
|
195
|
-
} else return;
|
|
196
|
-
if (!element.contains(domRange.startContainer)) {
|
|
197
|
-
this.setIndex(element, Infinity);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
sel.removeAllRanges();
|
|
201
|
-
sel.addRange(domRange);
|
|
202
|
-
}
|
|
203
|
-
static trySetIndex(element, offset) {
|
|
204
|
-
try {
|
|
205
|
-
this.setIndex(element, offset);
|
|
206
|
-
} catch (e) {
|
|
207
|
-
console.error(e);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Sets the caret at character `offset` within `element` by walking text nodes.
|
|
212
|
-
* Use Infinity to position at the very end of all text.
|
|
213
|
-
*/
|
|
214
|
-
static setIndex(element, offset) {
|
|
215
|
-
const selection = window.getSelection();
|
|
216
|
-
if (!selection) return;
|
|
217
|
-
const walker = document.createTreeWalker(element, 4);
|
|
218
|
-
let node = nextText(walker);
|
|
219
|
-
if (!node) return;
|
|
220
|
-
let remaining = isFinite(offset) ? Math.max(0, offset) : Infinity;
|
|
221
|
-
for (;;) {
|
|
222
|
-
const next = nextText(walker);
|
|
223
|
-
if (!next || remaining <= node.length) {
|
|
224
|
-
const charOffset = isFinite(remaining) ? Math.min(remaining, node.length) : node.length;
|
|
225
|
-
const range = document.createRange();
|
|
226
|
-
range.setStart(node, charOffset);
|
|
227
|
-
range.collapse(true);
|
|
228
|
-
selection.removeAllRanges();
|
|
229
|
-
selection.addRange(range);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
remaining -= node.length;
|
|
233
|
-
node = next;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
static getCaretIndex(element) {
|
|
237
|
-
let position = 0;
|
|
238
|
-
const selection = window.getSelection();
|
|
239
|
-
if (!selection?.rangeCount) return position;
|
|
240
|
-
const range = selection.getRangeAt(0);
|
|
241
|
-
const preCaretRange = range.cloneRange();
|
|
242
|
-
preCaretRange.selectNodeContents(element);
|
|
243
|
-
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
|
244
|
-
position = preCaretRange.toString().length;
|
|
245
|
-
return position;
|
|
246
|
-
}
|
|
247
|
-
static setCaretToEnd(element) {
|
|
248
|
-
if (!element) return;
|
|
249
|
-
this.setIndex(element, Infinity);
|
|
250
|
-
}
|
|
251
|
-
static getIndex() {
|
|
252
|
-
return window.getSelection()?.anchorOffset ?? NaN;
|
|
253
|
-
}
|
|
254
|
-
static setIndex1(offset) {
|
|
255
|
-
const selection = window.getSelection();
|
|
256
|
-
if (!selection?.anchorNode || !selection.rangeCount) return;
|
|
257
|
-
const range = selection.getRangeAt(0);
|
|
258
|
-
range.setStart(range.startContainer.firstChild ?? range.startContainer, offset);
|
|
259
|
-
range.setEnd(range.startContainer.firstChild ?? range.startContainer, offset);
|
|
260
|
-
}
|
|
261
|
-
setCaretRightTo(element, offset) {
|
|
262
|
-
const range = window.getSelection()?.getRangeAt(0);
|
|
263
|
-
range?.setStart(range.endContainer, offset);
|
|
264
|
-
range?.setEnd(range.endContainer, offset);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
//#endregion
|
|
268
84
|
//#region ../../core/src/shared/signals/alien-signals/system.ts
|
|
269
85
|
let ReactiveFlags = /* @__PURE__ */ function(ReactiveFlags) {
|
|
270
86
|
ReactiveFlags[ReactiveFlags["None"] = 0] = "None";
|
|
@@ -764,30 +580,6 @@ function batch(fn, opts) {
|
|
|
764
580
|
mutableScope = prevMutable;
|
|
765
581
|
}
|
|
766
582
|
}
|
|
767
|
-
function trigger(fn) {
|
|
768
|
-
const sub = {
|
|
769
|
-
deps: void 0,
|
|
770
|
-
depsTail: void 0,
|
|
771
|
-
flags: ReactiveFlags.Watching
|
|
772
|
-
};
|
|
773
|
-
const prevSub = setActiveSub(sub);
|
|
774
|
-
try {
|
|
775
|
-
fn();
|
|
776
|
-
} finally {
|
|
777
|
-
activeSub = prevSub;
|
|
778
|
-
let dep = sub.deps;
|
|
779
|
-
while (dep !== void 0) {
|
|
780
|
-
const subs = dep.dep.subs;
|
|
781
|
-
dep = unlink(dep, sub);
|
|
782
|
-
if (subs !== void 0) {
|
|
783
|
-
sub.flags = ReactiveFlags.None;
|
|
784
|
-
propagate(subs);
|
|
785
|
-
shallowPropagate(subs);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
if (!batchDepth) flush();
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
583
|
function untracked(fn) {
|
|
792
584
|
const prev = setActiveSub(void 0);
|
|
793
585
|
try {
|
|
@@ -803,358 +595,33 @@ function listen(target, event, handler, options) {
|
|
|
803
595
|
});
|
|
804
596
|
}
|
|
805
597
|
//#endregion
|
|
806
|
-
//#region ../../core/src/features/
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
store.nodes.focus.clear();
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
function recover(store) {
|
|
837
|
-
const recovery = store.caret.recovery();
|
|
838
|
-
if (!recovery) return;
|
|
839
|
-
const { anchor, caret, isNext } = recovery;
|
|
840
|
-
const isStale = !anchor.target || !anchor.target.isConnected;
|
|
841
|
-
let target;
|
|
842
|
-
switch (true) {
|
|
843
|
-
case isNext && isStale: {
|
|
844
|
-
const container = store.slots.container();
|
|
845
|
-
target = (recovery.childIndex != null ? childAt(container, recovery.childIndex + 2) : void 0) ?? store.nodes.focus.tail ?? void 0;
|
|
846
|
-
break;
|
|
847
|
-
}
|
|
848
|
-
case isNext:
|
|
849
|
-
target = anchor.prev.target;
|
|
850
|
-
break;
|
|
851
|
-
case isStale:
|
|
852
|
-
target = store.nodes.focus.head ?? void 0;
|
|
853
|
-
break;
|
|
854
|
-
default: target = anchor.next.target;
|
|
855
|
-
}
|
|
856
|
-
store.nodes.focus.target = target;
|
|
857
|
-
target?.focus();
|
|
858
|
-
queueMicrotask(() => {
|
|
859
|
-
if (!target?.isConnected) return;
|
|
860
|
-
store.nodes.focus.target = target;
|
|
861
|
-
store.nodes.focus.caret = caret;
|
|
862
|
-
});
|
|
863
|
-
store.caret.recovery(void 0);
|
|
864
|
-
}
|
|
865
|
-
//#endregion
|
|
866
|
-
//#region ../../core/src/features/caret/selection.ts
|
|
867
|
-
function enableSelection(store) {
|
|
868
|
-
let pressedNode = null;
|
|
869
|
-
let isPressed = false;
|
|
870
|
-
const scope = effectScope(() => {
|
|
871
|
-
listen(document, "mousedown", (e) => {
|
|
872
|
-
pressedNode = nodeTarget(e);
|
|
873
|
-
isPressed = true;
|
|
874
|
-
});
|
|
875
|
-
listen(document, "mousemove", (e) => {
|
|
876
|
-
const container = store.slots.container();
|
|
877
|
-
if (!container) return;
|
|
878
|
-
const currentIsPressed = isPressed;
|
|
879
|
-
const isNotInnerSome = !container.contains(pressedNode) || pressedNode !== e.target;
|
|
880
|
-
const isInside = window.getSelection()?.containsNode(container, true);
|
|
881
|
-
if (currentIsPressed && isNotInnerSome && isInside) {
|
|
882
|
-
if (store.caret.selecting() !== "drag") store.caret.selecting("drag");
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
listen(document, "mouseup", () => {
|
|
886
|
-
isPressed = false;
|
|
887
|
-
pressedNode = null;
|
|
888
|
-
if (store.caret.selecting() === "drag") {
|
|
889
|
-
const sel = window.getSelection();
|
|
890
|
-
if (!sel || sel.isCollapsed) store.caret.selecting(void 0);
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
listen(document, "selectionchange", () => {
|
|
894
|
-
if (store.caret.selecting() !== "drag") return;
|
|
895
|
-
const sel = window.getSelection();
|
|
896
|
-
if (!sel || sel.isCollapsed) store.caret.selecting(void 0);
|
|
897
|
-
});
|
|
898
|
-
alienEffect(() => {
|
|
899
|
-
if (store.caret.selecting() !== "drag") return;
|
|
900
|
-
const container = store.slots.container();
|
|
901
|
-
if (!container) return;
|
|
902
|
-
container.querySelectorAll("[contenteditable=\"true\"]").forEach((el) => el.contentEditable = "false");
|
|
903
|
-
});
|
|
904
|
-
});
|
|
905
|
-
return () => {
|
|
906
|
-
if (store.caret.selecting() === "drag") store.caret.selecting(void 0);
|
|
907
|
-
scope();
|
|
908
|
-
pressedNode = null;
|
|
909
|
-
isPressed = false;
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
//#endregion
|
|
913
|
-
//#region ../../core/src/features/caret/CaretFeature.ts
|
|
914
|
-
var CaretFeature = class {
|
|
915
|
-
#disposers = [];
|
|
916
|
-
constructor(_store) {
|
|
917
|
-
this._store = _store;
|
|
918
|
-
this.recovery = signal(void 0);
|
|
919
|
-
this.selecting = signal(void 0);
|
|
920
|
-
}
|
|
921
|
-
enable() {
|
|
922
|
-
if (this.#disposers.length) return;
|
|
923
|
-
this.#disposers = [enableFocus(this._store), enableSelection(this._store)];
|
|
924
|
-
}
|
|
925
|
-
disable() {
|
|
926
|
-
this.#disposers.forEach((d) => d());
|
|
927
|
-
this.#disposers = [];
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
//#endregion
|
|
931
|
-
//#region ../../core/src/features/caret/selectionHelpers.ts
|
|
932
|
-
function isFullSelection(store) {
|
|
933
|
-
const sel = window.getSelection();
|
|
934
|
-
const container = store.slots.container();
|
|
935
|
-
if (!sel?.rangeCount || !container?.firstChild || !container.lastChild) return false;
|
|
936
|
-
try {
|
|
937
|
-
const range = sel.getRangeAt(0);
|
|
938
|
-
return container.contains(range.startContainer) && container.contains(range.endContainer) && range.toString().length > 0;
|
|
939
|
-
} catch {
|
|
940
|
-
return false;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
function selectAllText(store, event) {
|
|
944
|
-
if ((event.ctrlKey || event.metaKey) && event.code === "KeyA") {
|
|
945
|
-
if (store.slots.isBlock()) return;
|
|
946
|
-
event.preventDefault();
|
|
947
|
-
const selection = window.getSelection();
|
|
948
|
-
const anchorNode = store.slots.container()?.firstChild;
|
|
949
|
-
const focusNode = store.slots.container()?.lastChild;
|
|
950
|
-
if (!selection || !anchorNode || !focusNode) return;
|
|
951
|
-
selection.setBaseAndExtent(anchorNode, 0, focusNode, 1);
|
|
952
|
-
store.caret.selecting("all");
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
//#endregion
|
|
956
|
-
//#region ../../core/src/shared/escape.ts
|
|
957
|
-
const escape = (str) => {
|
|
958
|
-
return str.replace(/[.*+?^${}()|[\]\\\\]/g, "\\$&");
|
|
959
|
-
};
|
|
960
|
-
//#endregion
|
|
961
|
-
//#region ../../core/src/features/caret/TriggerFinder.ts
|
|
962
|
-
/** Regex to match word characters from the start of a string */
|
|
963
|
-
const wordRegex = /* @__PURE__ */ new RegExp(/^\w*/);
|
|
964
|
-
var TriggerFinder = class TriggerFinder {
|
|
965
|
-
constructor() {
|
|
966
|
-
const caretPosition = Caret.getCurrentPosition();
|
|
967
|
-
this.node = Caret.getSelectedNode();
|
|
968
|
-
this.span = Caret.getFocusedSpan();
|
|
969
|
-
this.dividedText = this.getDividedTextBy(caretPosition);
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Find overlay match in text using provided options and trigger extractor.
|
|
973
|
-
* @template T - Type of option objects
|
|
974
|
-
* @param options - Array of options to search through
|
|
975
|
-
* @param getTrigger - Function that extracts trigger from each option
|
|
976
|
-
* @returns OverlayMatch with correct option type or undefined
|
|
977
|
-
*
|
|
978
|
-
* @example
|
|
979
|
-
* // React usage
|
|
980
|
-
* TriggerFinder.find(options, (opt) => opt.slotProps?.overlay?.trigger ?? '@')
|
|
981
|
-
*
|
|
982
|
-
* @example
|
|
983
|
-
* // Other framework usage
|
|
984
|
-
* TriggerFinder.find(vueOptions, (opt) => opt.overlay?.trigger ?? '@')
|
|
985
|
-
*/
|
|
986
|
-
static find(options, getTrigger) {
|
|
987
|
-
if (!options) return;
|
|
988
|
-
if (!Caret.isSelectedPosition) return;
|
|
989
|
-
try {
|
|
990
|
-
return new TriggerFinder().find(options, getTrigger);
|
|
991
|
-
} catch {
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
getDividedTextBy(position) {
|
|
996
|
-
return {
|
|
997
|
-
left: this.span.slice(0, position),
|
|
998
|
-
right: this.span.slice(position)
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Find overlay match in provided options.
|
|
1003
|
-
* @template T - Type of option objects
|
|
1004
|
-
* @param options - Array of options
|
|
1005
|
-
* @param getTrigger - Function to extract trigger from each option
|
|
1006
|
-
*/
|
|
1007
|
-
find(options, getTrigger) {
|
|
1008
|
-
for (let i = 0; i < options.length; i++) {
|
|
1009
|
-
const option = options[i];
|
|
1010
|
-
const trigger = getTrigger(option, i);
|
|
1011
|
-
if (!trigger) continue;
|
|
1012
|
-
const match = this.matchInTextVia(trigger);
|
|
1013
|
-
if (match) return {
|
|
1014
|
-
value: match.word,
|
|
1015
|
-
source: match.annotation,
|
|
1016
|
-
index: match.index,
|
|
1017
|
-
span: this.span,
|
|
1018
|
-
node: this.node,
|
|
1019
|
-
option
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
matchInTextVia(trigger = "@") {
|
|
1024
|
-
const rightMatch = this.matchRightPart();
|
|
1025
|
-
const leftMatch = this.matchLeftPart(trigger);
|
|
1026
|
-
if (leftMatch) return {
|
|
1027
|
-
word: leftMatch.word + rightMatch.word,
|
|
1028
|
-
annotation: leftMatch.annotation + rightMatch.word,
|
|
1029
|
-
index: leftMatch.index
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
matchRightPart() {
|
|
1033
|
-
const { right } = this.dividedText;
|
|
1034
|
-
return { word: right.match(wordRegex)?.[0] };
|
|
1035
|
-
}
|
|
1036
|
-
matchLeftPart(trigger) {
|
|
1037
|
-
const regex = this.makeTriggerRegex(trigger);
|
|
1038
|
-
const { left } = this.dividedText;
|
|
1039
|
-
const match = left.match(regex);
|
|
1040
|
-
if (!match) return;
|
|
1041
|
-
const [annotation, word] = match;
|
|
1042
|
-
return {
|
|
1043
|
-
word,
|
|
1044
|
-
annotation,
|
|
1045
|
-
index: match.index ?? 0
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
makeTriggerRegex(trigger) {
|
|
1049
|
-
const patten = escape(trigger) + "(\\w*)$";
|
|
1050
|
-
return new RegExp(patten);
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1053
|
-
//#endregion
|
|
1054
|
-
//#region ../../core/src/shared/classes/NodeProxy.ts
|
|
1055
|
-
var NodeProxy = class NodeProxy {
|
|
1056
|
-
#target;
|
|
1057
|
-
#store;
|
|
1058
|
-
get target() {
|
|
1059
|
-
return this.#target;
|
|
1060
|
-
}
|
|
1061
|
-
set target(value) {
|
|
1062
|
-
this.#target = isHtmlElement(value) ? value : void 0;
|
|
1063
|
-
}
|
|
1064
|
-
get next() {
|
|
1065
|
-
return new NodeProxy(this.target?.nextSibling, this.#store);
|
|
1066
|
-
}
|
|
1067
|
-
get prev() {
|
|
1068
|
-
return new NodeProxy(this.target?.previousSibling, this.#store);
|
|
1069
|
-
}
|
|
1070
|
-
get isSpan() {
|
|
1071
|
-
return this.index % 2 === 0;
|
|
1072
|
-
}
|
|
1073
|
-
get isMark() {
|
|
1074
|
-
return !this.isSpan;
|
|
1075
|
-
}
|
|
1076
|
-
get isEditable() {
|
|
1077
|
-
return this.target?.isContentEditable ?? false;
|
|
1078
|
-
}
|
|
1079
|
-
get isCaretAtBeginning() {
|
|
1080
|
-
if (!this.target) return false;
|
|
1081
|
-
return Caret.getCaretIndex(this.target) === 0;
|
|
1082
|
-
}
|
|
1083
|
-
get isCaretAtEnd() {
|
|
1084
|
-
if (!this.target) return false;
|
|
1085
|
-
return Caret.getCaretIndex(this.target) === this.target.textContent.length;
|
|
1086
|
-
}
|
|
1087
|
-
get index() {
|
|
1088
|
-
if (!this.target?.parentElement) return -1;
|
|
1089
|
-
return [...this.target.parentElement.children].indexOf(this.target);
|
|
1090
|
-
}
|
|
1091
|
-
get caret() {
|
|
1092
|
-
if (this.target) return Caret.getCaretIndex(this.target);
|
|
1093
|
-
return -1;
|
|
1094
|
-
}
|
|
1095
|
-
set caret(value) {
|
|
1096
|
-
if (this.target) Caret.trySetIndex(this.target, value);
|
|
1097
|
-
}
|
|
1098
|
-
get length() {
|
|
1099
|
-
return this.target?.textContent.length ?? -1;
|
|
1100
|
-
}
|
|
1101
|
-
get content() {
|
|
1102
|
-
return this.target?.textContent ?? "";
|
|
1103
|
-
}
|
|
1104
|
-
set content(value) {
|
|
1105
|
-
if (this.target) this.target.textContent = value ?? "";
|
|
1106
|
-
}
|
|
1107
|
-
get head() {
|
|
1108
|
-
return firstHtmlChild(this.#store.slots.container() ?? void 0);
|
|
1109
|
-
}
|
|
1110
|
-
get tail() {
|
|
1111
|
-
return lastHtmlChild(this.#store.slots.container() ?? void 0);
|
|
1112
|
-
}
|
|
1113
|
-
get isFocused() {
|
|
1114
|
-
return this.target === document.activeElement;
|
|
1115
|
-
}
|
|
1116
|
-
constructor(target, store) {
|
|
1117
|
-
this.target = target;
|
|
1118
|
-
this.#store = store;
|
|
1119
|
-
}
|
|
1120
|
-
setCaretToEnd() {
|
|
1121
|
-
Caret.setCaretToEnd(this.target);
|
|
1122
|
-
}
|
|
1123
|
-
focus() {
|
|
1124
|
-
this.target?.focus();
|
|
1125
|
-
}
|
|
1126
|
-
clear() {
|
|
1127
|
-
this.target = void 0;
|
|
1128
|
-
}
|
|
1129
|
-
};
|
|
1130
|
-
//#endregion
|
|
1131
|
-
//#region ../../core/src/features/parsing/parser/constants.ts
|
|
1132
|
-
/**
|
|
1133
|
-
* Constants for ParserV2 - modern markup parser with nesting support
|
|
1134
|
-
*
|
|
1135
|
-
* This module contains the placeholder constants used by ParserV2.
|
|
1136
|
-
* Unlike the legacy Parser, ParserV2 supports:
|
|
1137
|
-
* - `__value__` - main content (replaces `__label__`)
|
|
1138
|
-
* - `__meta__` - metadata (replaces `__value__`)
|
|
1139
|
-
* - `__slot__` - nested content (new feature)
|
|
1140
|
-
*
|
|
1141
|
-
* For legacy Parser compatibility, see ../Parser/constants.ts
|
|
1142
|
-
* For Markup types, see ./types.ts
|
|
1143
|
-
*/
|
|
1144
|
-
const PLACEHOLDER = {
|
|
1145
|
-
Value: "__value__",
|
|
1146
|
-
Meta: "__meta__",
|
|
1147
|
-
Slot: "__slot__"
|
|
1148
|
-
};
|
|
1149
|
-
/**
|
|
1150
|
-
* Gap types used in markup descriptors
|
|
1151
|
-
* Represents the content type in gaps between segments
|
|
1152
|
-
*/
|
|
1153
|
-
const GAP_TYPE = {
|
|
1154
|
-
Value: "value",
|
|
1155
|
-
Meta: "meta",
|
|
1156
|
-
Slot: "slot"
|
|
1157
|
-
};
|
|
598
|
+
//#region ../../core/src/features/parsing/parser/constants.ts
|
|
599
|
+
/**
|
|
600
|
+
* Constants for ParserV2 - modern markup parser with nesting support
|
|
601
|
+
*
|
|
602
|
+
* This module contains the placeholder constants used by ParserV2.
|
|
603
|
+
* Unlike the legacy Parser, ParserV2 supports:
|
|
604
|
+
* - `__value__` - main content (replaces `__label__`)
|
|
605
|
+
* - `__meta__` - metadata (replaces `__value__`)
|
|
606
|
+
* - `__slot__` - nested content (new feature)
|
|
607
|
+
*
|
|
608
|
+
* For legacy Parser compatibility, see ../Parser/constants.ts
|
|
609
|
+
* For Markup types, see ./types.ts
|
|
610
|
+
*/
|
|
611
|
+
const PLACEHOLDER = {
|
|
612
|
+
Value: "__value__",
|
|
613
|
+
Meta: "__meta__",
|
|
614
|
+
Slot: "__slot__"
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* Gap types used in markup descriptors
|
|
618
|
+
* Represents the content type in gaps between segments
|
|
619
|
+
*/
|
|
620
|
+
const GAP_TYPE = {
|
|
621
|
+
Value: "value",
|
|
622
|
+
Meta: "meta",
|
|
623
|
+
Slot: "slot"
|
|
624
|
+
};
|
|
1158
625
|
//#endregion
|
|
1159
626
|
//#region ../../core/src/features/parsing/parser/core/MarkupDescriptor.ts
|
|
1160
627
|
/**
|
|
@@ -1640,6 +1107,11 @@ var PatternMatcher = class {
|
|
|
1640
1107
|
}
|
|
1641
1108
|
};
|
|
1642
1109
|
//#endregion
|
|
1110
|
+
//#region ../../core/src/shared/escape.ts
|
|
1111
|
+
const escape = (str) => {
|
|
1112
|
+
return str.replace(/[.*+?^${}()|[\]\\\\]/g, "\\$&");
|
|
1113
|
+
};
|
|
1114
|
+
//#endregion
|
|
1643
1115
|
//#region ../../core/src/features/parsing/parser/core/SegmentMatcher.ts
|
|
1644
1116
|
/**
|
|
1645
1117
|
* Computes regex pattern for dynamic segment using pre-computed exclusions
|
|
@@ -1986,11 +1458,7 @@ function processTokensWithCallback(tokens, callback) {
|
|
|
1986
1458
|
* ```
|
|
1987
1459
|
*/
|
|
1988
1460
|
function annotate(markup, params) {
|
|
1989
|
-
|
|
1990
|
-
if (params.value !== void 0) annotation = annotation.replaceAll(PLACEHOLDER.Value, params.value);
|
|
1991
|
-
if (params.meta !== void 0) annotation = annotation.replaceAll(PLACEHOLDER.Meta, params.meta);
|
|
1992
|
-
if (params.slot !== void 0) annotation = annotation.replaceAll(PLACEHOLDER.Slot, params.slot);
|
|
1993
|
-
return annotation;
|
|
1461
|
+
return markup.replaceAll(PLACEHOLDER.Value, params.value ?? "").replaceAll(PLACEHOLDER.Meta, params.meta ?? "").replaceAll(PLACEHOLDER.Slot, params.slot ?? "");
|
|
1994
1462
|
}
|
|
1995
1463
|
//#endregion
|
|
1996
1464
|
//#region ../../core/src/features/parsing/parser/utils/toString.ts
|
|
@@ -2259,100 +1727,7 @@ function findToken(tokens, target, depth = 0, parent) {
|
|
|
2259
1727
|
}
|
|
2260
1728
|
}
|
|
2261
1729
|
//#endregion
|
|
2262
|
-
//#region ../../core/src/features/parsing/preparsing/utils/findGap.ts
|
|
2263
|
-
function findGap(previous = "", current = "") {
|
|
2264
|
-
if (previous === current) return {};
|
|
2265
|
-
let left;
|
|
2266
|
-
for (let i = 0; i < previous.length; i++) if (previous[i] !== current[i]) {
|
|
2267
|
-
left = i;
|
|
2268
|
-
break;
|
|
2269
|
-
}
|
|
2270
|
-
let right;
|
|
2271
|
-
for (let i = 1; i <= previous.length; i++) if (previous.at(-i) !== current.at(-i)) {
|
|
2272
|
-
right = previous.length - i + 1;
|
|
2273
|
-
break;
|
|
2274
|
-
}
|
|
2275
|
-
return {
|
|
2276
|
-
left,
|
|
2277
|
-
right
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
|
-
//#endregion
|
|
2281
|
-
//#region ../../core/src/features/parsing/preparsing/utils/getClosestIndexes.ts
|
|
2282
|
-
function getClosestIndexes(array, target) {
|
|
2283
|
-
let left = -1, right = array.length;
|
|
2284
|
-
while (right - left > 1) {
|
|
2285
|
-
const middle = Math.round((left + right) / 2);
|
|
2286
|
-
if (array[middle] <= target) left = middle;
|
|
2287
|
-
else right = middle;
|
|
2288
|
-
}
|
|
2289
|
-
if (array[left] == target) right = left;
|
|
2290
|
-
return [left, right].filter((v) => array[v] !== void 0);
|
|
2291
|
-
}
|
|
2292
|
-
//#endregion
|
|
2293
1730
|
//#region ../../core/src/features/parsing/utils/valueParser.ts
|
|
2294
|
-
function getTokensByUI(store) {
|
|
2295
|
-
const { focus } = store.nodes;
|
|
2296
|
-
const parser = store.parsing.parser();
|
|
2297
|
-
const tokens = store.parsing.tokens();
|
|
2298
|
-
if (!parser) return tokens;
|
|
2299
|
-
const parsed = parser.parse(focus.content);
|
|
2300
|
-
if (parsed.length <= 1) return tokens;
|
|
2301
|
-
return tokens.toSpliced(focus.index, 1, ...parsed);
|
|
2302
|
-
}
|
|
2303
|
-
function computeTokensFromValue(store) {
|
|
2304
|
-
const value = store.props.value();
|
|
2305
|
-
const previousValue = store.value.last();
|
|
2306
|
-
const gap = findGap(previousValue, value);
|
|
2307
|
-
if (!gap.left && !gap.right) {
|
|
2308
|
-
store.value.last(value);
|
|
2309
|
-
return store.parsing.tokens();
|
|
2310
|
-
}
|
|
2311
|
-
if (gap.left === 0 && previousValue !== void 0 && gap.right !== void 0 && gap.right >= previousValue.length) {
|
|
2312
|
-
store.value.last(value);
|
|
2313
|
-
return parseWithParser(store, value ?? "");
|
|
2314
|
-
}
|
|
2315
|
-
store.value.last(value);
|
|
2316
|
-
const ranges = getRangeMap(store);
|
|
2317
|
-
const tokens = store.parsing.tokens();
|
|
2318
|
-
if (gap.left !== void 0 && ranges.includes(gap.left) && gap.right !== void 0 && Math.abs(gap.left - gap.right) > 1) {
|
|
2319
|
-
const updatedIndex = ranges.indexOf(gap.left);
|
|
2320
|
-
if (updatedIndex > 0) {
|
|
2321
|
-
const parsed = parseUnionLabels(store, updatedIndex - 1, updatedIndex);
|
|
2322
|
-
return tokens.toSpliced(updatedIndex - 1, 2, ...parsed);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
if (gap.left !== void 0) {
|
|
2326
|
-
const [updatedIndex] = getClosestIndexes(ranges, gap.left);
|
|
2327
|
-
const parsed = parseUnionLabels(store, updatedIndex);
|
|
2328
|
-
if (parsed.length === 1) return tokens;
|
|
2329
|
-
return tokens.toSpliced(updatedIndex, 1, ...parsed);
|
|
2330
|
-
}
|
|
2331
|
-
if (gap.right !== void 0) {
|
|
2332
|
-
const [updatedIndex] = getClosestIndexes(ranges, gap.right);
|
|
2333
|
-
const parsed = parseUnionLabels(store, updatedIndex);
|
|
2334
|
-
if (parsed.length === 1) return tokens;
|
|
2335
|
-
return tokens.toSpliced(updatedIndex, 1, ...parsed);
|
|
2336
|
-
}
|
|
2337
|
-
return parseWithParser(store, value ?? "");
|
|
2338
|
-
}
|
|
2339
|
-
function parseUnionLabels(store, ...indexes) {
|
|
2340
|
-
let span = "";
|
|
2341
|
-
const tokens = store.parsing.tokens();
|
|
2342
|
-
for (const index of indexes) {
|
|
2343
|
-
const token = tokens[index];
|
|
2344
|
-
span += token.content;
|
|
2345
|
-
}
|
|
2346
|
-
return parseWithParser(store, span);
|
|
2347
|
-
}
|
|
2348
|
-
function getRangeMap(store) {
|
|
2349
|
-
let position = 0;
|
|
2350
|
-
return store.parsing.tokens().map((token) => {
|
|
2351
|
-
const length = token.content.length;
|
|
2352
|
-
position += length;
|
|
2353
|
-
return position - length;
|
|
2354
|
-
});
|
|
2355
|
-
}
|
|
2356
1731
|
function parseWithParser(store, value) {
|
|
2357
1732
|
const parser = store.parsing.parser();
|
|
2358
1733
|
if (!parser) return [{
|
|
@@ -2366,12 +1741,86 @@ function parseWithParser(store, value) {
|
|
|
2366
1741
|
return parser.parse(value);
|
|
2367
1742
|
}
|
|
2368
1743
|
//#endregion
|
|
1744
|
+
//#region ../../core/src/features/parsing/tokenIndex.ts
|
|
1745
|
+
function pathEquals(a, b) {
|
|
1746
|
+
return a.length === b.length && a.every((part, index) => part === b[index]);
|
|
1747
|
+
}
|
|
1748
|
+
function pathKey(path) {
|
|
1749
|
+
return path.join(".");
|
|
1750
|
+
}
|
|
1751
|
+
function resolvePath(tokens, path) {
|
|
1752
|
+
if (path.length === 0) return void 0;
|
|
1753
|
+
let current = tokens;
|
|
1754
|
+
let token;
|
|
1755
|
+
for (const index of path) {
|
|
1756
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length) return void 0;
|
|
1757
|
+
token = current[index];
|
|
1758
|
+
current = token.type === "mark" ? token.children : [];
|
|
1759
|
+
}
|
|
1760
|
+
return token;
|
|
1761
|
+
}
|
|
1762
|
+
function snapshotTokenShape(token) {
|
|
1763
|
+
if (token.type === "text") return { kind: "text" };
|
|
1764
|
+
return {
|
|
1765
|
+
kind: "mark",
|
|
1766
|
+
descriptor: token.descriptor,
|
|
1767
|
+
descriptorIndex: token.descriptor.index
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
function shapeMatches(token, expected) {
|
|
1771
|
+
if (expected.kind === "text") return token.type === "text";
|
|
1772
|
+
return token.type === "mark" && token.descriptor === expected.descriptor && token.descriptor.index === expected.descriptorIndex;
|
|
1773
|
+
}
|
|
1774
|
+
function createTokenIndex(tokens, generation) {
|
|
1775
|
+
const paths = /* @__PURE__ */ new WeakMap();
|
|
1776
|
+
const visit = (items, parent) => {
|
|
1777
|
+
items.forEach((token, index) => {
|
|
1778
|
+
const path = [...parent, index];
|
|
1779
|
+
paths.set(token, path);
|
|
1780
|
+
if (token.type === "mark") visit(token.children, path);
|
|
1781
|
+
});
|
|
1782
|
+
};
|
|
1783
|
+
visit(tokens, []);
|
|
1784
|
+
return {
|
|
1785
|
+
generation,
|
|
1786
|
+
pathFor: (token) => paths.get(token),
|
|
1787
|
+
addressFor: (path) => resolvePath(tokens, path) ? {
|
|
1788
|
+
path: [...path],
|
|
1789
|
+
parseGeneration: generation
|
|
1790
|
+
} : void 0,
|
|
1791
|
+
resolve: (path) => resolvePath(tokens, path),
|
|
1792
|
+
resolveAddress(address, expected) {
|
|
1793
|
+
if (address.parseGeneration !== generation) return {
|
|
1794
|
+
ok: false,
|
|
1795
|
+
reason: "stale"
|
|
1796
|
+
};
|
|
1797
|
+
const token = resolvePath(tokens, address.path);
|
|
1798
|
+
if (!token) return {
|
|
1799
|
+
ok: false,
|
|
1800
|
+
reason: "stale"
|
|
1801
|
+
};
|
|
1802
|
+
if (expected && !shapeMatches(token, expected)) return {
|
|
1803
|
+
ok: false,
|
|
1804
|
+
reason: "stale"
|
|
1805
|
+
};
|
|
1806
|
+
return {
|
|
1807
|
+
ok: true,
|
|
1808
|
+
value: token
|
|
1809
|
+
};
|
|
1810
|
+
},
|
|
1811
|
+
key: pathKey,
|
|
1812
|
+
equals: pathEquals
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
//#endregion
|
|
2369
1816
|
//#region ../../core/src/features/parsing/ParseFeature.ts
|
|
2370
1817
|
var ParsingFeature = class {
|
|
1818
|
+
#generation = signal(0);
|
|
2371
1819
|
#scope;
|
|
2372
1820
|
constructor(_store) {
|
|
2373
1821
|
this._store = _store;
|
|
2374
1822
|
this.tokens = signal([]);
|
|
1823
|
+
this.index = computed(() => createTokenIndex(this.tokens(), this.#generation()));
|
|
2375
1824
|
this.parser = computed(() => {
|
|
2376
1825
|
if (!this._store.mark.enabled()) return;
|
|
2377
1826
|
const markups = this._store.props.options().map((opt) => opt.markup);
|
|
@@ -2380,6 +1829,15 @@ var ParsingFeature = class {
|
|
|
2380
1829
|
});
|
|
2381
1830
|
this.reparse = event();
|
|
2382
1831
|
}
|
|
1832
|
+
parseValue(value) {
|
|
1833
|
+
return parseWithParser(this._store, value);
|
|
1834
|
+
}
|
|
1835
|
+
acceptTokens(tokens) {
|
|
1836
|
+
batch(() => {
|
|
1837
|
+
this.tokens(tokens);
|
|
1838
|
+
this.#generation(this.#generation() + 1);
|
|
1839
|
+
}, { mutable: true });
|
|
1840
|
+
}
|
|
2383
1841
|
enable() {
|
|
2384
1842
|
if (this.#scope) return;
|
|
2385
1843
|
this.sync();
|
|
@@ -2392,29 +1850,450 @@ var ParsingFeature = class {
|
|
|
2392
1850
|
this.#scope?.();
|
|
2393
1851
|
this.#scope = void 0;
|
|
2394
1852
|
}
|
|
2395
|
-
sync() {
|
|
2396
|
-
|
|
2397
|
-
this.tokens(parseWithParser(this._store, inputValue));
|
|
2398
|
-
this._store.value.last(inputValue);
|
|
1853
|
+
sync(value = this._store.value.current()) {
|
|
1854
|
+
this.acceptTokens(this.parseValue(value));
|
|
2399
1855
|
}
|
|
2400
1856
|
#subscribeParse() {
|
|
2401
1857
|
watch(this.reparse, () => {
|
|
2402
1858
|
if (this._store.caret.recovery()) {
|
|
2403
1859
|
const text = toString(this.tokens());
|
|
2404
|
-
this.
|
|
2405
|
-
this._store.value.last(text);
|
|
1860
|
+
this.acceptTokens(this.parseValue(text));
|
|
2406
1861
|
return;
|
|
2407
1862
|
}
|
|
2408
|
-
this.
|
|
1863
|
+
this.sync();
|
|
2409
1864
|
});
|
|
2410
1865
|
}
|
|
2411
1866
|
#subscribeReactiveParse() {
|
|
2412
|
-
watch(computed(() =>
|
|
2413
|
-
if (!this._store.caret.recovery()) this.
|
|
1867
|
+
watch(computed(() => this.parser()), () => {
|
|
1868
|
+
if (!this._store.caret.recovery()) this.sync(this._store.value.current());
|
|
2414
1869
|
});
|
|
2415
1870
|
}
|
|
2416
1871
|
};
|
|
2417
1872
|
//#endregion
|
|
1873
|
+
//#region ../../core/src/shared/checkers/domGuards.ts
|
|
1874
|
+
/** Type guard: checks if a value is an HTMLElement. */
|
|
1875
|
+
function isHtmlElement(el) {
|
|
1876
|
+
return typeof HTMLElement !== "undefined" && el instanceof HTMLElement;
|
|
1877
|
+
}
|
|
1878
|
+
/** Get all children of an element as HTMLElement[], filtering out non-HTML elements. */
|
|
1879
|
+
function htmlChildren(parent) {
|
|
1880
|
+
if (!parent) return [];
|
|
1881
|
+
return Array.from(parent.children).filter((child) => child instanceof HTMLElement);
|
|
1882
|
+
}
|
|
1883
|
+
/** Get the first element child as HTMLElement, or null. */
|
|
1884
|
+
function firstHtmlChild(parent) {
|
|
1885
|
+
const child = parent?.firstElementChild;
|
|
1886
|
+
return child instanceof HTMLElement ? child : null;
|
|
1887
|
+
}
|
|
1888
|
+
/** Safely narrow an event's target to Node. */
|
|
1889
|
+
function nodeTarget(event) {
|
|
1890
|
+
const { target } = event;
|
|
1891
|
+
return target instanceof Node ? target : null;
|
|
1892
|
+
}
|
|
1893
|
+
/** Get the next node from a TreeWalker as Text, or null. */
|
|
1894
|
+
function nextText(walker) {
|
|
1895
|
+
const node = walker.nextNode();
|
|
1896
|
+
return node?.nodeType === 3 ? node : null;
|
|
1897
|
+
}
|
|
1898
|
+
//#endregion
|
|
1899
|
+
//#region ../../core/src/features/caret/Caret.ts
|
|
1900
|
+
var Caret = class {
|
|
1901
|
+
static get isSelectedPosition() {
|
|
1902
|
+
const selection = window.getSelection();
|
|
1903
|
+
if (!selection) return;
|
|
1904
|
+
return selection.isCollapsed;
|
|
1905
|
+
}
|
|
1906
|
+
static getCurrentPosition() {
|
|
1907
|
+
return window.getSelection()?.anchorOffset ?? 0;
|
|
1908
|
+
}
|
|
1909
|
+
static getFocusedSpan() {
|
|
1910
|
+
return window.getSelection()?.anchorNode?.textContent ?? "";
|
|
1911
|
+
}
|
|
1912
|
+
static getSelectedNode() {
|
|
1913
|
+
const node = window.getSelection()?.anchorNode;
|
|
1914
|
+
if (node && document.contains(node)) return node;
|
|
1915
|
+
throw new Error("Anchor node of selection is not exists!");
|
|
1916
|
+
}
|
|
1917
|
+
static getAbsolutePosition() {
|
|
1918
|
+
const rect = window.getSelection()?.getRangeAt(0).getBoundingClientRect();
|
|
1919
|
+
if (rect) return {
|
|
1920
|
+
left: rect.left,
|
|
1921
|
+
top: rect.top + rect.height + 1
|
|
1922
|
+
};
|
|
1923
|
+
return {
|
|
1924
|
+
left: 0,
|
|
1925
|
+
top: 0
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
/** Returns the raw DOMRect of the current caret position, or null if unavailable. */
|
|
1929
|
+
static getCaretRect() {
|
|
1930
|
+
try {
|
|
1931
|
+
return (window.getSelection()?.getRangeAt(0))?.getBoundingClientRect() ?? null;
|
|
1932
|
+
} catch {
|
|
1933
|
+
return null;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Returns true if the caret is on the first visual line of the element.
|
|
1938
|
+
*/
|
|
1939
|
+
static isCaretOnFirstLine(element) {
|
|
1940
|
+
const caretRect = this.getCaretRect();
|
|
1941
|
+
if (!caretRect || caretRect.height === 0) return true;
|
|
1942
|
+
const elRect = element.getBoundingClientRect();
|
|
1943
|
+
return caretRect.top < elRect.top + caretRect.height + 2;
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Returns true if the caret is on the last visual line of the element.
|
|
1947
|
+
*/
|
|
1948
|
+
static isCaretOnLastLine(element) {
|
|
1949
|
+
const caretRect = this.getCaretRect();
|
|
1950
|
+
if (!caretRect || caretRect.height === 0) return true;
|
|
1951
|
+
const elRect = element.getBoundingClientRect();
|
|
1952
|
+
return caretRect.bottom > elRect.bottom - caretRect.height - 2;
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Positions the caret in `element` at the character closest to the given x coordinate.
|
|
1956
|
+
* `y` defaults to the vertical center of the element.
|
|
1957
|
+
*/
|
|
1958
|
+
static setAtX(element, x, y) {
|
|
1959
|
+
const elRect = element.getBoundingClientRect();
|
|
1960
|
+
const targetY = y ?? elRect.top + elRect.height / 2;
|
|
1961
|
+
const caretDoc = document;
|
|
1962
|
+
const caretPos = caretDoc.caretRangeFromPoint?.(x, targetY) ?? caretDoc.caretPositionFromPoint?.(x, targetY);
|
|
1963
|
+
if (!caretPos) return;
|
|
1964
|
+
const sel = window.getSelection();
|
|
1965
|
+
if (!sel) return;
|
|
1966
|
+
let domRange;
|
|
1967
|
+
if (caretPos instanceof Range) domRange = caretPos;
|
|
1968
|
+
else if ("offsetNode" in caretPos) {
|
|
1969
|
+
domRange = document.createRange();
|
|
1970
|
+
domRange.setStart(caretPos.offsetNode, caretPos.offset);
|
|
1971
|
+
domRange.collapse(true);
|
|
1972
|
+
} else return;
|
|
1973
|
+
if (!element.contains(domRange.startContainer)) {
|
|
1974
|
+
this.setIndex(element, Infinity);
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
sel.removeAllRanges();
|
|
1978
|
+
sel.addRange(domRange);
|
|
1979
|
+
}
|
|
1980
|
+
static trySetIndex(element, offset) {
|
|
1981
|
+
try {
|
|
1982
|
+
this.setIndex(element, offset);
|
|
1983
|
+
} catch (e) {
|
|
1984
|
+
console.error(e);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Sets the caret at character `offset` within `element` by walking text nodes.
|
|
1989
|
+
* Use Infinity to position at the very end of all text.
|
|
1990
|
+
*/
|
|
1991
|
+
static setIndex(element, offset) {
|
|
1992
|
+
const selection = window.getSelection();
|
|
1993
|
+
if (!selection) return;
|
|
1994
|
+
const walker = document.createTreeWalker(element, 4);
|
|
1995
|
+
let node = nextText(walker);
|
|
1996
|
+
if (!node) return;
|
|
1997
|
+
let remaining = isFinite(offset) ? Math.max(0, offset) : Infinity;
|
|
1998
|
+
for (;;) {
|
|
1999
|
+
const next = nextText(walker);
|
|
2000
|
+
if (!next || remaining <= node.length) {
|
|
2001
|
+
const charOffset = isFinite(remaining) ? Math.min(remaining, node.length) : node.length;
|
|
2002
|
+
const range = document.createRange();
|
|
2003
|
+
range.setStart(node, charOffset);
|
|
2004
|
+
range.collapse(true);
|
|
2005
|
+
selection.removeAllRanges();
|
|
2006
|
+
selection.addRange(range);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
remaining -= node.length;
|
|
2010
|
+
node = next;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
static getCaretIndex(element) {
|
|
2014
|
+
let position = 0;
|
|
2015
|
+
const selection = window.getSelection();
|
|
2016
|
+
if (!selection?.rangeCount) return position;
|
|
2017
|
+
const range = selection.getRangeAt(0);
|
|
2018
|
+
const preCaretRange = range.cloneRange();
|
|
2019
|
+
preCaretRange.selectNodeContents(element);
|
|
2020
|
+
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
|
2021
|
+
position = preCaretRange.toString().length;
|
|
2022
|
+
return position;
|
|
2023
|
+
}
|
|
2024
|
+
static setCaretToEnd(element) {
|
|
2025
|
+
if (!element) return;
|
|
2026
|
+
this.setIndex(element, Infinity);
|
|
2027
|
+
}
|
|
2028
|
+
static getIndex() {
|
|
2029
|
+
return window.getSelection()?.anchorOffset ?? NaN;
|
|
2030
|
+
}
|
|
2031
|
+
static setIndex1(offset) {
|
|
2032
|
+
const selection = window.getSelection();
|
|
2033
|
+
if (!selection?.anchorNode || !selection.rangeCount) return;
|
|
2034
|
+
const range = selection.getRangeAt(0);
|
|
2035
|
+
range.setStart(range.startContainer.firstChild ?? range.startContainer, offset);
|
|
2036
|
+
range.setEnd(range.startContainer.firstChild ?? range.startContainer, offset);
|
|
2037
|
+
}
|
|
2038
|
+
setCaretRightTo(element, offset) {
|
|
2039
|
+
const range = window.getSelection()?.getRangeAt(0);
|
|
2040
|
+
range?.setStart(range.endContainer, offset);
|
|
2041
|
+
range?.setEnd(range.endContainer, offset);
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
//#endregion
|
|
2045
|
+
//#region ../../core/src/features/caret/focus.ts
|
|
2046
|
+
function enableFocus(store) {
|
|
2047
|
+
const container = store.dom.container();
|
|
2048
|
+
if (!container) return () => {};
|
|
2049
|
+
const scope = effectScope(() => {
|
|
2050
|
+
listen(container, "focusin", (e) => {
|
|
2051
|
+
const target = isHtmlElement(e.target) ? e.target : void 0;
|
|
2052
|
+
if (!target) {
|
|
2053
|
+
store.caret.location(void 0);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
const result = store.dom.locateNode(target);
|
|
2057
|
+
if (!result.ok) {
|
|
2058
|
+
if (result.reason === "control") return;
|
|
2059
|
+
store.caret.location(void 0);
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
const role = result.value.textElement?.contains(target) ? "text" : "markDescendant";
|
|
2063
|
+
store.caret.location({
|
|
2064
|
+
address: result.value.address,
|
|
2065
|
+
role
|
|
2066
|
+
});
|
|
2067
|
+
});
|
|
2068
|
+
listen(container, "focusout", () => {
|
|
2069
|
+
store.caret.location(void 0);
|
|
2070
|
+
});
|
|
2071
|
+
listen(container, "click", () => {
|
|
2072
|
+
const tokens = store.parsing.tokens();
|
|
2073
|
+
if (tokens.length === 1 && tokens[0].type === "text" && tokens[0].content === "") {
|
|
2074
|
+
const container = store.dom.container();
|
|
2075
|
+
(container ? firstHtmlChild(container) : null)?.focus();
|
|
2076
|
+
}
|
|
2077
|
+
});
|
|
2078
|
+
});
|
|
2079
|
+
return () => scope();
|
|
2080
|
+
}
|
|
2081
|
+
//#endregion
|
|
2082
|
+
//#region ../../core/src/features/caret/selection.ts
|
|
2083
|
+
function enableSelection(store) {
|
|
2084
|
+
let pressedNode = null;
|
|
2085
|
+
let isPressed = false;
|
|
2086
|
+
const scope = effectScope(() => {
|
|
2087
|
+
listen(document, "mousedown", (e) => {
|
|
2088
|
+
pressedNode = nodeTarget(e);
|
|
2089
|
+
isPressed = true;
|
|
2090
|
+
});
|
|
2091
|
+
listen(document, "mousemove", (e) => {
|
|
2092
|
+
const container = store.dom.container();
|
|
2093
|
+
if (!container) return;
|
|
2094
|
+
const currentIsPressed = isPressed;
|
|
2095
|
+
const isNotInnerSome = !container.contains(pressedNode) || pressedNode !== e.target;
|
|
2096
|
+
const isInside = window.getSelection()?.containsNode(container, true);
|
|
2097
|
+
if (currentIsPressed && isNotInnerSome && isInside) {
|
|
2098
|
+
if (store.caret.selecting() !== "drag") store.caret.selecting("drag");
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
listen(document, "mouseup", () => {
|
|
2102
|
+
isPressed = false;
|
|
2103
|
+
pressedNode = null;
|
|
2104
|
+
if (store.caret.selecting() === "drag") {
|
|
2105
|
+
const sel = window.getSelection();
|
|
2106
|
+
if (!sel || sel.isCollapsed) store.caret.selecting(void 0);
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
listen(document, "selectionchange", () => {
|
|
2110
|
+
const sel = window.getSelection();
|
|
2111
|
+
if (store.caret.selecting() === "drag" && (!sel || sel.isCollapsed)) store.caret.selecting(void 0);
|
|
2112
|
+
if (!sel?.focusNode) return;
|
|
2113
|
+
const result = store.dom.locateNode(sel.focusNode);
|
|
2114
|
+
if (!result.ok) {
|
|
2115
|
+
if (result.reason === "control") return;
|
|
2116
|
+
store.caret.location(void 0);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const role = result.value.textElement?.contains(sel.focusNode) ? "text" : "markDescendant";
|
|
2120
|
+
store.caret.location({
|
|
2121
|
+
address: result.value.address,
|
|
2122
|
+
role
|
|
2123
|
+
});
|
|
2124
|
+
});
|
|
2125
|
+
alienEffect(() => {
|
|
2126
|
+
if (store.caret.selecting() === "drag") store.dom.reconcile();
|
|
2127
|
+
});
|
|
2128
|
+
});
|
|
2129
|
+
return () => {
|
|
2130
|
+
if (store.caret.selecting() === "drag") store.caret.selecting(void 0);
|
|
2131
|
+
scope();
|
|
2132
|
+
pressedNode = null;
|
|
2133
|
+
isPressed = false;
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
//#endregion
|
|
2137
|
+
//#region ../../core/src/features/caret/CaretFeature.ts
|
|
2138
|
+
var CaretFeature = class {
|
|
2139
|
+
#disposers = [];
|
|
2140
|
+
constructor(_store) {
|
|
2141
|
+
this._store = _store;
|
|
2142
|
+
this.recovery = signal(void 0);
|
|
2143
|
+
this.location = signal(void 0);
|
|
2144
|
+
this.selecting = signal(void 0);
|
|
2145
|
+
}
|
|
2146
|
+
enable() {
|
|
2147
|
+
if (this.#disposers.length) return;
|
|
2148
|
+
this.#disposers = [enableFocus(this._store), enableSelection(this._store)];
|
|
2149
|
+
}
|
|
2150
|
+
disable() {
|
|
2151
|
+
this.#disposers.forEach((d) => d());
|
|
2152
|
+
this.#disposers = [];
|
|
2153
|
+
}
|
|
2154
|
+
placeAt(rawPosition, affinity = "after") {
|
|
2155
|
+
return this._store.dom.placeCaretAtRawPosition(rawPosition, affinity);
|
|
2156
|
+
}
|
|
2157
|
+
focus(address, boundary = "start") {
|
|
2158
|
+
return this._store.dom.focusAddress(address, boundary);
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
//#endregion
|
|
2162
|
+
//#region ../../core/src/features/caret/selectionHelpers.ts
|
|
2163
|
+
function isFullSelection(store) {
|
|
2164
|
+
const sel = window.getSelection();
|
|
2165
|
+
const container = store.dom.container();
|
|
2166
|
+
if (!sel?.rangeCount || !container?.firstChild || !container.lastChild) return false;
|
|
2167
|
+
try {
|
|
2168
|
+
const range = sel.getRangeAt(0);
|
|
2169
|
+
return container.contains(range.startContainer) && container.contains(range.endContainer) && range.toString().length > 0;
|
|
2170
|
+
} catch {
|
|
2171
|
+
return false;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
function selectAllText(store, event) {
|
|
2175
|
+
if ((event.ctrlKey || event.metaKey) && event.code === "KeyA") {
|
|
2176
|
+
if (store.slots.isBlock()) return;
|
|
2177
|
+
event.preventDefault();
|
|
2178
|
+
const selection = window.getSelection();
|
|
2179
|
+
const anchorNode = store.dom.container()?.firstChild;
|
|
2180
|
+
const focusNode = store.dom.container()?.lastChild;
|
|
2181
|
+
if (!selection || !anchorNode || !focusNode) return;
|
|
2182
|
+
selection.setBaseAndExtent(anchorNode, 0, focusNode, 1);
|
|
2183
|
+
store.caret.selecting("all");
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
//#endregion
|
|
2187
|
+
//#region ../../core/src/features/caret/TriggerFinder.ts
|
|
2188
|
+
/** Regex to match word characters from the start of a string */
|
|
2189
|
+
const wordRegex = /* @__PURE__ */ new RegExp(/^\w*/);
|
|
2190
|
+
var TriggerFinder = class TriggerFinder {
|
|
2191
|
+
constructor(store) {
|
|
2192
|
+
this.store = store;
|
|
2193
|
+
const caretPosition = Caret.getCurrentPosition();
|
|
2194
|
+
this.node = Caret.getSelectedNode();
|
|
2195
|
+
this.span = Caret.getFocusedSpan();
|
|
2196
|
+
this.dividedText = this.getDividedTextBy(caretPosition);
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Find overlay match in text using provided options and trigger extractor.
|
|
2200
|
+
* @template T - Type of option objects
|
|
2201
|
+
* @param options - Array of options to search through
|
|
2202
|
+
* @param getTrigger - Function that extracts trigger from each option
|
|
2203
|
+
* @returns OverlayMatch with correct option type or undefined
|
|
2204
|
+
*
|
|
2205
|
+
* @example
|
|
2206
|
+
* // React usage
|
|
2207
|
+
* TriggerFinder.find(options, (opt) => opt.slotProps?.overlay?.trigger ?? '@')
|
|
2208
|
+
*
|
|
2209
|
+
* @example
|
|
2210
|
+
* // Other framework usage
|
|
2211
|
+
* TriggerFinder.find(vueOptions, (opt) => opt.overlay?.trigger ?? '@')
|
|
2212
|
+
*/
|
|
2213
|
+
static find(options, getTrigger, store) {
|
|
2214
|
+
if (!options) return;
|
|
2215
|
+
if (!Caret.isSelectedPosition) return;
|
|
2216
|
+
try {
|
|
2217
|
+
return new TriggerFinder(store).find(options, getTrigger);
|
|
2218
|
+
} catch {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
getDividedTextBy(position) {
|
|
2223
|
+
return {
|
|
2224
|
+
left: this.span.slice(0, position),
|
|
2225
|
+
right: this.span.slice(position)
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Find overlay match in provided options.
|
|
2230
|
+
* @template T - Type of option objects
|
|
2231
|
+
* @param options - Array of options
|
|
2232
|
+
* @param getTrigger - Function to extract trigger from each option
|
|
2233
|
+
*/
|
|
2234
|
+
find(options, getTrigger) {
|
|
2235
|
+
for (let i = 0; i < options.length; i++) {
|
|
2236
|
+
const option = options[i];
|
|
2237
|
+
const trigger = getTrigger(option, i);
|
|
2238
|
+
if (!trigger) continue;
|
|
2239
|
+
const match = this.matchInTextVia(trigger);
|
|
2240
|
+
if (match) {
|
|
2241
|
+
const range = this.#rawRangeForMatch(match.annotation, match.index);
|
|
2242
|
+
if (!range) return void 0;
|
|
2243
|
+
return {
|
|
2244
|
+
value: match.word,
|
|
2245
|
+
source: match.annotation,
|
|
2246
|
+
range,
|
|
2247
|
+
span: this.span,
|
|
2248
|
+
node: this.node,
|
|
2249
|
+
option
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
#rawRangeForMatch(source, index) {
|
|
2255
|
+
if (!this.store) return {
|
|
2256
|
+
start: index,
|
|
2257
|
+
end: index + source.length
|
|
2258
|
+
};
|
|
2259
|
+
const boundary = this.store.dom.rawPositionFromBoundary(this.node, index + source.length, "after");
|
|
2260
|
+
if (!boundary.ok) return void 0;
|
|
2261
|
+
return {
|
|
2262
|
+
start: boundary.value - source.length,
|
|
2263
|
+
end: boundary.value
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
matchInTextVia(trigger = "@") {
|
|
2267
|
+
const rightMatch = this.matchRightPart();
|
|
2268
|
+
const leftMatch = this.matchLeftPart(trigger);
|
|
2269
|
+
if (leftMatch) return {
|
|
2270
|
+
word: leftMatch.word + rightMatch.word,
|
|
2271
|
+
annotation: leftMatch.annotation + rightMatch.word,
|
|
2272
|
+
index: leftMatch.index
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
matchRightPart() {
|
|
2276
|
+
const { right } = this.dividedText;
|
|
2277
|
+
return { word: right.match(wordRegex)?.[0] };
|
|
2278
|
+
}
|
|
2279
|
+
matchLeftPart(trigger) {
|
|
2280
|
+
const regex = this.makeTriggerRegex(trigger);
|
|
2281
|
+
const { left } = this.dividedText;
|
|
2282
|
+
const match = left.match(regex);
|
|
2283
|
+
if (!match) return;
|
|
2284
|
+
const [annotation, word] = match;
|
|
2285
|
+
return {
|
|
2286
|
+
word,
|
|
2287
|
+
annotation,
|
|
2288
|
+
index: match.index ?? 0
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
makeTriggerRegex(trigger) {
|
|
2292
|
+
const patten = escape(trigger) + "(\\w*)$";
|
|
2293
|
+
return new RegExp(patten);
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
//#endregion
|
|
2418
2297
|
//#region ../../core/src/features/clipboard/pasteMarkup.ts
|
|
2419
2298
|
/** Custom MIME type for markput markup syntax. */
|
|
2420
2299
|
const MARKPUT_MIME = "application/x-markput";
|
|
@@ -2442,98 +2321,25 @@ function consumeMarkupPaste(container) {
|
|
|
2442
2321
|
return markup;
|
|
2443
2322
|
}
|
|
2444
2323
|
//#endregion
|
|
2445
|
-
//#region ../../core/src/features/clipboard/selectionToTokens.ts
|
|
2446
|
-
/**
|
|
2447
|
-
* Walk up the DOM from `node` until reaching a direct child of `container`.
|
|
2448
|
-
* Returns the index of that child in container.children, or -1 if not found.
|
|
2449
|
-
*
|
|
2450
|
-
* Works for both drag and non-drag modes:
|
|
2451
|
-
* - Non-drag: container children are Token-rendered elements (1:1 with tokens)
|
|
2452
|
-
* - Drag: container children are Block wrappers (1:1 with tokens)
|
|
2453
|
-
* - Nested marks: walks past inner mark elements to the top-level container child
|
|
2454
|
-
*/
|
|
2455
|
-
function findContainerChildIndex(node, container) {
|
|
2456
|
-
let current = node instanceof HTMLElement ? node : node.parentElement;
|
|
2457
|
-
while (current && current.parentElement !== container) current = current.parentElement;
|
|
2458
|
-
if (!current) return -1;
|
|
2459
|
-
return Array.from(container.children).indexOf(current);
|
|
2460
|
-
}
|
|
2461
|
-
/**
|
|
2462
|
-
* Returns the character offset of a range boundary within a container child.
|
|
2463
|
-
* Walks all text nodes in document order to compute a cumulative character
|
|
2464
|
-
* offset, which correctly handles nested marks with multiple text nodes.
|
|
2465
|
-
* Falls back to 0 (start) or full text length (end) for out-of-child boundaries.
|
|
2466
|
-
*
|
|
2467
|
-
* Spec: selectionToTokens.spec.ts (unit) · Clipboard.react.spec.tsx (integration)
|
|
2468
|
-
*/
|
|
2469
|
-
function getBoundaryOffset(range, child, isStart) {
|
|
2470
|
-
const targetNode = isStart ? range.startContainer : range.endContainer;
|
|
2471
|
-
const targetOffset = isStart ? range.startOffset : range.endOffset;
|
|
2472
|
-
if (!child.contains(targetNode)) return isStart ? 0 : child.textContent.length;
|
|
2473
|
-
let charOffset = 0;
|
|
2474
|
-
const walker = document.createTreeWalker(child, NodeFilter.SHOW_TEXT);
|
|
2475
|
-
let current = walker.nextNode();
|
|
2476
|
-
while (current) {
|
|
2477
|
-
if (current === targetNode) return charOffset + targetOffset;
|
|
2478
|
-
charOffset += current.length;
|
|
2479
|
-
current = walker.nextNode();
|
|
2480
|
-
}
|
|
2481
|
-
return isStart ? 0 : child.textContent.length;
|
|
2482
|
-
}
|
|
2483
|
-
/**
|
|
2484
|
-
* Map a browser Selection to the subset of tokens it covers.
|
|
2485
|
-
* Returns null if selection is collapsed, empty, or outside the container.
|
|
2486
|
-
*/
|
|
2487
|
-
function selectionToTokens(store) {
|
|
2488
|
-
const container = store.slots.container();
|
|
2489
|
-
if (!container) return null;
|
|
2490
|
-
const sel = window.getSelection();
|
|
2491
|
-
if (!sel || sel.isCollapsed || !sel.rangeCount) return null;
|
|
2492
|
-
const range = sel.getRangeAt(0);
|
|
2493
|
-
if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) return null;
|
|
2494
|
-
const tokens = store.parsing.tokens();
|
|
2495
|
-
let startIndex = findContainerChildIndex(range.startContainer, container);
|
|
2496
|
-
let endIndex = findContainerChildIndex(range.endContainer, container);
|
|
2497
|
-
if (startIndex === -1 || endIndex === -1) return null;
|
|
2498
|
-
if (startIndex > endIndex) [startIndex, endIndex] = [endIndex, startIndex];
|
|
2499
|
-
const startChild = container.children.item(startIndex);
|
|
2500
|
-
const endChild = container.children.item(endIndex);
|
|
2501
|
-
return {
|
|
2502
|
-
tokens: tokens.slice(startIndex, endIndex + 1),
|
|
2503
|
-
startOffset: startChild ? getBoundaryOffset(range, startChild, true) : 0,
|
|
2504
|
-
endOffset: endChild ? getBoundaryOffset(range, endChild, false) : 0
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
//#endregion
|
|
2508
2324
|
//#region ../../core/src/features/clipboard/ClipboardFeature.ts
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
function
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
};
|
|
2528
|
-
if (isFirst) return {
|
|
2529
|
-
...token,
|
|
2530
|
-
content: token.content.slice(startOffset)
|
|
2531
|
-
};
|
|
2532
|
-
if (isLast) return {
|
|
2533
|
-
...token,
|
|
2534
|
-
content: token.content.slice(0, endOffset)
|
|
2535
|
-
};
|
|
2536
|
-
return token;
|
|
2325
|
+
function htmlFromRange(range) {
|
|
2326
|
+
const fragment = range.cloneContents();
|
|
2327
|
+
const div = document.createElement("div");
|
|
2328
|
+
div.appendChild(fragment);
|
|
2329
|
+
return div.innerHTML;
|
|
2330
|
+
}
|
|
2331
|
+
function serializeRawRange(tokens, range) {
|
|
2332
|
+
return toString(trimTokensForRawRange(tokens, range));
|
|
2333
|
+
}
|
|
2334
|
+
function trimTokensForRawRange(tokens, range) {
|
|
2335
|
+
return tokens.filter((token) => token.position.end > range.start && token.position.start < range.end).map((token) => {
|
|
2336
|
+
if (token.type === "text") {
|
|
2337
|
+
const start = Math.max(0, range.start - token.position.start);
|
|
2338
|
+
const end = Math.min(token.content.length, range.end - token.position.start);
|
|
2339
|
+
return Object.assign({}, token, { content: token.content.slice(start, end) });
|
|
2340
|
+
}
|
|
2341
|
+
if (token.children.length === 0) return token;
|
|
2342
|
+
return Object.assign({}, token, { children: trimTokensForRawRange(token.children, range) });
|
|
2537
2343
|
});
|
|
2538
2344
|
}
|
|
2539
2345
|
var ClipboardFeature = class {
|
|
@@ -2543,7 +2349,7 @@ var ClipboardFeature = class {
|
|
|
2543
2349
|
}
|
|
2544
2350
|
enable() {
|
|
2545
2351
|
if (this.#scope) return;
|
|
2546
|
-
const container = this.store.
|
|
2352
|
+
const container = this.store.dom.container();
|
|
2547
2353
|
if (!container) return;
|
|
2548
2354
|
this.#scope = effectScope(() => {
|
|
2549
2355
|
listen(container, "copy", (e) => {
|
|
@@ -2551,25 +2357,14 @@ var ClipboardFeature = class {
|
|
|
2551
2357
|
});
|
|
2552
2358
|
listen(container, "cut", (e) => {
|
|
2553
2359
|
if (!this.#handleCopy(e)) return;
|
|
2554
|
-
const
|
|
2555
|
-
if (!
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
const newValue = value.slice(0, rawStart) + value.slice(rawEnd);
|
|
2563
|
-
this.store.value.next(newValue);
|
|
2564
|
-
const newTokens = this.store.parsing.tokens();
|
|
2565
|
-
let targetIdx = newTokens.findIndex((t) => t.type === "text" && rawStart >= t.position.start && rawStart <= t.position.end);
|
|
2566
|
-
if (targetIdx === -1) targetIdx = newTokens.length - 1;
|
|
2567
|
-
const caretWithinToken = rawStart - newTokens[targetIdx].position.start;
|
|
2568
|
-
this.store.caret.recovery({
|
|
2569
|
-
anchor: this.store.nodes.focus,
|
|
2570
|
-
caret: caretWithinToken,
|
|
2571
|
-
isNext: true,
|
|
2572
|
-
childIndex: targetIdx - 2
|
|
2360
|
+
const raw = this.store.dom.readRawSelection();
|
|
2361
|
+
if (!raw.ok || raw.value.range.start === raw.value.range.end) return;
|
|
2362
|
+
this.store.value.replaceRange(raw.value.range, "", {
|
|
2363
|
+
source: "cut",
|
|
2364
|
+
recover: {
|
|
2365
|
+
kind: "caret",
|
|
2366
|
+
rawPosition: raw.value.range.start
|
|
2367
|
+
}
|
|
2573
2368
|
});
|
|
2574
2369
|
});
|
|
2575
2370
|
});
|
|
@@ -2579,20 +2374,15 @@ var ClipboardFeature = class {
|
|
|
2579
2374
|
this.#scope = void 0;
|
|
2580
2375
|
}
|
|
2581
2376
|
#handleCopy(e) {
|
|
2582
|
-
|
|
2583
|
-
|
|
2377
|
+
if (!this.store.dom.container()) return false;
|
|
2378
|
+
const raw = this.store.dom.readRawSelection();
|
|
2379
|
+
if (!raw.ok || raw.value.range.start === raw.value.range.end) return false;
|
|
2584
2380
|
const sel = window.getSelection();
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) return false;
|
|
2588
|
-
const result = selectionToTokens(this.store);
|
|
2589
|
-
if (!result) return false;
|
|
2381
|
+
const range = sel?.rangeCount ? sel.getRangeAt(0) : void 0;
|
|
2382
|
+
if (!range) return false;
|
|
2590
2383
|
const plainText = range.toString();
|
|
2591
|
-
const
|
|
2592
|
-
const
|
|
2593
|
-
div.appendChild(fragment);
|
|
2594
|
-
const html = div.innerHTML;
|
|
2595
|
-
const markup = toString(trimBoundaryTokens(result));
|
|
2384
|
+
const html = htmlFromRange(range);
|
|
2385
|
+
const markup = serializeRawRange(this.store.parsing.tokens(), raw.value.range);
|
|
2596
2386
|
e.preventDefault();
|
|
2597
2387
|
e.clipboardData?.setData("text/plain", plainText);
|
|
2598
2388
|
e.clipboardData?.setData("text/html", html);
|
|
@@ -2601,120 +2391,705 @@ var ClipboardFeature = class {
|
|
|
2601
2391
|
}
|
|
2602
2392
|
};
|
|
2603
2393
|
//#endregion
|
|
2604
|
-
//#region ../../core/src/features/dom/isTextTokenSpan.ts
|
|
2605
|
-
function isTextTokenSpan(el) {
|
|
2606
|
-
return el.tagName === "SPAN" && (el.attributes.length === 0 || el.attributes.length === 1 && el.hasAttribute("contenteditable"));
|
|
2607
|
-
}
|
|
2608
|
-
//#endregion
|
|
2609
2394
|
//#region ../../core/src/features/dom/DomFeature.ts
|
|
2395
|
+
function nextTextNode(walker) {
|
|
2396
|
+
const node = walker.nextNode();
|
|
2397
|
+
return node instanceof Text ? node : null;
|
|
2398
|
+
}
|
|
2399
|
+
function splitsSurrogatePair(text, offset) {
|
|
2400
|
+
if (offset <= 0 || offset >= text.length) return false;
|
|
2401
|
+
const prev = text.charCodeAt(offset - 1);
|
|
2402
|
+
const next = text.charCodeAt(offset);
|
|
2403
|
+
return prev >= 55296 && prev <= 56319 && next >= 56320 && next <= 57343;
|
|
2404
|
+
}
|
|
2405
|
+
function textOffsetWithin(surface, node, offset) {
|
|
2406
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
2407
|
+
if (splitsSurrogatePair(node.textContent ?? "", offset)) return void 0;
|
|
2408
|
+
return node instanceof Text ? textOffsetFromTreeWalker(surface, node, offset) : void 0;
|
|
2409
|
+
}
|
|
2410
|
+
if (node === surface) return elementBoundaryOffset(surface, offset);
|
|
2411
|
+
}
|
|
2412
|
+
function textOffsetFromTreeWalker(surface, target, targetOffset) {
|
|
2413
|
+
let total = 0;
|
|
2414
|
+
const walker = document.createTreeWalker(surface, NodeFilter.SHOW_TEXT);
|
|
2415
|
+
let current = nextTextNode(walker);
|
|
2416
|
+
while (current) {
|
|
2417
|
+
if (current === target) return total + targetOffset;
|
|
2418
|
+
total += current.length;
|
|
2419
|
+
current = nextTextNode(walker);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
function textLength(surface) {
|
|
2423
|
+
let total = 0;
|
|
2424
|
+
const walker = document.createTreeWalker(surface, NodeFilter.SHOW_TEXT);
|
|
2425
|
+
let current = nextTextNode(walker);
|
|
2426
|
+
while (current) {
|
|
2427
|
+
total += current.length;
|
|
2428
|
+
current = nextTextNode(walker);
|
|
2429
|
+
}
|
|
2430
|
+
return total;
|
|
2431
|
+
}
|
|
2432
|
+
function elementBoundaryOffset(surface, offset) {
|
|
2433
|
+
if (offset <= 0) return 0;
|
|
2434
|
+
if (offset >= surface.childNodes.length) return textLength(surface);
|
|
2435
|
+
let total = 0;
|
|
2436
|
+
for (let i = 0; i < offset; i++) {
|
|
2437
|
+
const child = surface.childNodes.item(i);
|
|
2438
|
+
if (child.nodeType === Node.TEXT_NODE && child instanceof Text) {
|
|
2439
|
+
total += child.length;
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
if (child instanceof HTMLElement) total += textLength(child);
|
|
2443
|
+
}
|
|
2444
|
+
return total;
|
|
2445
|
+
}
|
|
2446
|
+
function hasEditableAncestorBefore(node, boundary) {
|
|
2447
|
+
let current = node instanceof HTMLElement ? node : node.parentElement;
|
|
2448
|
+
while (current && current !== boundary) {
|
|
2449
|
+
if (current.isContentEditable || current.contentEditable === "true" || current.contentEditable === "plaintext-only") return true;
|
|
2450
|
+
current = current.parentElement;
|
|
2451
|
+
}
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2610
2454
|
var DomFeature = class {
|
|
2455
|
+
#domIndex = signal(void 0, { readonly: true });
|
|
2456
|
+
#pendingControls = /* @__PURE__ */ new Map();
|
|
2457
|
+
#pendingChildSequences = /* @__PURE__ */ new Map();
|
|
2458
|
+
#nextControlId = 0;
|
|
2459
|
+
#nextChildSequenceId = 0;
|
|
2460
|
+
#elementRoles = /* @__PURE__ */ new WeakMap();
|
|
2461
|
+
#pathElements = /* @__PURE__ */ new Map();
|
|
2462
|
+
#generation = 0;
|
|
2463
|
+
#rendering = false;
|
|
2464
|
+
#isComposing = false;
|
|
2465
|
+
#queuedRender = false;
|
|
2611
2466
|
#scope;
|
|
2612
2467
|
constructor(_store) {
|
|
2613
2468
|
this._store = _store;
|
|
2469
|
+
this.index = computed(() => this.#domIndex());
|
|
2470
|
+
this.container = signal(null);
|
|
2471
|
+
this.diagnostics = event();
|
|
2614
2472
|
}
|
|
2615
2473
|
enable() {
|
|
2616
2474
|
if (this.#scope) return;
|
|
2617
2475
|
this.#scope = effectScope(() => {
|
|
2618
|
-
|
|
2619
|
-
this
|
|
2620
|
-
this.reconcile();
|
|
2621
|
-
});
|
|
2622
|
-
alienEffect(() => {
|
|
2623
|
-
if (this._store.caret.selecting() === void 0) this.reconcile();
|
|
2476
|
+
watch(this._store.lifecycle.rendered, () => {
|
|
2477
|
+
this.#handleRendered();
|
|
2624
2478
|
});
|
|
2479
|
+
watch(computed(() => ({
|
|
2480
|
+
readOnly: this._store.props.readOnly(),
|
|
2481
|
+
selecting: this._store.caret.selecting()
|
|
2482
|
+
})), () => this.reconcile());
|
|
2625
2483
|
});
|
|
2626
2484
|
}
|
|
2627
2485
|
disable() {
|
|
2628
2486
|
this.#scope?.();
|
|
2629
2487
|
this.#scope = void 0;
|
|
2630
2488
|
}
|
|
2489
|
+
compositionStarted() {
|
|
2490
|
+
this.#isComposing = true;
|
|
2491
|
+
}
|
|
2492
|
+
compositionEnded() {
|
|
2493
|
+
if (!this.#isComposing) return;
|
|
2494
|
+
this.#isComposing = false;
|
|
2495
|
+
}
|
|
2496
|
+
controlFor(ownerPath) {
|
|
2497
|
+
const key = `control:${ownerPath ? pathKey(ownerPath) : "global"}:${++this.#nextControlId}`;
|
|
2498
|
+
const callback = (element) => {
|
|
2499
|
+
if (element) this.#pendingControls.set(key, {
|
|
2500
|
+
ownerPath: ownerPath ? [...ownerPath] : void 0,
|
|
2501
|
+
element
|
|
2502
|
+
});
|
|
2503
|
+
else this.#pendingControls.delete(key);
|
|
2504
|
+
};
|
|
2505
|
+
return callback;
|
|
2506
|
+
}
|
|
2507
|
+
childrenFor(ownerPath) {
|
|
2508
|
+
const key = `children:${pathKey(ownerPath)}:${++this.#nextChildSequenceId}`;
|
|
2509
|
+
const callback = (element) => {
|
|
2510
|
+
if (element) this.#pendingChildSequences.set(key, {
|
|
2511
|
+
ownerPath: [...ownerPath],
|
|
2512
|
+
element
|
|
2513
|
+
});
|
|
2514
|
+
else this.#pendingChildSequences.delete(key);
|
|
2515
|
+
};
|
|
2516
|
+
return callback;
|
|
2517
|
+
}
|
|
2631
2518
|
reconcile() {
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2519
|
+
this.#reconcileStructuralTextSurfaces();
|
|
2520
|
+
}
|
|
2521
|
+
locateNode(node) {
|
|
2522
|
+
if (!this.index()) return {
|
|
2523
|
+
ok: false,
|
|
2524
|
+
reason: "notIndexed"
|
|
2525
|
+
};
|
|
2526
|
+
const container = this.container();
|
|
2527
|
+
if (!container || !container.contains(node)) return {
|
|
2528
|
+
ok: false,
|
|
2529
|
+
reason: "outsideEditor"
|
|
2530
|
+
};
|
|
2531
|
+
let current = node;
|
|
2532
|
+
while (current) {
|
|
2533
|
+
if (current instanceof HTMLElement) {
|
|
2534
|
+
const role = this.#elementRoles.get(current);
|
|
2535
|
+
if (role?.role === "control") return {
|
|
2536
|
+
ok: false,
|
|
2537
|
+
reason: "control"
|
|
2538
|
+
};
|
|
2539
|
+
if (role) {
|
|
2540
|
+
const elements = this.#pathElements.get(pathKey(role.path));
|
|
2541
|
+
if (!elements?.tokenElement) return {
|
|
2542
|
+
ok: false,
|
|
2543
|
+
reason: "notIndexed"
|
|
2544
|
+
};
|
|
2545
|
+
return {
|
|
2546
|
+
ok: true,
|
|
2547
|
+
value: {
|
|
2548
|
+
address: role.address,
|
|
2549
|
+
tokenElement: elements.tokenElement,
|
|
2550
|
+
textElement: elements.textElement,
|
|
2551
|
+
rowElement: elements.rowElement
|
|
2552
|
+
}
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
if (current === container) break;
|
|
2557
|
+
current = current.parentNode;
|
|
2558
|
+
}
|
|
2559
|
+
return {
|
|
2560
|
+
ok: false,
|
|
2561
|
+
reason: "outsideEditor"
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
placeCaretAtRawPosition(rawPosition, affinity = "after") {
|
|
2565
|
+
if (!this.index()) return {
|
|
2566
|
+
ok: false,
|
|
2567
|
+
reason: "notIndexed"
|
|
2568
|
+
};
|
|
2569
|
+
const target = this.#findTextTargetForRawPosition(rawPosition, affinity);
|
|
2570
|
+
if (!target) return this.#focusMarkBoundaryForRawPosition(rawPosition);
|
|
2571
|
+
target.element.focus();
|
|
2572
|
+
this.#placeCaretInTextSurface(target.element, rawPosition - target.start);
|
|
2573
|
+
return {
|
|
2574
|
+
ok: true,
|
|
2575
|
+
value: void 0
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
focusAddress(address, boundary = "start") {
|
|
2579
|
+
if (!this.index()) return {
|
|
2580
|
+
ok: false,
|
|
2581
|
+
reason: "notIndexed"
|
|
2582
|
+
};
|
|
2583
|
+
if (!this._store.parsing.index().resolveAddress(address).ok) return {
|
|
2584
|
+
ok: false,
|
|
2585
|
+
reason: "stale"
|
|
2586
|
+
};
|
|
2587
|
+
const elements = this.#pathElements.get(pathKey(address.path));
|
|
2588
|
+
const target = elements?.textElement ?? elements?.tokenElement ?? elements?.rowElement;
|
|
2589
|
+
if (!target) return {
|
|
2590
|
+
ok: false,
|
|
2591
|
+
reason: "notIndexed"
|
|
2592
|
+
};
|
|
2593
|
+
target.focus();
|
|
2594
|
+
const role = target === elements?.textElement ? "text" : target === elements?.rowElement ? "row" : "markDescendant";
|
|
2595
|
+
if (role === "markDescendant") this.#placeCollapsedBoundary(target, boundary === "end" ? target.childNodes.length : 0);
|
|
2596
|
+
this._store.caret.location({
|
|
2597
|
+
address,
|
|
2598
|
+
role
|
|
2599
|
+
});
|
|
2600
|
+
return {
|
|
2601
|
+
ok: true,
|
|
2602
|
+
value: void 0
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
rawPositionFromBoundary(node, offset, affinity = "after") {
|
|
2606
|
+
if (!this.index()) return {
|
|
2607
|
+
ok: false,
|
|
2608
|
+
reason: "notIndexed"
|
|
2609
|
+
};
|
|
2610
|
+
if (this.#isComposing) return {
|
|
2611
|
+
ok: false,
|
|
2612
|
+
reason: "composing"
|
|
2613
|
+
};
|
|
2614
|
+
const container = this.container();
|
|
2615
|
+
if (container && node === container) return this.#rawPositionFromContainerBoundary(offset, affinity);
|
|
2616
|
+
const location = this.locateNode(node);
|
|
2617
|
+
if (!location.ok) return location.reason === "control" ? {
|
|
2618
|
+
ok: false,
|
|
2619
|
+
reason: "control"
|
|
2620
|
+
} : location;
|
|
2621
|
+
const token = this._store.parsing.index().resolveAddress(location.value.address);
|
|
2622
|
+
if (!token.ok) return {
|
|
2623
|
+
ok: false,
|
|
2624
|
+
reason: "notIndexed"
|
|
2625
|
+
};
|
|
2626
|
+
if (node instanceof HTMLElement) {
|
|
2627
|
+
if (this.#elementRoles.get(node)?.role === "childSequence") {
|
|
2628
|
+
const childCount = node.childNodes.length;
|
|
2629
|
+
if (offset <= 0) return {
|
|
2630
|
+
ok: true,
|
|
2631
|
+
value: token.value.position.start
|
|
2632
|
+
};
|
|
2633
|
+
if (offset >= childCount) return {
|
|
2634
|
+
ok: true,
|
|
2635
|
+
value: token.value.position.end
|
|
2636
|
+
};
|
|
2637
|
+
return this.#rawPositionFromTokenChildBoundary(node, offset, token.value, affinity);
|
|
2646
2638
|
}
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2639
|
+
}
|
|
2640
|
+
const textElement = location.value.textElement;
|
|
2641
|
+
if (textElement?.contains(node)) {
|
|
2642
|
+
const local = textOffsetWithin(textElement, node, offset);
|
|
2643
|
+
if (local === void 0) return {
|
|
2644
|
+
ok: false,
|
|
2645
|
+
reason: "invalidBoundary"
|
|
2646
|
+
};
|
|
2647
|
+
return {
|
|
2648
|
+
ok: true,
|
|
2649
|
+
value: token.value.position.start + local
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
if (node === location.value.tokenElement) {
|
|
2653
|
+
const childCount = location.value.tokenElement.childNodes.length;
|
|
2654
|
+
if (offset <= 0) return {
|
|
2655
|
+
ok: true,
|
|
2656
|
+
value: token.value.position.start
|
|
2657
|
+
};
|
|
2658
|
+
if (offset >= childCount) return {
|
|
2659
|
+
ok: true,
|
|
2660
|
+
value: token.value.position.end
|
|
2661
|
+
};
|
|
2662
|
+
return this.#rawPositionFromTokenChildBoundary(location.value.tokenElement, offset, token.value, affinity);
|
|
2663
|
+
}
|
|
2664
|
+
if (token.value.type === "mark" && location.value.tokenElement.contains(node)) {
|
|
2665
|
+
if (hasEditableAncestorBefore(node, location.value.tokenElement)) return {
|
|
2666
|
+
ok: false,
|
|
2667
|
+
reason: "invalidBoundary"
|
|
2668
|
+
};
|
|
2669
|
+
return {
|
|
2670
|
+
ok: true,
|
|
2671
|
+
value: affinity === "after" ? token.value.position.start : token.value.position.end
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
if (location.value.rowElement && node === location.value.rowElement) return {
|
|
2675
|
+
ok: true,
|
|
2676
|
+
value: offset <= 0 ? token.value.position.start : token.value.position.end
|
|
2677
|
+
};
|
|
2678
|
+
return {
|
|
2679
|
+
ok: false,
|
|
2680
|
+
reason: "invalidBoundary"
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
readRawSelection() {
|
|
2684
|
+
if (!this.index()) return {
|
|
2685
|
+
ok: false,
|
|
2686
|
+
reason: "notIndexed"
|
|
2687
|
+
};
|
|
2688
|
+
const selection = window.getSelection();
|
|
2689
|
+
if (!selection || selection.rangeCount === 0) return {
|
|
2690
|
+
ok: false,
|
|
2691
|
+
reason: "invalidBoundary"
|
|
2692
|
+
};
|
|
2693
|
+
const range = selection.getRangeAt(0);
|
|
2694
|
+
const start = this.rawPositionFromBoundary(range.startContainer, range.startOffset, "after");
|
|
2695
|
+
const end = this.rawPositionFromBoundary(range.endContainer, range.endOffset, "before");
|
|
2696
|
+
if (!start.ok) {
|
|
2697
|
+
const reason = start.reason === "composing" ? "invalidBoundary" : start.reason;
|
|
2698
|
+
return {
|
|
2699
|
+
ok: false,
|
|
2700
|
+
reason: reason === "control" || reason === "outsideEditor" ? "mixedBoundary" : reason
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
if (!end.ok) {
|
|
2704
|
+
const reason = end.reason === "composing" ? "invalidBoundary" : end.reason;
|
|
2705
|
+
return {
|
|
2706
|
+
ok: false,
|
|
2707
|
+
reason: reason === "control" || reason === "outsideEditor" ? "mixedBoundary" : reason
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
const rangeValue = start.value <= end.value ? {
|
|
2711
|
+
start: start.value,
|
|
2712
|
+
end: end.value
|
|
2713
|
+
} : {
|
|
2714
|
+
start: end.value,
|
|
2715
|
+
end: start.value
|
|
2716
|
+
};
|
|
2717
|
+
const direction = rangeValue.start === rangeValue.end ? void 0 : selection.anchorNode === range.endContainer && selection.anchorOffset === range.endOffset ? "backward" : "forward";
|
|
2718
|
+
return {
|
|
2719
|
+
ok: true,
|
|
2720
|
+
value: direction ? {
|
|
2721
|
+
range: rangeValue,
|
|
2722
|
+
direction
|
|
2723
|
+
} : { range: rangeValue }
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
#handleRendered() {
|
|
2727
|
+
if (this.#rendering) {
|
|
2728
|
+
this.#queuedRender = true;
|
|
2729
|
+
this.diagnostics({
|
|
2730
|
+
kind: "renderReentry",
|
|
2731
|
+
reason: "rendered event queued during DOM indexing"
|
|
2732
|
+
});
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
this.#rendering = true;
|
|
2736
|
+
try {
|
|
2737
|
+
this.#commitRendered();
|
|
2738
|
+
} finally {
|
|
2739
|
+
this.#rendering = false;
|
|
2740
|
+
const queued = this.#queuedRender;
|
|
2741
|
+
this.#queuedRender = false;
|
|
2742
|
+
if (queued) this.#handleRendered();
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
#commitRendered() {
|
|
2746
|
+
const container = this.container();
|
|
2747
|
+
if (!container) {
|
|
2748
|
+
this.diagnostics({
|
|
2749
|
+
kind: "missingContainer",
|
|
2750
|
+
reason: "container is not registered"
|
|
2751
|
+
});
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
const tokenIndex = this._store.parsing.index();
|
|
2755
|
+
const pathElements = /* @__PURE__ */ new Map();
|
|
2756
|
+
const elementRoles = /* @__PURE__ */ new WeakMap();
|
|
2757
|
+
const controlElements = /* @__PURE__ */ new Set();
|
|
2758
|
+
for (const { element } of this.#pendingControls.values()) {
|
|
2759
|
+
controlElements.add(element);
|
|
2760
|
+
elementRoles.set(element, { role: "control" });
|
|
2650
2761
|
}
|
|
2651
2762
|
const tokens = this._store.parsing.tokens();
|
|
2652
|
-
if (
|
|
2653
|
-
else this.#
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2763
|
+
if (this._store.props.layout() === "block") this.#indexBlockTokens(container, tokens, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2764
|
+
else this.#indexTokenSequence(container, tokens, [], void 0, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2765
|
+
this.#pathElements = pathElements;
|
|
2766
|
+
this.#elementRoles = elementRoles;
|
|
2767
|
+
this.#reconcileStructuralTextSurfaces();
|
|
2768
|
+
batch(() => this.#domIndex({ generation: ++this.#generation }), { mutable: true });
|
|
2769
|
+
this.#clearStaleCaretLocation();
|
|
2770
|
+
this.#applyPendingRecovery();
|
|
2771
|
+
}
|
|
2772
|
+
#elementChildren(element) {
|
|
2773
|
+
return Array.from(element.children).filter((child) => child instanceof HTMLElement);
|
|
2774
|
+
}
|
|
2775
|
+
#isControlRoot(element, controlElements) {
|
|
2776
|
+
if (controlElements.has(element)) return true;
|
|
2777
|
+
for (const control of controlElements) if (element.contains(control)) return true;
|
|
2778
|
+
return false;
|
|
2779
|
+
}
|
|
2780
|
+
#childSequenceHostsFor(ownerPath) {
|
|
2781
|
+
const hosts = [];
|
|
2782
|
+
for (const registration of this.#pendingChildSequences.values()) if (pathEquals(registration.ownerPath, ownerPath)) hosts.push(registration.element);
|
|
2783
|
+
return hosts;
|
|
2784
|
+
}
|
|
2785
|
+
#indexNestedTokenSequence(token, path, address, ownerElement, rowElement, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2786
|
+
if (token.type !== "mark" || token.children.length === 0) return;
|
|
2787
|
+
const hosts = this.#childSequenceHostsFor(path);
|
|
2788
|
+
if (hosts.length === 0) {
|
|
2789
|
+
this.#indexTokenSequence(ownerElement, token.children, path, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
const ownerKey = pathKey(path);
|
|
2793
|
+
if (hosts.length !== 1) {
|
|
2794
|
+
this.diagnostics({
|
|
2795
|
+
kind: "ambiguousStructure",
|
|
2796
|
+
path,
|
|
2797
|
+
reason: `expected exactly 1 child sequence host for owner path ${ownerKey} but found ${hosts.length}`
|
|
2798
|
+
});
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
const host = hosts[0];
|
|
2802
|
+
if (!ownerElement.contains(host)) {
|
|
2803
|
+
this.diagnostics({
|
|
2804
|
+
kind: "ambiguousStructure",
|
|
2805
|
+
path,
|
|
2806
|
+
reason: `child sequence host for owner path ${ownerKey} is not contained by owner token element`
|
|
2807
|
+
});
|
|
2808
|
+
return;
|
|
2663
2809
|
}
|
|
2810
|
+
elementRoles.set(host, {
|
|
2811
|
+
role: "childSequence",
|
|
2812
|
+
path,
|
|
2813
|
+
address
|
|
2814
|
+
});
|
|
2815
|
+
this.#indexTokenSequence(host, token.children, path, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2664
2816
|
}
|
|
2665
|
-
#
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
const
|
|
2675
|
-
if (
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
const el = childAt(parent, childIdx);
|
|
2683
|
-
if (el && !isTextTokenSpan(el)) break;
|
|
2684
|
-
childIdx++;
|
|
2685
|
-
}
|
|
2686
|
-
const el = childAt(parent, childIdx);
|
|
2687
|
-
if (el) {
|
|
2688
|
-
this.#reconcileMarkChildren(token.children, el, editable);
|
|
2689
|
-
childIdx++;
|
|
2817
|
+
#indexBlockTokens(container, tokens, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2818
|
+
const rows = this.#elementChildren(container);
|
|
2819
|
+
if (rows.length !== tokens.length) this.diagnostics({
|
|
2820
|
+
kind: "ambiguousStructure",
|
|
2821
|
+
reason: `expected ${tokens.length} block rows but found ${rows.length}`
|
|
2822
|
+
});
|
|
2823
|
+
tokens.forEach((token, i) => {
|
|
2824
|
+
const row = rows.at(i);
|
|
2825
|
+
if (!row) return;
|
|
2826
|
+
const candidates = this.#elementChildren(row).filter((child) => !this.#isControlRoot(child, controlElements));
|
|
2827
|
+
if (candidates.length !== 1) {
|
|
2828
|
+
this.diagnostics({
|
|
2829
|
+
kind: "ambiguousStructure",
|
|
2830
|
+
path: [i],
|
|
2831
|
+
reason: `expected 1 block token element but found ${candidates.length}`
|
|
2832
|
+
});
|
|
2833
|
+
return;
|
|
2690
2834
|
}
|
|
2835
|
+
this.#indexTokenElement(token, [i], candidates[0], row, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
#indexTokenSequence(parent, tokens, basePath, rowElement, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2839
|
+
const elements = this.#elementChildren(parent).filter((child) => !this.#isControlRoot(child, controlElements));
|
|
2840
|
+
if (elements.length !== tokens.length) {
|
|
2841
|
+
this.diagnostics({
|
|
2842
|
+
kind: "ambiguousStructure",
|
|
2843
|
+
path: basePath.length ? basePath : void 0,
|
|
2844
|
+
reason: `expected ${tokens.length} child token elements but found ${elements.length}`
|
|
2845
|
+
});
|
|
2846
|
+
return;
|
|
2691
2847
|
}
|
|
2848
|
+
tokens.forEach((token, i) => {
|
|
2849
|
+
const element = elements.at(i);
|
|
2850
|
+
if (!element) return;
|
|
2851
|
+
this.#indexTokenElement(token, [...basePath, i], element, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2852
|
+
});
|
|
2692
2853
|
}
|
|
2693
|
-
#
|
|
2694
|
-
const
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2854
|
+
#indexTokenElement(token, path, element, rowElement, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2855
|
+
const address = tokenIndex.addressFor(path);
|
|
2856
|
+
if (!address) {
|
|
2857
|
+
this.diagnostics({
|
|
2858
|
+
kind: "stalePath",
|
|
2859
|
+
path,
|
|
2860
|
+
reason: "structural path no longer resolves"
|
|
2861
|
+
});
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
const record = {
|
|
2865
|
+
path: [...path],
|
|
2866
|
+
address,
|
|
2867
|
+
tokenElement: element,
|
|
2868
|
+
textElement: token.type === "text" ? element : void 0,
|
|
2869
|
+
rowElement
|
|
2870
|
+
};
|
|
2871
|
+
pathElements.set(tokenIndex.key(path), record);
|
|
2872
|
+
elementRoles.set(element, {
|
|
2873
|
+
role: token.type === "text" ? "text" : "token",
|
|
2874
|
+
path,
|
|
2875
|
+
address
|
|
2876
|
+
});
|
|
2877
|
+
if (rowElement && path.length === 1) elementRoles.set(rowElement, {
|
|
2878
|
+
role: "row",
|
|
2879
|
+
path,
|
|
2880
|
+
address
|
|
2881
|
+
});
|
|
2882
|
+
this.#indexNestedTokenSequence(token, path, address, element, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2883
|
+
}
|
|
2884
|
+
#reconcileStructuralTextSurfaces() {
|
|
2885
|
+
const tokenIndex = this._store.parsing.index();
|
|
2886
|
+
const editable = this._store.props.readOnly() || this._store.caret.selecting() ? "false" : "true";
|
|
2887
|
+
for (const record of this.#pathElements.values()) {
|
|
2888
|
+
const resolved = tokenIndex.resolveAddress(record.address);
|
|
2889
|
+
if (!resolved.ok) {
|
|
2890
|
+
this.diagnostics({
|
|
2891
|
+
kind: "stalePath",
|
|
2892
|
+
path: record.path,
|
|
2893
|
+
reason: "structural path became stale during reconciliation"
|
|
2894
|
+
});
|
|
2895
|
+
continue;
|
|
2896
|
+
}
|
|
2897
|
+
if (record.textElement) {
|
|
2898
|
+
if (resolved.value.type !== "text") {
|
|
2899
|
+
this.diagnostics({
|
|
2900
|
+
kind: "missingRole",
|
|
2901
|
+
path: record.path,
|
|
2902
|
+
reason: "text role registered for non-text token"
|
|
2903
|
+
});
|
|
2904
|
+
continue;
|
|
2703
2905
|
}
|
|
2906
|
+
if (record.textElement.textContent !== resolved.value.content) record.textElement.textContent = resolved.value.content;
|
|
2907
|
+
record.textElement.contentEditable = editable;
|
|
2704
2908
|
continue;
|
|
2705
2909
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2910
|
+
if (resolved.value.type === "mark") if (this._store.props.readOnly()) record.tokenElement.removeAttribute("tabindex");
|
|
2911
|
+
else record.tokenElement.tabIndex = 0;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
#rawPositionFromContainerBoundary(offset, affinity) {
|
|
2915
|
+
const tokens = this._store.parsing.tokens();
|
|
2916
|
+
if (tokens.length === 0) return {
|
|
2917
|
+
ok: true,
|
|
2918
|
+
value: 0
|
|
2919
|
+
};
|
|
2920
|
+
if (offset <= 0) return {
|
|
2921
|
+
ok: true,
|
|
2922
|
+
value: tokens[0].position.start
|
|
2923
|
+
};
|
|
2924
|
+
if (offset >= tokens.length) return {
|
|
2925
|
+
ok: true,
|
|
2926
|
+
value: tokens[tokens.length - 1].position.end
|
|
2927
|
+
};
|
|
2928
|
+
const before = tokens[offset - 1];
|
|
2929
|
+
const after = tokens[offset];
|
|
2930
|
+
return {
|
|
2931
|
+
ok: true,
|
|
2932
|
+
value: affinity === "before" ? before.position.end : after.position.start
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
#rawPositionFromTokenChildBoundary(tokenElement, offset, token, affinity) {
|
|
2936
|
+
if (token.type === "text") {
|
|
2937
|
+
const textElement = this.#pathElements.get(pathKey(this._store.parsing.index().pathFor(token) ?? []))?.textElement;
|
|
2938
|
+
if (!textElement || textLength(textElement) === 0) return {
|
|
2939
|
+
ok: true,
|
|
2940
|
+
value: token.position.start
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
const before = this.#locateRegisteredDescendant(tokenElement.childNodes.item(offset - 1));
|
|
2944
|
+
const after = this.#locateRegisteredDescendant(tokenElement.childNodes.item(offset));
|
|
2945
|
+
if (before?.ok && after?.ok) {
|
|
2946
|
+
const beforeToken = this._store.parsing.index().resolveAddress(before.value.address);
|
|
2947
|
+
const afterToken = this._store.parsing.index().resolveAddress(after.value.address);
|
|
2948
|
+
if (beforeToken.ok && afterToken.ok) return {
|
|
2949
|
+
ok: true,
|
|
2950
|
+
value: affinity === "before" ? beforeToken.value.position.end : afterToken.value.position.start
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
return {
|
|
2954
|
+
ok: true,
|
|
2955
|
+
value: affinity === "before" ? token.position.start : token.position.end
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
#locateRegisteredDescendant(node) {
|
|
2959
|
+
if (!node) return void 0;
|
|
2960
|
+
return this.locateNode(node);
|
|
2961
|
+
}
|
|
2962
|
+
#findTextTargetForRawPosition(rawPosition, affinity) {
|
|
2963
|
+
const candidates = [];
|
|
2964
|
+
const tokenIndex = this._store.parsing.index();
|
|
2965
|
+
for (const record of this.#pathElements.values()) {
|
|
2966
|
+
if (!record.textElement) continue;
|
|
2967
|
+
const resolved = tokenIndex.resolveAddress(record.address);
|
|
2968
|
+
if (!resolved.ok || resolved.value.type !== "text") continue;
|
|
2969
|
+
candidates.push({
|
|
2970
|
+
element: record.textElement,
|
|
2971
|
+
start: resolved.value.position.start,
|
|
2972
|
+
end: resolved.value.position.end
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
candidates.sort((a, b) => a.start - b.start);
|
|
2976
|
+
const containing = candidates.find((candidate) => rawPosition >= candidate.start && rawPosition <= candidate.end);
|
|
2977
|
+
if (containing) return containing;
|
|
2978
|
+
if (affinity === "before") return [...candidates].toReversed().find((candidate) => candidate.end <= rawPosition);
|
|
2979
|
+
return candidates.find((candidate) => candidate.start >= rawPosition);
|
|
2980
|
+
}
|
|
2981
|
+
#focusMarkBoundaryForRawPosition(rawPosition) {
|
|
2982
|
+
const tokenIndex = this._store.parsing.index();
|
|
2983
|
+
for (const record of this.#pathElements.values()) {
|
|
2984
|
+
const resolved = tokenIndex.resolveAddress(record.address);
|
|
2985
|
+
if (!resolved.ok || resolved.value.type !== "mark") continue;
|
|
2986
|
+
if (rawPosition !== resolved.value.position.start && rawPosition !== resolved.value.position.end) continue;
|
|
2987
|
+
const boundary = rawPosition === resolved.value.position.end ? "end" : "start";
|
|
2988
|
+
record.tokenElement.focus();
|
|
2989
|
+
this.#placeCollapsedBoundary(record.tokenElement, boundary === "end" ? record.tokenElement.childNodes.length : 0);
|
|
2990
|
+
this._store.caret.location({
|
|
2991
|
+
address: record.address,
|
|
2992
|
+
role: "markDescendant"
|
|
2993
|
+
});
|
|
2994
|
+
return {
|
|
2995
|
+
ok: true,
|
|
2996
|
+
value: void 0
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
return {
|
|
3000
|
+
ok: false,
|
|
3001
|
+
reason: "invalidBoundary"
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
#placeCaretInTextSurface(surface, offset) {
|
|
3005
|
+
const selection = window.getSelection();
|
|
3006
|
+
if (!selection) return;
|
|
3007
|
+
const boundary = this.#boundaryInTextSurface(surface, offset);
|
|
3008
|
+
if (!boundary) return;
|
|
3009
|
+
const range = document.createRange();
|
|
3010
|
+
range.setStart(boundary.node, boundary.offset);
|
|
3011
|
+
range.collapse(true);
|
|
3012
|
+
selection.removeAllRanges();
|
|
3013
|
+
selection.addRange(range);
|
|
3014
|
+
}
|
|
3015
|
+
#placeCollapsedBoundary(element, offset) {
|
|
3016
|
+
const selection = window.getSelection();
|
|
3017
|
+
if (!selection) return;
|
|
3018
|
+
const range = document.createRange();
|
|
3019
|
+
range.setStart(element, Math.min(Math.max(offset, 0), element.childNodes.length));
|
|
3020
|
+
range.collapse(true);
|
|
3021
|
+
selection.removeAllRanges();
|
|
3022
|
+
selection.addRange(range);
|
|
3023
|
+
}
|
|
3024
|
+
#applyPendingRecovery() {
|
|
3025
|
+
const recovery = this._store.caret.recovery();
|
|
3026
|
+
if (!recovery) return;
|
|
3027
|
+
if (recovery.kind === "caret") {
|
|
3028
|
+
const result = this._store.caret.placeAt(recovery.rawPosition, recovery.affinity);
|
|
3029
|
+
this._store.caret.recovery(void 0);
|
|
3030
|
+
if (!result.ok) this.diagnostics({
|
|
3031
|
+
kind: "recoveryFailed",
|
|
3032
|
+
reason: `pending caret recovery could not be applied: ${result.reason}`
|
|
3033
|
+
});
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
const result = this.#placeSelection(recovery.selection);
|
|
3037
|
+
this._store.caret.recovery(void 0);
|
|
3038
|
+
if (!result.ok) this.diagnostics({
|
|
3039
|
+
kind: "recoveryFailed",
|
|
3040
|
+
reason: `pending selection recovery could not be applied: ${result.reason}`
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
#placeSelection(selection) {
|
|
3044
|
+
const start = this.#findTextTargetForRawPosition(selection.range.start, "after");
|
|
3045
|
+
const end = this.#findTextTargetForRawPosition(selection.range.end, "before");
|
|
3046
|
+
const browserSelection = window.getSelection();
|
|
3047
|
+
if (!start || !end || !browserSelection) return {
|
|
3048
|
+
ok: false,
|
|
3049
|
+
reason: "invalidBoundary"
|
|
3050
|
+
};
|
|
3051
|
+
const startBoundary = this.#boundaryInTextSurface(start.element, selection.range.start - start.start);
|
|
3052
|
+
const endBoundary = this.#boundaryInTextSurface(end.element, selection.range.end - end.start);
|
|
3053
|
+
if (!startBoundary || !endBoundary) return {
|
|
3054
|
+
ok: false,
|
|
3055
|
+
reason: "invalidBoundary"
|
|
3056
|
+
};
|
|
3057
|
+
const range = document.createRange();
|
|
3058
|
+
range.setStart(startBoundary.node, startBoundary.offset);
|
|
3059
|
+
range.setEnd(endBoundary.node, endBoundary.offset);
|
|
3060
|
+
browserSelection.removeAllRanges();
|
|
3061
|
+
browserSelection.addRange(range);
|
|
3062
|
+
return {
|
|
3063
|
+
ok: true,
|
|
3064
|
+
value: void 0
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
#boundaryInTextSurface(surface, offset) {
|
|
3068
|
+
const walker = document.createTreeWalker(surface, NodeFilter.SHOW_TEXT);
|
|
3069
|
+
let remaining = Math.max(0, offset);
|
|
3070
|
+
let node = nextTextNode(walker);
|
|
3071
|
+
while (node) {
|
|
3072
|
+
if (remaining <= node.length) return {
|
|
3073
|
+
node,
|
|
3074
|
+
offset: remaining
|
|
3075
|
+
};
|
|
3076
|
+
remaining -= node.length;
|
|
3077
|
+
node = nextTextNode(walker);
|
|
2709
3078
|
}
|
|
3079
|
+
const text = surface.firstChild instanceof Text ? surface.firstChild : document.createTextNode("");
|
|
3080
|
+
if (!text.parentNode) surface.append(text);
|
|
3081
|
+
return {
|
|
3082
|
+
node: text,
|
|
3083
|
+
offset: text.length
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
#clearStaleCaretLocation() {
|
|
3087
|
+
const location = this._store.caret.location();
|
|
3088
|
+
if (!location) return;
|
|
3089
|
+
if (!this._store.parsing.index().resolveAddress(location.address).ok || !this.#pathElements.has(pathKey(location.address.path))) this._store.caret.location(void 0);
|
|
2710
3090
|
}
|
|
2711
3091
|
};
|
|
2712
3092
|
//#endregion
|
|
2713
|
-
//#region ../../core/src/features/editing/utils/createNewSpan.ts
|
|
2714
|
-
function createNewSpan(span, annotation, index, source) {
|
|
2715
|
-
return span.slice(0, index) + annotation + span.slice(index + source.length);
|
|
2716
|
-
}
|
|
2717
|
-
//#endregion
|
|
2718
3093
|
//#region ../../core/src/features/editing/createRowContent.ts
|
|
2719
3094
|
function createRowContent(options) {
|
|
2720
3095
|
const firstOption = options[0];
|
|
@@ -2726,47 +3101,6 @@ function createRowContent(options) {
|
|
|
2726
3101
|
});
|
|
2727
3102
|
}
|
|
2728
3103
|
//#endregion
|
|
2729
|
-
//#region ../../core/src/features/editing/utils/deleteMark.ts
|
|
2730
|
-
function deleteMark(place, store) {
|
|
2731
|
-
const placeIndex = {
|
|
2732
|
-
prev: 2,
|
|
2733
|
-
self: 1,
|
|
2734
|
-
next: 0
|
|
2735
|
-
}[place];
|
|
2736
|
-
const { focus } = store.nodes;
|
|
2737
|
-
const targetIndex = Math.max(0, focus.index - placeIndex);
|
|
2738
|
-
const tokens = store.parsing.tokens();
|
|
2739
|
-
const spliced = tokens.splice(focus.index - placeIndex, 3);
|
|
2740
|
-
const span1 = spliced.at(0);
|
|
2741
|
-
const span2 = spliced.at(2);
|
|
2742
|
-
const content1 = span1?.content ?? "";
|
|
2743
|
-
const content2 = span2?.content ?? "";
|
|
2744
|
-
store.parsing.tokens(tokens.toSpliced(focus.index - placeIndex, 0, {
|
|
2745
|
-
type: "text",
|
|
2746
|
-
content: content1 + content2,
|
|
2747
|
-
position: {
|
|
2748
|
-
start: span1?.position.start ?? 0,
|
|
2749
|
-
end: span2?.position.end ?? (content1 + content2).length
|
|
2750
|
-
}
|
|
2751
|
-
}));
|
|
2752
|
-
let caretAnchor = focus;
|
|
2753
|
-
for (let i = 0; i < placeIndex; i++) caretAnchor = caretAnchor.prev;
|
|
2754
|
-
const caret = caretAnchor.length;
|
|
2755
|
-
store.caret.recovery({
|
|
2756
|
-
anchor: caretAnchor.prev,
|
|
2757
|
-
caret
|
|
2758
|
-
});
|
|
2759
|
-
store.value.change();
|
|
2760
|
-
queueMicrotask(() => {
|
|
2761
|
-
const container = store.slots.container();
|
|
2762
|
-
const target = container ? childAt(container, targetIndex) : null;
|
|
2763
|
-
if (!target) return;
|
|
2764
|
-
store.nodes.focus.target = target;
|
|
2765
|
-
target.focus();
|
|
2766
|
-
store.nodes.focus.caret = caret;
|
|
2767
|
-
});
|
|
2768
|
-
}
|
|
2769
|
-
//#endregion
|
|
2770
3104
|
//#region ../../core/src/features/drag/operations.ts
|
|
2771
3105
|
function gapText(value, a, b) {
|
|
2772
3106
|
return value.substring(a.position.end, b.position.start);
|
|
@@ -2876,16 +3210,16 @@ var DragFeature = class {
|
|
|
2876
3210
|
this.#unsub = watch(this.action, (action) => {
|
|
2877
3211
|
switch (action.type) {
|
|
2878
3212
|
case "reorder":
|
|
2879
|
-
this.#reorder(action
|
|
3213
|
+
this.#reorder(action);
|
|
2880
3214
|
break;
|
|
2881
3215
|
case "add":
|
|
2882
|
-
this.#add(action
|
|
3216
|
+
this.#add(action);
|
|
2883
3217
|
break;
|
|
2884
3218
|
case "delete":
|
|
2885
|
-
this.#delete(action
|
|
3219
|
+
this.#delete(action);
|
|
2886
3220
|
break;
|
|
2887
3221
|
case "duplicate":
|
|
2888
|
-
this.#duplicate(action
|
|
3222
|
+
this.#duplicate(action);
|
|
2889
3223
|
break;
|
|
2890
3224
|
}
|
|
2891
3225
|
});
|
|
@@ -2894,36 +3228,74 @@ var DragFeature = class {
|
|
|
2894
3228
|
this.#unsub?.();
|
|
2895
3229
|
this.#unsub = void 0;
|
|
2896
3230
|
}
|
|
2897
|
-
#reorder(
|
|
2898
|
-
const value = this.store.
|
|
2899
|
-
|
|
2900
|
-
const newValue = reorderDragRows(value,
|
|
2901
|
-
if (newValue !== value) this.store.value.
|
|
3231
|
+
#reorder(action) {
|
|
3232
|
+
const value = this.store.value.current();
|
|
3233
|
+
const rows = this.store.parsing.tokens();
|
|
3234
|
+
const newValue = reorderDragRows(value, rows, action.source, action.target);
|
|
3235
|
+
if (newValue !== value) this.store.value.replaceAll(newValue, {
|
|
3236
|
+
source: "drag",
|
|
3237
|
+
recover: this.#recoverAfterDrag(action, rows, newValue)
|
|
3238
|
+
});
|
|
2902
3239
|
}
|
|
2903
|
-
#add(
|
|
2904
|
-
const value = this.store.
|
|
2905
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3240
|
+
#add(action) {
|
|
3241
|
+
const value = this.store.value.current();
|
|
2906
3242
|
const rawRows = this.store.parsing.tokens();
|
|
2907
3243
|
const rows = rawRows.length > 0 ? rawRows : [EMPTY_TEXT_TOKEN];
|
|
2908
3244
|
const newRowContent = createRowContent(this.store.props.options());
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
childAt(container, afterIndex + 1)?.focus();
|
|
3245
|
+
const newValue = addDragRow(value, rows, action.afterIndex, newRowContent);
|
|
3246
|
+
this.store.value.replaceAll(newValue, {
|
|
3247
|
+
source: "drag",
|
|
3248
|
+
recover: this.#recoverAfterDrag(action, rows, newValue)
|
|
2914
3249
|
});
|
|
2915
3250
|
}
|
|
2916
|
-
#delete(
|
|
2917
|
-
const value = this.store.
|
|
2918
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3251
|
+
#delete(action) {
|
|
3252
|
+
const value = this.store.value.current();
|
|
2919
3253
|
const rows = this.store.parsing.tokens();
|
|
2920
|
-
|
|
3254
|
+
const newValue = deleteDragRow(value, rows, action.index);
|
|
3255
|
+
this.store.value.replaceAll(newValue, {
|
|
3256
|
+
source: "drag",
|
|
3257
|
+
recover: this.#recoverAfterDrag(action, rows, newValue)
|
|
3258
|
+
});
|
|
2921
3259
|
}
|
|
2922
|
-
#duplicate(
|
|
2923
|
-
const value = this.store.
|
|
2924
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3260
|
+
#duplicate(action) {
|
|
3261
|
+
const value = this.store.value.current();
|
|
2925
3262
|
const rows = this.store.parsing.tokens();
|
|
2926
|
-
|
|
3263
|
+
const newValue = duplicateDragRow(value, rows, action.index);
|
|
3264
|
+
this.store.value.replaceAll(newValue, {
|
|
3265
|
+
source: "drag",
|
|
3266
|
+
recover: this.#recoverAfterDrag(action, rows, newValue)
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
#recoverAfterDrag(action, previousRows, nextValue) {
|
|
3270
|
+
if (action.type === "add") {
|
|
3271
|
+
const after = previousRows.at(action.afterIndex);
|
|
3272
|
+
return {
|
|
3273
|
+
kind: "caret",
|
|
3274
|
+
rawPosition: after ? after.position.end : nextValue.length
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
if (action.type === "duplicate") {
|
|
3278
|
+
const row = previousRows.at(action.index);
|
|
3279
|
+
return row ? {
|
|
3280
|
+
kind: "caret",
|
|
3281
|
+
rawPosition: row.position.end
|
|
3282
|
+
} : void 0;
|
|
3283
|
+
}
|
|
3284
|
+
if (action.type === "delete") {
|
|
3285
|
+
const next = previousRows.at(action.index + 1) ?? (action.index > 0 ? previousRows.at(action.index - 1) : void 0);
|
|
3286
|
+
return next ? {
|
|
3287
|
+
kind: "caret",
|
|
3288
|
+
rawPosition: Math.min(next.position.start, nextValue.length)
|
|
3289
|
+
} : {
|
|
3290
|
+
kind: "caret",
|
|
3291
|
+
rawPosition: 0
|
|
3292
|
+
};
|
|
3293
|
+
}
|
|
3294
|
+
const moved = previousRows.at(action.source);
|
|
3295
|
+
return moved ? {
|
|
3296
|
+
kind: "caret",
|
|
3297
|
+
rawPosition: Math.min(moved.position.start, nextValue.length)
|
|
3298
|
+
} : void 0;
|
|
2927
3299
|
}
|
|
2928
3300
|
};
|
|
2929
3301
|
//#endregion
|
|
@@ -2940,178 +3312,52 @@ function getDragTargetIndex(blockIndex, position) {
|
|
|
2940
3312
|
}
|
|
2941
3313
|
//#endregion
|
|
2942
3314
|
//#region ../../core/src/features/drag/config.ts
|
|
2943
|
-
function getAlwaysShowHandle(draggable) {
|
|
2944
|
-
return typeof draggable === "object" && !!draggable.alwaysShowHandle;
|
|
2945
|
-
}
|
|
2946
|
-
//#endregion
|
|
2947
|
-
//#region ../../core/src/features/navigation/navigation.ts
|
|
2948
|
-
function shiftFocusPrev(store, event) {
|
|
2949
|
-
const { focus } = store.nodes;
|
|
2950
|
-
if (focus.isMark && !focus.isEditable || focus.isCaretAtBeginning) {
|
|
2951
|
-
let prev = focus.prev;
|
|
2952
|
-
while (prev.target && prev.isMark && !prev.isEditable) prev = prev.prev;
|
|
2953
|
-
if (!prev.target) return false;
|
|
2954
|
-
event.preventDefault();
|
|
2955
|
-
prev.target.focus();
|
|
2956
|
-
Caret.setCaretToEnd(prev.target);
|
|
2957
|
-
return true;
|
|
2958
|
-
}
|
|
2959
|
-
return false;
|
|
2960
|
-
}
|
|
2961
|
-
function shiftFocusNext(store, event) {
|
|
2962
|
-
const { focus } = store.nodes;
|
|
2963
|
-
if (focus.isMark && !focus.isEditable || focus.isCaretAtEnd) {
|
|
2964
|
-
let next = focus.next;
|
|
2965
|
-
while (next.target && next.isMark && !next.isEditable) next = next.next;
|
|
2966
|
-
if (!next.target) return false;
|
|
2967
|
-
event.preventDefault();
|
|
2968
|
-
next.target.focus();
|
|
2969
|
-
Caret.trySetIndex(next.target, 0);
|
|
2970
|
-
return true;
|
|
2971
|
-
}
|
|
2972
|
-
return false;
|
|
2973
|
-
}
|
|
2974
|
-
//#endregion
|
|
2975
|
-
//#region ../../core/src/features/keyboard/arrowNav.ts
|
|
2976
|
-
function enableArrowNav(store) {
|
|
2977
|
-
const container = store.slots.container();
|
|
2978
|
-
if (!container) return () => {};
|
|
2979
|
-
const scope = effectScope(() => {
|
|
2980
|
-
listen(container, "keydown", (e) => {
|
|
2981
|
-
if (store.slots.isBlock()) return;
|
|
2982
|
-
if (!store.nodes.focus.target) return;
|
|
2983
|
-
if (e.key === KEYBOARD.LEFT) shiftFocusPrev(store, e);
|
|
2984
|
-
else if (e.key === KEYBOARD.RIGHT) shiftFocusNext(store, e);
|
|
2985
|
-
selectAllText(store, e);
|
|
2986
|
-
});
|
|
2987
|
-
});
|
|
2988
|
-
return () => scope();
|
|
2989
|
-
}
|
|
2990
|
-
//#endregion
|
|
2991
|
-
//#region ../../core/src/features/keyboard/rawPosition.ts
|
|
2992
|
-
function getCaretRawPosInBlock(blockDiv, token) {
|
|
2993
|
-
const selection = window.getSelection();
|
|
2994
|
-
if (!selection?.rangeCount) return token.position.end;
|
|
2995
|
-
const { focusNode, focusOffset } = selection;
|
|
2996
|
-
if (!focusNode) return token.position.end;
|
|
2997
|
-
return getDomRawPos(focusNode, focusOffset, blockDiv, token);
|
|
2998
|
-
}
|
|
2999
|
-
function setCaretAtRawPos(blockDiv, token, rawAbsolutePos) {
|
|
3000
|
-
const sel = window.getSelection();
|
|
3001
|
-
if (!sel) return;
|
|
3002
|
-
if (token.type === "mark") {
|
|
3003
|
-
if (setCaretInMarkAtRawPos(blockDiv, token, rawAbsolutePos)) return;
|
|
3004
|
-
Caret.setCaretToEnd(blockDiv);
|
|
3005
|
-
return;
|
|
3006
|
-
}
|
|
3007
|
-
const offsetWithinToken = rawAbsolutePos - token.position.start;
|
|
3008
|
-
const textNode = nextText(document.createTreeWalker(blockDiv, 4));
|
|
3009
|
-
if (textNode) {
|
|
3010
|
-
const charOffset = Math.min(offsetWithinToken, textNode.length);
|
|
3011
|
-
const range = document.createRange();
|
|
3012
|
-
range.setStart(textNode, charOffset);
|
|
3013
|
-
range.collapse(true);
|
|
3014
|
-
sel.removeAllRanges();
|
|
3015
|
-
sel.addRange(range);
|
|
3016
|
-
return;
|
|
3017
|
-
}
|
|
3018
|
-
Caret.setCaretToEnd(blockDiv);
|
|
3019
|
-
}
|
|
3020
|
-
function getDomRawPos(node, offset, blockDiv, token) {
|
|
3021
|
-
if (node === blockDiv) {
|
|
3022
|
-
const sel = window.getSelection();
|
|
3023
|
-
if (sel?.focusNode && sel.focusNode !== blockDiv) return getDomRawPos(sel.focusNode, sel.focusOffset, blockDiv, token);
|
|
3024
|
-
return token.position.end;
|
|
3025
|
-
}
|
|
3026
|
-
if (node.nodeType === Node.TEXT_NODE && node.parentElement === blockDiv) {
|
|
3027
|
-
if (token.type === "mark") return getDomRawPosInMark(node, offset, blockDiv, token);
|
|
3028
|
-
return token.position.start + Math.min(offset, token.content.length);
|
|
3029
|
-
}
|
|
3030
|
-
let child = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
3031
|
-
while (child && child.parentElement !== blockDiv) child = child.parentElement;
|
|
3032
|
-
if (!child) return token.position.end;
|
|
3033
|
-
if (token.type === "mark") return getDomRawPosInMark(node, offset, blockDiv, token);
|
|
3034
|
-
return token.position.start + Math.min(offset, token.content.length);
|
|
3315
|
+
function getAlwaysShowHandle(draggable) {
|
|
3316
|
+
return typeof draggable === "object" && !!draggable.alwaysShowHandle;
|
|
3035
3317
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
if (isHtmlElement(childNode) && tokenChild.type === "text") {
|
|
3051
|
-
if (!isTextTokenSpan(childNode)) continue;
|
|
3052
|
-
if (node === childNode) {
|
|
3053
|
-
const charOffset = offset === 0 ? 0 : tokenChild.content.length;
|
|
3054
|
-
return tokenChild.position.start + Math.min(charOffset, tokenChild.content.length);
|
|
3055
|
-
}
|
|
3056
|
-
if (childNode.contains(node)) return tokenChild.position.start + Math.min(offset, tokenChild.content.length);
|
|
3057
|
-
tokenIdx++;
|
|
3058
|
-
} else if (isTextNode(childNode) && tokenChild.type === "text") {
|
|
3059
|
-
if (node === childNode) return tokenChild.position.start + Math.min(offset, tokenChild.content.length);
|
|
3060
|
-
tokenIdx++;
|
|
3061
|
-
} else if (isHtmlElement(childNode) && tokenChild.type === "mark") {
|
|
3062
|
-
if (childNode === node || childNode.contains(node)) return getDomRawPosInMark(node, offset, childNode, tokenChild);
|
|
3063
|
-
tokenIdx++;
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
return markToken.slot?.end ?? markToken.position.end;
|
|
3318
|
+
//#endregion
|
|
3319
|
+
//#region ../../core/src/features/keyboard/arrowNav.ts
|
|
3320
|
+
function enableArrowNav(store) {
|
|
3321
|
+
const container = store.dom.container();
|
|
3322
|
+
if (!container) return () => {};
|
|
3323
|
+
const scope = effectScope(() => {
|
|
3324
|
+
listen(container, "keydown", (e) => {
|
|
3325
|
+
if (store.slots.isBlock()) return;
|
|
3326
|
+
if (e.key === KEYBOARD.LEFT) shiftFocus(store, e, "prev");
|
|
3327
|
+
else if (e.key === KEYBOARD.RIGHT) shiftFocus(store, e, "next");
|
|
3328
|
+
selectAllText(store, e);
|
|
3329
|
+
});
|
|
3330
|
+
});
|
|
3331
|
+
return () => scope();
|
|
3067
3332
|
}
|
|
3068
|
-
function
|
|
3069
|
-
const
|
|
3070
|
-
if (!
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
const
|
|
3075
|
-
if (
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3094
|
-
return true;
|
|
3095
|
-
}
|
|
3096
|
-
tokenIdx++;
|
|
3097
|
-
} else if (isTextNode(childNode) && tokenChild.type === "text") {
|
|
3098
|
-
if (rawAbsolutePos >= tokenChild.position.start && rawAbsolutePos <= tokenChild.position.end) {
|
|
3099
|
-
const offset = Math.min(rawAbsolutePos - tokenChild.position.start, childNode.length);
|
|
3100
|
-
const range = document.createRange();
|
|
3101
|
-
range.setStart(childNode, offset);
|
|
3102
|
-
range.collapse(true);
|
|
3103
|
-
sel.removeAllRanges();
|
|
3104
|
-
sel.addRange(range);
|
|
3105
|
-
return true;
|
|
3106
|
-
}
|
|
3107
|
-
tokenIdx++;
|
|
3108
|
-
} else if (isHtmlElement(childNode) && tokenChild.type === "mark") {
|
|
3109
|
-
const nextChild = tokenIdx + 1 < markToken.children.length ? markToken.children[tokenIdx + 1] : null;
|
|
3110
|
-
if (!(rawAbsolutePos === tokenChild.position.end && nextChild?.position.start === rawAbsolutePos) && rawAbsolutePos >= tokenChild.position.start && rawAbsolutePos <= tokenChild.position.end) return setCaretInMarkAtRawPos(childNode, tokenChild, rawAbsolutePos);
|
|
3111
|
-
tokenIdx++;
|
|
3112
|
-
}
|
|
3333
|
+
function shiftFocus(store, event, direction) {
|
|
3334
|
+
const location = store.caret.location();
|
|
3335
|
+
if (!location) return false;
|
|
3336
|
+
const token = store.parsing.index().resolveAddress(location.address);
|
|
3337
|
+
if (!token.ok) return false;
|
|
3338
|
+
if (!(token.value.type === "mark" && location.role !== "text")) {
|
|
3339
|
+
const selection = store.dom.readRawSelection();
|
|
3340
|
+
if (!selection.ok || selection.value.range.start !== selection.value.range.end) return false;
|
|
3341
|
+
const atStart = selection.value.range.start <= token.value.position.start;
|
|
3342
|
+
const atEnd = selection.value.range.end >= token.value.position.end;
|
|
3343
|
+
if (direction === "prev" && !atStart) return false;
|
|
3344
|
+
if (direction === "next" && !atEnd) return false;
|
|
3345
|
+
}
|
|
3346
|
+
const path = location.address.path;
|
|
3347
|
+
const siblingIndex = direction === "prev" ? path[path.length - 1] - 1 : path[path.length - 1] + 1;
|
|
3348
|
+
const siblingPath = [...path.slice(0, -1), siblingIndex];
|
|
3349
|
+
const siblingAddress = store.parsing.index().addressFor(siblingPath);
|
|
3350
|
+
if (!siblingAddress) return false;
|
|
3351
|
+
event.preventDefault();
|
|
3352
|
+
if (!store.caret.focus(siblingAddress, direction === "prev" ? "end" : "start").ok) return false;
|
|
3353
|
+
const sibling = store.parsing.index().resolve(siblingPath);
|
|
3354
|
+
if (sibling?.type === "mark") return true;
|
|
3355
|
+
if (direction === "prev") {
|
|
3356
|
+
store.caret.placeAt(sibling?.position.end ?? 0, "before");
|
|
3357
|
+
return true;
|
|
3113
3358
|
}
|
|
3114
|
-
|
|
3359
|
+
store.caret.placeAt(sibling?.position.start ?? 0, "after");
|
|
3360
|
+
return true;
|
|
3115
3361
|
}
|
|
3116
3362
|
//#endregion
|
|
3117
3363
|
//#region ../../core/src/features/keyboard/blockEdit.ts
|
|
@@ -3120,14 +3366,14 @@ function isTextLikeRow(token) {
|
|
|
3120
3366
|
return token.descriptor.hasSlot && token.descriptor.segments.length === 1;
|
|
3121
3367
|
}
|
|
3122
3368
|
function enableBlockEdit(store) {
|
|
3123
|
-
const container = store.
|
|
3369
|
+
const container = store.dom.container();
|
|
3124
3370
|
if (!container) return () => {};
|
|
3125
3371
|
const scope = effectScope(() => {
|
|
3126
3372
|
listen(container, "keydown", (e) => {
|
|
3127
3373
|
if (!store.slots.isBlock()) return;
|
|
3128
3374
|
if (e.key === KEYBOARD.LEFT || e.key === KEYBOARD.RIGHT) handleBlockArrowLeftRight(store, e, e.key === KEYBOARD.LEFT ? "left" : "right");
|
|
3129
3375
|
else if (e.key === KEYBOARD.UP || e.key === KEYBOARD.DOWN) handleArrowUpDown(store, e);
|
|
3130
|
-
handleDelete
|
|
3376
|
+
handleDelete(store, e);
|
|
3131
3377
|
handleEnter(store, e);
|
|
3132
3378
|
});
|
|
3133
3379
|
listen(container, "beforeinput", (e) => {
|
|
@@ -3138,8 +3384,8 @@ function enableBlockEdit(store) {
|
|
|
3138
3384
|
});
|
|
3139
3385
|
return () => scope();
|
|
3140
3386
|
}
|
|
3141
|
-
function handleDelete
|
|
3142
|
-
const container = store.
|
|
3387
|
+
function handleDelete(store, event) {
|
|
3388
|
+
const container = store.dom.container();
|
|
3143
3389
|
if (!container) return;
|
|
3144
3390
|
const blockDivs = htmlChildren(container);
|
|
3145
3391
|
const blockIndex = blockDivs.findIndex((div) => div === document.activeElement || div.contains(document.activeElement));
|
|
@@ -3148,7 +3394,6 @@ function handleDelete$1(store, event) {
|
|
|
3148
3394
|
if (blockIndex >= rows.length) return;
|
|
3149
3395
|
const token = rows[blockIndex];
|
|
3150
3396
|
const value = store.value.current();
|
|
3151
|
-
if (!store.props.onChange()) return;
|
|
3152
3397
|
if (event.key === KEYBOARD.BACKSPACE) {
|
|
3153
3398
|
const blockDiv = blockDivs[blockIndex];
|
|
3154
3399
|
const caretAtStart = Caret.getCaretIndex(blockDiv) === 0;
|
|
@@ -3158,12 +3403,12 @@ function handleDelete$1(store, event) {
|
|
|
3158
3403
|
if (blockIndex >= rows.length - 1) return value.slice(0, rows[blockIndex - 1].position.end);
|
|
3159
3404
|
return value.slice(0, rows[blockIndex].position.start) + value.slice(rows[blockIndex + 1].position.start);
|
|
3160
3405
|
})();
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3406
|
+
const previous = rows.at(Math.max(0, blockIndex - 1));
|
|
3407
|
+
store.value.replaceAll(newValue, {
|
|
3408
|
+
source: "block",
|
|
3409
|
+
recover: {
|
|
3410
|
+
kind: "caret",
|
|
3411
|
+
rawPosition: previous ? previous.position.end : 0
|
|
3167
3412
|
}
|
|
3168
3413
|
});
|
|
3169
3414
|
return;
|
|
@@ -3175,13 +3420,11 @@ function handleDelete$1(store, event) {
|
|
|
3175
3420
|
event.preventDefault();
|
|
3176
3421
|
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3177
3422
|
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3178
|
-
store.value.
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
const updatedToken = store.parsing.tokens()[blockIndex - 1];
|
|
3184
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3423
|
+
store.value.replaceAll(newValue, {
|
|
3424
|
+
source: "block",
|
|
3425
|
+
recover: {
|
|
3426
|
+
kind: "caret",
|
|
3427
|
+
rawPosition: joinPos
|
|
3185
3428
|
}
|
|
3186
3429
|
});
|
|
3187
3430
|
return;
|
|
@@ -3189,8 +3432,7 @@ function handleDelete$1(store, event) {
|
|
|
3189
3432
|
event.preventDefault();
|
|
3190
3433
|
queueMicrotask(() => {
|
|
3191
3434
|
const target = blockDivs[blockIndex - 1];
|
|
3192
|
-
target
|
|
3193
|
-
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3435
|
+
focusRow(store, prevToken, target, "end");
|
|
3194
3436
|
});
|
|
3195
3437
|
return;
|
|
3196
3438
|
}
|
|
@@ -3206,13 +3448,11 @@ function handleDelete$1(store, event) {
|
|
|
3206
3448
|
event.preventDefault();
|
|
3207
3449
|
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3208
3450
|
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3209
|
-
store.value.
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
const updatedToken = store.parsing.tokens()[blockIndex - 1];
|
|
3215
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3451
|
+
store.value.replaceAll(newValue, {
|
|
3452
|
+
source: "block",
|
|
3453
|
+
recover: {
|
|
3454
|
+
kind: "caret",
|
|
3455
|
+
rawPosition: joinPos
|
|
3216
3456
|
}
|
|
3217
3457
|
});
|
|
3218
3458
|
return;
|
|
@@ -3220,8 +3460,7 @@ function handleDelete$1(store, event) {
|
|
|
3220
3460
|
event.preventDefault();
|
|
3221
3461
|
queueMicrotask(() => {
|
|
3222
3462
|
const target = blockDivs[blockIndex - 1];
|
|
3223
|
-
target
|
|
3224
|
-
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3463
|
+
focusRow(store, prevToken, target, "end");
|
|
3225
3464
|
});
|
|
3226
3465
|
return;
|
|
3227
3466
|
}
|
|
@@ -3232,13 +3471,11 @@ function handleDelete$1(store, event) {
|
|
|
3232
3471
|
event.preventDefault();
|
|
3233
3472
|
const joinPos = getMergeDragRowJoinPos(rows, blockIndex + 1);
|
|
3234
3473
|
const newValue = mergeDragRows(value, rows, blockIndex + 1);
|
|
3235
|
-
store.value.
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
const updatedToken = store.parsing.tokens()[blockIndex];
|
|
3241
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3474
|
+
store.value.replaceAll(newValue, {
|
|
3475
|
+
source: "block",
|
|
3476
|
+
recover: {
|
|
3477
|
+
kind: "caret",
|
|
3478
|
+
rawPosition: joinPos
|
|
3242
3479
|
}
|
|
3243
3480
|
});
|
|
3244
3481
|
return;
|
|
@@ -3246,8 +3483,7 @@ function handleDelete$1(store, event) {
|
|
|
3246
3483
|
event.preventDefault();
|
|
3247
3484
|
queueMicrotask(() => {
|
|
3248
3485
|
const target = blockDivs[blockIndex + 1];
|
|
3249
|
-
target
|
|
3250
|
-
Caret.trySetIndex(target, 0);
|
|
3486
|
+
focusRow(store, nextToken, target, "start");
|
|
3251
3487
|
});
|
|
3252
3488
|
return;
|
|
3253
3489
|
}
|
|
@@ -3256,7 +3492,7 @@ function handleDelete$1(store, event) {
|
|
|
3256
3492
|
function handleEnter(store, event) {
|
|
3257
3493
|
if (event.key !== KEYBOARD.ENTER) return;
|
|
3258
3494
|
if (event.shiftKey) return;
|
|
3259
|
-
const container = store.
|
|
3495
|
+
const container = store.dom.container();
|
|
3260
3496
|
if (!container) return;
|
|
3261
3497
|
const activeElement = document.activeElement;
|
|
3262
3498
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
@@ -3270,41 +3506,47 @@ function handleEnter(store, event) {
|
|
|
3270
3506
|
if (blockIndex === -1) return;
|
|
3271
3507
|
const rows = store.parsing.tokens();
|
|
3272
3508
|
const token = rows[blockIndex];
|
|
3273
|
-
const blockDiv = blockDivs[blockIndex];
|
|
3274
3509
|
const value = store.value.current();
|
|
3275
|
-
if (!store.props.onChange()) return;
|
|
3276
3510
|
const newRowContent = createRowContent(store.props.options());
|
|
3277
3511
|
if (!isTextLikeRow(token)) {
|
|
3278
3512
|
const newValue = addDragRow(value, rows, blockIndex, newRowContent);
|
|
3279
|
-
store.value.
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
if (newBlockEl) {
|
|
3285
|
-
newBlockEl.focus();
|
|
3286
|
-
Caret.trySetIndex(newBlockEl, 0);
|
|
3287
|
-
}
|
|
3513
|
+
store.value.replaceAll(newValue, {
|
|
3514
|
+
source: "block",
|
|
3515
|
+
recover: {
|
|
3516
|
+
kind: "caret",
|
|
3517
|
+
rawPosition: token.position.end + newRowContent.length
|
|
3288
3518
|
}
|
|
3289
3519
|
});
|
|
3290
3520
|
return;
|
|
3291
3521
|
}
|
|
3292
|
-
const
|
|
3293
|
-
const
|
|
3294
|
-
store.value.
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
}
|
|
3522
|
+
const raw = store.dom.readRawSelection();
|
|
3523
|
+
const absolutePos = raw.ok ? raw.value.range.start : token.position.end;
|
|
3524
|
+
store.value.replaceRange({
|
|
3525
|
+
start: absolutePos,
|
|
3526
|
+
end: absolutePos
|
|
3527
|
+
}, newRowContent, {
|
|
3528
|
+
source: "block",
|
|
3529
|
+
recover: {
|
|
3530
|
+
kind: "caret",
|
|
3531
|
+
rawPosition: absolutePos + newRowContent.length
|
|
3303
3532
|
}
|
|
3304
3533
|
});
|
|
3305
3534
|
}
|
|
3535
|
+
function focusRow(store, token, row, caret) {
|
|
3536
|
+
if (token.type === "mark") {
|
|
3537
|
+
const path = store.parsing.index().pathFor(token);
|
|
3538
|
+
const address = path ? store.parsing.index().addressFor(path) : void 0;
|
|
3539
|
+
if (address && store.caret.focus(address).ok) return;
|
|
3540
|
+
}
|
|
3541
|
+
row.focus();
|
|
3542
|
+
if (caret === "start") {
|
|
3543
|
+
Caret.trySetIndex(row, 0);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
Caret.setCaretToEnd(row);
|
|
3547
|
+
}
|
|
3306
3548
|
function handleBlockArrowLeftRight(store, event, direction) {
|
|
3307
|
-
const container = store.
|
|
3549
|
+
const container = store.dom.container();
|
|
3308
3550
|
if (!container) return false;
|
|
3309
3551
|
const activeElement = document.activeElement;
|
|
3310
3552
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return false;
|
|
@@ -3330,7 +3572,7 @@ function handleBlockArrowLeftRight(store, event, direction) {
|
|
|
3330
3572
|
return true;
|
|
3331
3573
|
}
|
|
3332
3574
|
function handleArrowUpDown(store, event) {
|
|
3333
|
-
const container = store.
|
|
3575
|
+
const container = store.dom.container();
|
|
3334
3576
|
if (!container) return;
|
|
3335
3577
|
const activeElement = document.activeElement;
|
|
3336
3578
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
@@ -3359,58 +3601,19 @@ function handleArrowUpDown(store, event) {
|
|
|
3359
3601
|
}
|
|
3360
3602
|
}
|
|
3361
3603
|
function handleBlockBeforeInput(store, event) {
|
|
3362
|
-
const container = store.
|
|
3604
|
+
const container = store.dom.container();
|
|
3363
3605
|
if (!container) return;
|
|
3364
3606
|
const activeElement = document.activeElement;
|
|
3365
3607
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3366
|
-
|
|
3367
|
-
const blockIndex = blockDivs.findIndex((div) => div === activeElement || div.contains(activeElement));
|
|
3368
|
-
if (blockIndex === -1) return;
|
|
3369
|
-
const blockDiv = blockDivs[blockIndex];
|
|
3370
|
-
const rows = store.parsing.tokens();
|
|
3371
|
-
if (blockIndex >= rows.length) return;
|
|
3372
|
-
const token = rows[blockIndex];
|
|
3373
|
-
const value = store.value.current();
|
|
3374
|
-
const focusAndSetCaret = (newRawPos) => {
|
|
3375
|
-
queueMicrotask(() => {
|
|
3376
|
-
const target = childAt(container, blockIndex);
|
|
3377
|
-
if (!target) return;
|
|
3378
|
-
target.focus();
|
|
3379
|
-
const updatedToken = store.parsing.tokens()[blockIndex];
|
|
3380
|
-
setCaretAtRawPos(target, updatedToken, newRawPos);
|
|
3381
|
-
});
|
|
3382
|
-
};
|
|
3608
|
+
if (htmlChildren(container).findIndex((div) => div === activeElement || div.contains(activeElement)) === -1) return;
|
|
3383
3609
|
switch (event.inputType) {
|
|
3384
|
-
case "insertText":
|
|
3385
|
-
event.
|
|
3386
|
-
const data = event.data ?? "";
|
|
3387
|
-
const ranges = event.getTargetRanges();
|
|
3388
|
-
let rawFrom;
|
|
3389
|
-
let rawTo;
|
|
3390
|
-
if (ranges.length > 0) {
|
|
3391
|
-
const rawStart = getDomRawPos(ranges[0].startContainer, ranges[0].startOffset, blockDiv, token);
|
|
3392
|
-
const rawEnd = getDomRawPos(ranges[0].endContainer, ranges[0].endOffset, blockDiv, token);
|
|
3393
|
-
[rawFrom, rawTo] = rawStart <= rawEnd ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
|
3394
|
-
} else rawFrom = rawTo = getCaretRawPosInBlock(blockDiv, token);
|
|
3395
|
-
store.value.next(value.slice(0, rawFrom) + data + value.slice(rawTo));
|
|
3396
|
-
focusAndSetCaret(rawFrom + data.length);
|
|
3610
|
+
case "insertText":
|
|
3611
|
+
replaceBlockRange(store, event, event.data ?? "");
|
|
3397
3612
|
break;
|
|
3398
|
-
}
|
|
3399
3613
|
case "insertFromPaste":
|
|
3400
3614
|
case "insertReplacementText": {
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
const pasteData = (c ? consumeMarkupPaste(c) : void 0) ?? event.dataTransfer?.getData("text/plain") ?? "";
|
|
3404
|
-
const ranges = event.getTargetRanges();
|
|
3405
|
-
let rawFrom;
|
|
3406
|
-
let rawTo;
|
|
3407
|
-
if (ranges.length > 0) {
|
|
3408
|
-
const rawStart = getDomRawPos(ranges[0].startContainer, ranges[0].startOffset, blockDiv, token);
|
|
3409
|
-
const rawEnd = getDomRawPos(ranges[0].endContainer, ranges[0].endOffset, blockDiv, token);
|
|
3410
|
-
[rawFrom, rawTo] = rawStart <= rawEnd ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
|
3411
|
-
} else rawFrom = rawTo = getCaretRawPosInBlock(blockDiv, token);
|
|
3412
|
-
store.value.next(value.slice(0, rawFrom) + pasteData + value.slice(rawTo));
|
|
3413
|
-
focusAndSetCaret(rawFrom + pasteData.length);
|
|
3615
|
+
const c = store.dom.container();
|
|
3616
|
+
replaceBlockRange(store, event, (c ? consumeMarkupPaste(c) : void 0) ?? event.dataTransfer?.getData("text/plain") ?? "");
|
|
3414
3617
|
break;
|
|
3415
3618
|
}
|
|
3416
3619
|
case "deleteContentBackward":
|
|
@@ -3418,84 +3621,131 @@ function handleBlockBeforeInput(store, event) {
|
|
|
3418
3621
|
case "deleteWordBackward":
|
|
3419
3622
|
case "deleteWordForward":
|
|
3420
3623
|
case "deleteSoftLineBackward":
|
|
3421
|
-
case "deleteSoftLineForward":
|
|
3422
|
-
|
|
3423
|
-
if (!ranges.length) return;
|
|
3424
|
-
const rawStart = getDomRawPos(ranges[0].startContainer, ranges[0].startOffset, blockDiv, token);
|
|
3425
|
-
const rawEnd = getDomRawPos(ranges[0].endContainer, ranges[0].endOffset, blockDiv, token);
|
|
3426
|
-
const [rawFrom, rawTo] = rawStart <= rawEnd ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
|
3427
|
-
if (rawFrom === rawTo) return;
|
|
3428
|
-
event.preventDefault();
|
|
3429
|
-
store.value.next(value.slice(0, rawFrom) + value.slice(rawTo));
|
|
3430
|
-
focusAndSetCaret(rawFrom);
|
|
3624
|
+
case "deleteSoftLineForward":
|
|
3625
|
+
replaceBlockRange(store, event, "");
|
|
3431
3626
|
break;
|
|
3432
|
-
}
|
|
3433
3627
|
}
|
|
3434
3628
|
}
|
|
3629
|
+
function replaceBlockRange(store, event, replacement) {
|
|
3630
|
+
const raw = rawRangeFromInputEvent$1(store, event);
|
|
3631
|
+
if (!raw.ok) return;
|
|
3632
|
+
const range = rangeForBlockInput(store, event, raw.value.range);
|
|
3633
|
+
if (!range) return;
|
|
3634
|
+
event.preventDefault();
|
|
3635
|
+
store.value.replaceRange(range, replacement, {
|
|
3636
|
+
source: "block",
|
|
3637
|
+
recover: {
|
|
3638
|
+
kind: "caret",
|
|
3639
|
+
rawPosition: range.start + replacement.length
|
|
3640
|
+
}
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
function rawRangeFromInputEvent$1(store, event) {
|
|
3644
|
+
const ranges = event.getTargetRanges();
|
|
3645
|
+
if (ranges.length === 0) return store.dom.readRawSelection();
|
|
3646
|
+
return rawRangeFromTargetRange$1(store, ranges[0]);
|
|
3647
|
+
}
|
|
3648
|
+
function rawRangeFromTargetRange$1(store, range) {
|
|
3649
|
+
const start = store.dom.rawPositionFromBoundary(range.startContainer, range.startOffset, "after");
|
|
3650
|
+
const end = store.dom.rawPositionFromBoundary(range.endContainer, range.endOffset, "before");
|
|
3651
|
+
if (!start.ok) return {
|
|
3652
|
+
ok: false,
|
|
3653
|
+
reason: rawSelectionReason$1(start)
|
|
3654
|
+
};
|
|
3655
|
+
if (!end.ok) return {
|
|
3656
|
+
ok: false,
|
|
3657
|
+
reason: rawSelectionReason$1(end)
|
|
3658
|
+
};
|
|
3659
|
+
return {
|
|
3660
|
+
ok: true,
|
|
3661
|
+
value: { range: start.value <= end.value ? {
|
|
3662
|
+
start: start.value,
|
|
3663
|
+
end: end.value
|
|
3664
|
+
} : {
|
|
3665
|
+
start: end.value,
|
|
3666
|
+
end: start.value
|
|
3667
|
+
} }
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
function rawSelectionReason$1(result) {
|
|
3671
|
+
if (result.ok) return "invalidBoundary";
|
|
3672
|
+
if (result.reason === "composing") return "invalidBoundary";
|
|
3673
|
+
return result.reason;
|
|
3674
|
+
}
|
|
3675
|
+
function rangeForBlockInput(store, event, range) {
|
|
3676
|
+
if (!event.inputType.startsWith("delete")) return range;
|
|
3677
|
+
if (range.start !== range.end) return range;
|
|
3678
|
+
if (event.inputType.endsWith("Backward") && range.start > 0) return {
|
|
3679
|
+
start: range.start - 1,
|
|
3680
|
+
end: range.start
|
|
3681
|
+
};
|
|
3682
|
+
if (event.inputType.endsWith("Forward") && range.end < store.value.current().length) return {
|
|
3683
|
+
start: range.start,
|
|
3684
|
+
end: range.end + 1
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3435
3687
|
//#endregion
|
|
3436
3688
|
//#region ../../core/src/features/keyboard/input.ts
|
|
3437
3689
|
function enableInput(store) {
|
|
3438
|
-
const container = store.
|
|
3690
|
+
const container = store.dom.container();
|
|
3439
3691
|
if (!container) return () => {};
|
|
3692
|
+
let compositionRange;
|
|
3440
3693
|
const scope = effectScope(() => {
|
|
3441
|
-
listen(container, "keydown", (e) => {
|
|
3442
|
-
if (!store.slots.isBlock()) handleDelete(store, e);
|
|
3443
|
-
});
|
|
3444
3694
|
listen(container, "paste", (e) => {
|
|
3445
|
-
const c = store.
|
|
3695
|
+
const c = store.dom.container();
|
|
3446
3696
|
if (c) captureMarkupPaste(e, c);
|
|
3447
3697
|
handlePaste(store, e);
|
|
3448
3698
|
});
|
|
3699
|
+
listen(container, "compositionstart", () => {
|
|
3700
|
+
const selection = store.dom.readRawSelection();
|
|
3701
|
+
compositionRange = selection.ok ? selection.value.range : void 0;
|
|
3702
|
+
store.dom.compositionStarted();
|
|
3703
|
+
});
|
|
3704
|
+
listen(container, "compositionend", (e) => {
|
|
3705
|
+
const range = compositionRange;
|
|
3706
|
+
compositionRange = void 0;
|
|
3707
|
+
store.dom.compositionEnded();
|
|
3708
|
+
if (store.slots.isBlock()) return;
|
|
3709
|
+
if (!range) return;
|
|
3710
|
+
const data = e.data;
|
|
3711
|
+
store.value.replaceRange(range, data, {
|
|
3712
|
+
source: "input",
|
|
3713
|
+
recover: {
|
|
3714
|
+
kind: "caret",
|
|
3715
|
+
rawPosition: range.start + data.length
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
});
|
|
3449
3719
|
listen(container, "beforeinput", (e) => {
|
|
3450
3720
|
handleBeforeInput(store, e);
|
|
3451
3721
|
}, true);
|
|
3722
|
+
listen(container, "keydown", (e) => {
|
|
3723
|
+
handleDeleteKey(store, e);
|
|
3724
|
+
});
|
|
3452
3725
|
});
|
|
3453
3726
|
return () => scope();
|
|
3454
3727
|
}
|
|
3455
|
-
function
|
|
3456
|
-
|
|
3457
|
-
if (event.key !== KEYBOARD.
|
|
3458
|
-
if (
|
|
3459
|
-
if (focus.isEditable) {
|
|
3460
|
-
if (event.key === KEYBOARD.BACKSPACE && !focus.isCaretAtBeginning) return;
|
|
3461
|
-
if (event.key === KEYBOARD.DELETE && !focus.isCaretAtEnd) return;
|
|
3462
|
-
}
|
|
3728
|
+
function handleDeleteKey(store, event) {
|
|
3729
|
+
if (store.slots.isBlock()) return;
|
|
3730
|
+
if (event.key !== KEYBOARD.BACKSPACE && event.key !== KEYBOARD.DELETE) return;
|
|
3731
|
+
if (store.caret.selecting() === "all" && isFullSelection(store)) {
|
|
3463
3732
|
event.preventDefault();
|
|
3464
|
-
|
|
3733
|
+
replaceAllContentWith(store, "");
|
|
3465
3734
|
return;
|
|
3466
3735
|
}
|
|
3467
|
-
if (
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
return;
|
|
3479
|
-
}
|
|
3480
|
-
}
|
|
3481
|
-
if (focus.isSpan && focus.isEditable && window.getSelection()?.isCollapsed) {
|
|
3482
|
-
const content = focus.content;
|
|
3483
|
-
const caret = focus.caret;
|
|
3484
|
-
if (event.key === KEYBOARD.BACKSPACE && caret > 0) {
|
|
3485
|
-
event.preventDefault();
|
|
3486
|
-
focus.content = content.slice(0, caret - 1) + content.slice(caret);
|
|
3487
|
-
focus.caret = caret - 1;
|
|
3488
|
-
store.value.change();
|
|
3489
|
-
return;
|
|
3490
|
-
}
|
|
3491
|
-
if (event.key === KEYBOARD.DELETE && caret >= 0 && caret < content.length) {
|
|
3492
|
-
event.preventDefault();
|
|
3493
|
-
focus.content = content.slice(0, caret) + content.slice(caret + 1);
|
|
3494
|
-
focus.caret = caret;
|
|
3495
|
-
store.value.change();
|
|
3496
|
-
return;
|
|
3736
|
+
if (store.caret.selecting() === "all") store.caret.selecting(void 0);
|
|
3737
|
+
const raw = store.dom.readRawSelection();
|
|
3738
|
+
if (!raw.ok) return;
|
|
3739
|
+
const range = rangeForDelete(store, event.key === KEYBOARD.BACKSPACE ? "deleteContentBackward" : "deleteContentForward", raw.value.range);
|
|
3740
|
+
if (!range) return;
|
|
3741
|
+
event.preventDefault();
|
|
3742
|
+
store.value.replaceRange(range, "", {
|
|
3743
|
+
source: "input",
|
|
3744
|
+
recover: {
|
|
3745
|
+
kind: "caret",
|
|
3746
|
+
rawPosition: range.start
|
|
3497
3747
|
}
|
|
3498
|
-
}
|
|
3748
|
+
});
|
|
3499
3749
|
}
|
|
3500
3750
|
function handleBeforeInput(store, event) {
|
|
3501
3751
|
const selecting = store.caret.selecting();
|
|
@@ -3510,101 +3760,87 @@ function handleBeforeInput(store, event) {
|
|
|
3510
3760
|
}
|
|
3511
3761
|
if (selecting === "all") store.caret.selecting(void 0);
|
|
3512
3762
|
if (store.slots.isBlock()) return;
|
|
3513
|
-
const
|
|
3514
|
-
if (!
|
|
3515
|
-
|
|
3516
|
-
if (
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
const container = store.slots.container();
|
|
3520
|
-
if (!container) return false;
|
|
3521
|
-
const markup = consumeMarkupPaste(container);
|
|
3522
|
-
if (!markup) return false;
|
|
3763
|
+
const raw = rawRangeFromInputEvent(store, event);
|
|
3764
|
+
if (!raw.ok) return;
|
|
3765
|
+
const replacement = replacementForInput(store, event);
|
|
3766
|
+
if (replacement === void 0) return;
|
|
3767
|
+
const range = rangeForInput(store, event, raw.value.range);
|
|
3768
|
+
if (!range) return;
|
|
3523
3769
|
event.preventDefault();
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
let rawEndPos;
|
|
3531
|
-
if (ranges.length > 0) {
|
|
3532
|
-
const cumStart = getBoundaryOffset(ranges[0], childElement, true);
|
|
3533
|
-
const cumEnd = getBoundaryOffset(ranges[0], childElement, false);
|
|
3534
|
-
rawInsertPos = token.position.start + cumStart;
|
|
3535
|
-
rawEndPos = token.position.start + cumEnd;
|
|
3536
|
-
} else {
|
|
3537
|
-
rawInsertPos = token.position.start + offset;
|
|
3538
|
-
rawEndPos = token.position.start + offset;
|
|
3539
|
-
}
|
|
3540
|
-
const caretPos = rawInsertPos + markup.length;
|
|
3541
|
-
const newValue = currentValue.slice(0, rawInsertPos) + markup + currentValue.slice(rawEndPos);
|
|
3542
|
-
store.value.next(newValue);
|
|
3543
|
-
const newTokens = store.parsing.tokens();
|
|
3544
|
-
let targetIdx = newTokens.findIndex((t) => t.type === "text" && caretPos >= t.position.start && caretPos <= t.position.end);
|
|
3545
|
-
if (targetIdx === -1) targetIdx = newTokens.length - 1;
|
|
3546
|
-
const caretWithinToken = caretPos - newTokens[targetIdx].position.start;
|
|
3547
|
-
store.caret.recovery({
|
|
3548
|
-
anchor: store.nodes.focus,
|
|
3549
|
-
caret: caretWithinToken,
|
|
3550
|
-
isNext: true,
|
|
3551
|
-
childIndex: targetIdx - 2
|
|
3770
|
+
store.value.replaceRange(range, replacement, {
|
|
3771
|
+
source: "input",
|
|
3772
|
+
recover: {
|
|
3773
|
+
kind: "caret",
|
|
3774
|
+
rawPosition: range.start + replacement.length
|
|
3775
|
+
}
|
|
3552
3776
|
});
|
|
3553
|
-
return true;
|
|
3554
3777
|
}
|
|
3555
|
-
function
|
|
3556
|
-
const
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3778
|
+
function rawRangeFromInputEvent(store, event) {
|
|
3779
|
+
const ranges = getTargetRanges(event);
|
|
3780
|
+
if (ranges.length === 0) return store.dom.readRawSelection();
|
|
3781
|
+
return rawRangeFromTargetRange(store, ranges[0]);
|
|
3782
|
+
}
|
|
3783
|
+
function rawRangeFromTargetRange(store, range) {
|
|
3784
|
+
const start = store.dom.rawPositionFromBoundary(range.startContainer, range.startOffset, "after");
|
|
3785
|
+
const end = store.dom.rawPositionFromBoundary(range.endContainer, range.endOffset, "before");
|
|
3786
|
+
if (!start.ok) return {
|
|
3787
|
+
ok: false,
|
|
3788
|
+
reason: rawSelectionReason(start)
|
|
3789
|
+
};
|
|
3790
|
+
if (!end.ok) return {
|
|
3791
|
+
ok: false,
|
|
3792
|
+
reason: rawSelectionReason(end)
|
|
3793
|
+
};
|
|
3794
|
+
return {
|
|
3795
|
+
ok: true,
|
|
3796
|
+
value: { range: start.value <= end.value ? {
|
|
3797
|
+
start: start.value,
|
|
3798
|
+
end: end.value
|
|
3799
|
+
} : {
|
|
3800
|
+
start: end.value,
|
|
3801
|
+
end: start.value
|
|
3802
|
+
} }
|
|
3803
|
+
};
|
|
3804
|
+
}
|
|
3805
|
+
function rawSelectionReason(result) {
|
|
3806
|
+
if (result.ok) return "invalidBoundary";
|
|
3807
|
+
if (result.reason === "composing") return "invalidBoundary";
|
|
3808
|
+
return result.reason;
|
|
3809
|
+
}
|
|
3810
|
+
function getTargetRanges(event) {
|
|
3811
|
+
return event.getTargetRanges();
|
|
3812
|
+
}
|
|
3813
|
+
function replacementForInput(store, event) {
|
|
3814
|
+
if (event.inputType.startsWith("delete")) return "";
|
|
3815
|
+
if (event.inputType === "insertFromPaste" || event.inputType === "insertReplacementText") {
|
|
3816
|
+
const container = store.dom.container();
|
|
3817
|
+
return (container ? consumeMarkupPaste(container) : void 0) ?? event.dataTransfer?.getData("text/plain") ?? event.data ?? "";
|
|
3818
|
+
}
|
|
3819
|
+
if (event.inputType === "insertText") return event.data ?? "";
|
|
3820
|
+
}
|
|
3821
|
+
function rangeForInput(store, event, range) {
|
|
3822
|
+
if (!event.inputType.startsWith("delete")) return range;
|
|
3823
|
+
return rangeForDelete(store, event.inputType, range);
|
|
3824
|
+
}
|
|
3825
|
+
function rangeForDelete(store, inputType, range) {
|
|
3826
|
+
if (range.start !== range.end) return range;
|
|
3827
|
+
const adjacentMark = adjacentMarkRange(store.parsing.tokens(), range.start, inputType.endsWith("Backward"));
|
|
3828
|
+
if (adjacentMark) return adjacentMark;
|
|
3829
|
+
if (inputType.endsWith("Backward") && range.start > 0) return {
|
|
3830
|
+
start: range.start - 1,
|
|
3831
|
+
end: range.start
|
|
3832
|
+
};
|
|
3833
|
+
if (inputType.endsWith("Forward") && range.end < store.value.current().length) return {
|
|
3834
|
+
start: range.start,
|
|
3835
|
+
end: range.end + 1
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
function adjacentMarkRange(tokens, position, backward) {
|
|
3839
|
+
for (const token of tokens) {
|
|
3840
|
+
const nested = token.type === "mark" ? adjacentMarkRange(token.children, position, backward) : void 0;
|
|
3841
|
+
if (nested) return nested;
|
|
3842
|
+
if (token.type === "mark" && (backward ? token.position.end === position : token.position.start === position)) return token.position;
|
|
3604
3843
|
}
|
|
3605
|
-
focus.content = newContent;
|
|
3606
|
-
focus.caret = newCaret;
|
|
3607
|
-
return true;
|
|
3608
3844
|
}
|
|
3609
3845
|
function handlePaste(store, event) {
|
|
3610
3846
|
const selecting = store.caret.selecting();
|
|
@@ -3613,31 +3849,16 @@ function handlePaste(store, event) {
|
|
|
3613
3849
|
return;
|
|
3614
3850
|
}
|
|
3615
3851
|
event.preventDefault();
|
|
3616
|
-
const c = store.
|
|
3852
|
+
const c = store.dom.container();
|
|
3617
3853
|
replaceAllContentWith(store, (c ? consumeMarkupPaste(c) : void 0) ?? event.clipboardData?.getData("text/plain") ?? "");
|
|
3618
3854
|
}
|
|
3619
3855
|
function replaceAllContentWith(store, newContent) {
|
|
3620
|
-
store.nodes.focus.target = null;
|
|
3621
3856
|
store.caret.selecting(void 0);
|
|
3622
|
-
store.value.
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
position: {
|
|
3628
|
-
start: 0,
|
|
3629
|
-
end: newContent.length
|
|
3630
|
-
}
|
|
3631
|
-
}]);
|
|
3632
|
-
queueMicrotask(() => {
|
|
3633
|
-
const rawFirstChild = store.slots.container()?.firstChild;
|
|
3634
|
-
const firstChild = isHtmlElement(rawFirstChild) ? rawFirstChild : null;
|
|
3635
|
-
if (firstChild) {
|
|
3636
|
-
store.caret.recovery({
|
|
3637
|
-
anchor: store.nodes.focus,
|
|
3638
|
-
caret: newContent.length
|
|
3639
|
-
});
|
|
3640
|
-
firstChild.focus();
|
|
3857
|
+
store.value.replaceAll(newContent, {
|
|
3858
|
+
source: "input",
|
|
3859
|
+
recover: {
|
|
3860
|
+
kind: "caret",
|
|
3861
|
+
rawPosition: newContent.length
|
|
3641
3862
|
}
|
|
3642
3863
|
});
|
|
3643
3864
|
}
|
|
@@ -3664,8 +3885,7 @@ var KeyboardFeature = class {
|
|
|
3664
3885
|
//#endregion
|
|
3665
3886
|
//#region ../../core/src/features/lifecycle/LifecycleFeature.ts
|
|
3666
3887
|
var LifecycleFeature = class {
|
|
3667
|
-
constructor(
|
|
3668
|
-
this._store = _store;
|
|
3888
|
+
constructor() {
|
|
3669
3889
|
this.mounted = event();
|
|
3670
3890
|
this.unmounted = event();
|
|
3671
3891
|
this.rendered = event();
|
|
@@ -3674,70 +3894,80 @@ var LifecycleFeature = class {
|
|
|
3674
3894
|
disable() {}
|
|
3675
3895
|
};
|
|
3676
3896
|
//#endregion
|
|
3677
|
-
//#region ../../core/src/features/mark/
|
|
3678
|
-
var
|
|
3679
|
-
#
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
this.
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
this.#readOnly = value;
|
|
3699
|
-
}
|
|
3700
|
-
get content() {
|
|
3701
|
-
return this.#token.content;
|
|
3702
|
-
}
|
|
3703
|
-
set content(value) {
|
|
3704
|
-
this.#token.content = value;
|
|
3705
|
-
this.#emitChange();
|
|
3897
|
+
//#region ../../core/src/features/mark/MarkController.ts
|
|
3898
|
+
var MarkController = class MarkController {
|
|
3899
|
+
#shape;
|
|
3900
|
+
constructor(store, address, snapshot, shape) {
|
|
3901
|
+
this.store = store;
|
|
3902
|
+
this.address = address;
|
|
3903
|
+
this.snapshot = snapshot;
|
|
3904
|
+
this.#shape = shape;
|
|
3905
|
+
}
|
|
3906
|
+
static fromToken(store, token) {
|
|
3907
|
+
const index = store.parsing.index();
|
|
3908
|
+
const path = index.pathFor(token);
|
|
3909
|
+
if (!path) throw new Error("Cannot create MarkController for unindexed token");
|
|
3910
|
+
const address = index.addressFor(path);
|
|
3911
|
+
if (!address) throw new Error("Cannot create MarkController for unresolved token path");
|
|
3912
|
+
return new MarkController(store, address, {
|
|
3913
|
+
value: token.value,
|
|
3914
|
+
meta: token.meta,
|
|
3915
|
+
slot: token.slot?.content,
|
|
3916
|
+
readOnly: store.props.readOnly()
|
|
3917
|
+
}, snapshotTokenShape(token));
|
|
3706
3918
|
}
|
|
3707
3919
|
get value() {
|
|
3708
|
-
return this
|
|
3709
|
-
}
|
|
3710
|
-
set value(v) {
|
|
3711
|
-
this.#token.value = v ?? "";
|
|
3712
|
-
this.#emitChange();
|
|
3920
|
+
return this.snapshot.value;
|
|
3713
3921
|
}
|
|
3714
3922
|
get meta() {
|
|
3715
|
-
return this
|
|
3716
|
-
}
|
|
3717
|
-
set meta(v) {
|
|
3718
|
-
this.#token.meta = v;
|
|
3719
|
-
this.#emitChange();
|
|
3923
|
+
return this.snapshot.meta;
|
|
3720
3924
|
}
|
|
3721
3925
|
get slot() {
|
|
3722
|
-
return this
|
|
3723
|
-
}
|
|
3724
|
-
get #tokenInfo() {
|
|
3725
|
-
return findToken(this.#store.parsing.tokens(), this.#token);
|
|
3726
|
-
}
|
|
3727
|
-
get depth() {
|
|
3728
|
-
return this.#tokenInfo?.depth ?? 0;
|
|
3926
|
+
return this.snapshot.slot;
|
|
3729
3927
|
}
|
|
3730
|
-
get
|
|
3731
|
-
return this
|
|
3732
|
-
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3928
|
+
get readOnly() {
|
|
3929
|
+
return this.snapshot.readOnly;
|
|
3930
|
+
}
|
|
3931
|
+
remove() {
|
|
3932
|
+
const resolved = this.#resolve();
|
|
3933
|
+
if (!resolved.ok) return resolved;
|
|
3934
|
+
return this.store.value.replaceRange(resolved.value.position, "", { source: "mark" });
|
|
3935
|
+
}
|
|
3936
|
+
update(patch) {
|
|
3937
|
+
const resolved = this.#resolve();
|
|
3938
|
+
if (!resolved.ok) return resolved;
|
|
3939
|
+
const token = resolved.value;
|
|
3940
|
+
const value = patch.value ?? token.value;
|
|
3941
|
+
const meta = patch.meta?.kind === "clear" ? void 0 : patch.meta?.kind === "set" ? patch.meta.value : token.meta;
|
|
3942
|
+
const slot = patch.slot?.kind === "clear" ? void 0 : patch.slot?.kind === "set" ? patch.slot.value : token.slot?.content;
|
|
3943
|
+
const serialized = this.#serialize(token, {
|
|
3944
|
+
value,
|
|
3945
|
+
meta,
|
|
3946
|
+
slot
|
|
3947
|
+
});
|
|
3948
|
+
return this.store.value.replaceRange(token.position, serialized, { source: "mark" });
|
|
3735
3949
|
}
|
|
3736
|
-
|
|
3737
|
-
return
|
|
3950
|
+
#serialize(token, fields) {
|
|
3951
|
+
return annotate(token.descriptor.markup, {
|
|
3952
|
+
value: fields.value,
|
|
3953
|
+
meta: token.descriptor.gapTypes.includes("meta") ? fields.meta ?? "" : void 0,
|
|
3954
|
+
slot: token.descriptor.hasSlot ? fields.slot ?? "" : void 0
|
|
3955
|
+
});
|
|
3738
3956
|
}
|
|
3739
|
-
#
|
|
3740
|
-
this
|
|
3957
|
+
#resolve() {
|
|
3958
|
+
if (this.store.props.readOnly()) return {
|
|
3959
|
+
ok: false,
|
|
3960
|
+
reason: "readOnly"
|
|
3961
|
+
};
|
|
3962
|
+
const resolved = this.store.parsing.index().resolveAddress(this.address, this.#shape);
|
|
3963
|
+
if (!resolved.ok || resolved.value.type !== "mark") return {
|
|
3964
|
+
ok: false,
|
|
3965
|
+
reason: "stale"
|
|
3966
|
+
};
|
|
3967
|
+
return {
|
|
3968
|
+
ok: true,
|
|
3969
|
+
value: resolved.value
|
|
3970
|
+
};
|
|
3741
3971
|
}
|
|
3742
3972
|
};
|
|
3743
3973
|
//#endregion
|
|
@@ -3822,7 +4052,6 @@ function buildContainerProps(isDraggableBlock, readOnly, className, style, slotP
|
|
|
3822
4052
|
var SlotsFeature = class {
|
|
3823
4053
|
constructor(_store) {
|
|
3824
4054
|
this._store = _store;
|
|
3825
|
-
this.container = signal(null);
|
|
3826
4055
|
this.isBlock = computed(() => this._store.props.layout() === "block");
|
|
3827
4056
|
this.isDraggable = computed(() => !!this._store.props.draggable());
|
|
3828
4057
|
this.containerComponent = computed(() => resolveSlot("container", this._store.props.slots()));
|
|
@@ -3838,7 +4067,6 @@ var SlotsFeature = class {
|
|
|
3838
4067
|
//#endregion
|
|
3839
4068
|
//#region ../../core/src/features/mark/MarkFeature.ts
|
|
3840
4069
|
var MarkFeature = class {
|
|
3841
|
-
#scope;
|
|
3842
4070
|
constructor(_store) {
|
|
3843
4071
|
this._store = _store;
|
|
3844
4072
|
this.enabled = computed(() => {
|
|
@@ -3851,25 +4079,9 @@ var MarkFeature = class {
|
|
|
3851
4079
|
const Span = this._store.props.Span();
|
|
3852
4080
|
return (token) => resolveMarkSlot(token, options, Mark, Span);
|
|
3853
4081
|
});
|
|
3854
|
-
this.remove = event();
|
|
3855
|
-
}
|
|
3856
|
-
enable() {
|
|
3857
|
-
if (this.#scope) return;
|
|
3858
|
-
this.#scope = effectScope(() => {
|
|
3859
|
-
watch(this.remove, (payload) => {
|
|
3860
|
-
const { token } = payload;
|
|
3861
|
-
const tokens = this._store.parsing.tokens();
|
|
3862
|
-
if (!findToken(tokens, token)) return;
|
|
3863
|
-
const value = toString(tokens);
|
|
3864
|
-
const nextValue = value.slice(0, token.position.start) + value.slice(token.position.end);
|
|
3865
|
-
this._store.value.next(nextValue);
|
|
3866
|
-
});
|
|
3867
|
-
});
|
|
3868
|
-
}
|
|
3869
|
-
disable() {
|
|
3870
|
-
this.#scope?.();
|
|
3871
|
-
this.#scope = void 0;
|
|
3872
4082
|
}
|
|
4083
|
+
enable() {}
|
|
4084
|
+
disable() {}
|
|
3873
4085
|
};
|
|
3874
4086
|
//#endregion
|
|
3875
4087
|
//#region ../../core/src/features/overlay/filterSuggestions.ts
|
|
@@ -3888,8 +4100,8 @@ function createMarkFromOverlay(match, value, meta) {
|
|
|
3888
4100
|
meta,
|
|
3889
4101
|
content: "",
|
|
3890
4102
|
position: {
|
|
3891
|
-
start: match.
|
|
3892
|
-
end: match.
|
|
4103
|
+
start: match.range.start,
|
|
4104
|
+
end: match.range.end
|
|
3893
4105
|
},
|
|
3894
4106
|
descriptor: {
|
|
3895
4107
|
markup,
|
|
@@ -3920,9 +4132,37 @@ var OverlayFeature = class {
|
|
|
3920
4132
|
this.close = event();
|
|
3921
4133
|
}
|
|
3922
4134
|
#probeTrigger() {
|
|
3923
|
-
const match = TriggerFinder.find(this._store.props.options(), (option) => option.overlay?.trigger);
|
|
4135
|
+
const match = TriggerFinder.find(this._store.props.options(), (option) => option.overlay?.trigger, this._store) ?? this.#probeTriggerFromRecovery();
|
|
3924
4136
|
this.match(match);
|
|
3925
4137
|
}
|
|
4138
|
+
#probeTriggerFromRecovery() {
|
|
4139
|
+
const recovery = this._store.caret.recovery();
|
|
4140
|
+
if (recovery?.kind !== "caret") return;
|
|
4141
|
+
const value = this._store.value.current();
|
|
4142
|
+
const cursor = recovery.rawPosition;
|
|
4143
|
+
const left = value.slice(0, cursor);
|
|
4144
|
+
const rightWord = value.slice(cursor).match(/^\w*/)?.[0] ?? "";
|
|
4145
|
+
for (const option of this._store.props.options()) {
|
|
4146
|
+
const trigger = option.overlay?.trigger;
|
|
4147
|
+
if (!trigger) continue;
|
|
4148
|
+
const match = left.match(new RegExp(`${escape(trigger)}(\\w*)$`));
|
|
4149
|
+
if (!match) continue;
|
|
4150
|
+
const [sourceLeft, wordLeft] = match;
|
|
4151
|
+
const source = sourceLeft + rightWord;
|
|
4152
|
+
const start = cursor - sourceLeft.length;
|
|
4153
|
+
return {
|
|
4154
|
+
value: wordLeft + rightWord,
|
|
4155
|
+
source,
|
|
4156
|
+
range: {
|
|
4157
|
+
start,
|
|
4158
|
+
end: start + source.length
|
|
4159
|
+
},
|
|
4160
|
+
span: value,
|
|
4161
|
+
node: window.getSelection()?.anchorNode ?? this._store.dom.container() ?? document.body,
|
|
4162
|
+
option
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
3926
4166
|
enable() {
|
|
3927
4167
|
if (this.#scope) return;
|
|
3928
4168
|
this.#scope = effectScope(() => {
|
|
@@ -3936,55 +4176,40 @@ var OverlayFeature = class {
|
|
|
3936
4176
|
});
|
|
3937
4177
|
alienEffect(() => {
|
|
3938
4178
|
if (this.match()) {
|
|
3939
|
-
this._store.nodes.input.target = this._store.nodes.focus.target;
|
|
3940
4179
|
listen(window, "keydown", (e) => {
|
|
3941
4180
|
if (e.key === KEYBOARD.ESC) this.close();
|
|
3942
4181
|
});
|
|
3943
4182
|
listen(document, "click", (e) => {
|
|
3944
4183
|
const target = e.target instanceof HTMLElement ? e.target : null;
|
|
3945
4184
|
if (this.element()?.contains(target)) return;
|
|
3946
|
-
if (this._store.
|
|
4185
|
+
if (this._store.dom.container()?.contains(target)) return;
|
|
3947
4186
|
this.close();
|
|
3948
4187
|
}, true);
|
|
3949
4188
|
}
|
|
3950
4189
|
});
|
|
3951
4190
|
const selectionChangeHandler = () => {
|
|
3952
|
-
if (!this._store.
|
|
4191
|
+
if (!this._store.dom.container()?.contains(document.activeElement)) return;
|
|
3953
4192
|
const showOverlayOn = this._store.props.showOverlayOn();
|
|
3954
4193
|
const type = "selectionChange";
|
|
3955
4194
|
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
3956
4195
|
};
|
|
3957
4196
|
listen(document, "selectionchange", selectionChangeHandler);
|
|
3958
4197
|
watch(this.select, (overlayEvent) => {
|
|
3959
|
-
const
|
|
3960
|
-
const onChange = this._store.props.onChange();
|
|
3961
|
-
const { mark, match: { option, span, index, source } } = overlayEvent;
|
|
4198
|
+
const { mark, match: { option, range } } = overlayEvent;
|
|
3962
4199
|
const markup = option.markup;
|
|
3963
4200
|
if (!markup) return;
|
|
3964
4201
|
const annotation = mark.type === "mark" ? annotate(markup, {
|
|
3965
4202
|
value: mark.value,
|
|
3966
4203
|
meta: mark.meta
|
|
3967
4204
|
}) : annotate(markup, { value: mark.content });
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
} : {
|
|
3975
|
-
caret: index + annotation.length,
|
|
3976
|
-
anchor: this._store.nodes.input
|
|
4205
|
+
this._store.value.replaceRange(range, annotation, {
|
|
4206
|
+
source: "overlay",
|
|
4207
|
+
recover: {
|
|
4208
|
+
kind: "caret",
|
|
4209
|
+
rawPosition: range.start + annotation.length
|
|
4210
|
+
}
|
|
3977
4211
|
});
|
|
3978
|
-
|
|
3979
|
-
this._store.nodes.input.content = newSpan;
|
|
3980
|
-
const tokens = this._store.parsing.tokens();
|
|
3981
|
-
const inputToken = tokens[this._store.nodes.input.index];
|
|
3982
|
-
if (inputToken.type === "text") inputToken.content = newSpan;
|
|
3983
|
-
this._store.nodes.focus.target = this._store.nodes.input.target;
|
|
3984
|
-
this._store.nodes.input.clear();
|
|
3985
|
-
onChange?.(toString(tokens));
|
|
3986
|
-
this._store.parsing.reparse();
|
|
3987
|
-
}
|
|
4212
|
+
this.match(void 0);
|
|
3988
4213
|
});
|
|
3989
4214
|
});
|
|
3990
4215
|
}
|
|
@@ -4057,45 +4282,47 @@ var PropsFeature = class {
|
|
|
4057
4282
|
}
|
|
4058
4283
|
};
|
|
4059
4284
|
//#endregion
|
|
4285
|
+
//#region ../../core/src/features/value/ControlledEcho.ts
|
|
4286
|
+
var ControlledEcho = class {
|
|
4287
|
+
#pending;
|
|
4288
|
+
propose(candidate, recovery) {
|
|
4289
|
+
this.#pending = {
|
|
4290
|
+
candidate,
|
|
4291
|
+
recovery
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
onEcho(value) {
|
|
4295
|
+
const pending = this.#pending;
|
|
4296
|
+
if (!pending) return void 0;
|
|
4297
|
+
this.#pending = void 0;
|
|
4298
|
+
return pending.candidate === value ? pending.recovery : void 0;
|
|
4299
|
+
}
|
|
4300
|
+
supersede() {
|
|
4301
|
+
this.#pending = void 0;
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
//#endregion
|
|
4060
4305
|
//#region ../../core/src/features/value/ValueFeature.ts
|
|
4061
4306
|
var ValueFeature = class {
|
|
4307
|
+
#controlledEcho = new ControlledEcho();
|
|
4062
4308
|
#scope;
|
|
4063
4309
|
constructor(_store) {
|
|
4064
4310
|
this._store = _store;
|
|
4065
|
-
this.
|
|
4066
|
-
this.
|
|
4067
|
-
this.current = computed(() => this.last() ?? this._store.props.value() ?? "");
|
|
4311
|
+
this.current = signal("");
|
|
4312
|
+
this.isControlledMode = computed(() => this._store.props.value() !== void 0);
|
|
4068
4313
|
this.change = event();
|
|
4069
4314
|
}
|
|
4070
4315
|
enable() {
|
|
4071
4316
|
if (this.#scope) return;
|
|
4317
|
+
this.#commitAccepted(this._store.props.value() ?? this._store.props.defaultValue() ?? "");
|
|
4072
4318
|
this.#scope = effectScope(() => {
|
|
4073
|
-
watch(this.
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
trigger(this._store.parsing.tokens);
|
|
4081
|
-
return;
|
|
4082
|
-
}
|
|
4083
|
-
const tokens = this._store.parsing.tokens();
|
|
4084
|
-
if (focus.index >= tokens.length) return;
|
|
4085
|
-
const token = tokens[focus.index];
|
|
4086
|
-
if (token.type === "text") token.content = focus.content;
|
|
4087
|
-
else token.value = focus.content;
|
|
4088
|
-
onChange?.(toString(tokens));
|
|
4089
|
-
this._store.parsing.reparse();
|
|
4090
|
-
});
|
|
4091
|
-
watch(this.next, (newValue) => {
|
|
4092
|
-
if (newValue === void 0) return;
|
|
4093
|
-
const newTokens = parseWithParser(this._store, newValue);
|
|
4094
|
-
batch(() => {
|
|
4095
|
-
this._store.parsing.tokens(newTokens);
|
|
4096
|
-
this.last(newValue);
|
|
4097
|
-
});
|
|
4098
|
-
this._store.props.onChange()?.(newValue);
|
|
4319
|
+
watch(this._store.props.value, (value) => {
|
|
4320
|
+
if (value === void 0) return;
|
|
4321
|
+
if (value === this.current()) return;
|
|
4322
|
+
const recovery = this.#controlledEcho.onEcho(value);
|
|
4323
|
+
this.#commitAccepted(value);
|
|
4324
|
+
if (recovery) this._store.caret.recovery(recovery);
|
|
4325
|
+
this.change();
|
|
4099
4326
|
});
|
|
4100
4327
|
});
|
|
4101
4328
|
}
|
|
@@ -4103,6 +4330,52 @@ var ValueFeature = class {
|
|
|
4103
4330
|
this.#scope?.();
|
|
4104
4331
|
this.#scope = void 0;
|
|
4105
4332
|
}
|
|
4333
|
+
replaceRange(range, replacement, options) {
|
|
4334
|
+
const current = this.current();
|
|
4335
|
+
if (this._store.props.readOnly()) return {
|
|
4336
|
+
ok: false,
|
|
4337
|
+
reason: "readOnly"
|
|
4338
|
+
};
|
|
4339
|
+
if (range.start < 0 || range.end < range.start || range.end > current.length) return {
|
|
4340
|
+
ok: false,
|
|
4341
|
+
reason: "invalidRange"
|
|
4342
|
+
};
|
|
4343
|
+
const candidate = current.slice(0, range.start) + replacement + current.slice(range.end);
|
|
4344
|
+
return this.#commitCandidate(candidate, options?.recover);
|
|
4345
|
+
}
|
|
4346
|
+
replaceAll(next, options) {
|
|
4347
|
+
return this.replaceRange({
|
|
4348
|
+
start: 0,
|
|
4349
|
+
end: this.current().length
|
|
4350
|
+
}, next, options);
|
|
4351
|
+
}
|
|
4352
|
+
#commitCandidate(candidate, recovery) {
|
|
4353
|
+
if (this.isControlledMode()) {
|
|
4354
|
+
this.#controlledEcho.propose(candidate, recovery);
|
|
4355
|
+
this._store.props.onChange()?.(candidate);
|
|
4356
|
+
return {
|
|
4357
|
+
ok: true,
|
|
4358
|
+
accepted: "pendingControlledEcho",
|
|
4359
|
+
value: candidate
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
this._store.props.onChange()?.(candidate);
|
|
4363
|
+
this.#commitAccepted(candidate);
|
|
4364
|
+
this._store.caret.recovery(recovery);
|
|
4365
|
+
this.change();
|
|
4366
|
+
return {
|
|
4367
|
+
ok: true,
|
|
4368
|
+
accepted: "immediate",
|
|
4369
|
+
value: candidate
|
|
4370
|
+
};
|
|
4371
|
+
}
|
|
4372
|
+
#commitAccepted(value) {
|
|
4373
|
+
const tokens = this._store.parsing.parseValue(value);
|
|
4374
|
+
batch(() => {
|
|
4375
|
+
this._store.parsing.acceptTokens(tokens);
|
|
4376
|
+
this.current(value);
|
|
4377
|
+
});
|
|
4378
|
+
}
|
|
4106
4379
|
};
|
|
4107
4380
|
//#endregion
|
|
4108
4381
|
//#region ../../core/src/shared/utils/menuUtils.ts
|
|
@@ -4281,13 +4554,9 @@ var Store = class {
|
|
|
4281
4554
|
constructor() {
|
|
4282
4555
|
this.key = new KeyGenerator();
|
|
4283
4556
|
this.blocks = new BlockRegistry();
|
|
4284
|
-
this.nodes = {
|
|
4285
|
-
focus: new NodeProxy(void 0, this),
|
|
4286
|
-
input: new NodeProxy(void 0, this)
|
|
4287
|
-
};
|
|
4288
4557
|
this.props = new PropsFeature(this);
|
|
4289
4558
|
this.handler = new MarkputHandler(this);
|
|
4290
|
-
this.lifecycle = new LifecycleFeature(
|
|
4559
|
+
this.lifecycle = new LifecycleFeature();
|
|
4291
4560
|
this.value = new ValueFeature(this);
|
|
4292
4561
|
this.mark = new MarkFeature(this);
|
|
4293
4562
|
this.overlay = new OverlayFeature(this);
|
|
@@ -4379,14 +4648,24 @@ const Popup = ({ ref, style, children }) => {
|
|
|
4379
4648
|
//#endregion
|
|
4380
4649
|
//#region src/components/BlockMenu.tsx
|
|
4381
4650
|
const BlockMenu = memo(({ token }) => {
|
|
4382
|
-
const { blockStore, menuOpen, menuPosition } = useMarkput((s) =>
|
|
4383
|
-
blockStore
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4651
|
+
const { blockStore, menuOpen, menuPosition, dom, index } = useMarkput((s) => {
|
|
4652
|
+
const blockStore = s.blocks.get(token);
|
|
4653
|
+
return {
|
|
4654
|
+
blockStore,
|
|
4655
|
+
menuOpen: blockStore.state.menuOpen,
|
|
4656
|
+
menuPosition: blockStore.state.menuPosition,
|
|
4657
|
+
dom: s.dom,
|
|
4658
|
+
index: s.parsing.index
|
|
4659
|
+
};
|
|
4660
|
+
});
|
|
4661
|
+
const path = index.pathFor(token);
|
|
4662
|
+
const controlRef = useMemo(() => path ? dom.controlFor(path) : void 0, [dom, path]);
|
|
4387
4663
|
if (!menuOpen) return null;
|
|
4388
4664
|
return /* @__PURE__ */ jsx(Popup, {
|
|
4389
|
-
ref: (el) =>
|
|
4665
|
+
ref: (el) => {
|
|
4666
|
+
blockStore.attachMenu(el);
|
|
4667
|
+
controlRef?.(el);
|
|
4668
|
+
},
|
|
4390
4669
|
style: {
|
|
4391
4670
|
top: menuPosition.top,
|
|
4392
4671
|
left: menuPosition.left
|
|
@@ -4412,20 +4691,30 @@ BlockMenu.displayName = "BlockMenu";
|
|
|
4412
4691
|
//#region src/components/DragHandle.tsx
|
|
4413
4692
|
const iconGrip = `${styles$1.Icon} ${styles$1.IconGrip}`;
|
|
4414
4693
|
const DragHandle = memo(({ token, blockIndex }) => {
|
|
4415
|
-
const { blockStore, action, readOnly, draggable, isDragging, isHovered } = useMarkput((s) =>
|
|
4416
|
-
blockStore
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4694
|
+
const { blockStore, action, readOnly, draggable, isDragging, isHovered, dom, index } = useMarkput((s) => {
|
|
4695
|
+
const blockStore = s.blocks.get(token);
|
|
4696
|
+
return {
|
|
4697
|
+
blockStore,
|
|
4698
|
+
action: s.drag.action,
|
|
4699
|
+
readOnly: s.props.readOnly,
|
|
4700
|
+
draggable: s.props.draggable,
|
|
4701
|
+
isDragging: blockStore.state.isDragging,
|
|
4702
|
+
isHovered: blockStore.state.isHovered,
|
|
4703
|
+
dom: s.dom,
|
|
4704
|
+
index: s.parsing.index
|
|
4705
|
+
};
|
|
4706
|
+
});
|
|
4423
4707
|
const alwaysShowHandle = useMemo(() => getAlwaysShowHandle(draggable), [draggable]);
|
|
4708
|
+
const path = index.pathFor(token);
|
|
4709
|
+
const controlRef = useMemo(() => path ? dom.controlFor(path) : void 0, [dom, path]);
|
|
4424
4710
|
if (readOnly) return null;
|
|
4425
4711
|
return /* @__PURE__ */ jsx("div", {
|
|
4712
|
+
ref: controlRef,
|
|
4426
4713
|
className: cx(styles$1.SidePanel, alwaysShowHandle ? styles$1.SidePanelAlways : isHovered && !isDragging && styles$1.SidePanelVisible),
|
|
4427
4714
|
children: /* @__PURE__ */ jsx("button", {
|
|
4428
|
-
ref: (el) =>
|
|
4715
|
+
ref: (el) => {
|
|
4716
|
+
blockStore.attachGrip(el, blockIndex, { action });
|
|
4717
|
+
},
|
|
4429
4718
|
type: "button",
|
|
4430
4719
|
draggable: true,
|
|
4431
4720
|
className: cx(styles$1.GripButton, isDragging && styles$1.GripButtonDragging),
|
|
@@ -4438,8 +4727,16 @@ DragHandle.displayName = "DragHandle";
|
|
|
4438
4727
|
//#endregion
|
|
4439
4728
|
//#region src/components/DropIndicator.tsx
|
|
4440
4729
|
const DropIndicator = memo(({ token, position }) => {
|
|
4441
|
-
|
|
4730
|
+
const dropPosition = useMarkput((s) => s.blocks.get(token).state.dropPosition);
|
|
4731
|
+
const { dom, index } = useMarkput((s) => ({
|
|
4732
|
+
dom: s.dom,
|
|
4733
|
+
index: s.parsing.index
|
|
4734
|
+
}));
|
|
4735
|
+
const path = index.pathFor(token);
|
|
4736
|
+
const controlRef = useMemo(() => path ? dom.controlFor(path) : void 0, [dom, path]);
|
|
4737
|
+
if (dropPosition !== position) return null;
|
|
4442
4738
|
return /* @__PURE__ */ jsx("div", {
|
|
4739
|
+
ref: controlRef,
|
|
4443
4740
|
className: styles$1.DropIndicator,
|
|
4444
4741
|
style: position === "before" ? { top: -1 } : { bottom: -1 }
|
|
4445
4742
|
});
|
|
@@ -4449,25 +4746,50 @@ DropIndicator.displayName = "DropIndicator";
|
|
|
4449
4746
|
//#region src/lib/providers/TokenContext.ts
|
|
4450
4747
|
const TokenContext = createContext(void 0);
|
|
4451
4748
|
TokenContext.displayName = "TokenProvider";
|
|
4452
|
-
function
|
|
4749
|
+
function useTokenContext() {
|
|
4453
4750
|
const value = useContext(TokenContext);
|
|
4454
4751
|
if (value === void 0) throw new Error("Token not found. Make sure to wrap component in TokenContext.Provider.");
|
|
4455
4752
|
return value;
|
|
4456
4753
|
}
|
|
4457
4754
|
//#endregion
|
|
4755
|
+
//#region src/components/TokenChildren.tsx
|
|
4756
|
+
const sequenceHostStyle = { display: "contents" };
|
|
4757
|
+
const TokenChildren = memo(({ ownerPath, children }) => {
|
|
4758
|
+
const { dom } = useMarkput((s) => ({ dom: s.dom }));
|
|
4759
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4760
|
+
ref: useMemo(() => dom.childrenFor(ownerPath), [dom, ownerPath]),
|
|
4761
|
+
style: sequenceHostStyle,
|
|
4762
|
+
children
|
|
4763
|
+
});
|
|
4764
|
+
});
|
|
4765
|
+
TokenChildren.displayName = "TokenChildren";
|
|
4766
|
+
//#endregion
|
|
4458
4767
|
//#region src/components/Token.tsx
|
|
4459
|
-
const Token = memo(({
|
|
4460
|
-
const { resolveMarkSlot, key } = useMarkput((s) => ({
|
|
4768
|
+
const Token = memo(({ token }) => {
|
|
4769
|
+
const { resolveMarkSlot, key, index, store } = useMarkput((s) => ({
|
|
4461
4770
|
resolveMarkSlot: s.mark.slot,
|
|
4462
|
-
key: s.key
|
|
4771
|
+
key: s.key,
|
|
4772
|
+
index: s.parsing.index,
|
|
4773
|
+
store: s
|
|
4463
4774
|
}));
|
|
4464
|
-
const
|
|
4775
|
+
const path = index.pathFor(token);
|
|
4776
|
+
const address = path ? index.addressFor(path) : void 0;
|
|
4777
|
+
if (!path || !address) return null;
|
|
4778
|
+
const [Component, props] = resolveMarkSlot(token);
|
|
4779
|
+
const children = token.type === "mark" && token.children.length > 0 ? /* @__PURE__ */ jsx(TokenChildren, {
|
|
4780
|
+
ownerPath: path,
|
|
4781
|
+
children: token.children.map((child) => /* @__PURE__ */ jsx(Token, { token: child }, key.get(child)))
|
|
4782
|
+
}) : void 0;
|
|
4465
4783
|
return /* @__PURE__ */ jsx(TokenContext, {
|
|
4466
|
-
value:
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
}
|
|
4784
|
+
value: {
|
|
4785
|
+
store,
|
|
4786
|
+
token,
|
|
4787
|
+
address
|
|
4788
|
+
},
|
|
4789
|
+
children: children ? /* @__PURE__ */ jsx(Component, {
|
|
4790
|
+
...props,
|
|
4791
|
+
children
|
|
4792
|
+
}) : /* @__PURE__ */ jsx(Component, { ...props })
|
|
4471
4793
|
});
|
|
4472
4794
|
});
|
|
4473
4795
|
Token.displayName = "Token";
|
|
@@ -4483,8 +4805,11 @@ const Block = memo(({ token }) => {
|
|
|
4483
4805
|
tokens: s.parsing.tokens
|
|
4484
4806
|
}));
|
|
4485
4807
|
const blockIndex = tokens.indexOf(token);
|
|
4808
|
+
const setBlockRef = (el) => {
|
|
4809
|
+
blockStore.attachContainer(el, blockIndex, { action });
|
|
4810
|
+
};
|
|
4486
4811
|
return /* @__PURE__ */ jsxs(Component, {
|
|
4487
|
-
ref:
|
|
4812
|
+
ref: setBlockRef,
|
|
4488
4813
|
"data-testid": "block",
|
|
4489
4814
|
...slotProps,
|
|
4490
4815
|
className: cx(styles$1.Block, slotProps?.className),
|
|
@@ -4501,7 +4826,7 @@ const Block = memo(({ token }) => {
|
|
|
4501
4826
|
token,
|
|
4502
4827
|
blockIndex
|
|
4503
4828
|
}),
|
|
4504
|
-
/* @__PURE__ */ jsx(Token, {
|
|
4829
|
+
/* @__PURE__ */ jsx(Token, { token }),
|
|
4505
4830
|
/* @__PURE__ */ jsx(DropIndicator, {
|
|
4506
4831
|
token,
|
|
4507
4832
|
position: "after"
|
|
@@ -4514,38 +4839,32 @@ Block.displayName = "Block";
|
|
|
4514
4839
|
//#endregion
|
|
4515
4840
|
//#region src/components/Container.tsx
|
|
4516
4841
|
const Container = memo(() => {
|
|
4517
|
-
const
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
const { isBlock, tokens, key, lifecycleEmit, Component, props } = useMarkput((s) => ({
|
|
4842
|
+
const { dom, lifecycle, isBlock, tokens, key, Component, props } = useMarkput((s) => ({
|
|
4843
|
+
dom: s.dom,
|
|
4844
|
+
lifecycle: s.lifecycle,
|
|
4521
4845
|
isBlock: s.slots.isBlock,
|
|
4522
4846
|
tokens: s.parsing.tokens,
|
|
4523
4847
|
key: s.key,
|
|
4524
|
-
lifecycleEmit: s.lifecycle,
|
|
4525
4848
|
Component: s.slots.containerComponent,
|
|
4526
4849
|
props: s.slots.containerProps
|
|
4527
4850
|
}));
|
|
4528
4851
|
useLayoutEffect(() => {
|
|
4529
|
-
|
|
4530
|
-
}
|
|
4852
|
+
lifecycle.rendered();
|
|
4853
|
+
});
|
|
4531
4854
|
return /* @__PURE__ */ jsx(Component, {
|
|
4532
|
-
ref:
|
|
4855
|
+
ref: dom.container,
|
|
4533
4856
|
...props,
|
|
4534
|
-
children: isBlock ? tokens.map((t) => /* @__PURE__ */ jsx(Block, { token: t }, key.get(t))) : tokens.map((t) => /* @__PURE__ */ jsx(Token, {
|
|
4857
|
+
children: isBlock ? tokens.map((t) => /* @__PURE__ */ jsx(Block, { token: t }, key.get(t))) : tokens.map((t) => /* @__PURE__ */ jsx(Token, { token: t }, key.get(t)))
|
|
4535
4858
|
});
|
|
4536
4859
|
});
|
|
4537
4860
|
Container.displayName = "Container";
|
|
4538
4861
|
//#endregion
|
|
4539
4862
|
//#region src/lib/hooks/useOverlay.tsx
|
|
4540
4863
|
function useOverlay() {
|
|
4541
|
-
const match = useMarkput((s) =>
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
if (!ctx) throw new Error("Store not found");
|
|
4546
|
-
storeRef.current = ctx;
|
|
4547
|
-
}
|
|
4548
|
-
const store = storeRef.current;
|
|
4864
|
+
const { match, overlay } = useMarkput((s) => ({
|
|
4865
|
+
match: s.overlay.match,
|
|
4866
|
+
overlay: s.overlay
|
|
4867
|
+
}));
|
|
4549
4868
|
const style = useMemo(() => {
|
|
4550
4869
|
if (!match) return {
|
|
4551
4870
|
left: 0,
|
|
@@ -4553,36 +4872,34 @@ function useOverlay() {
|
|
|
4553
4872
|
};
|
|
4554
4873
|
return Caret.getAbsolutePosition();
|
|
4555
4874
|
}, [match]);
|
|
4556
|
-
const close = useCallback(() =>
|
|
4875
|
+
const close = useCallback(() => overlay.close(), [overlay]);
|
|
4557
4876
|
return {
|
|
4558
4877
|
match,
|
|
4559
4878
|
style,
|
|
4560
4879
|
select: useCallback((value) => {
|
|
4561
4880
|
if (!match) return;
|
|
4562
4881
|
const mark = createMarkFromOverlay(match, value.value, value.meta);
|
|
4563
|
-
|
|
4882
|
+
overlay.select({
|
|
4564
4883
|
mark,
|
|
4565
4884
|
match
|
|
4566
4885
|
});
|
|
4567
|
-
|
|
4568
|
-
}, [match,
|
|
4886
|
+
overlay.close();
|
|
4887
|
+
}, [match, overlay]),
|
|
4569
4888
|
close,
|
|
4570
4889
|
ref: useMemo(() => ({
|
|
4571
4890
|
get current() {
|
|
4572
|
-
return
|
|
4891
|
+
return overlay.element();
|
|
4573
4892
|
},
|
|
4574
4893
|
set current(v) {
|
|
4575
|
-
|
|
4894
|
+
overlay.element(v);
|
|
4576
4895
|
}
|
|
4577
|
-
}), [
|
|
4896
|
+
}), [overlay])
|
|
4578
4897
|
};
|
|
4579
4898
|
}
|
|
4580
4899
|
//#endregion
|
|
4581
4900
|
//#region src/components/Suggestions/Suggestions.tsx
|
|
4582
4901
|
const Suggestions = () => {
|
|
4583
|
-
const
|
|
4584
|
-
if (!storeCtx) throw new Error("Store not found");
|
|
4585
|
-
const store = storeCtx;
|
|
4902
|
+
const container = useMarkput((s) => s.dom.container);
|
|
4586
4903
|
const { match, select, style, ref } = useOverlay();
|
|
4587
4904
|
const [active, setActive] = useState(NaN);
|
|
4588
4905
|
const data = match?.option.overlay?.data ?? [];
|
|
@@ -4593,7 +4910,6 @@ const Suggestions = () => {
|
|
|
4593
4910
|
const filteredRef = useRef(filtered);
|
|
4594
4911
|
filteredRef.current = filtered;
|
|
4595
4912
|
useEffect(() => {
|
|
4596
|
-
const container = store.slots.container();
|
|
4597
4913
|
if (!container) return;
|
|
4598
4914
|
const handler = (event) => {
|
|
4599
4915
|
const result = navigateSuggestions(event.key, activeRef.current, length);
|
|
@@ -4617,7 +4933,11 @@ const Suggestions = () => {
|
|
|
4617
4933
|
};
|
|
4618
4934
|
container.addEventListener("keydown", handler);
|
|
4619
4935
|
return () => container.removeEventListener("keydown", handler);
|
|
4620
|
-
}, [
|
|
4936
|
+
}, [
|
|
4937
|
+
container,
|
|
4938
|
+
length,
|
|
4939
|
+
select
|
|
4940
|
+
]);
|
|
4621
4941
|
if (!filtered.length) return null;
|
|
4622
4942
|
return /* @__PURE__ */ jsx(Popup, {
|
|
4623
4943
|
ref,
|
|
@@ -4649,8 +4969,14 @@ OverlayRenderer.displayName = "OverlayRenderer";
|
|
|
4649
4969
|
//#endregion
|
|
4650
4970
|
//#region src/components/MarkedInput.tsx
|
|
4651
4971
|
function MarkedInput(props) {
|
|
4652
|
-
const [store] = useState(() =>
|
|
4653
|
-
|
|
4972
|
+
const [store] = useState(() => {
|
|
4973
|
+
const nextStore = new Store();
|
|
4974
|
+
nextStore.props.set(props);
|
|
4975
|
+
return nextStore;
|
|
4976
|
+
});
|
|
4977
|
+
useLayoutEffect(() => {
|
|
4978
|
+
store.props.set(props);
|
|
4979
|
+
});
|
|
4654
4980
|
useLayoutEffect(() => {
|
|
4655
4981
|
store.lifecycle.mounted();
|
|
4656
4982
|
return () => store.lifecycle.unmounted();
|
|
@@ -4663,31 +4989,34 @@ function MarkedInput(props) {
|
|
|
4663
4989
|
}
|
|
4664
4990
|
//#endregion
|
|
4665
4991
|
//#region src/lib/hooks/useMark.tsx
|
|
4666
|
-
const useMark = (
|
|
4667
|
-
const { store,
|
|
4668
|
-
|
|
4669
|
-
readOnly: s.props.readOnly
|
|
4670
|
-
}));
|
|
4671
|
-
const token = useToken();
|
|
4992
|
+
const useMark = () => {
|
|
4993
|
+
const { store, token } = useTokenContext();
|
|
4994
|
+
const readOnly = useMarkput((s) => s.props.readOnly);
|
|
4672
4995
|
if (token.type !== "mark") throw new Error("useMark must be called within a mark token context");
|
|
4673
|
-
|
|
4674
|
-
const mark = useMemo(() => new MarkHandler({
|
|
4675
|
-
ref,
|
|
4996
|
+
return useMemo(() => MarkController.fromToken(store, token), [
|
|
4676
4997
|
store,
|
|
4677
|
-
token
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4998
|
+
token,
|
|
4999
|
+
readOnly
|
|
5000
|
+
]);
|
|
5001
|
+
};
|
|
5002
|
+
//#endregion
|
|
5003
|
+
//#region src/lib/hooks/useMarkInfo.tsx
|
|
5004
|
+
const useMarkInfo = () => {
|
|
5005
|
+
const { store, token } = useTokenContext();
|
|
5006
|
+
if (token.type !== "mark") throw new Error("useMarkInfo must be called within a mark token context");
|
|
5007
|
+
const index = store.parsing.index();
|
|
5008
|
+
const path = index.pathFor(token);
|
|
5009
|
+
if (!path) throw new Error("Mark token is not indexed");
|
|
5010
|
+
const address = index.addressFor(path);
|
|
5011
|
+
if (!address) throw new Error("Mark token path is stale");
|
|
5012
|
+
return {
|
|
5013
|
+
address,
|
|
5014
|
+
depth: findToken(store.parsing.tokens(), token)?.depth ?? 0,
|
|
5015
|
+
hasNestedMarks: token.children.some((child) => child.type === "mark"),
|
|
5016
|
+
key: index.key(path)
|
|
5017
|
+
};
|
|
4684
5018
|
};
|
|
4685
|
-
function useUncontrolledInit(ref, options, token) {
|
|
4686
|
-
useEffect(() => {
|
|
4687
|
-
if (!options.controlled && ref.current) ref.current.textContent = token.content;
|
|
4688
|
-
}, []);
|
|
4689
|
-
}
|
|
4690
5019
|
//#endregion
|
|
4691
|
-
export {
|
|
5020
|
+
export { MarkController, MarkedInput, MarkputHandler, annotate, denote, useMark, useMarkInfo, useMarkput, useOverlay };
|
|
4692
5021
|
|
|
4693
5022
|
//# sourceMappingURL=index.js.map
|