@markput/react 0.12.1 → 0.14.1
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 +471 -406
- package/index.d.ts.map +1 -1
- package/index.js +2111 -1977
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -26,6 +26,12 @@ function merge(...objects) {
|
|
|
26
26
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
+
//#region ../../core/src/shared/utils/replaceInString.ts
|
|
30
|
+
function replaceInString(current, range, replacement) {
|
|
31
|
+
if (range.start < 0 || range.end < range.start || range.end > current.length) return void 0;
|
|
32
|
+
return current.slice(0, range.start) + replacement + current.slice(range.end);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
29
35
|
//#region ../../core/src/shared/constants.ts
|
|
30
36
|
const KEYBOARD = {
|
|
31
37
|
UP: "ArrowUp",
|
|
@@ -54,17 +60,21 @@ const DEFAULT_OPTIONS = [{
|
|
|
54
60
|
//#endregion
|
|
55
61
|
//#region ../../core/src/shared/classes/MarkputHandler.ts
|
|
56
62
|
var MarkputHandler = class {
|
|
57
|
-
constructor(
|
|
58
|
-
this.
|
|
63
|
+
constructor(dom, overlayFeature, parsing) {
|
|
64
|
+
this.dom = dom;
|
|
65
|
+
this.overlayFeature = overlayFeature;
|
|
66
|
+
this.parsing = parsing;
|
|
59
67
|
}
|
|
60
68
|
get container() {
|
|
61
|
-
return this.
|
|
69
|
+
return this.dom.container();
|
|
62
70
|
}
|
|
63
71
|
get overlay() {
|
|
64
|
-
return this.
|
|
72
|
+
return this.overlayFeature.element();
|
|
65
73
|
}
|
|
66
74
|
focus() {
|
|
67
|
-
this.
|
|
75
|
+
const firstAddress = this.parsing.index().addressFor([0]);
|
|
76
|
+
if (firstAddress && this.dom.focusAddress(firstAddress).ok) return;
|
|
77
|
+
this.container?.focus();
|
|
68
78
|
}
|
|
69
79
|
};
|
|
70
80
|
//#endregion
|
|
@@ -79,192 +89,6 @@ var KeyGenerator = class {
|
|
|
79
89
|
}
|
|
80
90
|
};
|
|
81
91
|
//#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
92
|
//#region ../../core/src/shared/signals/alien-signals/system.ts
|
|
269
93
|
let ReactiveFlags = /* @__PURE__ */ function(ReactiveFlags) {
|
|
270
94
|
ReactiveFlags[ReactiveFlags["None"] = 0] = "None";
|
|
@@ -666,7 +490,8 @@ function signal(initial, opts) {
|
|
|
666
490
|
};
|
|
667
491
|
return signalOper.bind(node);
|
|
668
492
|
}
|
|
669
|
-
function computed(
|
|
493
|
+
function computed(getterOrOpts, opts) {
|
|
494
|
+
const isWritable = typeof getterOrOpts !== "function";
|
|
670
495
|
const node = {
|
|
671
496
|
value: void 0,
|
|
672
497
|
subs: void 0,
|
|
@@ -674,10 +499,19 @@ function computed(getter, opts) {
|
|
|
674
499
|
deps: void 0,
|
|
675
500
|
depsTail: void 0,
|
|
676
501
|
flags: ReactiveFlags.None,
|
|
677
|
-
getter,
|
|
678
|
-
equalsFn: opts?.equals ?? void 0
|
|
502
|
+
getter: isWritable ? getterOrOpts.get : getterOrOpts,
|
|
503
|
+
equalsFn: (isWritable ? getterOrOpts.equals : opts?.equals) ?? void 0
|
|
504
|
+
};
|
|
505
|
+
const readFn = computedOper.bind(node);
|
|
506
|
+
if (!isWritable) return readFn;
|
|
507
|
+
const writableComputed = function writableComputedOper(...args) {
|
|
508
|
+
if (args.length === 0) return readFn();
|
|
509
|
+
const next = args[0];
|
|
510
|
+
if (next === void 0) return;
|
|
511
|
+
getterOrOpts.set(next);
|
|
679
512
|
};
|
|
680
|
-
|
|
513
|
+
Object.defineProperty(writableComputed, "name", { value: "bound " + computedOper.name });
|
|
514
|
+
return writableComputed;
|
|
681
515
|
}
|
|
682
516
|
function event() {
|
|
683
517
|
const node = {
|
|
@@ -700,7 +534,7 @@ function event() {
|
|
|
700
534
|
callable.read = eventReadOper.bind(node);
|
|
701
535
|
return callable;
|
|
702
536
|
}
|
|
703
|
-
function
|
|
537
|
+
function effect(fn) {
|
|
704
538
|
const e = {
|
|
705
539
|
fn,
|
|
706
540
|
cleanup: void 0,
|
|
@@ -741,7 +575,7 @@ function effectScope(fn) {
|
|
|
741
575
|
function watch(dep, fn) {
|
|
742
576
|
let initialized = false;
|
|
743
577
|
let oldValue;
|
|
744
|
-
return
|
|
578
|
+
return effect(() => {
|
|
745
579
|
const newValue = "read" in dep ? dep.read() : dep();
|
|
746
580
|
if (!initialized) {
|
|
747
581
|
initialized = true;
|
|
@@ -764,30 +598,6 @@ function batch(fn, opts) {
|
|
|
764
598
|
mutableScope = prevMutable;
|
|
765
599
|
}
|
|
766
600
|
}
|
|
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
601
|
function untracked(fn) {
|
|
792
602
|
const prev = setActiveSub(void 0);
|
|
793
603
|
try {
|
|
@@ -796,443 +606,136 @@ function untracked(fn) {
|
|
|
796
606
|
setActiveSub(prev);
|
|
797
607
|
}
|
|
798
608
|
}
|
|
609
|
+
function model(opts) {
|
|
610
|
+
let internal;
|
|
611
|
+
const ensureInternal = () => {
|
|
612
|
+
if (internal !== void 0) return internal;
|
|
613
|
+
internal = signal(opts.default !== void 0 ? untracked(opts.default) : void 0, { equals: opts.equals });
|
|
614
|
+
return internal;
|
|
615
|
+
};
|
|
616
|
+
const getFn = opts.get ?? ((value) => value);
|
|
617
|
+
const setFn = opts.set ?? ((next, previous) => next ?? previous);
|
|
618
|
+
const reader = computed(() => getFn(ensureInternal()()));
|
|
619
|
+
const callable = function modelOper(...args) {
|
|
620
|
+
if (args.length === 0) return reader();
|
|
621
|
+
const sig = ensureInternal();
|
|
622
|
+
sig(setFn(args[0], sig()));
|
|
623
|
+
};
|
|
624
|
+
Object.defineProperty(callable, "name", { value: "bound " + computedOper.name });
|
|
625
|
+
return callable;
|
|
626
|
+
}
|
|
799
627
|
function listen(target, event, handler, options) {
|
|
800
|
-
return
|
|
628
|
+
return effect(() => {
|
|
801
629
|
target.addEventListener(event, handler, options);
|
|
802
630
|
return () => target.removeEventListener(event, handler, options);
|
|
803
631
|
});
|
|
804
632
|
}
|
|
805
633
|
//#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
|
-
}
|
|
634
|
+
//#region ../../core/src/features/parsing/parser/constants.ts
|
|
635
|
+
/**
|
|
636
|
+
* Constants for ParserV2 - modern markup parser with nesting support
|
|
637
|
+
*
|
|
638
|
+
* This module contains the placeholder constants used by ParserV2.
|
|
639
|
+
* Unlike the legacy Parser, ParserV2 supports:
|
|
640
|
+
* - `__value__` - main content (replaces `__label__`)
|
|
641
|
+
* - `__meta__` - metadata (replaces `__value__`)
|
|
642
|
+
* - `__slot__` - nested content (new feature)
|
|
643
|
+
*
|
|
644
|
+
* For legacy Parser compatibility, see ../Parser/constants.ts
|
|
645
|
+
* For Markup types, see ./types.ts
|
|
646
|
+
*/
|
|
647
|
+
const PLACEHOLDER = {
|
|
648
|
+
Value: "__value__",
|
|
649
|
+
Meta: "__meta__",
|
|
650
|
+
Slot: "__slot__"
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* Gap types used in markup descriptors
|
|
654
|
+
* Represents the content type in gaps between segments
|
|
655
|
+
*/
|
|
656
|
+
const GAP_TYPE = {
|
|
657
|
+
Value: "value",
|
|
658
|
+
Meta: "meta",
|
|
659
|
+
Slot: "slot"
|
|
660
|
+
};
|
|
865
661
|
//#endregion
|
|
866
|
-
//#region ../../core/src/features/
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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;
|
|
662
|
+
//#region ../../core/src/features/parsing/parser/core/MarkupDescriptor.ts
|
|
663
|
+
/**
|
|
664
|
+
* Creates a segment-based markup descriptor from a markup template
|
|
665
|
+
*
|
|
666
|
+
* Examples:
|
|
667
|
+
* - `#[__value__]` -> segments: ["#[", "]"], gapTypes: ["value"]
|
|
668
|
+
* - `#[__slot__]` -> segments: ["#[", "]"], gapTypes: ["slot"]
|
|
669
|
+
* - `@[__value__](__meta__)` -> segments: ["@[", "](", ")"], gapTypes: ["value", "meta"]
|
|
670
|
+
* - `@[__slot__](__meta__)` -> segments: ["@[", "](", ")"], gapTypes: ["slot", "meta"]
|
|
671
|
+
* - `@[__value__](__slot__)` -> segments: ["@[", "](", ")"], gapTypes: ["value", "slot"]
|
|
672
|
+
* - `<__value__>__meta__</__value__>` -> segments: [{pattern: '<([^>]+)>'}, {pattern: '</([^>]+)>'}], gapTypes: ["value", "meta", "value"] (dynamic)
|
|
673
|
+
* - `<__value__ __meta__>__slot__</__value__>` -> segments: [{pattern: '<([^> ]+) '}, " ", {pattern: '>__slot__</([^>]+)>'}], gapTypes: ["value", "meta", "slot", "value"] (dynamic)
|
|
674
|
+
*/
|
|
675
|
+
function createMarkupDescriptor(markup, index) {
|
|
676
|
+
const { segments: rawSegments, gapTypes: rawGapTypes, counts, valueGapIndices } = scanMarkupStructure(markup);
|
|
677
|
+
validateMarkup(counts, markup);
|
|
678
|
+
const hasTwoValues = counts.value === 2;
|
|
679
|
+
const { segments, gapTypes } = hasTwoValues ? convertTwoValuePattern(rawSegments, rawGapTypes, valueGapIndices) : {
|
|
680
|
+
segments: rawSegments,
|
|
681
|
+
gapTypes: rawGapTypes
|
|
682
|
+
};
|
|
683
|
+
return {
|
|
684
|
+
markup,
|
|
685
|
+
index,
|
|
686
|
+
segments,
|
|
687
|
+
gapTypes,
|
|
688
|
+
hasSlot: counts.slot === 1,
|
|
689
|
+
hasTwoValues,
|
|
690
|
+
segmentGlobalIndices: Array.from({ length: segments.length })
|
|
910
691
|
};
|
|
911
692
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
}
|
|
693
|
+
/**
|
|
694
|
+
* Maps placeholder types to their text representations
|
|
695
|
+
*/
|
|
696
|
+
const PLACEHOLDER_TEXT = {
|
|
697
|
+
[GAP_TYPE.Value]: PLACEHOLDER.Value,
|
|
698
|
+
[GAP_TYPE.Meta]: PLACEHOLDER.Meta,
|
|
699
|
+
[GAP_TYPE.Slot]: PLACEHOLDER.Slot
|
|
929
700
|
};
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
701
|
+
/**
|
|
702
|
+
* Parses markup template into segments, gap types and placeholder counts
|
|
703
|
+
*/
|
|
704
|
+
function scanMarkupStructure(markup) {
|
|
705
|
+
const segments = [];
|
|
706
|
+
const gapTypes = [];
|
|
707
|
+
const valueGapIndices = [];
|
|
708
|
+
const counts = {
|
|
709
|
+
value: 0,
|
|
710
|
+
meta: 0,
|
|
711
|
+
slot: 0
|
|
712
|
+
};
|
|
713
|
+
const placeholders = [];
|
|
714
|
+
const placeholderTypes = [
|
|
715
|
+
GAP_TYPE.Value,
|
|
716
|
+
GAP_TYPE.Meta,
|
|
717
|
+
GAP_TYPE.Slot
|
|
718
|
+
];
|
|
719
|
+
for (const type of placeholderTypes) {
|
|
720
|
+
const text = PLACEHOLDER_TEXT[type];
|
|
721
|
+
let position = markup.indexOf(text);
|
|
722
|
+
while (position !== -1) {
|
|
723
|
+
placeholders.push({
|
|
724
|
+
type,
|
|
725
|
+
position
|
|
726
|
+
});
|
|
727
|
+
position = markup.indexOf(text, position + text.length);
|
|
728
|
+
}
|
|
941
729
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
};
|
|
1158
|
-
//#endregion
|
|
1159
|
-
//#region ../../core/src/features/parsing/parser/core/MarkupDescriptor.ts
|
|
1160
|
-
/**
|
|
1161
|
-
* Creates a segment-based markup descriptor from a markup template
|
|
1162
|
-
*
|
|
1163
|
-
* Examples:
|
|
1164
|
-
* - `#[__value__]` -> segments: ["#[", "]"], gapTypes: ["value"]
|
|
1165
|
-
* - `#[__slot__]` -> segments: ["#[", "]"], gapTypes: ["slot"]
|
|
1166
|
-
* - `@[__value__](__meta__)` -> segments: ["@[", "](", ")"], gapTypes: ["value", "meta"]
|
|
1167
|
-
* - `@[__slot__](__meta__)` -> segments: ["@[", "](", ")"], gapTypes: ["slot", "meta"]
|
|
1168
|
-
* - `@[__value__](__slot__)` -> segments: ["@[", "](", ")"], gapTypes: ["value", "slot"]
|
|
1169
|
-
* - `<__value__>__meta__</__value__>` -> segments: [{pattern: '<([^>]+)>'}, {pattern: '</([^>]+)>'}], gapTypes: ["value", "meta", "value"] (dynamic)
|
|
1170
|
-
* - `<__value__ __meta__>__slot__</__value__>` -> segments: [{pattern: '<([^> ]+) '}, " ", {pattern: '>__slot__</([^>]+)>'}], gapTypes: ["value", "meta", "slot", "value"] (dynamic)
|
|
1171
|
-
*/
|
|
1172
|
-
function createMarkupDescriptor(markup, index) {
|
|
1173
|
-
const { segments: rawSegments, gapTypes: rawGapTypes, counts, valueGapIndices } = scanMarkupStructure(markup);
|
|
1174
|
-
validateMarkup(counts, markup);
|
|
1175
|
-
const hasTwoValues = counts.value === 2;
|
|
1176
|
-
const { segments, gapTypes } = hasTwoValues ? convertTwoValuePattern(rawSegments, rawGapTypes, valueGapIndices) : {
|
|
1177
|
-
segments: rawSegments,
|
|
1178
|
-
gapTypes: rawGapTypes
|
|
1179
|
-
};
|
|
1180
|
-
return {
|
|
1181
|
-
markup,
|
|
1182
|
-
index,
|
|
1183
|
-
segments,
|
|
1184
|
-
gapTypes,
|
|
1185
|
-
hasSlot: counts.slot === 1,
|
|
1186
|
-
hasTwoValues,
|
|
1187
|
-
segmentGlobalIndices: Array.from({ length: segments.length })
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
/**
|
|
1191
|
-
* Maps placeholder types to their text representations
|
|
1192
|
-
*/
|
|
1193
|
-
const PLACEHOLDER_TEXT = {
|
|
1194
|
-
[GAP_TYPE.Value]: PLACEHOLDER.Value,
|
|
1195
|
-
[GAP_TYPE.Meta]: PLACEHOLDER.Meta,
|
|
1196
|
-
[GAP_TYPE.Slot]: PLACEHOLDER.Slot
|
|
1197
|
-
};
|
|
1198
|
-
/**
|
|
1199
|
-
* Parses markup template into segments, gap types and placeholder counts
|
|
1200
|
-
*/
|
|
1201
|
-
function scanMarkupStructure(markup) {
|
|
1202
|
-
const segments = [];
|
|
1203
|
-
const gapTypes = [];
|
|
1204
|
-
const valueGapIndices = [];
|
|
1205
|
-
const counts = {
|
|
1206
|
-
value: 0,
|
|
1207
|
-
meta: 0,
|
|
1208
|
-
slot: 0
|
|
1209
|
-
};
|
|
1210
|
-
const placeholders = [];
|
|
1211
|
-
const placeholderTypes = [
|
|
1212
|
-
GAP_TYPE.Value,
|
|
1213
|
-
GAP_TYPE.Meta,
|
|
1214
|
-
GAP_TYPE.Slot
|
|
1215
|
-
];
|
|
1216
|
-
for (const type of placeholderTypes) {
|
|
1217
|
-
const text = PLACEHOLDER_TEXT[type];
|
|
1218
|
-
let position = markup.indexOf(text);
|
|
1219
|
-
while (position !== -1) {
|
|
1220
|
-
placeholders.push({
|
|
1221
|
-
type,
|
|
1222
|
-
position
|
|
1223
|
-
});
|
|
1224
|
-
position = markup.indexOf(text, position + text.length);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
placeholders.sort((a, b) => a.position - b.position);
|
|
1228
|
-
let currentParsePosition = 0;
|
|
1229
|
-
for (const placeholder of placeholders) {
|
|
1230
|
-
const segment = markup.substring(currentParsePosition, placeholder.position);
|
|
1231
|
-
if (segment.length > 0) segments.push(segment);
|
|
1232
|
-
gapTypes.push(placeholder.type);
|
|
1233
|
-
counts[placeholder.type]++;
|
|
1234
|
-
if (placeholder.type === GAP_TYPE.Value) valueGapIndices.push(gapTypes.length - 1);
|
|
1235
|
-
currentParsePosition = placeholder.position + PLACEHOLDER_TEXT[placeholder.type].length;
|
|
730
|
+
placeholders.sort((a, b) => a.position - b.position);
|
|
731
|
+
let currentParsePosition = 0;
|
|
732
|
+
for (const placeholder of placeholders) {
|
|
733
|
+
const segment = markup.substring(currentParsePosition, placeholder.position);
|
|
734
|
+
if (segment.length > 0) segments.push(segment);
|
|
735
|
+
gapTypes.push(placeholder.type);
|
|
736
|
+
counts[placeholder.type]++;
|
|
737
|
+
if (placeholder.type === GAP_TYPE.Value) valueGapIndices.push(gapTypes.length - 1);
|
|
738
|
+
currentParsePosition = placeholder.position + PLACEHOLDER_TEXT[placeholder.type].length;
|
|
1236
739
|
}
|
|
1237
740
|
const finalSegment = markup.substring(currentParsePosition);
|
|
1238
741
|
if (finalSegment.length > 0) segments.push(finalSegment);
|
|
@@ -1640,6 +1143,11 @@ var PatternMatcher = class {
|
|
|
1640
1143
|
}
|
|
1641
1144
|
};
|
|
1642
1145
|
//#endregion
|
|
1146
|
+
//#region ../../core/src/shared/escape.ts
|
|
1147
|
+
const escape = (str) => {
|
|
1148
|
+
return str.replace(/[.*+?^${}()|[\]\\\\]/g, "\\$&");
|
|
1149
|
+
};
|
|
1150
|
+
//#endregion
|
|
1643
1151
|
//#region ../../core/src/features/parsing/parser/core/SegmentMatcher.ts
|
|
1644
1152
|
/**
|
|
1645
1153
|
* Computes regex pattern for dynamic segment using pre-computed exclusions
|
|
@@ -1986,11 +1494,7 @@ function processTokensWithCallback(tokens, callback) {
|
|
|
1986
1494
|
* ```
|
|
1987
1495
|
*/
|
|
1988
1496
|
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;
|
|
1497
|
+
return markup.replaceAll(PLACEHOLDER.Value, params.value ?? "").replaceAll(PLACEHOLDER.Meta, params.meta ?? "").replaceAll(PLACEHOLDER.Slot, params.slot ?? "");
|
|
1994
1498
|
}
|
|
1995
1499
|
//#endregion
|
|
1996
1500
|
//#region ../../core/src/features/parsing/parser/utils/toString.ts
|
|
@@ -2259,162 +1763,493 @@ function findToken(tokens, target, depth = 0, parent) {
|
|
|
2259
1763
|
}
|
|
2260
1764
|
}
|
|
2261
1765
|
//#endregion
|
|
2262
|
-
//#region ../../core/src/features/parsing/
|
|
2263
|
-
function
|
|
2264
|
-
|
|
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
|
-
};
|
|
1766
|
+
//#region ../../core/src/features/parsing/tokenIndex.ts
|
|
1767
|
+
function pathEquals(a, b) {
|
|
1768
|
+
return a.length === b.length && a.every((part, index) => part === b[index]);
|
|
2279
1769
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
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);
|
|
1770
|
+
function pathKey(path) {
|
|
1771
|
+
return path.join(".");
|
|
2291
1772
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
const
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
return
|
|
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 ?? "");
|
|
1773
|
+
function resolvePath(tokens, path) {
|
|
1774
|
+
if (path.length === 0) return void 0;
|
|
1775
|
+
let current = tokens;
|
|
1776
|
+
let token;
|
|
1777
|
+
for (const index of path) {
|
|
1778
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length) return void 0;
|
|
1779
|
+
token = current[index];
|
|
1780
|
+
current = token.type === "mark" ? token.children : [];
|
|
1781
|
+
}
|
|
1782
|
+
return token;
|
|
2338
1783
|
}
|
|
2339
|
-
function
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
}
|
|
2346
|
-
return parseWithParser(store, span);
|
|
1784
|
+
function snapshotTokenShape(token) {
|
|
1785
|
+
if (token.type === "text") return { kind: "text" };
|
|
1786
|
+
return {
|
|
1787
|
+
kind: "mark",
|
|
1788
|
+
descriptor: token.descriptor,
|
|
1789
|
+
descriptorIndex: token.descriptor.index
|
|
1790
|
+
};
|
|
2347
1791
|
}
|
|
2348
|
-
function
|
|
2349
|
-
|
|
2350
|
-
return
|
|
2351
|
-
const length = token.content.length;
|
|
2352
|
-
position += length;
|
|
2353
|
-
return position - length;
|
|
2354
|
-
});
|
|
1792
|
+
function shapeMatches(token, expected) {
|
|
1793
|
+
if (expected.kind === "text") return token.type === "text";
|
|
1794
|
+
return token.type === "mark" && token.descriptor === expected.descriptor && token.descriptor.index === expected.descriptorIndex;
|
|
2355
1795
|
}
|
|
2356
|
-
function
|
|
2357
|
-
const
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
return
|
|
1796
|
+
function createTokenIndex(tokens, generation) {
|
|
1797
|
+
const paths = /* @__PURE__ */ new WeakMap();
|
|
1798
|
+
const visit = (items, parent) => {
|
|
1799
|
+
items.forEach((token, index) => {
|
|
1800
|
+
const path = [...parent, index];
|
|
1801
|
+
paths.set(token, path);
|
|
1802
|
+
if (token.type === "mark") visit(token.children, path);
|
|
1803
|
+
});
|
|
1804
|
+
};
|
|
1805
|
+
visit(tokens, []);
|
|
1806
|
+
return {
|
|
1807
|
+
generation,
|
|
1808
|
+
pathFor: (token) => paths.get(token),
|
|
1809
|
+
addressFor: (path) => resolvePath(tokens, path) ? {
|
|
1810
|
+
path: [...path],
|
|
1811
|
+
parseGeneration: generation
|
|
1812
|
+
} : void 0,
|
|
1813
|
+
resolve: (path) => resolvePath(tokens, path),
|
|
1814
|
+
resolveAddress(address, expected) {
|
|
1815
|
+
if (address.parseGeneration !== generation) return {
|
|
1816
|
+
ok: false,
|
|
1817
|
+
reason: "stale"
|
|
1818
|
+
};
|
|
1819
|
+
const token = resolvePath(tokens, address.path);
|
|
1820
|
+
if (!token) return {
|
|
1821
|
+
ok: false,
|
|
1822
|
+
reason: "stale"
|
|
1823
|
+
};
|
|
1824
|
+
if (expected && !shapeMatches(token, expected)) return {
|
|
1825
|
+
ok: false,
|
|
1826
|
+
reason: "stale"
|
|
1827
|
+
};
|
|
1828
|
+
return {
|
|
1829
|
+
ok: true,
|
|
1830
|
+
value: token
|
|
1831
|
+
};
|
|
1832
|
+
},
|
|
1833
|
+
key: pathKey,
|
|
1834
|
+
equals: pathEquals
|
|
1835
|
+
};
|
|
2367
1836
|
}
|
|
2368
1837
|
//#endregion
|
|
2369
|
-
//#region ../../core/src/features/parsing/
|
|
2370
|
-
var
|
|
1838
|
+
//#region ../../core/src/features/parsing/ParseController.ts
|
|
1839
|
+
var ParseController = class {
|
|
1840
|
+
#generation = signal(0);
|
|
2371
1841
|
#scope;
|
|
2372
|
-
constructor(
|
|
2373
|
-
this.
|
|
1842
|
+
constructor(lifecycle, value, mark, props, slots) {
|
|
1843
|
+
this.lifecycle = lifecycle;
|
|
1844
|
+
this.value = value;
|
|
1845
|
+
this.mark = mark;
|
|
1846
|
+
this.props = props;
|
|
1847
|
+
this.slots = slots;
|
|
2374
1848
|
this.tokens = signal([]);
|
|
1849
|
+
this.index = computed(() => createTokenIndex(this.tokens(), this.#generation()));
|
|
2375
1850
|
this.parser = computed(() => {
|
|
2376
|
-
if (!this.
|
|
2377
|
-
const markups = this.
|
|
1851
|
+
if (!this.mark.enabled()) return;
|
|
1852
|
+
const markups = this.props.options().map((opt) => opt.markup);
|
|
2378
1853
|
if (!markups.some(Boolean)) return;
|
|
2379
|
-
return new Parser(markups, this.
|
|
1854
|
+
return new Parser(markups, this.slots.isBlock() ? { skipEmptyText: true } : void 0);
|
|
2380
1855
|
});
|
|
2381
1856
|
this.reparse = event();
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
this.sync();
|
|
2386
|
-
this.#scope = effectScope(() => {
|
|
2387
|
-
this.#subscribeParse();
|
|
2388
|
-
this.#subscribeReactiveParse();
|
|
1857
|
+
lifecycle.onMounted(() => {
|
|
1858
|
+
this.acceptTokens(this.#parseValue(value.current()));
|
|
1859
|
+
this.#subscribeValue();
|
|
2389
1860
|
});
|
|
1861
|
+
const toggle = (enabled) => {
|
|
1862
|
+
if (enabled && !this.#scope) this.#scope = effectScope(() => {
|
|
1863
|
+
this.#subscribeReactiveParse();
|
|
1864
|
+
this.#subscribeReparse();
|
|
1865
|
+
});
|
|
1866
|
+
if (!enabled && this.#scope) {
|
|
1867
|
+
this.#scope();
|
|
1868
|
+
this.#scope = void 0;
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
watch(this.mark.enabled, toggle);
|
|
1872
|
+
toggle(this.mark.enabled());
|
|
2390
1873
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
this.
|
|
1874
|
+
acceptTokens(tokens) {
|
|
1875
|
+
batch(() => {
|
|
1876
|
+
this.tokens(tokens);
|
|
1877
|
+
this.#generation(this.#generation() + 1);
|
|
1878
|
+
}, { mutable: true });
|
|
1879
|
+
}
|
|
1880
|
+
#parseValue(value) {
|
|
1881
|
+
const parser = this.parser();
|
|
1882
|
+
if (!parser) return [{
|
|
1883
|
+
type: "text",
|
|
1884
|
+
content: value,
|
|
1885
|
+
position: {
|
|
1886
|
+
start: 0,
|
|
1887
|
+
end: value.length
|
|
1888
|
+
}
|
|
1889
|
+
}];
|
|
1890
|
+
return parser.parse(value);
|
|
1891
|
+
}
|
|
1892
|
+
#subscribeValue() {
|
|
1893
|
+
watch(this.value.current, (v) => {
|
|
1894
|
+
this.acceptTokens(this.#parseValue(v));
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
#subscribeReactiveParse() {
|
|
1898
|
+
watch(computed(() => this.parser()), () => {
|
|
1899
|
+
this.acceptTokens(this.#parseValue(this.value.current()));
|
|
1900
|
+
});
|
|
2399
1901
|
}
|
|
2400
|
-
#
|
|
1902
|
+
#subscribeReparse() {
|
|
2401
1903
|
watch(this.reparse, () => {
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
1904
|
+
this.acceptTokens(this.#parseValue(this.value.current()));
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
//#endregion
|
|
1909
|
+
//#region ../../core/src/shared/checkers/domGuards.ts
|
|
1910
|
+
/** Type guard: checks if a value is an HTMLElement. */
|
|
1911
|
+
function isHtmlElement(el) {
|
|
1912
|
+
return typeof HTMLElement !== "undefined" && el instanceof HTMLElement;
|
|
1913
|
+
}
|
|
1914
|
+
/** Get all children of an element as HTMLElement[], filtering out non-HTML elements. */
|
|
1915
|
+
function htmlChildren(parent) {
|
|
1916
|
+
if (!parent) return [];
|
|
1917
|
+
return Array.from(parent.children).filter((child) => child instanceof HTMLElement);
|
|
1918
|
+
}
|
|
1919
|
+
/** Get the first element child as HTMLElement, or null. */
|
|
1920
|
+
function firstHtmlChild(parent) {
|
|
1921
|
+
const child = parent?.firstElementChild;
|
|
1922
|
+
return child instanceof HTMLElement ? child : null;
|
|
1923
|
+
}
|
|
1924
|
+
/** Safely narrow an event's target to Node. */
|
|
1925
|
+
function nodeTarget(event) {
|
|
1926
|
+
const { target } = event;
|
|
1927
|
+
return target instanceof Node ? target : null;
|
|
1928
|
+
}
|
|
1929
|
+
/** Get the next node from a TreeWalker as Text, or null. */
|
|
1930
|
+
function nextText(walker) {
|
|
1931
|
+
const node = walker.nextNode();
|
|
1932
|
+
return node?.nodeType === 3 ? node : null;
|
|
1933
|
+
}
|
|
1934
|
+
//#endregion
|
|
1935
|
+
//#region ../../core/src/features/caret/CaretModel.ts
|
|
1936
|
+
var CaretModel = class {
|
|
1937
|
+
constructor(lifecycle, dom, value) {
|
|
1938
|
+
this.lifecycle = lifecycle;
|
|
1939
|
+
this.dom = dom;
|
|
1940
|
+
this.value = value;
|
|
1941
|
+
this.selection = signal(void 0, { equals: shallow });
|
|
1942
|
+
this.position = computed({
|
|
1943
|
+
get: () => this.selection()?.start,
|
|
1944
|
+
set: (value) => this.selection(value !== void 0 ? {
|
|
1945
|
+
start: value,
|
|
1946
|
+
end: value
|
|
1947
|
+
} : void 0)
|
|
1948
|
+
});
|
|
1949
|
+
this.isUserSelecting = signal(false);
|
|
1950
|
+
this.isAllSelected = computed(() => {
|
|
1951
|
+
const s = this.selection();
|
|
1952
|
+
const v = this.value.current();
|
|
1953
|
+
return s?.start === 0 && s.end === v.length && v.length > 0;
|
|
1954
|
+
});
|
|
1955
|
+
lifecycle.onMounted(() => {
|
|
1956
|
+
this.#enableFocusTracking();
|
|
1957
|
+
this.#enableSelectionTracking();
|
|
1958
|
+
watch(dom.indexed, () => {
|
|
1959
|
+
dom.reconcile({ isUserSelecting: this.isUserSelecting() });
|
|
1960
|
+
this.#applyRangeToDOM();
|
|
1961
|
+
});
|
|
1962
|
+
effect(() => {
|
|
1963
|
+
const isUserSelecting = this.isUserSelecting();
|
|
1964
|
+
dom.readOnly();
|
|
1965
|
+
dom.reconcile({ isUserSelecting });
|
|
1966
|
+
});
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
selectAll() {
|
|
1970
|
+
this.selection({
|
|
1971
|
+
start: 0,
|
|
1972
|
+
end: this.value.current().length
|
|
1973
|
+
});
|
|
1974
|
+
this.#applyRangeToDOM();
|
|
1975
|
+
}
|
|
1976
|
+
#enableFocusTracking() {
|
|
1977
|
+
const container = this.dom.container();
|
|
1978
|
+
if (!container) return;
|
|
1979
|
+
listen(container, "focusin", (e) => {
|
|
1980
|
+
const target = e.target instanceof HTMLElement ? e.target : void 0;
|
|
1981
|
+
if (!target) {
|
|
1982
|
+
this.selection(void 0);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
const result = this.dom.locateNode(target);
|
|
1986
|
+
if (!result.ok) {
|
|
1987
|
+
if (result.reason === "control") return;
|
|
1988
|
+
this.selection(void 0);
|
|
2406
1989
|
return;
|
|
2407
1990
|
}
|
|
2408
|
-
|
|
1991
|
+
const rawSel = this.dom.readRawSelection();
|
|
1992
|
+
if (rawSel.ok) this.selection(rawSel.value.range);
|
|
1993
|
+
});
|
|
1994
|
+
listen(container, "focusout", () => {
|
|
1995
|
+
queueMicrotask(() => {
|
|
1996
|
+
if (!container.contains(document.activeElement)) this.selection(void 0);
|
|
1997
|
+
});
|
|
2409
1998
|
});
|
|
2410
1999
|
}
|
|
2411
|
-
#
|
|
2412
|
-
|
|
2413
|
-
|
|
2000
|
+
#enableSelectionTracking() {
|
|
2001
|
+
let pressedAt = null;
|
|
2002
|
+
listen(document, "mousedown", (e) => {
|
|
2003
|
+
pressedAt = nodeTarget(e);
|
|
2004
|
+
});
|
|
2005
|
+
listen(document, "mousemove", (e) => {
|
|
2006
|
+
if (pressedAt === null) return;
|
|
2007
|
+
const container = this.dom.container();
|
|
2008
|
+
if (!container) return;
|
|
2009
|
+
const startedOutsideEditor = !container.contains(pressedAt);
|
|
2010
|
+
const sweepingAcrossNodes = pressedAt !== e.target;
|
|
2011
|
+
const selectionIntersectsEditor = window.getSelection()?.containsNode(container, true) ?? false;
|
|
2012
|
+
if ((startedOutsideEditor || sweepingAcrossNodes) && selectionIntersectsEditor) this.isUserSelecting(true);
|
|
2013
|
+
});
|
|
2014
|
+
listen(document, "mouseup", () => {
|
|
2015
|
+
pressedAt = null;
|
|
2016
|
+
if (!this.isUserSelecting()) return;
|
|
2017
|
+
const sel = window.getSelection();
|
|
2018
|
+
if (!sel || sel.isCollapsed) this.isUserSelecting(false);
|
|
2019
|
+
});
|
|
2020
|
+
listen(document, "selectionchange", () => {
|
|
2021
|
+
const sel = window.getSelection();
|
|
2022
|
+
if (this.isUserSelecting() && (!sel || sel.isCollapsed)) this.isUserSelecting(false);
|
|
2023
|
+
if (!sel?.focusNode) return;
|
|
2024
|
+
const result = this.dom.locateNode(sel.focusNode);
|
|
2025
|
+
if (!result.ok) {
|
|
2026
|
+
if (result.reason === "control") return;
|
|
2027
|
+
this.selection(void 0);
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
const rawSel = this.dom.readRawSelection();
|
|
2031
|
+
if (rawSel.ok) this.selection(rawSel.value.range);
|
|
2032
|
+
else this.selection(void 0);
|
|
2414
2033
|
});
|
|
2415
2034
|
}
|
|
2035
|
+
#applyRangeToDOM() {
|
|
2036
|
+
if (this.isUserSelecting()) return;
|
|
2037
|
+
const sel = this.selection();
|
|
2038
|
+
if (sel === void 0) return;
|
|
2039
|
+
if (sel.start === sel.end) {
|
|
2040
|
+
const result = this.dom.placeAt(sel.start);
|
|
2041
|
+
if (!result.ok) {
|
|
2042
|
+
this.selection(void 0);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const applied = result.value.applied;
|
|
2046
|
+
if (applied !== sel.start) this.selection({
|
|
2047
|
+
start: applied,
|
|
2048
|
+
end: applied
|
|
2049
|
+
});
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
const result = this.dom.placeRange(sel);
|
|
2053
|
+
if (!result.ok) {
|
|
2054
|
+
this.selection(void 0);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
this.selection(result.value.applied);
|
|
2058
|
+
}
|
|
2416
2059
|
};
|
|
2417
2060
|
//#endregion
|
|
2061
|
+
//#region ../../core/src/features/caret/TriggerFinder.ts
|
|
2062
|
+
/** Regex to match word characters from the start of a string */
|
|
2063
|
+
const wordRegex = /* @__PURE__ */ new RegExp(/^\w*/);
|
|
2064
|
+
var TriggerFinder = class TriggerFinder {
|
|
2065
|
+
constructor(dom) {
|
|
2066
|
+
this.dom = dom;
|
|
2067
|
+
const sel = window.getSelection();
|
|
2068
|
+
const node = sel?.anchorNode;
|
|
2069
|
+
if (!sel || !node || !document.contains(node)) throw new Error("Anchor node of selection is not exists!");
|
|
2070
|
+
this.node = node;
|
|
2071
|
+
this.span = node.textContent ?? "";
|
|
2072
|
+
this.dividedText = this.getDividedTextBy(sel.anchorOffset);
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Find overlay match in text using provided options and trigger extractor.
|
|
2076
|
+
* @template T - Type of option objects
|
|
2077
|
+
* @param options - Array of options to search through
|
|
2078
|
+
* @param getTrigger - Function that extracts trigger from each option
|
|
2079
|
+
* @returns OverlayMatch with correct option type or undefined
|
|
2080
|
+
*
|
|
2081
|
+
* @example
|
|
2082
|
+
* // React usage
|
|
2083
|
+
* TriggerFinder.find(options, (opt) => opt.slotProps?.overlay?.trigger ?? '@')
|
|
2084
|
+
*
|
|
2085
|
+
* @example
|
|
2086
|
+
* // Other framework usage
|
|
2087
|
+
* TriggerFinder.find(vueOptions, (opt) => opt.overlay?.trigger ?? '@')
|
|
2088
|
+
*/
|
|
2089
|
+
static find(options, getTrigger, dom) {
|
|
2090
|
+
if (!options) return;
|
|
2091
|
+
if (!window.getSelection()?.isCollapsed) return;
|
|
2092
|
+
try {
|
|
2093
|
+
return new TriggerFinder(dom).find(options, getTrigger);
|
|
2094
|
+
} catch {
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
getDividedTextBy(position) {
|
|
2099
|
+
return {
|
|
2100
|
+
left: this.span.slice(0, position),
|
|
2101
|
+
right: this.span.slice(position)
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Find overlay match in provided options.
|
|
2106
|
+
* @template T - Type of option objects
|
|
2107
|
+
* @param options - Array of options
|
|
2108
|
+
* @param getTrigger - Function to extract trigger from each option
|
|
2109
|
+
*/
|
|
2110
|
+
find(options, getTrigger) {
|
|
2111
|
+
for (let i = 0; i < options.length; i++) {
|
|
2112
|
+
const option = options[i];
|
|
2113
|
+
const trigger = getTrigger(option, i);
|
|
2114
|
+
if (!trigger) continue;
|
|
2115
|
+
const match = this.matchInTextVia(trigger);
|
|
2116
|
+
if (match) {
|
|
2117
|
+
const range = this.#rawRangeForMatch(match.annotation, match.index);
|
|
2118
|
+
if (!range) return void 0;
|
|
2119
|
+
return {
|
|
2120
|
+
value: match.word,
|
|
2121
|
+
source: match.annotation,
|
|
2122
|
+
range,
|
|
2123
|
+
span: this.span,
|
|
2124
|
+
node: this.node,
|
|
2125
|
+
option
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
#rawRangeForMatch(source, index) {
|
|
2131
|
+
if (!this.dom) return {
|
|
2132
|
+
start: index,
|
|
2133
|
+
end: index + source.length
|
|
2134
|
+
};
|
|
2135
|
+
const boundary = this.dom.rawPositionFromBoundary(this.node, index + source.length, "after");
|
|
2136
|
+
if (!boundary.ok) return void 0;
|
|
2137
|
+
return {
|
|
2138
|
+
start: boundary.value - source.length,
|
|
2139
|
+
end: boundary.value
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
matchInTextVia(trigger = "@") {
|
|
2143
|
+
const rightMatch = this.matchRightPart();
|
|
2144
|
+
const leftMatch = this.matchLeftPart(trigger);
|
|
2145
|
+
if (leftMatch) return {
|
|
2146
|
+
word: leftMatch.word + rightMatch.word,
|
|
2147
|
+
annotation: leftMatch.annotation + rightMatch.word,
|
|
2148
|
+
index: leftMatch.index
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
matchRightPart() {
|
|
2152
|
+
const { right } = this.dividedText;
|
|
2153
|
+
return { word: right.match(wordRegex)?.[0] };
|
|
2154
|
+
}
|
|
2155
|
+
matchLeftPart(trigger) {
|
|
2156
|
+
const regex = this.makeTriggerRegex(trigger);
|
|
2157
|
+
const { left } = this.dividedText;
|
|
2158
|
+
const match = left.match(regex);
|
|
2159
|
+
if (!match) return;
|
|
2160
|
+
const [annotation, word] = match;
|
|
2161
|
+
return {
|
|
2162
|
+
word,
|
|
2163
|
+
annotation,
|
|
2164
|
+
index: match.index ?? 0
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
makeTriggerRegex(trigger) {
|
|
2168
|
+
const patten = escape(trigger) + "(\\w*)$";
|
|
2169
|
+
return new RegExp(patten);
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
//#endregion
|
|
2173
|
+
//#region ../../core/src/features/caret/caretDom.ts
|
|
2174
|
+
function getCaretIndex(element) {
|
|
2175
|
+
let position = 0;
|
|
2176
|
+
const selection = window.getSelection();
|
|
2177
|
+
if (!selection?.rangeCount) return position;
|
|
2178
|
+
const range = selection.getRangeAt(0);
|
|
2179
|
+
const preCaretRange = range.cloneRange();
|
|
2180
|
+
preCaretRange.selectNodeContents(element);
|
|
2181
|
+
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
|
2182
|
+
position = preCaretRange.toString().length;
|
|
2183
|
+
return position;
|
|
2184
|
+
}
|
|
2185
|
+
function getRect() {
|
|
2186
|
+
try {
|
|
2187
|
+
return (window.getSelection()?.getRangeAt(0))?.getBoundingClientRect() ?? null;
|
|
2188
|
+
} catch {
|
|
2189
|
+
return null;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
function isOnFirstLine(element) {
|
|
2193
|
+
const caretRect = getRect();
|
|
2194
|
+
if (!caretRect || caretRect.height === 0) return true;
|
|
2195
|
+
const elRect = element.getBoundingClientRect();
|
|
2196
|
+
return caretRect.top < elRect.top + caretRect.height + 2;
|
|
2197
|
+
}
|
|
2198
|
+
function isOnLastLine(element) {
|
|
2199
|
+
const caretRect = getRect();
|
|
2200
|
+
if (!caretRect || caretRect.height === 0) return true;
|
|
2201
|
+
const elRect = element.getBoundingClientRect();
|
|
2202
|
+
return caretRect.bottom > elRect.bottom - caretRect.height - 2;
|
|
2203
|
+
}
|
|
2204
|
+
function setAtElement(element, offset) {
|
|
2205
|
+
try {
|
|
2206
|
+
const selection = window.getSelection();
|
|
2207
|
+
if (!selection) return;
|
|
2208
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
2209
|
+
let node = nextText(walker);
|
|
2210
|
+
if (!node) return;
|
|
2211
|
+
let remaining = isFinite(offset) ? Math.max(0, offset) : Infinity;
|
|
2212
|
+
for (;;) {
|
|
2213
|
+
const next = nextText(walker);
|
|
2214
|
+
if (!next || remaining <= node.length) {
|
|
2215
|
+
const charOffset = isFinite(remaining) ? Math.min(remaining, node.length) : node.length;
|
|
2216
|
+
const range = document.createRange();
|
|
2217
|
+
range.setStart(node, charOffset);
|
|
2218
|
+
range.collapse(true);
|
|
2219
|
+
selection.removeAllRanges();
|
|
2220
|
+
selection.addRange(range);
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
remaining -= node.length;
|
|
2224
|
+
node = next;
|
|
2225
|
+
}
|
|
2226
|
+
} catch (e) {
|
|
2227
|
+
console.error(e);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function setAtX(element, x, y) {
|
|
2231
|
+
const elRect = element.getBoundingClientRect();
|
|
2232
|
+
const targetY = y ?? elRect.top + elRect.height / 2;
|
|
2233
|
+
const caretDoc = document;
|
|
2234
|
+
const caretPos = caretDoc.caretRangeFromPoint?.(x, targetY) ?? caretDoc.caretPositionFromPoint?.(x, targetY);
|
|
2235
|
+
if (!caretPos) return;
|
|
2236
|
+
const sel = window.getSelection();
|
|
2237
|
+
if (!sel) return;
|
|
2238
|
+
let domRange;
|
|
2239
|
+
if (caretPos instanceof Range) domRange = caretPos;
|
|
2240
|
+
else if ("offsetNode" in caretPos) {
|
|
2241
|
+
domRange = document.createRange();
|
|
2242
|
+
domRange.setStart(caretPos.offsetNode, caretPos.offset);
|
|
2243
|
+
domRange.collapse(true);
|
|
2244
|
+
} else return;
|
|
2245
|
+
if (!element.contains(domRange.startContainer)) {
|
|
2246
|
+
setAtElement(element, Infinity);
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
sel.removeAllRanges();
|
|
2250
|
+
sel.addRange(domRange);
|
|
2251
|
+
}
|
|
2252
|
+
//#endregion
|
|
2418
2253
|
//#region ../../core/src/features/clipboard/pasteMarkup.ts
|
|
2419
2254
|
/** Custom MIME type for markput markup syntax. */
|
|
2420
2255
|
const MARKPUT_MIME = "application/x-markput";
|
|
@@ -2442,157 +2277,57 @@ function consumeMarkupPaste(container) {
|
|
|
2442
2277
|
return markup;
|
|
2443
2278
|
}
|
|
2444
2279
|
//#endregion
|
|
2445
|
-
//#region ../../core/src/features/clipboard/
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
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);
|
|
2280
|
+
//#region ../../core/src/features/clipboard/ClipboardController.ts
|
|
2281
|
+
function htmlFromRange(range) {
|
|
2282
|
+
const fragment = range.cloneContents();
|
|
2283
|
+
const div = document.createElement("div");
|
|
2284
|
+
div.appendChild(fragment);
|
|
2285
|
+
return div.innerHTML;
|
|
2460
2286
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
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
|
-
};
|
|
2287
|
+
function serializeRawRange(tokens, range) {
|
|
2288
|
+
return toString(trimTokensForRawRange(tokens, range));
|
|
2506
2289
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
* directly; do not use `position` on the returned tokens for any other purpose.
|
|
2517
|
-
*/
|
|
2518
|
-
function trimBoundaryTokens({ tokens, startOffset, endOffset }) {
|
|
2519
|
-
if (tokens.length === 0) return tokens;
|
|
2520
|
-
return tokens.map((token, i) => {
|
|
2521
|
-
if (token.type !== "text") return token;
|
|
2522
|
-
const isFirst = i === 0;
|
|
2523
|
-
const isLast = i === tokens.length - 1;
|
|
2524
|
-
if (isFirst && isLast) return {
|
|
2525
|
-
...token,
|
|
2526
|
-
content: token.content.slice(startOffset, endOffset)
|
|
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;
|
|
2290
|
+
function trimTokensForRawRange(tokens, range) {
|
|
2291
|
+
return tokens.filter((token) => token.position.end > range.start && token.position.start < range.end).map((token) => {
|
|
2292
|
+
if (token.type === "text") {
|
|
2293
|
+
const start = Math.max(0, range.start - token.position.start);
|
|
2294
|
+
const end = Math.min(token.content.length, range.end - token.position.start);
|
|
2295
|
+
return Object.assign({}, token, { content: token.content.slice(start, end) });
|
|
2296
|
+
}
|
|
2297
|
+
if (token.children.length === 0) return token;
|
|
2298
|
+
return Object.assign({}, token, { children: trimTokensForRawRange(token.children, range) });
|
|
2537
2299
|
});
|
|
2538
2300
|
}
|
|
2539
|
-
var
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
this.
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
this.#scope = effectScope(() => {
|
|
2301
|
+
var ClipboardController = class {
|
|
2302
|
+
constructor(lifecycle, edit, dom, parsing) {
|
|
2303
|
+
this.lifecycle = lifecycle;
|
|
2304
|
+
this.edit = edit;
|
|
2305
|
+
this.dom = dom;
|
|
2306
|
+
this.parsing = parsing;
|
|
2307
|
+
lifecycle.onMounted(() => {
|
|
2308
|
+
const container = dom.container();
|
|
2309
|
+
if (!container) return;
|
|
2549
2310
|
listen(container, "copy", (e) => {
|
|
2550
2311
|
this.#handleCopy(e);
|
|
2551
2312
|
});
|
|
2552
2313
|
listen(container, "cut", (e) => {
|
|
2553
2314
|
if (!this.#handleCopy(e)) return;
|
|
2554
|
-
const
|
|
2555
|
-
if (!
|
|
2556
|
-
|
|
2557
|
-
const last = result.tokens[result.tokens.length - 1];
|
|
2558
|
-
const rawStart = first.type === "text" ? first.position.start + result.startOffset : first.position.start;
|
|
2559
|
-
const rawEnd = last.type === "text" ? last.position.start + result.endOffset : last.position.end;
|
|
2560
|
-
const value = this.store.value.current();
|
|
2561
|
-
if (rawStart === rawEnd) return;
|
|
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
|
|
2573
|
-
});
|
|
2315
|
+
const raw = dom.readRawSelection();
|
|
2316
|
+
if (!raw.ok || raw.value.range.start === raw.value.range.end) return;
|
|
2317
|
+
edit.replace(raw.value.range, "");
|
|
2574
2318
|
});
|
|
2575
2319
|
});
|
|
2576
2320
|
}
|
|
2577
|
-
disable() {
|
|
2578
|
-
this.#scope?.();
|
|
2579
|
-
this.#scope = void 0;
|
|
2580
|
-
}
|
|
2581
2321
|
#handleCopy(e) {
|
|
2582
|
-
|
|
2583
|
-
|
|
2322
|
+
if (!this.dom.container()) return false;
|
|
2323
|
+
const raw = this.dom.readRawSelection();
|
|
2324
|
+
if (!raw.ok || raw.value.range.start === raw.value.range.end) return false;
|
|
2584
2325
|
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;
|
|
2326
|
+
const range = sel?.rangeCount ? sel.getRangeAt(0) : void 0;
|
|
2327
|
+
if (!range) return false;
|
|
2590
2328
|
const plainText = range.toString();
|
|
2591
|
-
const
|
|
2592
|
-
const
|
|
2593
|
-
div.appendChild(fragment);
|
|
2594
|
-
const html = div.innerHTML;
|
|
2595
|
-
const markup = toString(trimBoundaryTokens(result));
|
|
2329
|
+
const html = htmlFromRange(range);
|
|
2330
|
+
const markup = serializeRawRange(this.parsing.tokens(), raw.value.range);
|
|
2596
2331
|
e.preventDefault();
|
|
2597
2332
|
e.clipboardData?.setData("text/plain", plainText);
|
|
2598
2333
|
e.clipboardData?.setData("text/html", html);
|
|
@@ -2601,120 +2336,698 @@ var ClipboardFeature = class {
|
|
|
2601
2336
|
}
|
|
2602
2337
|
};
|
|
2603
2338
|
//#endregion
|
|
2604
|
-
//#region ../../core/src/features/dom/
|
|
2605
|
-
function
|
|
2606
|
-
|
|
2339
|
+
//#region ../../core/src/features/dom/DomController.ts
|
|
2340
|
+
function nextTextNode(walker) {
|
|
2341
|
+
const node = walker.nextNode();
|
|
2342
|
+
return node instanceof Text ? node : null;
|
|
2607
2343
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
if (
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2344
|
+
function splitsSurrogatePair(text, offset) {
|
|
2345
|
+
if (offset <= 0 || offset >= text.length) return false;
|
|
2346
|
+
const prev = text.charCodeAt(offset - 1);
|
|
2347
|
+
const next = text.charCodeAt(offset);
|
|
2348
|
+
return prev >= 55296 && prev <= 56319 && next >= 56320 && next <= 57343;
|
|
2349
|
+
}
|
|
2350
|
+
function textOffsetWithin(surface, node, offset) {
|
|
2351
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
2352
|
+
if (splitsSurrogatePair(node.textContent ?? "", offset)) return void 0;
|
|
2353
|
+
return node instanceof Text ? textOffsetFromTreeWalker(surface, node, offset) : void 0;
|
|
2354
|
+
}
|
|
2355
|
+
if (node === surface) return elementBoundaryOffset(surface, offset);
|
|
2356
|
+
}
|
|
2357
|
+
function textOffsetFromTreeWalker(surface, target, targetOffset) {
|
|
2358
|
+
let total = 0;
|
|
2359
|
+
const walker = document.createTreeWalker(surface, NodeFilter.SHOW_TEXT);
|
|
2360
|
+
let current = nextTextNode(walker);
|
|
2361
|
+
while (current) {
|
|
2362
|
+
if (current === target) return total + targetOffset;
|
|
2363
|
+
total += current.length;
|
|
2364
|
+
current = nextTextNode(walker);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
function textLength(surface) {
|
|
2368
|
+
let total = 0;
|
|
2369
|
+
const walker = document.createTreeWalker(surface, NodeFilter.SHOW_TEXT);
|
|
2370
|
+
let current = nextTextNode(walker);
|
|
2371
|
+
while (current) {
|
|
2372
|
+
total += current.length;
|
|
2373
|
+
current = nextTextNode(walker);
|
|
2374
|
+
}
|
|
2375
|
+
return total;
|
|
2376
|
+
}
|
|
2377
|
+
function elementBoundaryOffset(surface, offset) {
|
|
2378
|
+
if (offset <= 0) return 0;
|
|
2379
|
+
if (offset >= surface.childNodes.length) return textLength(surface);
|
|
2380
|
+
let total = 0;
|
|
2381
|
+
for (let i = 0; i < offset; i++) {
|
|
2382
|
+
const child = surface.childNodes.item(i);
|
|
2383
|
+
if (child.nodeType === Node.TEXT_NODE && child instanceof Text) {
|
|
2384
|
+
total += child.length;
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
if (child instanceof HTMLElement) total += textLength(child);
|
|
2388
|
+
}
|
|
2389
|
+
return total;
|
|
2390
|
+
}
|
|
2391
|
+
function hasEditableAncestorBefore(node, boundary) {
|
|
2392
|
+
let current = node instanceof HTMLElement ? node : node.parentElement;
|
|
2393
|
+
while (current && current !== boundary) {
|
|
2394
|
+
if (current.isContentEditable || current.contentEditable === "true" || current.contentEditable === "plaintext-only") return true;
|
|
2395
|
+
current = current.parentElement;
|
|
2396
|
+
}
|
|
2397
|
+
return false;
|
|
2398
|
+
}
|
|
2399
|
+
var DomController = class {
|
|
2400
|
+
#domIndex = signal(void 0, { readonly: true });
|
|
2401
|
+
#pendingControls = /* @__PURE__ */ new Map();
|
|
2402
|
+
#pendingChildSequences = /* @__PURE__ */ new Map();
|
|
2403
|
+
#nextControlId = 0;
|
|
2404
|
+
#nextChildSequenceId = 0;
|
|
2405
|
+
#elementRoles = /* @__PURE__ */ new WeakMap();
|
|
2406
|
+
#pathElements = /* @__PURE__ */ new Map();
|
|
2407
|
+
#generation = 0;
|
|
2408
|
+
#rendering = false;
|
|
2409
|
+
#isComposing = false;
|
|
2410
|
+
#queuedRender = false;
|
|
2411
|
+
constructor(lifecycle, props, parsing, value) {
|
|
2412
|
+
this.lifecycle = lifecycle;
|
|
2413
|
+
this.props = props;
|
|
2414
|
+
this.parsing = parsing;
|
|
2415
|
+
this.value = value;
|
|
2416
|
+
this.index = computed(() => this.#domIndex());
|
|
2417
|
+
this.container = signal(null);
|
|
2418
|
+
this.diagnostics = event();
|
|
2419
|
+
this.indexed = event();
|
|
2420
|
+
this.readOnly = computed(() => this.props.readOnly());
|
|
2421
|
+
lifecycle.onMounted(() => {
|
|
2422
|
+
const container = this.container();
|
|
2423
|
+
if (container) listen(container, "click", () => {
|
|
2424
|
+
const tokens = this.parsing.tokens();
|
|
2425
|
+
if (tokens.length === 1 && tokens[0].type === "text" && tokens[0].content === "") {
|
|
2426
|
+
const c = this.container();
|
|
2427
|
+
(c ? firstHtmlChild(c) : null)?.focus();
|
|
2428
|
+
}
|
|
2621
2429
|
});
|
|
2622
|
-
|
|
2623
|
-
|
|
2430
|
+
watch(lifecycle.rendered, () => {
|
|
2431
|
+
this.#handleRendered();
|
|
2624
2432
|
});
|
|
2433
|
+
watch(computed(() => props.readOnly()), () => this.reconcile());
|
|
2625
2434
|
});
|
|
2626
2435
|
}
|
|
2627
|
-
|
|
2628
|
-
this.#
|
|
2629
|
-
this.#scope = void 0;
|
|
2436
|
+
compositionStarted() {
|
|
2437
|
+
this.#isComposing = true;
|
|
2630
2438
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
const
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2439
|
+
compositionEnded() {
|
|
2440
|
+
if (!this.#isComposing) return;
|
|
2441
|
+
this.#isComposing = false;
|
|
2442
|
+
}
|
|
2443
|
+
controlFor(ownerPath) {
|
|
2444
|
+
const key = `control:${ownerPath ? pathKey(ownerPath) : "global"}:${++this.#nextControlId}`;
|
|
2445
|
+
const callback = (element) => {
|
|
2446
|
+
if (element) this.#pendingControls.set(key, {
|
|
2447
|
+
ownerPath: ownerPath ? [...ownerPath] : void 0,
|
|
2448
|
+
element
|
|
2449
|
+
});
|
|
2450
|
+
else this.#pendingControls.delete(key);
|
|
2451
|
+
};
|
|
2452
|
+
return callback;
|
|
2453
|
+
}
|
|
2454
|
+
childrenFor(ownerPath) {
|
|
2455
|
+
const key = `children:${pathKey(ownerPath)}:${++this.#nextChildSequenceId}`;
|
|
2456
|
+
const callback = (element) => {
|
|
2457
|
+
if (element) this.#pendingChildSequences.set(key, {
|
|
2458
|
+
ownerPath: [...ownerPath],
|
|
2459
|
+
element
|
|
2460
|
+
});
|
|
2461
|
+
else this.#pendingChildSequences.delete(key);
|
|
2462
|
+
};
|
|
2463
|
+
return callback;
|
|
2464
|
+
}
|
|
2465
|
+
reconcile(opts) {
|
|
2466
|
+
this.#reconcileStructuralTextSurfaces(opts?.isUserSelecting);
|
|
2467
|
+
}
|
|
2468
|
+
locateNode(node) {
|
|
2469
|
+
if (!this.index()) return {
|
|
2470
|
+
ok: false,
|
|
2471
|
+
reason: "notIndexed"
|
|
2472
|
+
};
|
|
2473
|
+
const container = this.container();
|
|
2474
|
+
if (!container || !container.contains(node)) return {
|
|
2475
|
+
ok: false,
|
|
2476
|
+
reason: "outsideEditor"
|
|
2477
|
+
};
|
|
2478
|
+
let current = node;
|
|
2479
|
+
while (current) {
|
|
2480
|
+
if (current instanceof HTMLElement) {
|
|
2481
|
+
const role = this.#elementRoles.get(current);
|
|
2482
|
+
if (role?.role === "control") return {
|
|
2483
|
+
ok: false,
|
|
2484
|
+
reason: "control"
|
|
2485
|
+
};
|
|
2486
|
+
if (role) {
|
|
2487
|
+
const elements = this.#pathElements.get(pathKey(role.path));
|
|
2488
|
+
if (!elements?.tokenElement) return {
|
|
2489
|
+
ok: false,
|
|
2490
|
+
reason: "notIndexed"
|
|
2491
|
+
};
|
|
2492
|
+
return {
|
|
2493
|
+
ok: true,
|
|
2494
|
+
value: {
|
|
2495
|
+
address: role.address,
|
|
2496
|
+
tokenElement: elements.tokenElement,
|
|
2497
|
+
textElement: elements.textElement,
|
|
2498
|
+
rowElement: elements.rowElement
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2646
2502
|
}
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
if (el) el.contentEditable = value;
|
|
2503
|
+
if (current === container) break;
|
|
2504
|
+
current = current.parentNode;
|
|
2650
2505
|
}
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2506
|
+
return {
|
|
2507
|
+
ok: false,
|
|
2508
|
+
reason: "outsideEditor"
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
placeAt(rawPosition, affinity = "after") {
|
|
2512
|
+
if (!this.index()) return {
|
|
2513
|
+
ok: false,
|
|
2514
|
+
reason: "notIndexed"
|
|
2515
|
+
};
|
|
2516
|
+
const maxPos = this.value.current().length;
|
|
2517
|
+
const clamped = Math.min(rawPosition, maxPos);
|
|
2518
|
+
const target = this.#findTextTargetForRawPosition(clamped, affinity);
|
|
2519
|
+
if (!target) {
|
|
2520
|
+
const boundary = this.#focusMarkBoundaryForRawPosition(clamped);
|
|
2521
|
+
if (!boundary.ok) return boundary;
|
|
2522
|
+
return {
|
|
2523
|
+
ok: true,
|
|
2524
|
+
value: { applied: clamped }
|
|
2525
|
+
};
|
|
2663
2526
|
}
|
|
2527
|
+
target.element.focus();
|
|
2528
|
+
this.#placeCaretInTextSurface(target.element, clamped - target.start);
|
|
2529
|
+
return {
|
|
2530
|
+
ok: true,
|
|
2531
|
+
value: { applied: clamped }
|
|
2532
|
+
};
|
|
2664
2533
|
}
|
|
2665
|
-
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2534
|
+
placeRange(range) {
|
|
2535
|
+
const maxPos = this.value.current().length;
|
|
2536
|
+
const clamped = {
|
|
2537
|
+
start: Math.min(range.start, maxPos),
|
|
2538
|
+
end: Math.min(range.end, maxPos)
|
|
2539
|
+
};
|
|
2540
|
+
const result = this.#placeSelection({
|
|
2541
|
+
range: clamped,
|
|
2542
|
+
direction: void 0
|
|
2543
|
+
});
|
|
2544
|
+
if (!result.ok) return result;
|
|
2545
|
+
return {
|
|
2546
|
+
ok: true,
|
|
2547
|
+
value: { applied: clamped }
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
focusAddress(address, boundary = "start") {
|
|
2551
|
+
if (!this.index()) return {
|
|
2552
|
+
ok: false,
|
|
2553
|
+
reason: "notIndexed"
|
|
2554
|
+
};
|
|
2555
|
+
if (!this.parsing.index().resolveAddress(address).ok) return {
|
|
2556
|
+
ok: false,
|
|
2557
|
+
reason: "stale"
|
|
2558
|
+
};
|
|
2559
|
+
const elements = this.#pathElements.get(pathKey(address.path));
|
|
2560
|
+
const target = elements?.textElement ?? elements?.tokenElement ?? elements?.rowElement;
|
|
2561
|
+
if (!target) return {
|
|
2562
|
+
ok: false,
|
|
2563
|
+
reason: "notIndexed"
|
|
2564
|
+
};
|
|
2565
|
+
target.focus();
|
|
2566
|
+
if ((target === elements?.textElement ? "text" : target === elements?.rowElement ? "row" : "markDescendant") === "markDescendant") this.#placeCollapsedBoundary(target, boundary === "end" ? target.childNodes.length : 0);
|
|
2567
|
+
return {
|
|
2568
|
+
ok: true,
|
|
2569
|
+
value: void 0
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
rawPositionFromBoundary(node, offset, affinity = "after") {
|
|
2573
|
+
if (!this.index()) return {
|
|
2574
|
+
ok: false,
|
|
2575
|
+
reason: "notIndexed"
|
|
2576
|
+
};
|
|
2577
|
+
if (this.#isComposing) return {
|
|
2578
|
+
ok: false,
|
|
2579
|
+
reason: "composing"
|
|
2580
|
+
};
|
|
2581
|
+
const container = this.container();
|
|
2582
|
+
if (container && node === container) return this.#rawPositionFromContainerBoundary(offset, affinity);
|
|
2583
|
+
const location = this.locateNode(node);
|
|
2584
|
+
if (!location.ok) return location.reason === "control" ? {
|
|
2585
|
+
ok: false,
|
|
2586
|
+
reason: "control"
|
|
2587
|
+
} : location;
|
|
2588
|
+
const token = this.parsing.index().resolveAddress(location.value.address);
|
|
2589
|
+
if (!token.ok) return {
|
|
2590
|
+
ok: false,
|
|
2591
|
+
reason: "notIndexed"
|
|
2592
|
+
};
|
|
2593
|
+
if (node instanceof HTMLElement) {
|
|
2594
|
+
if (this.#elementRoles.get(node)?.role === "childSequence") {
|
|
2595
|
+
const childCount = node.childNodes.length;
|
|
2596
|
+
if (offset <= 0) return {
|
|
2597
|
+
ok: true,
|
|
2598
|
+
value: token.value.position.start
|
|
2599
|
+
};
|
|
2600
|
+
if (offset >= childCount) return {
|
|
2601
|
+
ok: true,
|
|
2602
|
+
value: token.value.position.end
|
|
2603
|
+
};
|
|
2604
|
+
return this.#rawPositionFromTokenChildBoundary(node, offset, token.value, affinity);
|
|
2685
2605
|
}
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2606
|
+
}
|
|
2607
|
+
const textElement = location.value.textElement;
|
|
2608
|
+
if (textElement?.contains(node)) {
|
|
2609
|
+
const local = textOffsetWithin(textElement, node, offset);
|
|
2610
|
+
if (local === void 0) return {
|
|
2611
|
+
ok: false,
|
|
2612
|
+
reason: "invalidBoundary"
|
|
2613
|
+
};
|
|
2614
|
+
return {
|
|
2615
|
+
ok: true,
|
|
2616
|
+
value: token.value.position.start + local
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
if (node === location.value.tokenElement) {
|
|
2620
|
+
const childCount = location.value.tokenElement.childNodes.length;
|
|
2621
|
+
if (offset <= 0) return {
|
|
2622
|
+
ok: true,
|
|
2623
|
+
value: token.value.position.start
|
|
2624
|
+
};
|
|
2625
|
+
if (offset >= childCount) return {
|
|
2626
|
+
ok: true,
|
|
2627
|
+
value: token.value.position.end
|
|
2628
|
+
};
|
|
2629
|
+
return this.#rawPositionFromTokenChildBoundary(location.value.tokenElement, offset, token.value, affinity);
|
|
2630
|
+
}
|
|
2631
|
+
if (token.value.type === "mark" && location.value.tokenElement.contains(node)) {
|
|
2632
|
+
if (hasEditableAncestorBefore(node, location.value.tokenElement)) return {
|
|
2633
|
+
ok: false,
|
|
2634
|
+
reason: "invalidBoundary"
|
|
2635
|
+
};
|
|
2636
|
+
return {
|
|
2637
|
+
ok: true,
|
|
2638
|
+
value: affinity === "after" ? token.value.position.start : token.value.position.end
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
if (location.value.rowElement && node === location.value.rowElement) return {
|
|
2642
|
+
ok: true,
|
|
2643
|
+
value: offset <= 0 ? token.value.position.start : token.value.position.end
|
|
2644
|
+
};
|
|
2645
|
+
return {
|
|
2646
|
+
ok: false,
|
|
2647
|
+
reason: "invalidBoundary"
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
readRawSelection() {
|
|
2651
|
+
if (!this.index()) return {
|
|
2652
|
+
ok: false,
|
|
2653
|
+
reason: "notIndexed"
|
|
2654
|
+
};
|
|
2655
|
+
const selection = window.getSelection();
|
|
2656
|
+
if (!selection || selection.rangeCount === 0) return {
|
|
2657
|
+
ok: false,
|
|
2658
|
+
reason: "invalidBoundary"
|
|
2659
|
+
};
|
|
2660
|
+
const range = selection.getRangeAt(0);
|
|
2661
|
+
const start = this.rawPositionFromBoundary(range.startContainer, range.startOffset, "after");
|
|
2662
|
+
const end = this.rawPositionFromBoundary(range.endContainer, range.endOffset, "before");
|
|
2663
|
+
if (!start.ok) {
|
|
2664
|
+
const reason = start.reason === "composing" ? "invalidBoundary" : start.reason;
|
|
2665
|
+
return {
|
|
2666
|
+
ok: false,
|
|
2667
|
+
reason: reason === "control" || reason === "outsideEditor" ? "mixedBoundary" : reason
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
if (!end.ok) {
|
|
2671
|
+
const reason = end.reason === "composing" ? "invalidBoundary" : end.reason;
|
|
2672
|
+
return {
|
|
2673
|
+
ok: false,
|
|
2674
|
+
reason: reason === "control" || reason === "outsideEditor" ? "mixedBoundary" : reason
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
const rangeValue = start.value <= end.value ? {
|
|
2678
|
+
start: start.value,
|
|
2679
|
+
end: end.value
|
|
2680
|
+
} : {
|
|
2681
|
+
start: end.value,
|
|
2682
|
+
end: start.value
|
|
2683
|
+
};
|
|
2684
|
+
const direction = rangeValue.start === rangeValue.end ? void 0 : selection.anchorNode === range.endContainer && selection.anchorOffset === range.endOffset ? "backward" : "forward";
|
|
2685
|
+
return {
|
|
2686
|
+
ok: true,
|
|
2687
|
+
value: direction ? {
|
|
2688
|
+
range: rangeValue,
|
|
2689
|
+
direction
|
|
2690
|
+
} : { range: rangeValue }
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
#handleRendered() {
|
|
2694
|
+
if (this.#rendering) {
|
|
2695
|
+
this.#queuedRender = true;
|
|
2696
|
+
this.diagnostics({
|
|
2697
|
+
kind: "renderReentry",
|
|
2698
|
+
reason: "rendered event queued during DOM indexing"
|
|
2699
|
+
});
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
this.#rendering = true;
|
|
2703
|
+
try {
|
|
2704
|
+
this.#commitRendered();
|
|
2705
|
+
} finally {
|
|
2706
|
+
this.#rendering = false;
|
|
2707
|
+
const queued = this.#queuedRender;
|
|
2708
|
+
this.#queuedRender = false;
|
|
2709
|
+
if (queued) this.#handleRendered();
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
#commitRendered() {
|
|
2713
|
+
const container = this.container();
|
|
2714
|
+
if (!container) {
|
|
2715
|
+
this.diagnostics({
|
|
2716
|
+
kind: "missingContainer",
|
|
2717
|
+
reason: "container is not registered"
|
|
2718
|
+
});
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
const tokenIndex = this.parsing.index();
|
|
2722
|
+
const pathElements = /* @__PURE__ */ new Map();
|
|
2723
|
+
const elementRoles = /* @__PURE__ */ new WeakMap();
|
|
2724
|
+
const controlElements = /* @__PURE__ */ new Set();
|
|
2725
|
+
for (const { element } of this.#pendingControls.values()) {
|
|
2726
|
+
controlElements.add(element);
|
|
2727
|
+
elementRoles.set(element, { role: "control" });
|
|
2728
|
+
}
|
|
2729
|
+
const tokens = this.parsing.tokens();
|
|
2730
|
+
if (this.props.layout() === "block") this.#indexBlockTokens(container, tokens, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2731
|
+
else this.#indexTokenSequence(container, tokens, [], void 0, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2732
|
+
this.#pathElements = pathElements;
|
|
2733
|
+
this.#elementRoles = elementRoles;
|
|
2734
|
+
this.#reconcileStructuralTextSurfaces();
|
|
2735
|
+
batch(() => this.#domIndex({ generation: ++this.#generation }), { mutable: true });
|
|
2736
|
+
this.indexed();
|
|
2737
|
+
}
|
|
2738
|
+
#elementChildren(element) {
|
|
2739
|
+
return Array.from(element.children).filter((child) => child instanceof HTMLElement);
|
|
2740
|
+
}
|
|
2741
|
+
#isControlRoot(element, controlElements) {
|
|
2742
|
+
if (controlElements.has(element)) return true;
|
|
2743
|
+
for (const control of controlElements) if (element.contains(control)) return true;
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
#childSequenceHostsFor(ownerPath) {
|
|
2747
|
+
const hosts = [];
|
|
2748
|
+
for (const registration of this.#pendingChildSequences.values()) if (pathEquals(registration.ownerPath, ownerPath)) hosts.push(registration.element);
|
|
2749
|
+
return hosts;
|
|
2750
|
+
}
|
|
2751
|
+
#indexNestedTokenSequence(token, path, address, ownerElement, rowElement, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2752
|
+
if (token.type !== "mark" || token.children.length === 0) return;
|
|
2753
|
+
const hosts = this.#childSequenceHostsFor(path);
|
|
2754
|
+
if (hosts.length === 0) {
|
|
2755
|
+
this.#indexTokenSequence(ownerElement, token.children, path, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
const ownerKey = pathKey(path);
|
|
2759
|
+
if (hosts.length !== 1) {
|
|
2760
|
+
this.diagnostics({
|
|
2761
|
+
kind: "ambiguousStructure",
|
|
2762
|
+
path,
|
|
2763
|
+
reason: `expected exactly 1 child sequence host for owner path ${ownerKey} but found ${hosts.length}`
|
|
2764
|
+
});
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
const host = hosts[0];
|
|
2768
|
+
if (!ownerElement.contains(host)) {
|
|
2769
|
+
this.diagnostics({
|
|
2770
|
+
kind: "ambiguousStructure",
|
|
2771
|
+
path,
|
|
2772
|
+
reason: `child sequence host for owner path ${ownerKey} is not contained by owner token element`
|
|
2773
|
+
});
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
elementRoles.set(host, {
|
|
2777
|
+
role: "childSequence",
|
|
2778
|
+
path,
|
|
2779
|
+
address
|
|
2780
|
+
});
|
|
2781
|
+
this.#indexTokenSequence(host, token.children, path, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2782
|
+
}
|
|
2783
|
+
#indexBlockTokens(container, tokens, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2784
|
+
const rows = this.#elementChildren(container);
|
|
2785
|
+
if (rows.length !== tokens.length) this.diagnostics({
|
|
2786
|
+
kind: "ambiguousStructure",
|
|
2787
|
+
reason: `expected ${tokens.length} block rows but found ${rows.length}`
|
|
2788
|
+
});
|
|
2789
|
+
tokens.forEach((token, i) => {
|
|
2790
|
+
const row = rows.at(i);
|
|
2791
|
+
if (!row) return;
|
|
2792
|
+
const candidates = this.#elementChildren(row).filter((child) => !this.#isControlRoot(child, controlElements));
|
|
2793
|
+
if (candidates.length !== 1) {
|
|
2794
|
+
this.diagnostics({
|
|
2795
|
+
kind: "ambiguousStructure",
|
|
2796
|
+
path: [i],
|
|
2797
|
+
reason: `expected 1 block token element but found ${candidates.length}`
|
|
2798
|
+
});
|
|
2799
|
+
return;
|
|
2690
2800
|
}
|
|
2801
|
+
this.#indexTokenElement(token, [i], candidates[0], row, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
#indexTokenSequence(parent, tokens, basePath, rowElement, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2805
|
+
const elements = this.#elementChildren(parent).filter((child) => !this.#isControlRoot(child, controlElements));
|
|
2806
|
+
if (elements.length !== tokens.length) {
|
|
2807
|
+
this.diagnostics({
|
|
2808
|
+
kind: "ambiguousStructure",
|
|
2809
|
+
path: basePath.length ? basePath : void 0,
|
|
2810
|
+
reason: `expected ${tokens.length} child token elements but found ${elements.length}`
|
|
2811
|
+
});
|
|
2812
|
+
return;
|
|
2691
2813
|
}
|
|
2814
|
+
tokens.forEach((token, i) => {
|
|
2815
|
+
const element = elements.at(i);
|
|
2816
|
+
if (!element) return;
|
|
2817
|
+
this.#indexTokenElement(token, [...basePath, i], element, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2818
|
+
});
|
|
2692
2819
|
}
|
|
2693
|
-
#
|
|
2694
|
-
const
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2820
|
+
#indexTokenElement(token, path, element, rowElement, tokenIndex, controlElements, pathElements, elementRoles) {
|
|
2821
|
+
const address = tokenIndex.addressFor(path);
|
|
2822
|
+
if (!address) {
|
|
2823
|
+
this.diagnostics({
|
|
2824
|
+
kind: "stalePath",
|
|
2825
|
+
path,
|
|
2826
|
+
reason: "structural path no longer resolves"
|
|
2827
|
+
});
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
const record = {
|
|
2831
|
+
path: [...path],
|
|
2832
|
+
address,
|
|
2833
|
+
tokenElement: element,
|
|
2834
|
+
textElement: token.type === "text" ? element : void 0,
|
|
2835
|
+
rowElement
|
|
2836
|
+
};
|
|
2837
|
+
pathElements.set(tokenIndex.key(path), record);
|
|
2838
|
+
elementRoles.set(element, {
|
|
2839
|
+
role: token.type === "text" ? "text" : "token",
|
|
2840
|
+
path,
|
|
2841
|
+
address
|
|
2842
|
+
});
|
|
2843
|
+
if (rowElement && path.length === 1) elementRoles.set(rowElement, {
|
|
2844
|
+
role: "row",
|
|
2845
|
+
path,
|
|
2846
|
+
address
|
|
2847
|
+
});
|
|
2848
|
+
this.#indexNestedTokenSequence(token, path, address, element, rowElement, tokenIndex, controlElements, pathElements, elementRoles);
|
|
2849
|
+
}
|
|
2850
|
+
#reconcileStructuralTextSurfaces(isUserSelecting) {
|
|
2851
|
+
const tokenIndex = this.parsing.index();
|
|
2852
|
+
const editable = this.props.readOnly() || isUserSelecting ? "false" : "true";
|
|
2853
|
+
for (const record of this.#pathElements.values()) {
|
|
2854
|
+
const resolved = tokenIndex.resolveAddress(record.address);
|
|
2855
|
+
if (!resolved.ok) {
|
|
2856
|
+
this.diagnostics({
|
|
2857
|
+
kind: "stalePath",
|
|
2858
|
+
path: record.path,
|
|
2859
|
+
reason: "structural path became stale during reconciliation"
|
|
2860
|
+
});
|
|
2861
|
+
continue;
|
|
2862
|
+
}
|
|
2863
|
+
if (record.textElement) {
|
|
2864
|
+
if (resolved.value.type !== "text") {
|
|
2865
|
+
this.diagnostics({
|
|
2866
|
+
kind: "missingRole",
|
|
2867
|
+
path: record.path,
|
|
2868
|
+
reason: "text role registered for non-text token"
|
|
2869
|
+
});
|
|
2870
|
+
continue;
|
|
2703
2871
|
}
|
|
2872
|
+
if (record.textElement.textContent !== resolved.value.content) record.textElement.textContent = resolved.value.content;
|
|
2873
|
+
record.textElement.contentEditable = editable;
|
|
2704
2874
|
continue;
|
|
2705
2875
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
if (el.textContent !== token.content) el.textContent = token.content;
|
|
2876
|
+
if (resolved.value.type === "mark") if (this.props.readOnly()) record.tokenElement.removeAttribute("tabindex");
|
|
2877
|
+
else record.tokenElement.tabIndex = 0;
|
|
2709
2878
|
}
|
|
2710
2879
|
}
|
|
2880
|
+
#rawPositionFromContainerBoundary(offset, affinity) {
|
|
2881
|
+
const tokens = this.parsing.tokens();
|
|
2882
|
+
if (tokens.length === 0) return {
|
|
2883
|
+
ok: true,
|
|
2884
|
+
value: 0
|
|
2885
|
+
};
|
|
2886
|
+
if (offset <= 0) return {
|
|
2887
|
+
ok: true,
|
|
2888
|
+
value: tokens[0].position.start
|
|
2889
|
+
};
|
|
2890
|
+
if (offset >= tokens.length) return {
|
|
2891
|
+
ok: true,
|
|
2892
|
+
value: tokens[tokens.length - 1].position.end
|
|
2893
|
+
};
|
|
2894
|
+
const before = tokens[offset - 1];
|
|
2895
|
+
const after = tokens[offset];
|
|
2896
|
+
return {
|
|
2897
|
+
ok: true,
|
|
2898
|
+
value: affinity === "before" ? before.position.end : after.position.start
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
#rawPositionFromTokenChildBoundary(tokenElement, offset, token, affinity) {
|
|
2902
|
+
if (token.type === "text") {
|
|
2903
|
+
const textElement = this.#pathElements.get(pathKey(this.parsing.index().pathFor(token) ?? []))?.textElement;
|
|
2904
|
+
if (!textElement || textLength(textElement) === 0) return {
|
|
2905
|
+
ok: true,
|
|
2906
|
+
value: token.position.start
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
const before = this.#locateRegisteredDescendant(tokenElement.childNodes.item(offset - 1));
|
|
2910
|
+
const after = this.#locateRegisteredDescendant(tokenElement.childNodes.item(offset));
|
|
2911
|
+
if (before?.ok && after?.ok) {
|
|
2912
|
+
const beforeToken = this.parsing.index().resolveAddress(before.value.address);
|
|
2913
|
+
const afterToken = this.parsing.index().resolveAddress(after.value.address);
|
|
2914
|
+
if (beforeToken.ok && afterToken.ok) return {
|
|
2915
|
+
ok: true,
|
|
2916
|
+
value: affinity === "before" ? beforeToken.value.position.end : afterToken.value.position.start
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
return {
|
|
2920
|
+
ok: true,
|
|
2921
|
+
value: affinity === "before" ? token.position.start : token.position.end
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
#locateRegisteredDescendant(node) {
|
|
2925
|
+
if (!node) return void 0;
|
|
2926
|
+
return this.locateNode(node);
|
|
2927
|
+
}
|
|
2928
|
+
#findTextTargetForRawPosition(rawPosition, affinity) {
|
|
2929
|
+
const candidates = [];
|
|
2930
|
+
const tokenIndex = this.parsing.index();
|
|
2931
|
+
for (const record of this.#pathElements.values()) {
|
|
2932
|
+
if (!record.textElement) continue;
|
|
2933
|
+
const resolved = tokenIndex.resolveAddress(record.address);
|
|
2934
|
+
if (!resolved.ok || resolved.value.type !== "text") continue;
|
|
2935
|
+
candidates.push({
|
|
2936
|
+
element: record.textElement,
|
|
2937
|
+
start: resolved.value.position.start,
|
|
2938
|
+
end: resolved.value.position.end
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
candidates.sort((a, b) => a.start - b.start);
|
|
2942
|
+
const containing = candidates.find((candidate) => rawPosition >= candidate.start && rawPosition <= candidate.end);
|
|
2943
|
+
if (containing) return containing;
|
|
2944
|
+
if (affinity === "before") return [...candidates].toReversed().find((candidate) => candidate.end <= rawPosition);
|
|
2945
|
+
return candidates.find((candidate) => candidate.start >= rawPosition);
|
|
2946
|
+
}
|
|
2947
|
+
#focusMarkBoundaryForRawPosition(rawPosition) {
|
|
2948
|
+
const tokenIndex = this.parsing.index();
|
|
2949
|
+
for (const record of this.#pathElements.values()) {
|
|
2950
|
+
const resolved = tokenIndex.resolveAddress(record.address);
|
|
2951
|
+
if (!resolved.ok || resolved.value.type !== "mark") continue;
|
|
2952
|
+
if (rawPosition !== resolved.value.position.start && rawPosition !== resolved.value.position.end) continue;
|
|
2953
|
+
const boundary = rawPosition === resolved.value.position.end ? "end" : "start";
|
|
2954
|
+
record.tokenElement.focus();
|
|
2955
|
+
this.#placeCollapsedBoundary(record.tokenElement, boundary === "end" ? record.tokenElement.childNodes.length : 0);
|
|
2956
|
+
return {
|
|
2957
|
+
ok: true,
|
|
2958
|
+
value: void 0
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
return {
|
|
2962
|
+
ok: false,
|
|
2963
|
+
reason: "invalidBoundary"
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
#placeCaretInTextSurface(surface, offset) {
|
|
2967
|
+
const selection = window.getSelection();
|
|
2968
|
+
if (!selection) return;
|
|
2969
|
+
const boundary = this.#boundaryInTextSurface(surface, offset);
|
|
2970
|
+
if (!boundary) return;
|
|
2971
|
+
const range = document.createRange();
|
|
2972
|
+
range.setStart(boundary.node, boundary.offset);
|
|
2973
|
+
range.collapse(true);
|
|
2974
|
+
selection.removeAllRanges();
|
|
2975
|
+
selection.addRange(range);
|
|
2976
|
+
}
|
|
2977
|
+
#placeCollapsedBoundary(element, offset) {
|
|
2978
|
+
const selection = window.getSelection();
|
|
2979
|
+
if (!selection) return;
|
|
2980
|
+
const range = document.createRange();
|
|
2981
|
+
range.setStart(element, Math.min(Math.max(offset, 0), element.childNodes.length));
|
|
2982
|
+
range.collapse(true);
|
|
2983
|
+
selection.removeAllRanges();
|
|
2984
|
+
selection.addRange(range);
|
|
2985
|
+
}
|
|
2986
|
+
#placeSelection(selection) {
|
|
2987
|
+
const start = this.#findTextTargetForRawPosition(selection.range.start, "after");
|
|
2988
|
+
const end = this.#findTextTargetForRawPosition(selection.range.end, "before");
|
|
2989
|
+
const browserSelection = window.getSelection();
|
|
2990
|
+
if (!start || !end || !browserSelection) return {
|
|
2991
|
+
ok: false,
|
|
2992
|
+
reason: "invalidBoundary"
|
|
2993
|
+
};
|
|
2994
|
+
const startBoundary = this.#boundaryInTextSurface(start.element, selection.range.start - start.start);
|
|
2995
|
+
const endBoundary = this.#boundaryInTextSurface(end.element, selection.range.end - end.start);
|
|
2996
|
+
if (!startBoundary || !endBoundary) return {
|
|
2997
|
+
ok: false,
|
|
2998
|
+
reason: "invalidBoundary"
|
|
2999
|
+
};
|
|
3000
|
+
const range = document.createRange();
|
|
3001
|
+
range.setStart(startBoundary.node, startBoundary.offset);
|
|
3002
|
+
range.setEnd(endBoundary.node, endBoundary.offset);
|
|
3003
|
+
browserSelection.removeAllRanges();
|
|
3004
|
+
browserSelection.addRange(range);
|
|
3005
|
+
return {
|
|
3006
|
+
ok: true,
|
|
3007
|
+
value: void 0
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
#boundaryInTextSurface(surface, offset) {
|
|
3011
|
+
const walker = document.createTreeWalker(surface, NodeFilter.SHOW_TEXT);
|
|
3012
|
+
let remaining = Math.max(0, offset);
|
|
3013
|
+
let node = nextTextNode(walker);
|
|
3014
|
+
while (node) {
|
|
3015
|
+
if (remaining <= node.length) return {
|
|
3016
|
+
node,
|
|
3017
|
+
offset: remaining
|
|
3018
|
+
};
|
|
3019
|
+
remaining -= node.length;
|
|
3020
|
+
node = nextTextNode(walker);
|
|
3021
|
+
}
|
|
3022
|
+
const text = surface.firstChild instanceof Text ? surface.firstChild : document.createTextNode("");
|
|
3023
|
+
if (!text.parentNode) surface.append(text);
|
|
3024
|
+
return {
|
|
3025
|
+
node: text,
|
|
3026
|
+
offset: text.length
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
2711
3029
|
};
|
|
2712
3030
|
//#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
3031
|
//#region ../../core/src/features/editing/createRowContent.ts
|
|
2719
3032
|
function createRowContent(options) {
|
|
2720
3033
|
const firstOption = options[0];
|
|
@@ -2726,47 +3039,6 @@ function createRowContent(options) {
|
|
|
2726
3039
|
});
|
|
2727
3040
|
}
|
|
2728
3041
|
//#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
3042
|
//#region ../../core/src/features/drag/operations.ts
|
|
2771
3043
|
function gapText(value, a, b) {
|
|
2772
3044
|
return value.substring(a.position.end, b.position.start);
|
|
@@ -2864,66 +3136,96 @@ const EMPTY_TEXT_TOKEN = {
|
|
|
2864
3136
|
}
|
|
2865
3137
|
};
|
|
2866
3138
|
//#endregion
|
|
2867
|
-
//#region ../../core/src/features/drag/
|
|
2868
|
-
var
|
|
2869
|
-
constructor(store) {
|
|
2870
|
-
this.store = store;
|
|
2871
|
-
this.action = event();
|
|
2872
|
-
}
|
|
3139
|
+
//#region ../../core/src/features/drag/DragController.ts
|
|
3140
|
+
var DragController = class {
|
|
2873
3141
|
#unsub;
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
this
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3142
|
+
constructor(props, value, parsing, caret) {
|
|
3143
|
+
this.props = props;
|
|
3144
|
+
this.value = value;
|
|
3145
|
+
this.parsing = parsing;
|
|
3146
|
+
this.caret = caret;
|
|
3147
|
+
this.action = event();
|
|
3148
|
+
const isDragEnabled = computed(() => this.props.layout() === "block" && !!this.props.draggable());
|
|
3149
|
+
const toggle = (enabled) => {
|
|
3150
|
+
if (enabled && !this.#unsub) this.#unsub = watch(this.action, (action) => {
|
|
3151
|
+
switch (action.type) {
|
|
3152
|
+
case "reorder":
|
|
3153
|
+
this.#reorder(action);
|
|
3154
|
+
break;
|
|
3155
|
+
case "add":
|
|
3156
|
+
this.#add(action);
|
|
3157
|
+
break;
|
|
3158
|
+
case "delete":
|
|
3159
|
+
this.#delete(action);
|
|
3160
|
+
break;
|
|
3161
|
+
case "duplicate":
|
|
3162
|
+
this.#duplicate(action);
|
|
3163
|
+
break;
|
|
3164
|
+
}
|
|
3165
|
+
});
|
|
3166
|
+
if (!enabled && this.#unsub) {
|
|
3167
|
+
this.#unsub();
|
|
3168
|
+
this.#unsub = void 0;
|
|
2890
3169
|
}
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
const
|
|
2899
|
-
if (
|
|
2900
|
-
|
|
2901
|
-
|
|
3170
|
+
};
|
|
3171
|
+
watch(isDragEnabled, toggle);
|
|
3172
|
+
toggle(isDragEnabled());
|
|
3173
|
+
}
|
|
3174
|
+
#reorder(action) {
|
|
3175
|
+
const value = this.value.current();
|
|
3176
|
+
const rows = this.parsing.tokens();
|
|
3177
|
+
const newValue = reorderDragRows(value, rows, action.source, action.target);
|
|
3178
|
+
if (newValue !== value) {
|
|
3179
|
+
const range = this.#rangeAfterDrag(action, rows, newValue);
|
|
3180
|
+
if (range) this.caret.selection(range);
|
|
3181
|
+
this.value.current(newValue);
|
|
3182
|
+
}
|
|
2902
3183
|
}
|
|
2903
|
-
#add(
|
|
2904
|
-
const value = this.
|
|
2905
|
-
|
|
2906
|
-
const rawRows = this.store.parsing.tokens();
|
|
3184
|
+
#add(action) {
|
|
3185
|
+
const value = this.value.current();
|
|
3186
|
+
const rawRows = this.parsing.tokens();
|
|
2907
3187
|
const rows = rawRows.length > 0 ? rawRows : [EMPTY_TEXT_TOKEN];
|
|
2908
|
-
const newRowContent = createRowContent(this.
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
const
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
this.
|
|
2921
|
-
}
|
|
2922
|
-
#duplicate(
|
|
2923
|
-
const value = this.
|
|
2924
|
-
|
|
2925
|
-
const
|
|
2926
|
-
this
|
|
3188
|
+
const newRowContent = createRowContent(this.props.options());
|
|
3189
|
+
const newValue = addDragRow(value, rows, action.afterIndex, newRowContent);
|
|
3190
|
+
const range = this.#rangeAfterDrag(action, rows, newValue);
|
|
3191
|
+
if (range) this.caret.selection(range);
|
|
3192
|
+
this.value.current(newValue);
|
|
3193
|
+
}
|
|
3194
|
+
#delete(action) {
|
|
3195
|
+
const value = this.value.current();
|
|
3196
|
+
const rows = this.parsing.tokens();
|
|
3197
|
+
const newValue = deleteDragRow(value, rows, action.index);
|
|
3198
|
+
const range = this.#rangeAfterDrag(action, rows, newValue);
|
|
3199
|
+
if (range) this.caret.selection(range);
|
|
3200
|
+
this.value.current(newValue);
|
|
3201
|
+
}
|
|
3202
|
+
#duplicate(action) {
|
|
3203
|
+
const value = this.value.current();
|
|
3204
|
+
const rows = this.parsing.tokens();
|
|
3205
|
+
const newValue = duplicateDragRow(value, rows, action.index);
|
|
3206
|
+
const range = this.#rangeAfterDrag(action, rows, newValue);
|
|
3207
|
+
if (range) this.caret.selection(range);
|
|
3208
|
+
this.value.current(newValue);
|
|
3209
|
+
}
|
|
3210
|
+
#rangeAfterDrag(action, previousRows, nextValue) {
|
|
3211
|
+
let rawPosition;
|
|
3212
|
+
if (action.type === "add") {
|
|
3213
|
+
const after = previousRows.at(action.afterIndex);
|
|
3214
|
+
rawPosition = after ? after.position.end : nextValue.length;
|
|
3215
|
+
} else if (action.type === "duplicate") {
|
|
3216
|
+
const row = previousRows.at(action.index);
|
|
3217
|
+
rawPosition = row ? row.position.end : void 0;
|
|
3218
|
+
} else if (action.type === "delete") {
|
|
3219
|
+
const next = previousRows.at(action.index + 1) ?? (action.index > 0 ? previousRows.at(action.index - 1) : void 0);
|
|
3220
|
+
rawPosition = next ? Math.min(next.position.start, nextValue.length) : 0;
|
|
3221
|
+
} else {
|
|
3222
|
+
const moved = previousRows.at(action.source);
|
|
3223
|
+
rawPosition = moved ? Math.min(moved.position.start, nextValue.length) : void 0;
|
|
3224
|
+
}
|
|
3225
|
+
return rawPosition !== void 0 ? {
|
|
3226
|
+
start: rawPosition,
|
|
3227
|
+
end: rawPosition
|
|
3228
|
+
} : void 0;
|
|
2927
3229
|
}
|
|
2928
3230
|
};
|
|
2929
3231
|
//#endregion
|
|
@@ -2944,174 +3246,71 @@ function getAlwaysShowHandle(draggable) {
|
|
|
2944
3246
|
return typeof draggable === "object" && !!draggable.alwaysShowHandle;
|
|
2945
3247
|
}
|
|
2946
3248
|
//#endregion
|
|
2947
|
-
//#region ../../core/src/features/
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
return true;
|
|
3249
|
+
//#region ../../core/src/features/edit/EditController.ts
|
|
3250
|
+
/**
|
|
3251
|
+
* Single write path for text edits — delegates gating to {@link ValueModel.replace}
|
|
3252
|
+
* and only moves the caret when the edit is accepted. Wrapped in {@link batch}
|
|
3253
|
+
* so subscribers observe a consistent value/selection pair on one tick.
|
|
3254
|
+
*/
|
|
3255
|
+
var EditController = class {
|
|
3256
|
+
constructor(value, caret) {
|
|
3257
|
+
this.value = value;
|
|
3258
|
+
this.caret = caret;
|
|
2958
3259
|
}
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
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;
|
|
3260
|
+
replace(range, replacement) {
|
|
3261
|
+
batch(() => {
|
|
3262
|
+
if (!this.value.replace(range, replacement)) return;
|
|
3263
|
+
this.caret.position(range.start + replacement.length);
|
|
3264
|
+
});
|
|
2971
3265
|
}
|
|
2972
|
-
|
|
2973
|
-
}
|
|
3266
|
+
};
|
|
2974
3267
|
//#endregion
|
|
2975
3268
|
//#region ../../core/src/features/keyboard/arrowNav.ts
|
|
2976
3269
|
function enableArrowNav(store) {
|
|
2977
|
-
const container = store.
|
|
2978
|
-
if (!container) return
|
|
2979
|
-
|
|
2980
|
-
|
|
3270
|
+
const container = store.dom.container();
|
|
3271
|
+
if (!container) return;
|
|
3272
|
+
listen(container, "keydown", (e) => {
|
|
3273
|
+
if (store.slots.isBlock()) return;
|
|
3274
|
+
if (e.key === KEYBOARD.LEFT) shiftFocus(store, e, "prev");
|
|
3275
|
+
else if (e.key === KEYBOARD.RIGHT) shiftFocus(store, e, "next");
|
|
3276
|
+
if ((e.ctrlKey || e.metaKey) && e.code === "KeyA") {
|
|
2981
3277
|
if (store.slots.isBlock()) return;
|
|
2982
|
-
|
|
2983
|
-
|
|
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);
|
|
3035
|
-
}
|
|
3036
|
-
function getDomRawPosInMark(node, offset, markElement, markToken) {
|
|
3037
|
-
if (markToken.children.length === 0) {
|
|
3038
|
-
if (offset === 0) return markToken.position.start;
|
|
3039
|
-
const nestedLen = markToken.slot?.content.length ?? markToken.value.length;
|
|
3040
|
-
if (nestedLen > 0 && offset >= nestedLen) {
|
|
3041
|
-
if (markToken.content.endsWith("\n\n") && markToken.slot) return markToken.slot.end;
|
|
3042
|
-
return markToken.position.end;
|
|
3043
|
-
}
|
|
3044
|
-
return (markToken.slot?.start ?? markToken.position.start) + Math.min(offset, nestedLen);
|
|
3045
|
-
}
|
|
3046
|
-
let tokenIdx = 0;
|
|
3047
|
-
for (const childNode of Array.from(markElement.childNodes)) {
|
|
3048
|
-
if (tokenIdx >= markToken.children.length) break;
|
|
3049
|
-
const tokenChild = markToken.children[tokenIdx];
|
|
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;
|
|
3067
|
-
}
|
|
3068
|
-
function setCaretInMarkAtRawPos(markElement, markToken, rawAbsolutePos) {
|
|
3069
|
-
const sel = window.getSelection();
|
|
3070
|
-
if (!sel) return false;
|
|
3071
|
-
let tokenIdx = 0;
|
|
3072
|
-
for (const childNode of Array.from(markElement.childNodes)) {
|
|
3073
|
-
if (tokenIdx >= markToken.children.length) break;
|
|
3074
|
-
const tokenChild = markToken.children[tokenIdx];
|
|
3075
|
-
if (isHtmlElement(childNode) && tokenChild.type === "text") {
|
|
3076
|
-
if (!isTextTokenSpan(childNode)) continue;
|
|
3077
|
-
if (rawAbsolutePos >= tokenChild.position.start && rawAbsolutePos <= tokenChild.position.end) {
|
|
3078
|
-
const rawTextNode = childNode.firstChild;
|
|
3079
|
-
const textNode = isTextNode(rawTextNode) ? rawTextNode : null;
|
|
3080
|
-
const offset = rawAbsolutePos - tokenChild.position.start;
|
|
3081
|
-
if (textNode) {
|
|
3082
|
-
const range = document.createRange();
|
|
3083
|
-
range.setStart(textNode, Math.min(offset, textNode.length));
|
|
3084
|
-
range.collapse(true);
|
|
3085
|
-
sel.removeAllRanges();
|
|
3086
|
-
sel.addRange(range);
|
|
3087
|
-
} else {
|
|
3088
|
-
const range = document.createRange();
|
|
3089
|
-
range.setStart(childNode, 0);
|
|
3090
|
-
range.collapse(true);
|
|
3091
|
-
sel.removeAllRanges();
|
|
3092
|
-
sel.addRange(range);
|
|
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++;
|
|
3278
|
+
e.preventDefault();
|
|
3279
|
+
store.caret.selectAll();
|
|
3112
3280
|
}
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
function shiftFocus(store, event, direction) {
|
|
3284
|
+
const active = document.activeElement instanceof HTMLElement ? document.activeElement : void 0;
|
|
3285
|
+
const located = active ? store.dom.locateNode(active) : void 0;
|
|
3286
|
+
if (!located?.ok) return false;
|
|
3287
|
+
const isFocusedOnMarkElement = active === located.value.tokenElement && !located.value.textElement;
|
|
3288
|
+
const address = located.value.address;
|
|
3289
|
+
const token = store.parsing.index().resolveAddress(address);
|
|
3290
|
+
if (!token.ok) return false;
|
|
3291
|
+
if (!isFocusedOnMarkElement) {
|
|
3292
|
+
const selection = store.dom.readRawSelection();
|
|
3293
|
+
if (!selection.ok || selection.value.range.start !== selection.value.range.end) return false;
|
|
3294
|
+
const atStart = selection.value.range.start <= token.value.position.start;
|
|
3295
|
+
const atEnd = selection.value.range.end >= token.value.position.end;
|
|
3296
|
+
if (direction === "prev" && !atStart) return false;
|
|
3297
|
+
if (direction === "next" && !atEnd) return false;
|
|
3298
|
+
}
|
|
3299
|
+
const path = address.path;
|
|
3300
|
+
const siblingIndex = direction === "prev" ? path[path.length - 1] - 1 : path[path.length - 1] + 1;
|
|
3301
|
+
const siblingPath = [...path.slice(0, -1), siblingIndex];
|
|
3302
|
+
const siblingAddress = store.parsing.index().addressFor(siblingPath);
|
|
3303
|
+
if (!siblingAddress) return false;
|
|
3304
|
+
event.preventDefault();
|
|
3305
|
+
if (!store.dom.focusAddress(siblingAddress, direction === "prev" ? "end" : "start").ok) return false;
|
|
3306
|
+
const sibling = store.parsing.index().resolve(siblingPath);
|
|
3307
|
+
if (sibling?.type === "mark") return true;
|
|
3308
|
+
if (direction === "prev") {
|
|
3309
|
+
store.dom.placeAt(sibling?.position.end ?? 0, "before");
|
|
3310
|
+
return true;
|
|
3113
3311
|
}
|
|
3114
|
-
|
|
3312
|
+
store.dom.placeAt(sibling?.position.start ?? 0, "after");
|
|
3313
|
+
return true;
|
|
3115
3314
|
}
|
|
3116
3315
|
//#endregion
|
|
3117
3316
|
//#region ../../core/src/features/keyboard/blockEdit.ts
|
|
@@ -3120,26 +3319,23 @@ function isTextLikeRow(token) {
|
|
|
3120
3319
|
return token.descriptor.hasSlot && token.descriptor.segments.length === 1;
|
|
3121
3320
|
}
|
|
3122
3321
|
function enableBlockEdit(store) {
|
|
3123
|
-
const container = store.
|
|
3124
|
-
if (!container) return
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
handleEnter(store, e);
|
|
3132
|
-
});
|
|
3133
|
-
listen(container, "beforeinput", (e) => {
|
|
3134
|
-
if (!store.slots.isBlock()) return;
|
|
3135
|
-
if (e.defaultPrevented) return;
|
|
3136
|
-
handleBlockBeforeInput(store, e);
|
|
3137
|
-
}, true);
|
|
3322
|
+
const container = store.dom.container();
|
|
3323
|
+
if (!container) return;
|
|
3324
|
+
listen(container, "keydown", (e) => {
|
|
3325
|
+
if (!store.slots.isBlock()) return;
|
|
3326
|
+
if (e.key === KEYBOARD.LEFT || e.key === KEYBOARD.RIGHT) handleBlockArrowLeftRight(store, e, e.key === KEYBOARD.LEFT ? "left" : "right");
|
|
3327
|
+
else if (e.key === KEYBOARD.UP || e.key === KEYBOARD.DOWN) handleArrowUpDown(store, e);
|
|
3328
|
+
handleDelete(store, e);
|
|
3329
|
+
handleEnter(store, e);
|
|
3138
3330
|
});
|
|
3139
|
-
|
|
3331
|
+
listen(container, "beforeinput", (e) => {
|
|
3332
|
+
if (!store.slots.isBlock()) return;
|
|
3333
|
+
if (e.defaultPrevented) return;
|
|
3334
|
+
handleBlockBeforeInput(store, e);
|
|
3335
|
+
}, true);
|
|
3140
3336
|
}
|
|
3141
|
-
function handleDelete
|
|
3142
|
-
const container = store.
|
|
3337
|
+
function handleDelete(store, event) {
|
|
3338
|
+
const container = store.dom.container();
|
|
3143
3339
|
if (!container) return;
|
|
3144
3340
|
const blockDivs = htmlChildren(container);
|
|
3145
3341
|
const blockIndex = blockDivs.findIndex((div) => div === document.activeElement || div.contains(document.activeElement));
|
|
@@ -3148,24 +3344,19 @@ function handleDelete$1(store, event) {
|
|
|
3148
3344
|
if (blockIndex >= rows.length) return;
|
|
3149
3345
|
const token = rows[blockIndex];
|
|
3150
3346
|
const value = store.value.current();
|
|
3151
|
-
if (!store.props.onChange()) return;
|
|
3152
3347
|
if (event.key === KEYBOARD.BACKSPACE) {
|
|
3153
3348
|
const blockDiv = blockDivs[blockIndex];
|
|
3154
|
-
const caretAtStart =
|
|
3349
|
+
const caretAtStart = getCaretIndex(blockDiv) === 0;
|
|
3155
3350
|
if (("content" in token ? token.content : "") === "") {
|
|
3156
3351
|
event.preventDefault();
|
|
3157
3352
|
const newValue = rows.length <= 1 ? "" : (() => {
|
|
3158
3353
|
if (blockIndex >= rows.length - 1) return value.slice(0, rows[blockIndex - 1].position.end);
|
|
3159
3354
|
return value.slice(0, rows[blockIndex].position.start) + value.slice(rows[blockIndex + 1].position.start);
|
|
3160
3355
|
})();
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
target.focus();
|
|
3166
|
-
Caret.setCaretToEnd(target);
|
|
3167
|
-
}
|
|
3168
|
-
});
|
|
3356
|
+
const previous = rows.at(Math.max(0, blockIndex - 1));
|
|
3357
|
+
const pos = previous ? previous.position.end : 0;
|
|
3358
|
+
store.caret.position(pos);
|
|
3359
|
+
store.value.current(newValue);
|
|
3169
3360
|
return;
|
|
3170
3361
|
}
|
|
3171
3362
|
if (caretAtStart && blockIndex > 0) {
|
|
@@ -3175,29 +3366,18 @@ function handleDelete$1(store, event) {
|
|
|
3175
3366
|
event.preventDefault();
|
|
3176
3367
|
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3177
3368
|
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3178
|
-
store.
|
|
3179
|
-
|
|
3180
|
-
const target = childAt(container, blockIndex - 1);
|
|
3181
|
-
if (target) {
|
|
3182
|
-
target.focus();
|
|
3183
|
-
const updatedToken = store.parsing.tokens()[blockIndex - 1];
|
|
3184
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3185
|
-
}
|
|
3186
|
-
});
|
|
3369
|
+
store.caret.position(joinPos);
|
|
3370
|
+
store.value.current(newValue);
|
|
3187
3371
|
return;
|
|
3188
3372
|
}
|
|
3189
3373
|
event.preventDefault();
|
|
3190
|
-
|
|
3191
|
-
const target = blockDivs[blockIndex - 1];
|
|
3192
|
-
target.focus();
|
|
3193
|
-
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3194
|
-
});
|
|
3374
|
+
focusRow(store, prevToken, blockDivs[blockIndex - 1], "end");
|
|
3195
3375
|
return;
|
|
3196
3376
|
}
|
|
3197
3377
|
}
|
|
3198
3378
|
if (event.key === KEYBOARD.DELETE) {
|
|
3199
3379
|
const blockDiv = blockDivs[blockIndex];
|
|
3200
|
-
const caretIndex =
|
|
3380
|
+
const caretIndex = getCaretIndex(blockDiv);
|
|
3201
3381
|
const caretAtEnd = caretIndex === blockDiv.textContent.length;
|
|
3202
3382
|
if (caretIndex === 0 && blockIndex > 0) {
|
|
3203
3383
|
const prevToken = rows[blockIndex - 1];
|
|
@@ -3206,23 +3386,12 @@ function handleDelete$1(store, event) {
|
|
|
3206
3386
|
event.preventDefault();
|
|
3207
3387
|
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3208
3388
|
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3209
|
-
store.
|
|
3210
|
-
|
|
3211
|
-
const target = childAt(container, blockIndex - 1);
|
|
3212
|
-
if (target) {
|
|
3213
|
-
target.focus();
|
|
3214
|
-
const updatedToken = store.parsing.tokens()[blockIndex - 1];
|
|
3215
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3216
|
-
}
|
|
3217
|
-
});
|
|
3389
|
+
store.caret.position(joinPos);
|
|
3390
|
+
store.value.current(newValue);
|
|
3218
3391
|
return;
|
|
3219
3392
|
}
|
|
3220
3393
|
event.preventDefault();
|
|
3221
|
-
|
|
3222
|
-
const target = blockDivs[blockIndex - 1];
|
|
3223
|
-
target.focus();
|
|
3224
|
-
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3225
|
-
});
|
|
3394
|
+
focusRow(store, prevToken, blockDivs[blockIndex - 1], "end");
|
|
3226
3395
|
return;
|
|
3227
3396
|
}
|
|
3228
3397
|
if (caretAtEnd && blockIndex < rows.length - 1) {
|
|
@@ -3232,23 +3401,12 @@ function handleDelete$1(store, event) {
|
|
|
3232
3401
|
event.preventDefault();
|
|
3233
3402
|
const joinPos = getMergeDragRowJoinPos(rows, blockIndex + 1);
|
|
3234
3403
|
const newValue = mergeDragRows(value, rows, blockIndex + 1);
|
|
3235
|
-
store.
|
|
3236
|
-
|
|
3237
|
-
const target = childAt(container, blockIndex);
|
|
3238
|
-
if (target) {
|
|
3239
|
-
target.focus();
|
|
3240
|
-
const updatedToken = store.parsing.tokens()[blockIndex];
|
|
3241
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3242
|
-
}
|
|
3243
|
-
});
|
|
3404
|
+
store.caret.position(joinPos);
|
|
3405
|
+
store.value.current(newValue);
|
|
3244
3406
|
return;
|
|
3245
3407
|
}
|
|
3246
3408
|
event.preventDefault();
|
|
3247
|
-
|
|
3248
|
-
const target = blockDivs[blockIndex + 1];
|
|
3249
|
-
target.focus();
|
|
3250
|
-
Caret.trySetIndex(target, 0);
|
|
3251
|
-
});
|
|
3409
|
+
focusRow(store, nextToken, blockDivs[blockIndex + 1], "start");
|
|
3252
3410
|
return;
|
|
3253
3411
|
}
|
|
3254
3412
|
}
|
|
@@ -3256,7 +3414,7 @@ function handleDelete$1(store, event) {
|
|
|
3256
3414
|
function handleEnter(store, event) {
|
|
3257
3415
|
if (event.key !== KEYBOARD.ENTER) return;
|
|
3258
3416
|
if (event.shiftKey) return;
|
|
3259
|
-
const container = store.
|
|
3417
|
+
const container = store.dom.container();
|
|
3260
3418
|
if (!container) return;
|
|
3261
3419
|
const activeElement = document.activeElement;
|
|
3262
3420
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
@@ -3270,41 +3428,37 @@ function handleEnter(store, event) {
|
|
|
3270
3428
|
if (blockIndex === -1) return;
|
|
3271
3429
|
const rows = store.parsing.tokens();
|
|
3272
3430
|
const token = rows[blockIndex];
|
|
3273
|
-
const blockDiv = blockDivs[blockIndex];
|
|
3274
3431
|
const value = store.value.current();
|
|
3275
|
-
if (!store.props.onChange()) return;
|
|
3276
3432
|
const newRowContent = createRowContent(store.props.options());
|
|
3277
3433
|
if (!isTextLikeRow(token)) {
|
|
3278
3434
|
const newValue = addDragRow(value, rows, blockIndex, newRowContent);
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
if (newBlockIndex < container.children.length) {
|
|
3283
|
-
const newBlockEl = childAt(container, newBlockIndex);
|
|
3284
|
-
if (newBlockEl) {
|
|
3285
|
-
newBlockEl.focus();
|
|
3286
|
-
Caret.trySetIndex(newBlockEl, 0);
|
|
3287
|
-
}
|
|
3288
|
-
}
|
|
3289
|
-
});
|
|
3435
|
+
const pos = token.position.end + newRowContent.length;
|
|
3436
|
+
store.caret.position(pos);
|
|
3437
|
+
store.value.current(newValue);
|
|
3290
3438
|
return;
|
|
3291
3439
|
}
|
|
3292
|
-
const
|
|
3293
|
-
const
|
|
3294
|
-
store.
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
}
|
|
3440
|
+
const raw = store.dom.readRawSelection();
|
|
3441
|
+
const absolutePos = raw.ok ? raw.value.range.start : token.position.end;
|
|
3442
|
+
store.edit.replace({
|
|
3443
|
+
start: absolutePos,
|
|
3444
|
+
end: absolutePos
|
|
3445
|
+
}, newRowContent);
|
|
3446
|
+
}
|
|
3447
|
+
function focusRow(store, token, row, caret) {
|
|
3448
|
+
if (token.type === "mark") {
|
|
3449
|
+
const path = store.parsing.index().pathFor(token);
|
|
3450
|
+
const address = path ? store.parsing.index().addressFor(path) : void 0;
|
|
3451
|
+
if (address && store.dom.focusAddress(address).ok) return;
|
|
3452
|
+
}
|
|
3453
|
+
row.focus();
|
|
3454
|
+
if (caret === "start") {
|
|
3455
|
+
setAtElement(row, 0);
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
setAtElement(row, Infinity);
|
|
3305
3459
|
}
|
|
3306
3460
|
function handleBlockArrowLeftRight(store, event, direction) {
|
|
3307
|
-
const container = store.
|
|
3461
|
+
const container = store.dom.container();
|
|
3308
3462
|
if (!container) return false;
|
|
3309
3463
|
const activeElement = document.activeElement;
|
|
3310
3464
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return false;
|
|
@@ -3313,24 +3467,24 @@ function handleBlockArrowLeftRight(store, event, direction) {
|
|
|
3313
3467
|
if (blockIndex === -1) return false;
|
|
3314
3468
|
const blockDiv = blockDivs[blockIndex];
|
|
3315
3469
|
if (direction === "left") {
|
|
3316
|
-
if (
|
|
3470
|
+
if (getCaretIndex(blockDiv) !== 0) return false;
|
|
3317
3471
|
if (blockIndex === 0) return true;
|
|
3318
3472
|
event.preventDefault();
|
|
3319
3473
|
const prevBlock = blockDivs[blockIndex - 1];
|
|
3320
3474
|
prevBlock.focus();
|
|
3321
|
-
|
|
3475
|
+
setAtElement(prevBlock, Infinity);
|
|
3322
3476
|
return true;
|
|
3323
3477
|
}
|
|
3324
|
-
if (
|
|
3478
|
+
if (getCaretIndex(blockDiv) !== blockDiv.textContent.length) return false;
|
|
3325
3479
|
if (blockIndex >= blockDivs.length - 1) return true;
|
|
3326
3480
|
event.preventDefault();
|
|
3327
3481
|
const nextBlock = blockDivs[blockIndex + 1];
|
|
3328
3482
|
nextBlock.focus();
|
|
3329
|
-
|
|
3483
|
+
setAtElement(nextBlock, 0);
|
|
3330
3484
|
return true;
|
|
3331
3485
|
}
|
|
3332
3486
|
function handleArrowUpDown(store, event) {
|
|
3333
|
-
const container = store.
|
|
3487
|
+
const container = store.dom.container();
|
|
3334
3488
|
if (!container) return;
|
|
3335
3489
|
const activeElement = document.activeElement;
|
|
3336
3490
|
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
@@ -3339,78 +3493,37 @@ function handleArrowUpDown(store, event) {
|
|
|
3339
3493
|
if (blockIndex === -1) return;
|
|
3340
3494
|
const blockDiv = blockDivs[blockIndex];
|
|
3341
3495
|
if (event.key === KEYBOARD.UP) {
|
|
3342
|
-
if (!
|
|
3496
|
+
if (!isOnFirstLine(blockDiv)) return;
|
|
3343
3497
|
if (blockIndex === 0) return;
|
|
3344
3498
|
event.preventDefault();
|
|
3345
|
-
const caretX =
|
|
3499
|
+
const caretX = getRect()?.left ?? blockDiv.getBoundingClientRect().left;
|
|
3346
3500
|
const prevBlockDiv = blockDivs[blockIndex - 1];
|
|
3347
3501
|
prevBlockDiv.focus();
|
|
3348
|
-
|
|
3349
|
-
Caret.setAtX(prevBlockDiv, caretX, prevRect.bottom - 4);
|
|
3502
|
+
setAtX(prevBlockDiv, caretX, prevBlockDiv.getBoundingClientRect().bottom - 4);
|
|
3350
3503
|
} else if (event.key === KEYBOARD.DOWN) {
|
|
3351
|
-
if (!
|
|
3504
|
+
if (!isOnLastLine(blockDiv)) return;
|
|
3352
3505
|
if (blockIndex >= blockDivs.length - 1) return;
|
|
3353
3506
|
event.preventDefault();
|
|
3354
|
-
const caretX =
|
|
3507
|
+
const caretX = getRect()?.left ?? blockDiv.getBoundingClientRect().left;
|
|
3355
3508
|
const nextBlockDiv = blockDivs[blockIndex + 1];
|
|
3356
3509
|
nextBlockDiv.focus();
|
|
3357
|
-
|
|
3358
|
-
Caret.setAtX(nextBlockDiv, caretX, nextRect.top + 4);
|
|
3510
|
+
setAtX(nextBlockDiv, caretX, nextBlockDiv.getBoundingClientRect().top + 4);
|
|
3359
3511
|
}
|
|
3360
3512
|
}
|
|
3361
3513
|
function handleBlockBeforeInput(store, event) {
|
|
3362
|
-
const container = store.
|
|
3514
|
+
const container = store.dom.container();
|
|
3363
3515
|
if (!container) return;
|
|
3364
3516
|
const activeElement = document.activeElement;
|
|
3365
3517
|
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
|
-
};
|
|
3518
|
+
if (htmlChildren(container).findIndex((div) => div === activeElement || div.contains(activeElement)) === -1) return;
|
|
3383
3519
|
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);
|
|
3520
|
+
case "insertText":
|
|
3521
|
+
replaceBlockRange(store, event, event.data ?? "");
|
|
3397
3522
|
break;
|
|
3398
|
-
}
|
|
3399
3523
|
case "insertFromPaste":
|
|
3400
3524
|
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);
|
|
3525
|
+
const c = store.dom.container();
|
|
3526
|
+
replaceBlockRange(store, event, (c ? consumeMarkupPaste(c) : void 0) ?? event.dataTransfer?.getData("text/plain") ?? "");
|
|
3414
3527
|
break;
|
|
3415
3528
|
}
|
|
3416
3529
|
case "deleteContentBackward":
|
|
@@ -3418,88 +3531,112 @@ function handleBlockBeforeInput(store, event) {
|
|
|
3418
3531
|
case "deleteWordBackward":
|
|
3419
3532
|
case "deleteWordForward":
|
|
3420
3533
|
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);
|
|
3534
|
+
case "deleteSoftLineForward":
|
|
3535
|
+
replaceBlockRange(store, event, "");
|
|
3431
3536
|
break;
|
|
3432
|
-
}
|
|
3433
3537
|
}
|
|
3434
3538
|
}
|
|
3539
|
+
function replaceBlockRange(store, event, replacement) {
|
|
3540
|
+
const raw = rawRangeFromInputEvent$1(store, event);
|
|
3541
|
+
if (!raw.ok) return;
|
|
3542
|
+
const range = rangeForBlockInput(store, event, raw.value.range);
|
|
3543
|
+
if (!range) return;
|
|
3544
|
+
event.preventDefault();
|
|
3545
|
+
store.edit.replace(range, replacement);
|
|
3546
|
+
}
|
|
3547
|
+
function rawRangeFromInputEvent$1(store, event) {
|
|
3548
|
+
const ranges = event.getTargetRanges();
|
|
3549
|
+
if (ranges.length === 0) return store.dom.readRawSelection();
|
|
3550
|
+
return rawRangeFromTargetRange$1(store, ranges[0]);
|
|
3551
|
+
}
|
|
3552
|
+
function rawRangeFromTargetRange$1(store, range) {
|
|
3553
|
+
const start = store.dom.rawPositionFromBoundary(range.startContainer, range.startOffset, "after");
|
|
3554
|
+
const end = store.dom.rawPositionFromBoundary(range.endContainer, range.endOffset, "before");
|
|
3555
|
+
if (!start.ok) return {
|
|
3556
|
+
ok: false,
|
|
3557
|
+
reason: rawSelectionReason$1(start)
|
|
3558
|
+
};
|
|
3559
|
+
if (!end.ok) return {
|
|
3560
|
+
ok: false,
|
|
3561
|
+
reason: rawSelectionReason$1(end)
|
|
3562
|
+
};
|
|
3563
|
+
return {
|
|
3564
|
+
ok: true,
|
|
3565
|
+
value: { range: start.value <= end.value ? {
|
|
3566
|
+
start: start.value,
|
|
3567
|
+
end: end.value
|
|
3568
|
+
} : {
|
|
3569
|
+
start: end.value,
|
|
3570
|
+
end: start.value
|
|
3571
|
+
} }
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
function rawSelectionReason$1(result) {
|
|
3575
|
+
if (result.ok) return "invalidBoundary";
|
|
3576
|
+
if (result.reason === "composing") return "invalidBoundary";
|
|
3577
|
+
return result.reason;
|
|
3578
|
+
}
|
|
3579
|
+
function rangeForBlockInput(store, event, range) {
|
|
3580
|
+
if (!event.inputType.startsWith("delete")) return range;
|
|
3581
|
+
if (range.start !== range.end) return range;
|
|
3582
|
+
if (event.inputType.endsWith("Backward") && range.start > 0) return {
|
|
3583
|
+
start: range.start - 1,
|
|
3584
|
+
end: range.start
|
|
3585
|
+
};
|
|
3586
|
+
if (event.inputType.endsWith("Forward") && range.end < store.value.current().length) return {
|
|
3587
|
+
start: range.start,
|
|
3588
|
+
end: range.end + 1
|
|
3589
|
+
};
|
|
3590
|
+
}
|
|
3435
3591
|
//#endregion
|
|
3436
3592
|
//#region ../../core/src/features/keyboard/input.ts
|
|
3437
3593
|
function enableInput(store) {
|
|
3438
|
-
const container = store.
|
|
3439
|
-
if (!container) return
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3594
|
+
const container = store.dom.container();
|
|
3595
|
+
if (!container) return;
|
|
3596
|
+
let compositionRange;
|
|
3597
|
+
listen(container, "paste", (e) => {
|
|
3598
|
+
const c = store.dom.container();
|
|
3599
|
+
if (c) captureMarkupPaste(e, c);
|
|
3600
|
+
handlePaste(store, e);
|
|
3601
|
+
});
|
|
3602
|
+
listen(container, "compositionstart", () => {
|
|
3603
|
+
const selection = store.dom.readRawSelection();
|
|
3604
|
+
compositionRange = selection.ok ? selection.value.range : void 0;
|
|
3605
|
+
store.dom.compositionStarted();
|
|
3606
|
+
});
|
|
3607
|
+
listen(container, "compositionend", (e) => {
|
|
3608
|
+
const range = compositionRange;
|
|
3609
|
+
compositionRange = void 0;
|
|
3610
|
+
store.dom.compositionEnded();
|
|
3611
|
+
if (store.slots.isBlock()) return;
|
|
3612
|
+
if (!range) return;
|
|
3613
|
+
const data = e.data;
|
|
3614
|
+
store.edit.replace(range, data);
|
|
3615
|
+
});
|
|
3616
|
+
listen(container, "beforeinput", (e) => {
|
|
3617
|
+
handleBeforeInput(store, e);
|
|
3618
|
+
}, true);
|
|
3619
|
+
listen(container, "keydown", (e) => {
|
|
3620
|
+
handleDeleteKey(store, e);
|
|
3452
3621
|
});
|
|
3453
|
-
return () => scope();
|
|
3454
3622
|
}
|
|
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
|
-
}
|
|
3623
|
+
function handleDeleteKey(store, event) {
|
|
3624
|
+
if (store.slots.isBlock()) return;
|
|
3625
|
+
if (event.key !== KEYBOARD.BACKSPACE && event.key !== KEYBOARD.DELETE) return;
|
|
3626
|
+
if (store.caret.isAllSelected()) {
|
|
3463
3627
|
event.preventDefault();
|
|
3464
|
-
|
|
3628
|
+
replaceAllContentWith(store, "");
|
|
3465
3629
|
return;
|
|
3466
3630
|
}
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
}
|
|
3474
|
-
if (event.key === KEYBOARD.DELETE) {
|
|
3475
|
-
if (focus.isSpan && focus.isCaretAtEnd && focus.next.target) {
|
|
3476
|
-
event.preventDefault();
|
|
3477
|
-
deleteMark("next", store);
|
|
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;
|
|
3497
|
-
}
|
|
3498
|
-
}
|
|
3631
|
+
const raw = store.dom.readRawSelection();
|
|
3632
|
+
if (!raw.ok) return;
|
|
3633
|
+
const range = rangeForDelete(store, event.key === KEYBOARD.BACKSPACE ? "deleteContentBackward" : "deleteContentForward", raw.value.range);
|
|
3634
|
+
if (!range) return;
|
|
3635
|
+
event.preventDefault();
|
|
3636
|
+
store.edit.replace(range, "");
|
|
3499
3637
|
}
|
|
3500
3638
|
function handleBeforeInput(store, event) {
|
|
3501
|
-
|
|
3502
|
-
if (selecting === "all" && isFullSelection(store)) {
|
|
3639
|
+
if (store.caret.isAllSelected()) {
|
|
3503
3640
|
if (event.inputType === "insertFromPaste") {
|
|
3504
3641
|
event.preventDefault();
|
|
3505
3642
|
return;
|
|
@@ -3508,236 +3645,205 @@ function handleBeforeInput(store, event) {
|
|
|
3508
3645
|
replaceAllContentWith(store, event.inputType.startsWith("delete") ? "" : event.data ?? "");
|
|
3509
3646
|
return;
|
|
3510
3647
|
}
|
|
3511
|
-
if (selecting === "all") store.caret.selecting(void 0);
|
|
3512
3648
|
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;
|
|
3649
|
+
const raw = rawRangeFromInputEvent(store, event);
|
|
3650
|
+
if (!raw.ok) return;
|
|
3651
|
+
const replacement = replacementForInput(store, event);
|
|
3652
|
+
if (replacement === void 0) return;
|
|
3653
|
+
const range = rangeForInput(store, event, raw.value.range);
|
|
3654
|
+
if (!range) return;
|
|
3523
3655
|
event.preventDefault();
|
|
3524
|
-
|
|
3525
|
-
const offset = focus.caret;
|
|
3526
|
-
const currentValue = store.value.current();
|
|
3527
|
-
const ranges = event.getTargetRanges();
|
|
3528
|
-
const childElement = container.children[focus.index];
|
|
3529
|
-
let rawInsertPos;
|
|
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
|
|
3552
|
-
});
|
|
3553
|
-
return true;
|
|
3656
|
+
store.edit.replace(range, replacement);
|
|
3554
3657
|
}
|
|
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
|
-
const ranges = event.getTargetRanges();
|
|
3596
|
-
const start = ranges[0]?.startOffset ?? offset;
|
|
3597
|
-
const end = ranges[0]?.endOffset ?? offset;
|
|
3598
|
-
event.preventDefault();
|
|
3599
|
-
newContent = content.slice(0, start) + text + content.slice(end);
|
|
3600
|
-
newCaret = start + text.length;
|
|
3601
|
-
break;
|
|
3602
|
-
}
|
|
3603
|
-
default: return false;
|
|
3658
|
+
function rawRangeFromInputEvent(store, event) {
|
|
3659
|
+
const ranges = getTargetRanges(event);
|
|
3660
|
+
if (ranges.length === 0) return store.dom.readRawSelection();
|
|
3661
|
+
return rawRangeFromTargetRange(store, ranges[0]);
|
|
3662
|
+
}
|
|
3663
|
+
function rawRangeFromTargetRange(store, range) {
|
|
3664
|
+
const start = store.dom.rawPositionFromBoundary(range.startContainer, range.startOffset, "after");
|
|
3665
|
+
const end = store.dom.rawPositionFromBoundary(range.endContainer, range.endOffset, "before");
|
|
3666
|
+
if (!start.ok) return {
|
|
3667
|
+
ok: false,
|
|
3668
|
+
reason: rawSelectionReason(start)
|
|
3669
|
+
};
|
|
3670
|
+
if (!end.ok) return {
|
|
3671
|
+
ok: false,
|
|
3672
|
+
reason: rawSelectionReason(end)
|
|
3673
|
+
};
|
|
3674
|
+
return {
|
|
3675
|
+
ok: true,
|
|
3676
|
+
value: { range: start.value <= end.value ? {
|
|
3677
|
+
start: start.value,
|
|
3678
|
+
end: end.value
|
|
3679
|
+
} : {
|
|
3680
|
+
start: end.value,
|
|
3681
|
+
end: start.value
|
|
3682
|
+
} }
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
function rawSelectionReason(result) {
|
|
3686
|
+
if (result.ok) return "invalidBoundary";
|
|
3687
|
+
if (result.reason === "composing") return "invalidBoundary";
|
|
3688
|
+
return result.reason;
|
|
3689
|
+
}
|
|
3690
|
+
function getTargetRanges(event) {
|
|
3691
|
+
return event.getTargetRanges();
|
|
3692
|
+
}
|
|
3693
|
+
function replacementForInput(store, event) {
|
|
3694
|
+
if (event.inputType.startsWith("delete")) return "";
|
|
3695
|
+
if (event.inputType === "insertFromPaste" || event.inputType === "insertReplacementText") {
|
|
3696
|
+
const container = store.dom.container();
|
|
3697
|
+
return (container ? consumeMarkupPaste(container) : void 0) ?? event.dataTransfer?.getData("text/plain") ?? event.data ?? "";
|
|
3604
3698
|
}
|
|
3605
|
-
|
|
3606
|
-
focus.caret = newCaret;
|
|
3607
|
-
return true;
|
|
3699
|
+
if (event.inputType === "insertText") return event.data ?? "";
|
|
3608
3700
|
}
|
|
3609
|
-
function
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3701
|
+
function rangeForInput(store, event, range) {
|
|
3702
|
+
if (!event.inputType.startsWith("delete")) return range;
|
|
3703
|
+
return rangeForDelete(store, event.inputType, range);
|
|
3704
|
+
}
|
|
3705
|
+
function rangeForDelete(store, inputType, range) {
|
|
3706
|
+
if (range.start !== range.end) return range;
|
|
3707
|
+
const adjacentMark = adjacentMarkRange(store.parsing.tokens(), range.start, inputType.endsWith("Backward"));
|
|
3708
|
+
if (adjacentMark) return adjacentMark;
|
|
3709
|
+
if (inputType.endsWith("Backward") && range.start > 0) return {
|
|
3710
|
+
start: range.start - 1,
|
|
3711
|
+
end: range.start
|
|
3712
|
+
};
|
|
3713
|
+
if (inputType.endsWith("Forward") && range.end < store.value.current().length) return {
|
|
3714
|
+
start: range.start,
|
|
3715
|
+
end: range.end + 1
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
function adjacentMarkRange(tokens, position, backward) {
|
|
3719
|
+
for (const token of tokens) {
|
|
3720
|
+
const nested = token.type === "mark" ? adjacentMarkRange(token.children, position, backward) : void 0;
|
|
3721
|
+
if (nested) return nested;
|
|
3722
|
+
if (token.type === "mark" && (backward ? token.position.end === position : token.position.start === position)) return token.position;
|
|
3614
3723
|
}
|
|
3724
|
+
}
|
|
3725
|
+
function handlePaste(store, event) {
|
|
3726
|
+
if (!store.caret.isAllSelected()) return;
|
|
3615
3727
|
event.preventDefault();
|
|
3616
|
-
const c = store.
|
|
3728
|
+
const c = store.dom.container();
|
|
3617
3729
|
replaceAllContentWith(store, (c ? consumeMarkupPaste(c) : void 0) ?? event.clipboardData?.getData("text/plain") ?? "");
|
|
3618
3730
|
}
|
|
3619
3731
|
function replaceAllContentWith(store, newContent) {
|
|
3620
|
-
store.
|
|
3621
|
-
store.
|
|
3622
|
-
store.value.last(newContent);
|
|
3623
|
-
store.props.onChange()?.(newContent);
|
|
3624
|
-
if (store.props.value() === void 0) store.parsing.tokens(store.parsing.parser()?.parse(newContent) ?? [{
|
|
3625
|
-
type: "text",
|
|
3626
|
-
content: newContent,
|
|
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();
|
|
3641
|
-
}
|
|
3642
|
-
});
|
|
3732
|
+
store.caret.position(newContent.length);
|
|
3733
|
+
store.value.current(newContent);
|
|
3643
3734
|
}
|
|
3644
3735
|
//#endregion
|
|
3645
|
-
//#region ../../core/src/features/keyboard/
|
|
3646
|
-
var
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3736
|
+
//#region ../../core/src/features/keyboard/KeyboardController.ts
|
|
3737
|
+
var KeyboardController = class {
|
|
3738
|
+
constructor(lifecycle, dom, value, caret, edit, slots, parsing, props) {
|
|
3739
|
+
const ctx = {
|
|
3740
|
+
dom,
|
|
3741
|
+
value,
|
|
3742
|
+
caret,
|
|
3743
|
+
edit,
|
|
3744
|
+
slots,
|
|
3745
|
+
parsing,
|
|
3746
|
+
props
|
|
3747
|
+
};
|
|
3748
|
+
lifecycle.onMounted(() => {
|
|
3749
|
+
enableInput(ctx);
|
|
3750
|
+
enableBlockEdit(ctx);
|
|
3751
|
+
enableArrowNav(ctx);
|
|
3752
|
+
});
|
|
3662
3753
|
}
|
|
3663
3754
|
};
|
|
3664
3755
|
//#endregion
|
|
3665
|
-
//#region ../../core/src/features/lifecycle/
|
|
3666
|
-
var
|
|
3667
|
-
constructor(
|
|
3668
|
-
this._store = _store;
|
|
3756
|
+
//#region ../../core/src/features/lifecycle/Lifecycle.ts
|
|
3757
|
+
var Lifecycle = class {
|
|
3758
|
+
constructor() {
|
|
3669
3759
|
this.mounted = event();
|
|
3670
3760
|
this.unmounted = event();
|
|
3671
3761
|
this.rendered = event();
|
|
3672
3762
|
}
|
|
3673
|
-
|
|
3674
|
-
|
|
3763
|
+
/**
|
|
3764
|
+
* Run `setup` when the editor is mounted. Any reactive subscription
|
|
3765
|
+
* created inside `setup` (`watch`, `listen`, `effect`, nested
|
|
3766
|
+
* `effectScope`) is automatically disposed on `unmounted` and re-created
|
|
3767
|
+
* on the next `mounted`.
|
|
3768
|
+
*/
|
|
3769
|
+
onMounted(setup) {
|
|
3770
|
+
let scope;
|
|
3771
|
+
watch(this.mounted, () => {
|
|
3772
|
+
if (scope) return;
|
|
3773
|
+
scope = effectScope(setup);
|
|
3774
|
+
});
|
|
3775
|
+
watch(this.unmounted, () => {
|
|
3776
|
+
scope?.();
|
|
3777
|
+
scope = void 0;
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3675
3780
|
};
|
|
3676
3781
|
//#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();
|
|
3782
|
+
//#region ../../core/src/features/mark/MarkController.ts
|
|
3783
|
+
var MarkController = class MarkController {
|
|
3784
|
+
#shape;
|
|
3785
|
+
constructor(store, address, snapshot, shape) {
|
|
3786
|
+
this.store = store;
|
|
3787
|
+
this.address = address;
|
|
3788
|
+
this.snapshot = snapshot;
|
|
3789
|
+
this.#shape = shape;
|
|
3790
|
+
}
|
|
3791
|
+
static fromToken(store, token) {
|
|
3792
|
+
const index = store.parsing.index();
|
|
3793
|
+
const path = index.pathFor(token);
|
|
3794
|
+
if (!path) throw new Error("Cannot create MarkController for unindexed token");
|
|
3795
|
+
const address = index.addressFor(path);
|
|
3796
|
+
if (!address) throw new Error("Cannot create MarkController for unresolved token path");
|
|
3797
|
+
return new MarkController(store, address, {
|
|
3798
|
+
value: token.value,
|
|
3799
|
+
meta: token.meta,
|
|
3800
|
+
slot: token.slot?.content,
|
|
3801
|
+
readOnly: store.props.readOnly()
|
|
3802
|
+
}, snapshotTokenShape(token));
|
|
3706
3803
|
}
|
|
3707
3804
|
get value() {
|
|
3708
|
-
return this
|
|
3709
|
-
}
|
|
3710
|
-
set value(v) {
|
|
3711
|
-
this.#token.value = v ?? "";
|
|
3712
|
-
this.#emitChange();
|
|
3805
|
+
return this.snapshot.value;
|
|
3713
3806
|
}
|
|
3714
3807
|
get meta() {
|
|
3715
|
-
return this
|
|
3716
|
-
}
|
|
3717
|
-
set meta(v) {
|
|
3718
|
-
this.#token.meta = v;
|
|
3719
|
-
this.#emitChange();
|
|
3808
|
+
return this.snapshot.meta;
|
|
3720
3809
|
}
|
|
3721
3810
|
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;
|
|
3729
|
-
}
|
|
3730
|
-
get hasChildren() {
|
|
3731
|
-
return this.#token.children.some((child) => child.type === "mark");
|
|
3811
|
+
return this.snapshot.slot;
|
|
3732
3812
|
}
|
|
3733
|
-
get
|
|
3734
|
-
return this
|
|
3813
|
+
get readOnly() {
|
|
3814
|
+
return this.snapshot.readOnly;
|
|
3815
|
+
}
|
|
3816
|
+
remove() {
|
|
3817
|
+
const resolved = this.#resolve();
|
|
3818
|
+
if (!resolved) return;
|
|
3819
|
+
this.store.value.replace(resolved.position, "");
|
|
3820
|
+
}
|
|
3821
|
+
update(patch) {
|
|
3822
|
+
const resolved = this.#resolve();
|
|
3823
|
+
if (!resolved) return;
|
|
3824
|
+
const token = resolved;
|
|
3825
|
+
const value = patch.value ?? token.value;
|
|
3826
|
+
const meta = patch.meta?.kind === "clear" ? void 0 : patch.meta?.kind === "set" ? patch.meta.value : token.meta;
|
|
3827
|
+
const slot = patch.slot?.kind === "clear" ? void 0 : patch.slot?.kind === "set" ? patch.slot.value : token.slot?.content;
|
|
3828
|
+
const serialized = this.#serialize(token, {
|
|
3829
|
+
value,
|
|
3830
|
+
meta,
|
|
3831
|
+
slot
|
|
3832
|
+
});
|
|
3833
|
+
this.store.value.replace(token.position, serialized);
|
|
3735
3834
|
}
|
|
3736
|
-
|
|
3737
|
-
return
|
|
3835
|
+
#serialize(token, fields) {
|
|
3836
|
+
return annotate(token.descriptor.markup, {
|
|
3837
|
+
value: fields.value,
|
|
3838
|
+
meta: token.descriptor.gapTypes.includes("meta") ? fields.meta ?? "" : void 0,
|
|
3839
|
+
slot: token.descriptor.hasSlot ? fields.slot ?? "" : void 0
|
|
3840
|
+
});
|
|
3738
3841
|
}
|
|
3739
|
-
#
|
|
3740
|
-
this
|
|
3842
|
+
#resolve() {
|
|
3843
|
+
if (this.store.props.readOnly()) return void 0;
|
|
3844
|
+
const resolved = this.store.parsing.index().resolveAddress(this.address, this.#shape);
|
|
3845
|
+
if (!resolved.ok || resolved.value.type !== "mark") return void 0;
|
|
3846
|
+
return resolved.value;
|
|
3741
3847
|
}
|
|
3742
3848
|
};
|
|
3743
3849
|
//#endregion
|
|
@@ -3820,55 +3926,33 @@ function buildContainerProps(isDraggableBlock, readOnly, className, style, slotP
|
|
|
3820
3926
|
};
|
|
3821
3927
|
}
|
|
3822
3928
|
var SlotsFeature = class {
|
|
3823
|
-
constructor(
|
|
3824
|
-
this.
|
|
3825
|
-
this.
|
|
3826
|
-
this.
|
|
3827
|
-
this.
|
|
3828
|
-
this.
|
|
3829
|
-
this.
|
|
3830
|
-
this.
|
|
3831
|
-
this.
|
|
3832
|
-
this.
|
|
3833
|
-
|
|
3834
|
-
}
|
|
3835
|
-
enable() {}
|
|
3836
|
-
disable() {}
|
|
3929
|
+
constructor(props) {
|
|
3930
|
+
this.props = props;
|
|
3931
|
+
this.isBlock = computed(() => this.props.layout() === "block");
|
|
3932
|
+
this.isDraggable = computed(() => !!this.props.draggable());
|
|
3933
|
+
this.containerComponent = computed(() => resolveSlot("container", this.props.slots()));
|
|
3934
|
+
this.containerProps = computed(() => buildContainerProps(this.isDraggable() && this.isBlock(), this.props.readOnly(), this.props.className(), this.props.style(), this.props.slotProps()), { equals: shallow });
|
|
3935
|
+
this.blockComponent = computed(() => resolveSlot("block", this.props.slots()));
|
|
3936
|
+
this.blockProps = computed(() => resolveSlotProps("block", this.props.slotProps()));
|
|
3937
|
+
this.spanComponent = computed(() => resolveSlot("span", this.props.slots()));
|
|
3938
|
+
this.spanProps = computed(() => resolveSlotProps("span", this.props.slotProps()));
|
|
3939
|
+
}
|
|
3837
3940
|
};
|
|
3838
3941
|
//#endregion
|
|
3839
3942
|
//#region ../../core/src/features/mark/MarkFeature.ts
|
|
3840
3943
|
var MarkFeature = class {
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
this._store = _store;
|
|
3944
|
+
constructor(props) {
|
|
3945
|
+
this.props = props;
|
|
3844
3946
|
this.enabled = computed(() => {
|
|
3845
|
-
if (this.
|
|
3846
|
-
return this.
|
|
3947
|
+
if (this.props.Mark()) return true;
|
|
3948
|
+
return this.props.options().some((opt) => "Mark" in opt && opt.Mark != null);
|
|
3847
3949
|
});
|
|
3848
3950
|
this.slot = computed(() => {
|
|
3849
|
-
const options = this.
|
|
3850
|
-
const Mark = this.
|
|
3851
|
-
const Span = this.
|
|
3951
|
+
const options = this.props.options();
|
|
3952
|
+
const Mark = this.props.Mark();
|
|
3953
|
+
const Span = this.props.Span();
|
|
3852
3954
|
return (token) => resolveMarkSlot(token, options, Mark, Span);
|
|
3853
3955
|
});
|
|
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
3956
|
}
|
|
3873
3957
|
};
|
|
3874
3958
|
//#endregion
|
|
@@ -3888,8 +3972,8 @@ function createMarkFromOverlay(match, value, meta) {
|
|
|
3888
3972
|
meta,
|
|
3889
3973
|
content: "",
|
|
3890
3974
|
position: {
|
|
3891
|
-
start: match.
|
|
3892
|
-
end: match.
|
|
3975
|
+
start: match.range.start,
|
|
3976
|
+
end: match.range.end
|
|
3893
3977
|
},
|
|
3894
3978
|
descriptor: {
|
|
3895
3979
|
markup,
|
|
@@ -3905,92 +3989,124 @@ function createMarkFromOverlay(match, value, meta) {
|
|
|
3905
3989
|
};
|
|
3906
3990
|
}
|
|
3907
3991
|
//#endregion
|
|
3908
|
-
//#region ../../core/src/features/overlay/
|
|
3909
|
-
var
|
|
3992
|
+
//#region ../../core/src/features/overlay/OverlayController.ts
|
|
3993
|
+
var OverlayController = class {
|
|
3910
3994
|
#scope;
|
|
3911
|
-
constructor(
|
|
3912
|
-
this.
|
|
3995
|
+
constructor(lifecycle, props, value, dom, caret, edit, parsing) {
|
|
3996
|
+
this.lifecycle = lifecycle;
|
|
3997
|
+
this.props = props;
|
|
3998
|
+
this.value = value;
|
|
3999
|
+
this.dom = dom;
|
|
4000
|
+
this.caret = caret;
|
|
4001
|
+
this.edit = edit;
|
|
4002
|
+
this.parsing = parsing;
|
|
3913
4003
|
this.match = signal(void 0);
|
|
3914
4004
|
this.element = signal(null);
|
|
3915
4005
|
this.slot = computed(() => {
|
|
3916
|
-
const Overlay = this.
|
|
4006
|
+
const Overlay = this.props.Overlay();
|
|
3917
4007
|
return (option, defaultComponent) => resolveOverlaySlot(Overlay, option, defaultComponent);
|
|
3918
4008
|
});
|
|
3919
4009
|
this.select = event();
|
|
3920
4010
|
this.close = event();
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
this.match(void 0);
|
|
3931
|
-
});
|
|
3932
|
-
watch(this._store.value.change, () => {
|
|
3933
|
-
const showOverlayOn = this._store.props.showOverlayOn();
|
|
3934
|
-
const type = "change";
|
|
3935
|
-
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
3936
|
-
});
|
|
3937
|
-
alienEffect(() => {
|
|
3938
|
-
if (this.match()) {
|
|
3939
|
-
this._store.nodes.input.target = this._store.nodes.focus.target;
|
|
3940
|
-
listen(window, "keydown", (e) => {
|
|
3941
|
-
if (e.key === KEYBOARD.ESC) this.close();
|
|
3942
|
-
});
|
|
3943
|
-
listen(document, "click", (e) => {
|
|
3944
|
-
const target = e.target instanceof HTMLElement ? e.target : null;
|
|
3945
|
-
if (this.element()?.contains(target)) return;
|
|
3946
|
-
if (this._store.slots.container()?.contains(target)) return;
|
|
3947
|
-
this.close();
|
|
3948
|
-
}, true);
|
|
3949
|
-
}
|
|
3950
|
-
});
|
|
3951
|
-
const selectionChangeHandler = () => {
|
|
3952
|
-
if (!this._store.slots.container()?.contains(document.activeElement)) return;
|
|
3953
|
-
const showOverlayOn = this._store.props.showOverlayOn();
|
|
3954
|
-
const type = "selectionChange";
|
|
3955
|
-
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
4011
|
+
this.position = computed(() => {
|
|
4012
|
+
if (!this.match()) return {
|
|
4013
|
+
left: 0,
|
|
4014
|
+
top: 0
|
|
4015
|
+
};
|
|
4016
|
+
const rect = getRect();
|
|
4017
|
+
if (!rect) return {
|
|
4018
|
+
left: 0,
|
|
4019
|
+
top: 0
|
|
3956
4020
|
};
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
})
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
4021
|
+
return {
|
|
4022
|
+
left: rect.left,
|
|
4023
|
+
top: rect.top + rect.height + 1
|
|
4024
|
+
};
|
|
4025
|
+
});
|
|
4026
|
+
const hasOverlayTrigger = computed(() => this.props.options().some((opt) => opt.overlay?.trigger != null));
|
|
4027
|
+
const toggle = (enabled) => {
|
|
4028
|
+
if (enabled && !this.#scope) this.#scope = effectScope(() => {
|
|
4029
|
+
watch(this.close, () => {
|
|
4030
|
+
this.match(void 0);
|
|
4031
|
+
});
|
|
4032
|
+
watch(this.value.current, () => {
|
|
4033
|
+
const showOverlayOn = this.props.showOverlayOn();
|
|
4034
|
+
const type = "change";
|
|
4035
|
+
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
4036
|
+
});
|
|
4037
|
+
effect(() => {
|
|
4038
|
+
if (this.match()) {
|
|
4039
|
+
listen(window, "keydown", (e) => {
|
|
4040
|
+
if (e.key === KEYBOARD.ESC) this.close();
|
|
4041
|
+
});
|
|
4042
|
+
listen(document, "click", (e) => {
|
|
4043
|
+
const target = e.target instanceof HTMLElement ? e.target : null;
|
|
4044
|
+
if (this.element()?.contains(target)) return;
|
|
4045
|
+
if (this.dom.container()?.contains(target)) return;
|
|
4046
|
+
this.close();
|
|
4047
|
+
}, true);
|
|
4048
|
+
}
|
|
4049
|
+
});
|
|
4050
|
+
const selectionChangeHandler = () => {
|
|
4051
|
+
if (!this.dom.container()?.contains(document.activeElement)) return;
|
|
4052
|
+
const showOverlayOn = this.props.showOverlayOn();
|
|
4053
|
+
const type = "selectionChange";
|
|
4054
|
+
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
4055
|
+
};
|
|
4056
|
+
listen(document, "selectionchange", selectionChangeHandler);
|
|
4057
|
+
watch(this.select, (overlayEvent) => {
|
|
4058
|
+
const { mark, match: { option, range } } = overlayEvent;
|
|
4059
|
+
const markup = option.markup;
|
|
4060
|
+
if (!markup) return;
|
|
4061
|
+
const annotation = mark.type === "mark" ? annotate(markup, {
|
|
4062
|
+
value: mark.value,
|
|
4063
|
+
meta: mark.meta
|
|
4064
|
+
}) : annotate(markup, { value: mark.content });
|
|
4065
|
+
this.edit.replace(range, annotation);
|
|
4066
|
+
this.match(void 0);
|
|
3977
4067
|
});
|
|
3978
|
-
if (this._store.nodes.input.target) {
|
|
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
|
-
}
|
|
3988
4068
|
});
|
|
4069
|
+
if (!enabled && this.#scope) {
|
|
4070
|
+
this.#scope();
|
|
4071
|
+
this.#scope = void 0;
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
4074
|
+
this.lifecycle.onMounted(() => {
|
|
4075
|
+
watch(hasOverlayTrigger, toggle);
|
|
4076
|
+
toggle(hasOverlayTrigger());
|
|
3989
4077
|
});
|
|
3990
4078
|
}
|
|
3991
|
-
|
|
3992
|
-
this.#
|
|
3993
|
-
this
|
|
4079
|
+
#probeTrigger() {
|
|
4080
|
+
const match = TriggerFinder.find(this.props.options(), (option) => option.overlay?.trigger, this.dom) ?? this.#probeTriggerFromCaretRange();
|
|
4081
|
+
this.match(match);
|
|
4082
|
+
}
|
|
4083
|
+
#probeTriggerFromCaretRange() {
|
|
4084
|
+
const sel = this.caret.selection();
|
|
4085
|
+
if (!sel || sel.start !== sel.end) return;
|
|
4086
|
+
const cursor = sel.start;
|
|
4087
|
+
const value = this.value.current();
|
|
4088
|
+
const left = value.slice(0, cursor);
|
|
4089
|
+
const rightWord = value.slice(cursor).match(/^\w*/)?.[0] ?? "";
|
|
4090
|
+
for (const option of this.props.options()) {
|
|
4091
|
+
const trigger = option.overlay?.trigger;
|
|
4092
|
+
if (!trigger) continue;
|
|
4093
|
+
const match = left.match(new RegExp(`${escape(trigger)}(\\w*)$`));
|
|
4094
|
+
if (!match) continue;
|
|
4095
|
+
const [sourceLeft, wordLeft] = match;
|
|
4096
|
+
const source = sourceLeft + rightWord;
|
|
4097
|
+
const start = cursor - sourceLeft.length;
|
|
4098
|
+
return {
|
|
4099
|
+
value: wordLeft + rightWord,
|
|
4100
|
+
source,
|
|
4101
|
+
range: {
|
|
4102
|
+
start,
|
|
4103
|
+
end: start + source.length
|
|
4104
|
+
},
|
|
4105
|
+
span: value,
|
|
4106
|
+
node: window.getSelection()?.anchorNode ?? this.dom.container() ?? document.body,
|
|
4107
|
+
option
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
3994
4110
|
}
|
|
3995
4111
|
};
|
|
3996
4112
|
//#endregion
|
|
@@ -4024,10 +4140,9 @@ function navigateSuggestions(key, activeIndex, length) {
|
|
|
4024
4140
|
}
|
|
4025
4141
|
}
|
|
4026
4142
|
//#endregion
|
|
4027
|
-
//#region ../../core/src/features/props/
|
|
4028
|
-
var
|
|
4029
|
-
constructor(
|
|
4030
|
-
this._store = _store;
|
|
4143
|
+
//#region ../../core/src/features/props/PropsModel.ts
|
|
4144
|
+
var PropsModel = class {
|
|
4145
|
+
constructor() {
|
|
4031
4146
|
this.value = signal(void 0, { readonly: true });
|
|
4032
4147
|
this.defaultValue = signal(void 0, { readonly: true });
|
|
4033
4148
|
this.onChange = signal(void 0, { readonly: true });
|
|
@@ -4057,51 +4172,34 @@ var PropsFeature = class {
|
|
|
4057
4172
|
}
|
|
4058
4173
|
};
|
|
4059
4174
|
//#endregion
|
|
4060
|
-
//#region ../../core/src/features/value/
|
|
4061
|
-
var
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
this.
|
|
4065
|
-
this.
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
const onChange = this._store.props.onChange();
|
|
4075
|
-
const { focus } = this._store.nodes;
|
|
4076
|
-
if (!focus.target || !focus.target.isContentEditable) {
|
|
4077
|
-
const serialized = toString(this._store.parsing.tokens());
|
|
4078
|
-
onChange?.(serialized);
|
|
4079
|
-
this.last(serialized);
|
|
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);
|
|
4099
|
-
});
|
|
4175
|
+
//#region ../../core/src/features/value/ValueModel.ts
|
|
4176
|
+
var ValueModel = class {
|
|
4177
|
+
constructor(props) {
|
|
4178
|
+
this.props = props;
|
|
4179
|
+
this.isControlledMode = computed(() => this.props.value() !== void 0);
|
|
4180
|
+
this.current = model({
|
|
4181
|
+
default: () => this.props.defaultValue() ?? "",
|
|
4182
|
+
get: (value) => this.isControlledMode() ? this.props.value() ?? "" : value,
|
|
4183
|
+
set: (next, previous) => {
|
|
4184
|
+
if (next === void 0) return previous;
|
|
4185
|
+
if (this.props.readOnly()) return previous;
|
|
4186
|
+
this.props.onChange()?.(next);
|
|
4187
|
+
return this.isControlledMode() ? previous : next;
|
|
4188
|
+
}
|
|
4100
4189
|
});
|
|
4101
4190
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4191
|
+
/**
|
|
4192
|
+
* Attempts to replace `range` with `replacement`. Returns `true` when the
|
|
4193
|
+
* edit was accepted (range valid and not read-only), `false` otherwise.
|
|
4194
|
+
* Callers use the return value to gate downstream side effects such as
|
|
4195
|
+
* caret placement.
|
|
4196
|
+
*/
|
|
4197
|
+
replace(range, replacement) {
|
|
4198
|
+
if (this.props.readOnly()) return false;
|
|
4199
|
+
const next = replaceInString(this.current(), range, replacement);
|
|
4200
|
+
if (next === void 0) return false;
|
|
4201
|
+
this.current(next);
|
|
4202
|
+
return true;
|
|
4105
4203
|
}
|
|
4106
4204
|
};
|
|
4107
4205
|
//#endregion
|
|
@@ -4281,38 +4379,20 @@ var Store = class {
|
|
|
4281
4379
|
constructor() {
|
|
4282
4380
|
this.key = new KeyGenerator();
|
|
4283
4381
|
this.blocks = new BlockRegistry();
|
|
4284
|
-
this.
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
this.
|
|
4289
|
-
this.
|
|
4290
|
-
this.
|
|
4291
|
-
this.
|
|
4292
|
-
this.
|
|
4293
|
-
this.overlay = new
|
|
4294
|
-
this.
|
|
4295
|
-
this.
|
|
4296
|
-
this.
|
|
4297
|
-
this.
|
|
4298
|
-
this.drag = new DragFeature(this);
|
|
4299
|
-
this.clipboard = new ClipboardFeature(this);
|
|
4300
|
-
this.parsing = new ParsingFeature(this);
|
|
4301
|
-
const features = [
|
|
4302
|
-
this.lifecycle,
|
|
4303
|
-
this.value,
|
|
4304
|
-
this.mark,
|
|
4305
|
-
this.overlay,
|
|
4306
|
-
this.slots,
|
|
4307
|
-
this.caret,
|
|
4308
|
-
this.keyboard,
|
|
4309
|
-
this.dom,
|
|
4310
|
-
this.drag,
|
|
4311
|
-
this.clipboard,
|
|
4312
|
-
this.parsing
|
|
4313
|
-
];
|
|
4314
|
-
watch(this.lifecycle.mounted, () => features.forEach((f) => f.enable()));
|
|
4315
|
-
watch(this.lifecycle.unmounted, () => features.forEach((f) => f.disable()));
|
|
4382
|
+
this.lifecycle = new Lifecycle();
|
|
4383
|
+
this.props = new PropsModel();
|
|
4384
|
+
this.value = new ValueModel(this.props);
|
|
4385
|
+
this.mark = new MarkFeature(this.props);
|
|
4386
|
+
this.slots = new SlotsFeature(this.props);
|
|
4387
|
+
this.parsing = new ParseController(this.lifecycle, this.value, this.mark, this.props, this.slots);
|
|
4388
|
+
this.dom = new DomController(this.lifecycle, this.props, this.parsing, this.value);
|
|
4389
|
+
this.caret = new CaretModel(this.lifecycle, this.dom, this.value);
|
|
4390
|
+
this.edit = new EditController(this.value, this.caret);
|
|
4391
|
+
this.overlay = new OverlayController(this.lifecycle, this.props, this.value, this.dom, this.caret, this.edit, this.parsing);
|
|
4392
|
+
this.keyboard = new KeyboardController(this.lifecycle, this.dom, this.value, this.caret, this.edit, this.slots, this.parsing, this.props);
|
|
4393
|
+
this.drag = new DragController(this.props, this.value, this.parsing, this.caret);
|
|
4394
|
+
this.clipboard = new ClipboardController(this.lifecycle, this.edit, this.dom, this.parsing);
|
|
4395
|
+
this.handler = new MarkputHandler(this.dom, this.overlay, this.parsing);
|
|
4316
4396
|
}
|
|
4317
4397
|
};
|
|
4318
4398
|
//#endregion
|
|
@@ -4379,14 +4459,24 @@ const Popup = ({ ref, style, children }) => {
|
|
|
4379
4459
|
//#endregion
|
|
4380
4460
|
//#region src/components/BlockMenu.tsx
|
|
4381
4461
|
const BlockMenu = memo(({ token }) => {
|
|
4382
|
-
const { blockStore, menuOpen, menuPosition } = useMarkput((s) =>
|
|
4383
|
-
blockStore
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4462
|
+
const { blockStore, menuOpen, menuPosition, dom, index } = useMarkput((s) => {
|
|
4463
|
+
const blockStore = s.blocks.get(token);
|
|
4464
|
+
return {
|
|
4465
|
+
blockStore,
|
|
4466
|
+
menuOpen: blockStore.state.menuOpen,
|
|
4467
|
+
menuPosition: blockStore.state.menuPosition,
|
|
4468
|
+
dom: s.dom,
|
|
4469
|
+
index: s.parsing.index
|
|
4470
|
+
};
|
|
4471
|
+
});
|
|
4472
|
+
const path = index.pathFor(token);
|
|
4473
|
+
const controlRef = useMemo(() => path ? dom.controlFor(path) : void 0, [dom, path]);
|
|
4387
4474
|
if (!menuOpen) return null;
|
|
4388
4475
|
return /* @__PURE__ */ jsx(Popup, {
|
|
4389
|
-
ref: (el) =>
|
|
4476
|
+
ref: (el) => {
|
|
4477
|
+
blockStore.attachMenu(el);
|
|
4478
|
+
controlRef?.(el);
|
|
4479
|
+
},
|
|
4390
4480
|
style: {
|
|
4391
4481
|
top: menuPosition.top,
|
|
4392
4482
|
left: menuPosition.left
|
|
@@ -4412,20 +4502,30 @@ BlockMenu.displayName = "BlockMenu";
|
|
|
4412
4502
|
//#region src/components/DragHandle.tsx
|
|
4413
4503
|
const iconGrip = `${styles$1.Icon} ${styles$1.IconGrip}`;
|
|
4414
4504
|
const DragHandle = memo(({ token, blockIndex }) => {
|
|
4415
|
-
const { blockStore, action, readOnly, draggable, isDragging, isHovered } = useMarkput((s) =>
|
|
4416
|
-
blockStore
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4505
|
+
const { blockStore, action, readOnly, draggable, isDragging, isHovered, dom, index } = useMarkput((s) => {
|
|
4506
|
+
const blockStore = s.blocks.get(token);
|
|
4507
|
+
return {
|
|
4508
|
+
blockStore,
|
|
4509
|
+
action: s.drag.action,
|
|
4510
|
+
readOnly: s.props.readOnly,
|
|
4511
|
+
draggable: s.props.draggable,
|
|
4512
|
+
isDragging: blockStore.state.isDragging,
|
|
4513
|
+
isHovered: blockStore.state.isHovered,
|
|
4514
|
+
dom: s.dom,
|
|
4515
|
+
index: s.parsing.index
|
|
4516
|
+
};
|
|
4517
|
+
});
|
|
4423
4518
|
const alwaysShowHandle = useMemo(() => getAlwaysShowHandle(draggable), [draggable]);
|
|
4519
|
+
const path = index.pathFor(token);
|
|
4520
|
+
const controlRef = useMemo(() => path ? dom.controlFor(path) : void 0, [dom, path]);
|
|
4424
4521
|
if (readOnly) return null;
|
|
4425
4522
|
return /* @__PURE__ */ jsx("div", {
|
|
4523
|
+
ref: controlRef,
|
|
4426
4524
|
className: cx(styles$1.SidePanel, alwaysShowHandle ? styles$1.SidePanelAlways : isHovered && !isDragging && styles$1.SidePanelVisible),
|
|
4427
4525
|
children: /* @__PURE__ */ jsx("button", {
|
|
4428
|
-
ref: (el) =>
|
|
4526
|
+
ref: (el) => {
|
|
4527
|
+
blockStore.attachGrip(el, blockIndex, { action });
|
|
4528
|
+
},
|
|
4429
4529
|
type: "button",
|
|
4430
4530
|
draggable: true,
|
|
4431
4531
|
className: cx(styles$1.GripButton, isDragging && styles$1.GripButtonDragging),
|
|
@@ -4438,8 +4538,16 @@ DragHandle.displayName = "DragHandle";
|
|
|
4438
4538
|
//#endregion
|
|
4439
4539
|
//#region src/components/DropIndicator.tsx
|
|
4440
4540
|
const DropIndicator = memo(({ token, position }) => {
|
|
4441
|
-
|
|
4541
|
+
const dropPosition = useMarkput((s) => s.blocks.get(token).state.dropPosition);
|
|
4542
|
+
const { dom, index } = useMarkput((s) => ({
|
|
4543
|
+
dom: s.dom,
|
|
4544
|
+
index: s.parsing.index
|
|
4545
|
+
}));
|
|
4546
|
+
const path = index.pathFor(token);
|
|
4547
|
+
const controlRef = useMemo(() => path ? dom.controlFor(path) : void 0, [dom, path]);
|
|
4548
|
+
if (dropPosition !== position) return null;
|
|
4442
4549
|
return /* @__PURE__ */ jsx("div", {
|
|
4550
|
+
ref: controlRef,
|
|
4443
4551
|
className: styles$1.DropIndicator,
|
|
4444
4552
|
style: position === "before" ? { top: -1 } : { bottom: -1 }
|
|
4445
4553
|
});
|
|
@@ -4449,25 +4557,50 @@ DropIndicator.displayName = "DropIndicator";
|
|
|
4449
4557
|
//#region src/lib/providers/TokenContext.ts
|
|
4450
4558
|
const TokenContext = createContext(void 0);
|
|
4451
4559
|
TokenContext.displayName = "TokenProvider";
|
|
4452
|
-
function
|
|
4560
|
+
function useTokenContext() {
|
|
4453
4561
|
const value = useContext(TokenContext);
|
|
4454
4562
|
if (value === void 0) throw new Error("Token not found. Make sure to wrap component in TokenContext.Provider.");
|
|
4455
4563
|
return value;
|
|
4456
4564
|
}
|
|
4457
4565
|
//#endregion
|
|
4566
|
+
//#region src/components/TokenChildren.tsx
|
|
4567
|
+
const sequenceHostStyle = { display: "contents" };
|
|
4568
|
+
const TokenChildren = memo(({ ownerPath, children }) => {
|
|
4569
|
+
const { dom } = useMarkput((s) => ({ dom: s.dom }));
|
|
4570
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4571
|
+
ref: useMemo(() => dom.childrenFor(ownerPath), [dom, ownerPath]),
|
|
4572
|
+
style: sequenceHostStyle,
|
|
4573
|
+
children
|
|
4574
|
+
});
|
|
4575
|
+
});
|
|
4576
|
+
TokenChildren.displayName = "TokenChildren";
|
|
4577
|
+
//#endregion
|
|
4458
4578
|
//#region src/components/Token.tsx
|
|
4459
|
-
const Token = memo(({
|
|
4460
|
-
const { resolveMarkSlot, key } = useMarkput((s) => ({
|
|
4579
|
+
const Token = memo(({ token }) => {
|
|
4580
|
+
const { resolveMarkSlot, key, index, store } = useMarkput((s) => ({
|
|
4461
4581
|
resolveMarkSlot: s.mark.slot,
|
|
4462
|
-
key: s.key
|
|
4582
|
+
key: s.key,
|
|
4583
|
+
index: s.parsing.index,
|
|
4584
|
+
store: s
|
|
4463
4585
|
}));
|
|
4464
|
-
const
|
|
4586
|
+
const path = index.pathFor(token);
|
|
4587
|
+
const address = path ? index.addressFor(path) : void 0;
|
|
4588
|
+
if (!path || !address) return null;
|
|
4589
|
+
const [Component, props] = resolveMarkSlot(token);
|
|
4590
|
+
const children = token.type === "mark" && token.children.length > 0 ? /* @__PURE__ */ jsx(TokenChildren, {
|
|
4591
|
+
ownerPath: path,
|
|
4592
|
+
children: token.children.map((child) => /* @__PURE__ */ jsx(Token, { token: child }, key.get(child)))
|
|
4593
|
+
}) : void 0;
|
|
4465
4594
|
return /* @__PURE__ */ jsx(TokenContext, {
|
|
4466
|
-
value:
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
}
|
|
4595
|
+
value: {
|
|
4596
|
+
store,
|
|
4597
|
+
token,
|
|
4598
|
+
address
|
|
4599
|
+
},
|
|
4600
|
+
children: children ? /* @__PURE__ */ jsx(Component, {
|
|
4601
|
+
...props,
|
|
4602
|
+
children
|
|
4603
|
+
}) : /* @__PURE__ */ jsx(Component, { ...props })
|
|
4471
4604
|
});
|
|
4472
4605
|
});
|
|
4473
4606
|
Token.displayName = "Token";
|
|
@@ -4483,8 +4616,11 @@ const Block = memo(({ token }) => {
|
|
|
4483
4616
|
tokens: s.parsing.tokens
|
|
4484
4617
|
}));
|
|
4485
4618
|
const blockIndex = tokens.indexOf(token);
|
|
4619
|
+
const setBlockRef = (el) => {
|
|
4620
|
+
blockStore.attachContainer(el, blockIndex, { action });
|
|
4621
|
+
};
|
|
4486
4622
|
return /* @__PURE__ */ jsxs(Component, {
|
|
4487
|
-
ref:
|
|
4623
|
+
ref: setBlockRef,
|
|
4488
4624
|
"data-testid": "block",
|
|
4489
4625
|
...slotProps,
|
|
4490
4626
|
className: cx(styles$1.Block, slotProps?.className),
|
|
@@ -4501,7 +4637,7 @@ const Block = memo(({ token }) => {
|
|
|
4501
4637
|
token,
|
|
4502
4638
|
blockIndex
|
|
4503
4639
|
}),
|
|
4504
|
-
/* @__PURE__ */ jsx(Token, {
|
|
4640
|
+
/* @__PURE__ */ jsx(Token, { token }),
|
|
4505
4641
|
/* @__PURE__ */ jsx(DropIndicator, {
|
|
4506
4642
|
token,
|
|
4507
4643
|
position: "after"
|
|
@@ -4514,75 +4650,61 @@ Block.displayName = "Block";
|
|
|
4514
4650
|
//#endregion
|
|
4515
4651
|
//#region src/components/Container.tsx
|
|
4516
4652
|
const Container = memo(() => {
|
|
4517
|
-
const
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
const { isBlock, tokens, key, lifecycleEmit, Component, props } = useMarkput((s) => ({
|
|
4653
|
+
const { dom, lifecycle, isBlock, tokens, key, Component, props } = useMarkput((s) => ({
|
|
4654
|
+
dom: s.dom,
|
|
4655
|
+
lifecycle: s.lifecycle,
|
|
4521
4656
|
isBlock: s.slots.isBlock,
|
|
4522
4657
|
tokens: s.parsing.tokens,
|
|
4523
4658
|
key: s.key,
|
|
4524
|
-
lifecycleEmit: s.lifecycle,
|
|
4525
4659
|
Component: s.slots.containerComponent,
|
|
4526
4660
|
props: s.slots.containerProps
|
|
4527
4661
|
}));
|
|
4528
4662
|
useLayoutEffect(() => {
|
|
4529
|
-
|
|
4530
|
-
}
|
|
4663
|
+
lifecycle.rendered();
|
|
4664
|
+
});
|
|
4531
4665
|
return /* @__PURE__ */ jsx(Component, {
|
|
4532
|
-
ref:
|
|
4666
|
+
ref: dom.container,
|
|
4533
4667
|
...props,
|
|
4534
|
-
children: isBlock ? tokens.map((t) => /* @__PURE__ */ jsx(Block, { token: t }, key.get(t))) : tokens.map((t) => /* @__PURE__ */ jsx(Token, {
|
|
4668
|
+
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
4669
|
});
|
|
4536
4670
|
});
|
|
4537
4671
|
Container.displayName = "Container";
|
|
4538
4672
|
//#endregion
|
|
4539
4673
|
//#region src/lib/hooks/useOverlay.tsx
|
|
4540
4674
|
function useOverlay() {
|
|
4541
|
-
const match = useMarkput((s) =>
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
}
|
|
4548
|
-
const store = storeRef.current;
|
|
4549
|
-
const style = useMemo(() => {
|
|
4550
|
-
if (!match) return {
|
|
4551
|
-
left: 0,
|
|
4552
|
-
top: 0
|
|
4553
|
-
};
|
|
4554
|
-
return Caret.getAbsolutePosition();
|
|
4555
|
-
}, [match]);
|
|
4556
|
-
const close = useCallback(() => store.overlay.close(), [store]);
|
|
4675
|
+
const { match, overlay } = useMarkput((s) => ({
|
|
4676
|
+
match: s.overlay.match,
|
|
4677
|
+
overlay: s.overlay
|
|
4678
|
+
}));
|
|
4679
|
+
const style = useMarkput((s) => s.overlay.position());
|
|
4680
|
+
const close = useCallback(() => overlay.close(), [overlay]);
|
|
4557
4681
|
return {
|
|
4558
4682
|
match,
|
|
4559
4683
|
style,
|
|
4560
4684
|
select: useCallback((value) => {
|
|
4561
4685
|
if (!match) return;
|
|
4562
4686
|
const mark = createMarkFromOverlay(match, value.value, value.meta);
|
|
4563
|
-
|
|
4687
|
+
overlay.select({
|
|
4564
4688
|
mark,
|
|
4565
4689
|
match
|
|
4566
4690
|
});
|
|
4567
|
-
|
|
4568
|
-
}, [match,
|
|
4691
|
+
overlay.close();
|
|
4692
|
+
}, [match, overlay]),
|
|
4569
4693
|
close,
|
|
4570
4694
|
ref: useMemo(() => ({
|
|
4571
4695
|
get current() {
|
|
4572
|
-
return
|
|
4696
|
+
return overlay.element();
|
|
4573
4697
|
},
|
|
4574
4698
|
set current(v) {
|
|
4575
|
-
|
|
4699
|
+
overlay.element(v);
|
|
4576
4700
|
}
|
|
4577
|
-
}), [
|
|
4701
|
+
}), [overlay])
|
|
4578
4702
|
};
|
|
4579
4703
|
}
|
|
4580
4704
|
//#endregion
|
|
4581
4705
|
//#region src/components/Suggestions/Suggestions.tsx
|
|
4582
4706
|
const Suggestions = () => {
|
|
4583
|
-
const
|
|
4584
|
-
if (!storeCtx) throw new Error("Store not found");
|
|
4585
|
-
const store = storeCtx;
|
|
4707
|
+
const container = useMarkput((s) => s.dom.container);
|
|
4586
4708
|
const { match, select, style, ref } = useOverlay();
|
|
4587
4709
|
const [active, setActive] = useState(NaN);
|
|
4588
4710
|
const data = match?.option.overlay?.data ?? [];
|
|
@@ -4593,7 +4715,6 @@ const Suggestions = () => {
|
|
|
4593
4715
|
const filteredRef = useRef(filtered);
|
|
4594
4716
|
filteredRef.current = filtered;
|
|
4595
4717
|
useEffect(() => {
|
|
4596
|
-
const container = store.slots.container();
|
|
4597
4718
|
if (!container) return;
|
|
4598
4719
|
const handler = (event) => {
|
|
4599
4720
|
const result = navigateSuggestions(event.key, activeRef.current, length);
|
|
@@ -4617,7 +4738,11 @@ const Suggestions = () => {
|
|
|
4617
4738
|
};
|
|
4618
4739
|
container.addEventListener("keydown", handler);
|
|
4619
4740
|
return () => container.removeEventListener("keydown", handler);
|
|
4620
|
-
}, [
|
|
4741
|
+
}, [
|
|
4742
|
+
container,
|
|
4743
|
+
length,
|
|
4744
|
+
select
|
|
4745
|
+
]);
|
|
4621
4746
|
if (!filtered.length) return null;
|
|
4622
4747
|
return /* @__PURE__ */ jsx(Popup, {
|
|
4623
4748
|
ref,
|
|
@@ -4649,8 +4774,14 @@ OverlayRenderer.displayName = "OverlayRenderer";
|
|
|
4649
4774
|
//#endregion
|
|
4650
4775
|
//#region src/components/MarkedInput.tsx
|
|
4651
4776
|
function MarkedInput(props) {
|
|
4652
|
-
const [store] = useState(() =>
|
|
4653
|
-
|
|
4777
|
+
const [store] = useState(() => {
|
|
4778
|
+
const nextStore = new Store();
|
|
4779
|
+
nextStore.props.set(props);
|
|
4780
|
+
return nextStore;
|
|
4781
|
+
});
|
|
4782
|
+
useLayoutEffect(() => {
|
|
4783
|
+
store.props.set(props);
|
|
4784
|
+
});
|
|
4654
4785
|
useLayoutEffect(() => {
|
|
4655
4786
|
store.lifecycle.mounted();
|
|
4656
4787
|
return () => store.lifecycle.unmounted();
|
|
@@ -4663,31 +4794,34 @@ function MarkedInput(props) {
|
|
|
4663
4794
|
}
|
|
4664
4795
|
//#endregion
|
|
4665
4796
|
//#region src/lib/hooks/useMark.tsx
|
|
4666
|
-
const useMark = (
|
|
4667
|
-
const { store,
|
|
4668
|
-
|
|
4669
|
-
readOnly: s.props.readOnly
|
|
4670
|
-
}));
|
|
4671
|
-
const token = useToken();
|
|
4797
|
+
const useMark = () => {
|
|
4798
|
+
const { store, token } = useTokenContext();
|
|
4799
|
+
const readOnly = useMarkput((s) => s.props.readOnly);
|
|
4672
4800
|
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,
|
|
4801
|
+
return useMemo(() => MarkController.fromToken(store, token), [
|
|
4676
4802
|
store,
|
|
4677
|
-
token
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4803
|
+
token,
|
|
4804
|
+
readOnly
|
|
4805
|
+
]);
|
|
4806
|
+
};
|
|
4807
|
+
//#endregion
|
|
4808
|
+
//#region src/lib/hooks/useMarkInfo.tsx
|
|
4809
|
+
const useMarkInfo = () => {
|
|
4810
|
+
const { store, token } = useTokenContext();
|
|
4811
|
+
if (token.type !== "mark") throw new Error("useMarkInfo must be called within a mark token context");
|
|
4812
|
+
const index = store.parsing.index();
|
|
4813
|
+
const path = index.pathFor(token);
|
|
4814
|
+
if (!path) throw new Error("Mark token is not indexed");
|
|
4815
|
+
const address = index.addressFor(path);
|
|
4816
|
+
if (!address) throw new Error("Mark token path is stale");
|
|
4817
|
+
return {
|
|
4818
|
+
address,
|
|
4819
|
+
depth: findToken(store.parsing.tokens(), token)?.depth ?? 0,
|
|
4820
|
+
hasNestedMarks: token.children.some((child) => child.type === "mark"),
|
|
4821
|
+
key: index.key(path)
|
|
4822
|
+
};
|
|
4684
4823
|
};
|
|
4685
|
-
function useUncontrolledInit(ref, options, token) {
|
|
4686
|
-
useEffect(() => {
|
|
4687
|
-
if (!options.controlled && ref.current) ref.current.textContent = token.content;
|
|
4688
|
-
}, []);
|
|
4689
|
-
}
|
|
4690
4824
|
//#endregion
|
|
4691
|
-
export {
|
|
4825
|
+
export { MarkController, MarkedInput, MarkputHandler, annotate, denote, useMark, useMarkInfo, useMarkput, useOverlay };
|
|
4692
4826
|
|
|
4693
4827
|
//# sourceMappingURL=index.js.map
|