@markput/react 0.11.0 → 0.12.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 +205 -189
- package/index.d.ts.map +1 -1
- package/index.js +1627 -1596
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -58,10 +58,10 @@ var MarkputHandler = class {
|
|
|
58
58
|
this.store = store;
|
|
59
59
|
}
|
|
60
60
|
get container() {
|
|
61
|
-
return this.store.
|
|
61
|
+
return this.store.slots.container();
|
|
62
62
|
}
|
|
63
63
|
get overlay() {
|
|
64
|
-
return this.store.
|
|
64
|
+
return this.store.overlay.element();
|
|
65
65
|
}
|
|
66
66
|
focus() {
|
|
67
67
|
this.store.nodes.focus.head?.focus();
|
|
@@ -265,184 +265,6 @@ var Caret = class {
|
|
|
265
265
|
}
|
|
266
266
|
};
|
|
267
267
|
//#endregion
|
|
268
|
-
//#region ../../core/src/shared/escape.ts
|
|
269
|
-
const escape = (str) => {
|
|
270
|
-
return str.replace(/[.*+?^${}()|[\]\\\\]/g, "\\$&");
|
|
271
|
-
};
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region ../../core/src/features/caret/TriggerFinder.ts
|
|
274
|
-
/** Regex to match word characters from the start of a string */
|
|
275
|
-
const wordRegex = /* @__PURE__ */ new RegExp(/^\w*/);
|
|
276
|
-
var TriggerFinder = class TriggerFinder {
|
|
277
|
-
span;
|
|
278
|
-
node;
|
|
279
|
-
dividedText;
|
|
280
|
-
constructor() {
|
|
281
|
-
const caretPosition = Caret.getCurrentPosition();
|
|
282
|
-
this.node = Caret.getSelectedNode();
|
|
283
|
-
this.span = Caret.getFocusedSpan();
|
|
284
|
-
this.dividedText = this.getDividedTextBy(caretPosition);
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Find overlay match in text using provided options and trigger extractor.
|
|
288
|
-
* @template T - Type of option objects
|
|
289
|
-
* @param options - Array of options to search through
|
|
290
|
-
* @param getTrigger - Function that extracts trigger from each option
|
|
291
|
-
* @returns OverlayMatch with correct option type or undefined
|
|
292
|
-
*
|
|
293
|
-
* @example
|
|
294
|
-
* // React usage
|
|
295
|
-
* TriggerFinder.find(options, (opt) => opt.slotProps?.overlay?.trigger ?? '@')
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* // Other framework usage
|
|
299
|
-
* TriggerFinder.find(vueOptions, (opt) => opt.overlay?.trigger ?? '@')
|
|
300
|
-
*/
|
|
301
|
-
static find(options, getTrigger) {
|
|
302
|
-
if (!options) return;
|
|
303
|
-
if (!Caret.isSelectedPosition) return;
|
|
304
|
-
try {
|
|
305
|
-
return new TriggerFinder().find(options, getTrigger);
|
|
306
|
-
} catch {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
getDividedTextBy(position) {
|
|
311
|
-
return {
|
|
312
|
-
left: this.span.slice(0, position),
|
|
313
|
-
right: this.span.slice(position)
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Find overlay match in provided options.
|
|
318
|
-
* @template T - Type of option objects
|
|
319
|
-
* @param options - Array of options
|
|
320
|
-
* @param getTrigger - Function to extract trigger from each option
|
|
321
|
-
*/
|
|
322
|
-
find(options, getTrigger) {
|
|
323
|
-
for (let i = 0; i < options.length; i++) {
|
|
324
|
-
const option = options[i];
|
|
325
|
-
const trigger = getTrigger(option, i);
|
|
326
|
-
if (!trigger) continue;
|
|
327
|
-
const match = this.matchInTextVia(trigger);
|
|
328
|
-
if (match) return {
|
|
329
|
-
value: match.word,
|
|
330
|
-
source: match.annotation,
|
|
331
|
-
index: match.index,
|
|
332
|
-
span: this.span,
|
|
333
|
-
node: this.node,
|
|
334
|
-
option
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
matchInTextVia(trigger = "@") {
|
|
339
|
-
const rightMatch = this.matchRightPart();
|
|
340
|
-
const leftMatch = this.matchLeftPart(trigger);
|
|
341
|
-
if (leftMatch) return {
|
|
342
|
-
word: leftMatch.word + rightMatch.word,
|
|
343
|
-
annotation: leftMatch.annotation + rightMatch.word,
|
|
344
|
-
index: leftMatch.index
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
matchRightPart() {
|
|
348
|
-
const { right } = this.dividedText;
|
|
349
|
-
return { word: right.match(wordRegex)?.[0] };
|
|
350
|
-
}
|
|
351
|
-
matchLeftPart(trigger) {
|
|
352
|
-
const regex = this.makeTriggerRegex(trigger);
|
|
353
|
-
const { left } = this.dividedText;
|
|
354
|
-
const match = left.match(regex);
|
|
355
|
-
if (!match) return;
|
|
356
|
-
const [annotation, word] = match;
|
|
357
|
-
return {
|
|
358
|
-
word,
|
|
359
|
-
annotation,
|
|
360
|
-
index: match.index ?? 0
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
makeTriggerRegex(trigger) {
|
|
364
|
-
const patten = escape(trigger) + "(\\w*)$";
|
|
365
|
-
return new RegExp(patten);
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
//#endregion
|
|
369
|
-
//#region ../../core/src/shared/classes/NodeProxy.ts
|
|
370
|
-
var NodeProxy = class NodeProxy {
|
|
371
|
-
#target;
|
|
372
|
-
#store;
|
|
373
|
-
get target() {
|
|
374
|
-
return this.#target;
|
|
375
|
-
}
|
|
376
|
-
set target(value) {
|
|
377
|
-
this.#target = isHtmlElement(value) ? value : void 0;
|
|
378
|
-
}
|
|
379
|
-
get next() {
|
|
380
|
-
return new NodeProxy(this.target?.nextSibling, this.#store);
|
|
381
|
-
}
|
|
382
|
-
get prev() {
|
|
383
|
-
return new NodeProxy(this.target?.previousSibling, this.#store);
|
|
384
|
-
}
|
|
385
|
-
get isSpan() {
|
|
386
|
-
return this.index % 2 === 0;
|
|
387
|
-
}
|
|
388
|
-
get isMark() {
|
|
389
|
-
return !this.isSpan;
|
|
390
|
-
}
|
|
391
|
-
get isEditable() {
|
|
392
|
-
return this.target?.isContentEditable ?? false;
|
|
393
|
-
}
|
|
394
|
-
get isCaretAtBeginning() {
|
|
395
|
-
if (!this.target) return false;
|
|
396
|
-
return Caret.getCaretIndex(this.target) === 0;
|
|
397
|
-
}
|
|
398
|
-
get isCaretAtEnd() {
|
|
399
|
-
if (!this.target) return false;
|
|
400
|
-
return Caret.getCaretIndex(this.target) === this.target.textContent.length;
|
|
401
|
-
}
|
|
402
|
-
get index() {
|
|
403
|
-
if (!this.target?.parentElement) return -1;
|
|
404
|
-
return [...this.target.parentElement.children].indexOf(this.target);
|
|
405
|
-
}
|
|
406
|
-
get caret() {
|
|
407
|
-
if (this.target) return Caret.getCaretIndex(this.target);
|
|
408
|
-
return -1;
|
|
409
|
-
}
|
|
410
|
-
set caret(value) {
|
|
411
|
-
if (this.target) Caret.trySetIndex(this.target, value);
|
|
412
|
-
}
|
|
413
|
-
get length() {
|
|
414
|
-
return this.target?.textContent.length ?? -1;
|
|
415
|
-
}
|
|
416
|
-
get content() {
|
|
417
|
-
return this.target?.textContent ?? "";
|
|
418
|
-
}
|
|
419
|
-
set content(value) {
|
|
420
|
-
if (this.target) this.target.textContent = value ?? "";
|
|
421
|
-
}
|
|
422
|
-
get head() {
|
|
423
|
-
return firstHtmlChild(this.#store.refs.container ?? void 0);
|
|
424
|
-
}
|
|
425
|
-
get tail() {
|
|
426
|
-
return lastHtmlChild(this.#store.refs.container ?? void 0);
|
|
427
|
-
}
|
|
428
|
-
get isFocused() {
|
|
429
|
-
return this.target === document.activeElement;
|
|
430
|
-
}
|
|
431
|
-
constructor(target, store) {
|
|
432
|
-
this.target = target;
|
|
433
|
-
this.#store = store;
|
|
434
|
-
}
|
|
435
|
-
setCaretToEnd() {
|
|
436
|
-
Caret.setCaretToEnd(this.target);
|
|
437
|
-
}
|
|
438
|
-
focus() {
|
|
439
|
-
this.target?.focus();
|
|
440
|
-
}
|
|
441
|
-
clear() {
|
|
442
|
-
this.target = void 0;
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
//#endregion
|
|
446
268
|
//#region ../../core/src/shared/signals/alien-signals/system.ts
|
|
447
269
|
let ReactiveFlags = /* @__PURE__ */ function(ReactiveFlags) {
|
|
448
270
|
ReactiveFlags[ReactiveFlags["None"] = 0] = "None";
|
|
@@ -942,6 +764,30 @@ function batch(fn, opts) {
|
|
|
942
764
|
mutableScope = prevMutable;
|
|
943
765
|
}
|
|
944
766
|
}
|
|
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
|
+
}
|
|
945
791
|
function untracked(fn) {
|
|
946
792
|
const prev = setActiveSub(void 0);
|
|
947
793
|
try {
|
|
@@ -957,74 +803,399 @@ function listen(target, event, handler, options) {
|
|
|
957
803
|
});
|
|
958
804
|
}
|
|
959
805
|
//#endregion
|
|
960
|
-
//#region ../../core/src/features/
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
//#region ../../core/src/features/parsing/parser/core/MarkupDescriptor.ts
|
|
989
|
-
/**
|
|
990
|
-
* Creates a segment-based markup descriptor from a markup template
|
|
991
|
-
*
|
|
992
|
-
* Examples:
|
|
993
|
-
* - `#[__value__]` -> segments: ["#[", "]"], gapTypes: ["value"]
|
|
994
|
-
* - `#[__slot__]` -> segments: ["#[", "]"], gapTypes: ["slot"]
|
|
995
|
-
* - `@[__value__](__meta__)` -> segments: ["@[", "](", ")"], gapTypes: ["value", "meta"]
|
|
996
|
-
* - `@[__slot__](__meta__)` -> segments: ["@[", "](", ")"], gapTypes: ["slot", "meta"]
|
|
997
|
-
* - `@[__value__](__slot__)` -> segments: ["@[", "](", ")"], gapTypes: ["value", "slot"]
|
|
998
|
-
* - `<__value__>__meta__</__value__>` -> segments: [{pattern: '<([^>]+)>'}, {pattern: '</([^>]+)>'}], gapTypes: ["value", "meta", "value"] (dynamic)
|
|
999
|
-
* - `<__value__ __meta__>__slot__</__value__>` -> segments: [{pattern: '<([^> ]+) '}, " ", {pattern: '>__slot__</([^>]+)>'}], gapTypes: ["value", "meta", "slot", "value"] (dynamic)
|
|
1000
|
-
*/
|
|
1001
|
-
function createMarkupDescriptor(markup, index) {
|
|
1002
|
-
const { segments: rawSegments, gapTypes: rawGapTypes, counts, valueGapIndices } = scanMarkupStructure(markup);
|
|
1003
|
-
validateMarkup(counts, markup);
|
|
1004
|
-
const hasTwoValues = counts.value === 2;
|
|
1005
|
-
const { segments, gapTypes } = hasTwoValues ? convertTwoValuePattern(rawSegments, rawGapTypes, valueGapIndices) : {
|
|
1006
|
-
segments: rawSegments,
|
|
1007
|
-
gapTypes: rawGapTypes
|
|
1008
|
-
};
|
|
1009
|
-
return {
|
|
1010
|
-
markup,
|
|
1011
|
-
index,
|
|
1012
|
-
segments,
|
|
1013
|
-
gapTypes,
|
|
1014
|
-
hasSlot: counts.slot === 1,
|
|
1015
|
-
hasTwoValues,
|
|
1016
|
-
segmentGlobalIndices: Array.from({ length: segments.length })
|
|
806
|
+
//#region ../../core/src/features/caret/focus.ts
|
|
807
|
+
function enableFocus(store) {
|
|
808
|
+
const container = store.slots.container();
|
|
809
|
+
if (!container) return () => {};
|
|
810
|
+
const scope = effectScope(() => {
|
|
811
|
+
listen(container, "focusin", (e) => {
|
|
812
|
+
const target = isHtmlElement(e.target) ? e.target : void 0;
|
|
813
|
+
store.nodes.focus.target = target;
|
|
814
|
+
});
|
|
815
|
+
listen(container, "focusout", () => {
|
|
816
|
+
store.nodes.focus.target = void 0;
|
|
817
|
+
});
|
|
818
|
+
listen(container, "click", () => {
|
|
819
|
+
const tokens = store.parsing.tokens();
|
|
820
|
+
if (tokens.length === 1 && tokens[0].type === "text" && tokens[0].content === "") {
|
|
821
|
+
const container = store.slots.container();
|
|
822
|
+
(container ? firstHtmlChild(container) : null)?.focus();
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
watch(store.lifecycle.rendered, () => {
|
|
826
|
+
store.dom.reconcile();
|
|
827
|
+
if (!store.props.Mark()) return;
|
|
828
|
+
recover(store);
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
return () => {
|
|
832
|
+
scope();
|
|
833
|
+
store.nodes.focus.clear();
|
|
1017
834
|
};
|
|
1018
835
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
836
|
+
function recover(store) {
|
|
837
|
+
const recovery = store.caret.recovery();
|
|
838
|
+
if (!recovery) return;
|
|
839
|
+
const { anchor, caret, isNext } = recovery;
|
|
840
|
+
const isStale = !anchor.target || !anchor.target.isConnected;
|
|
841
|
+
let target;
|
|
842
|
+
switch (true) {
|
|
843
|
+
case isNext && isStale: {
|
|
844
|
+
const container = store.slots.container();
|
|
845
|
+
target = (recovery.childIndex != null ? childAt(container, recovery.childIndex + 2) : void 0) ?? store.nodes.focus.tail ?? void 0;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
case isNext:
|
|
849
|
+
target = anchor.prev.target;
|
|
850
|
+
break;
|
|
851
|
+
case isStale:
|
|
852
|
+
target = store.nodes.focus.head ?? void 0;
|
|
853
|
+
break;
|
|
854
|
+
default: target = anchor.next.target;
|
|
855
|
+
}
|
|
856
|
+
store.nodes.focus.target = target;
|
|
857
|
+
target?.focus();
|
|
858
|
+
queueMicrotask(() => {
|
|
859
|
+
if (!target?.isConnected) return;
|
|
860
|
+
store.nodes.focus.target = target;
|
|
861
|
+
store.nodes.focus.caret = caret;
|
|
862
|
+
});
|
|
863
|
+
store.caret.recovery(void 0);
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region ../../core/src/features/caret/selection.ts
|
|
867
|
+
function enableSelection(store) {
|
|
868
|
+
let pressedNode = null;
|
|
869
|
+
let isPressed = false;
|
|
870
|
+
const scope = effectScope(() => {
|
|
871
|
+
listen(document, "mousedown", (e) => {
|
|
872
|
+
pressedNode = nodeTarget(e);
|
|
873
|
+
isPressed = true;
|
|
874
|
+
});
|
|
875
|
+
listen(document, "mousemove", (e) => {
|
|
876
|
+
const container = store.slots.container();
|
|
877
|
+
if (!container) return;
|
|
878
|
+
const currentIsPressed = isPressed;
|
|
879
|
+
const isNotInnerSome = !container.contains(pressedNode) || pressedNode !== e.target;
|
|
880
|
+
const isInside = window.getSelection()?.containsNode(container, true);
|
|
881
|
+
if (currentIsPressed && isNotInnerSome && isInside) {
|
|
882
|
+
if (store.caret.selecting() !== "drag") store.caret.selecting("drag");
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
listen(document, "mouseup", () => {
|
|
886
|
+
isPressed = false;
|
|
887
|
+
pressedNode = null;
|
|
888
|
+
if (store.caret.selecting() === "drag") {
|
|
889
|
+
const sel = window.getSelection();
|
|
890
|
+
if (!sel || sel.isCollapsed) store.caret.selecting(void 0);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
listen(document, "selectionchange", () => {
|
|
894
|
+
if (store.caret.selecting() !== "drag") return;
|
|
895
|
+
const sel = window.getSelection();
|
|
896
|
+
if (!sel || sel.isCollapsed) store.caret.selecting(void 0);
|
|
897
|
+
});
|
|
898
|
+
alienEffect(() => {
|
|
899
|
+
if (store.caret.selecting() !== "drag") return;
|
|
900
|
+
const container = store.slots.container();
|
|
901
|
+
if (!container) return;
|
|
902
|
+
container.querySelectorAll("[contenteditable=\"true\"]").forEach((el) => el.contentEditable = "false");
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
return () => {
|
|
906
|
+
if (store.caret.selecting() === "drag") store.caret.selecting(void 0);
|
|
907
|
+
scope();
|
|
908
|
+
pressedNode = null;
|
|
909
|
+
isPressed = false;
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
//#endregion
|
|
913
|
+
//#region ../../core/src/features/caret/CaretFeature.ts
|
|
914
|
+
var CaretFeature = class {
|
|
915
|
+
#disposers = [];
|
|
916
|
+
constructor(_store) {
|
|
917
|
+
this._store = _store;
|
|
918
|
+
this.recovery = signal(void 0);
|
|
919
|
+
this.selecting = signal(void 0);
|
|
920
|
+
}
|
|
921
|
+
enable() {
|
|
922
|
+
if (this.#disposers.length) return;
|
|
923
|
+
this.#disposers = [enableFocus(this._store), enableSelection(this._store)];
|
|
924
|
+
}
|
|
925
|
+
disable() {
|
|
926
|
+
this.#disposers.forEach((d) => d());
|
|
927
|
+
this.#disposers = [];
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
//#endregion
|
|
931
|
+
//#region ../../core/src/features/caret/selectionHelpers.ts
|
|
932
|
+
function isFullSelection(store) {
|
|
933
|
+
const sel = window.getSelection();
|
|
934
|
+
const container = store.slots.container();
|
|
935
|
+
if (!sel?.rangeCount || !container?.firstChild || !container.lastChild) return false;
|
|
936
|
+
try {
|
|
937
|
+
const range = sel.getRangeAt(0);
|
|
938
|
+
return container.contains(range.startContainer) && container.contains(range.endContainer) && range.toString().length > 0;
|
|
939
|
+
} catch {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function selectAllText(store, event) {
|
|
944
|
+
if ((event.ctrlKey || event.metaKey) && event.code === "KeyA") {
|
|
945
|
+
if (store.slots.isBlock()) return;
|
|
946
|
+
event.preventDefault();
|
|
947
|
+
const selection = window.getSelection();
|
|
948
|
+
const anchorNode = store.slots.container()?.firstChild;
|
|
949
|
+
const focusNode = store.slots.container()?.lastChild;
|
|
950
|
+
if (!selection || !anchorNode || !focusNode) return;
|
|
951
|
+
selection.setBaseAndExtent(anchorNode, 0, focusNode, 1);
|
|
952
|
+
store.caret.selecting("all");
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
//#endregion
|
|
956
|
+
//#region ../../core/src/shared/escape.ts
|
|
957
|
+
const escape = (str) => {
|
|
958
|
+
return str.replace(/[.*+?^${}()|[\]\\\\]/g, "\\$&");
|
|
959
|
+
};
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region ../../core/src/features/caret/TriggerFinder.ts
|
|
962
|
+
/** Regex to match word characters from the start of a string */
|
|
963
|
+
const wordRegex = /* @__PURE__ */ new RegExp(/^\w*/);
|
|
964
|
+
var TriggerFinder = class TriggerFinder {
|
|
965
|
+
constructor() {
|
|
966
|
+
const caretPosition = Caret.getCurrentPosition();
|
|
967
|
+
this.node = Caret.getSelectedNode();
|
|
968
|
+
this.span = Caret.getFocusedSpan();
|
|
969
|
+
this.dividedText = this.getDividedTextBy(caretPosition);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Find overlay match in text using provided options and trigger extractor.
|
|
973
|
+
* @template T - Type of option objects
|
|
974
|
+
* @param options - Array of options to search through
|
|
975
|
+
* @param getTrigger - Function that extracts trigger from each option
|
|
976
|
+
* @returns OverlayMatch with correct option type or undefined
|
|
977
|
+
*
|
|
978
|
+
* @example
|
|
979
|
+
* // React usage
|
|
980
|
+
* TriggerFinder.find(options, (opt) => opt.slotProps?.overlay?.trigger ?? '@')
|
|
981
|
+
*
|
|
982
|
+
* @example
|
|
983
|
+
* // Other framework usage
|
|
984
|
+
* TriggerFinder.find(vueOptions, (opt) => opt.overlay?.trigger ?? '@')
|
|
985
|
+
*/
|
|
986
|
+
static find(options, getTrigger) {
|
|
987
|
+
if (!options) return;
|
|
988
|
+
if (!Caret.isSelectedPosition) return;
|
|
989
|
+
try {
|
|
990
|
+
return new TriggerFinder().find(options, getTrigger);
|
|
991
|
+
} catch {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
getDividedTextBy(position) {
|
|
996
|
+
return {
|
|
997
|
+
left: this.span.slice(0, position),
|
|
998
|
+
right: this.span.slice(position)
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Find overlay match in provided options.
|
|
1003
|
+
* @template T - Type of option objects
|
|
1004
|
+
* @param options - Array of options
|
|
1005
|
+
* @param getTrigger - Function to extract trigger from each option
|
|
1006
|
+
*/
|
|
1007
|
+
find(options, getTrigger) {
|
|
1008
|
+
for (let i = 0; i < options.length; i++) {
|
|
1009
|
+
const option = options[i];
|
|
1010
|
+
const trigger = getTrigger(option, i);
|
|
1011
|
+
if (!trigger) continue;
|
|
1012
|
+
const match = this.matchInTextVia(trigger);
|
|
1013
|
+
if (match) return {
|
|
1014
|
+
value: match.word,
|
|
1015
|
+
source: match.annotation,
|
|
1016
|
+
index: match.index,
|
|
1017
|
+
span: this.span,
|
|
1018
|
+
node: this.node,
|
|
1019
|
+
option
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
matchInTextVia(trigger = "@") {
|
|
1024
|
+
const rightMatch = this.matchRightPart();
|
|
1025
|
+
const leftMatch = this.matchLeftPart(trigger);
|
|
1026
|
+
if (leftMatch) return {
|
|
1027
|
+
word: leftMatch.word + rightMatch.word,
|
|
1028
|
+
annotation: leftMatch.annotation + rightMatch.word,
|
|
1029
|
+
index: leftMatch.index
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
matchRightPart() {
|
|
1033
|
+
const { right } = this.dividedText;
|
|
1034
|
+
return { word: right.match(wordRegex)?.[0] };
|
|
1035
|
+
}
|
|
1036
|
+
matchLeftPart(trigger) {
|
|
1037
|
+
const regex = this.makeTriggerRegex(trigger);
|
|
1038
|
+
const { left } = this.dividedText;
|
|
1039
|
+
const match = left.match(regex);
|
|
1040
|
+
if (!match) return;
|
|
1041
|
+
const [annotation, word] = match;
|
|
1042
|
+
return {
|
|
1043
|
+
word,
|
|
1044
|
+
annotation,
|
|
1045
|
+
index: match.index ?? 0
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
makeTriggerRegex(trigger) {
|
|
1049
|
+
const patten = escape(trigger) + "(\\w*)$";
|
|
1050
|
+
return new RegExp(patten);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
//#endregion
|
|
1054
|
+
//#region ../../core/src/shared/classes/NodeProxy.ts
|
|
1055
|
+
var NodeProxy = class NodeProxy {
|
|
1056
|
+
#target;
|
|
1057
|
+
#store;
|
|
1058
|
+
get target() {
|
|
1059
|
+
return this.#target;
|
|
1060
|
+
}
|
|
1061
|
+
set target(value) {
|
|
1062
|
+
this.#target = isHtmlElement(value) ? value : void 0;
|
|
1063
|
+
}
|
|
1064
|
+
get next() {
|
|
1065
|
+
return new NodeProxy(this.target?.nextSibling, this.#store);
|
|
1066
|
+
}
|
|
1067
|
+
get prev() {
|
|
1068
|
+
return new NodeProxy(this.target?.previousSibling, this.#store);
|
|
1069
|
+
}
|
|
1070
|
+
get isSpan() {
|
|
1071
|
+
return this.index % 2 === 0;
|
|
1072
|
+
}
|
|
1073
|
+
get isMark() {
|
|
1074
|
+
return !this.isSpan;
|
|
1075
|
+
}
|
|
1076
|
+
get isEditable() {
|
|
1077
|
+
return this.target?.isContentEditable ?? false;
|
|
1078
|
+
}
|
|
1079
|
+
get isCaretAtBeginning() {
|
|
1080
|
+
if (!this.target) return false;
|
|
1081
|
+
return Caret.getCaretIndex(this.target) === 0;
|
|
1082
|
+
}
|
|
1083
|
+
get isCaretAtEnd() {
|
|
1084
|
+
if (!this.target) return false;
|
|
1085
|
+
return Caret.getCaretIndex(this.target) === this.target.textContent.length;
|
|
1086
|
+
}
|
|
1087
|
+
get index() {
|
|
1088
|
+
if (!this.target?.parentElement) return -1;
|
|
1089
|
+
return [...this.target.parentElement.children].indexOf(this.target);
|
|
1090
|
+
}
|
|
1091
|
+
get caret() {
|
|
1092
|
+
if (this.target) return Caret.getCaretIndex(this.target);
|
|
1093
|
+
return -1;
|
|
1094
|
+
}
|
|
1095
|
+
set caret(value) {
|
|
1096
|
+
if (this.target) Caret.trySetIndex(this.target, value);
|
|
1097
|
+
}
|
|
1098
|
+
get length() {
|
|
1099
|
+
return this.target?.textContent.length ?? -1;
|
|
1100
|
+
}
|
|
1101
|
+
get content() {
|
|
1102
|
+
return this.target?.textContent ?? "";
|
|
1103
|
+
}
|
|
1104
|
+
set content(value) {
|
|
1105
|
+
if (this.target) this.target.textContent = value ?? "";
|
|
1106
|
+
}
|
|
1107
|
+
get head() {
|
|
1108
|
+
return firstHtmlChild(this.#store.slots.container() ?? void 0);
|
|
1109
|
+
}
|
|
1110
|
+
get tail() {
|
|
1111
|
+
return lastHtmlChild(this.#store.slots.container() ?? void 0);
|
|
1112
|
+
}
|
|
1113
|
+
get isFocused() {
|
|
1114
|
+
return this.target === document.activeElement;
|
|
1115
|
+
}
|
|
1116
|
+
constructor(target, store) {
|
|
1117
|
+
this.target = target;
|
|
1118
|
+
this.#store = store;
|
|
1119
|
+
}
|
|
1120
|
+
setCaretToEnd() {
|
|
1121
|
+
Caret.setCaretToEnd(this.target);
|
|
1122
|
+
}
|
|
1123
|
+
focus() {
|
|
1124
|
+
this.target?.focus();
|
|
1125
|
+
}
|
|
1126
|
+
clear() {
|
|
1127
|
+
this.target = void 0;
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
//#endregion
|
|
1131
|
+
//#region ../../core/src/features/parsing/parser/constants.ts
|
|
1132
|
+
/**
|
|
1133
|
+
* Constants for ParserV2 - modern markup parser with nesting support
|
|
1134
|
+
*
|
|
1135
|
+
* This module contains the placeholder constants used by ParserV2.
|
|
1136
|
+
* Unlike the legacy Parser, ParserV2 supports:
|
|
1137
|
+
* - `__value__` - main content (replaces `__label__`)
|
|
1138
|
+
* - `__meta__` - metadata (replaces `__value__`)
|
|
1139
|
+
* - `__slot__` - nested content (new feature)
|
|
1140
|
+
*
|
|
1141
|
+
* For legacy Parser compatibility, see ../Parser/constants.ts
|
|
1142
|
+
* For Markup types, see ./types.ts
|
|
1143
|
+
*/
|
|
1144
|
+
const PLACEHOLDER = {
|
|
1145
|
+
Value: "__value__",
|
|
1146
|
+
Meta: "__meta__",
|
|
1147
|
+
Slot: "__slot__"
|
|
1148
|
+
};
|
|
1149
|
+
/**
|
|
1150
|
+
* Gap types used in markup descriptors
|
|
1151
|
+
* Represents the content type in gaps between segments
|
|
1152
|
+
*/
|
|
1153
|
+
const GAP_TYPE = {
|
|
1154
|
+
Value: "value",
|
|
1155
|
+
Meta: "meta",
|
|
1156
|
+
Slot: "slot"
|
|
1157
|
+
};
|
|
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
|
+
/**
|
|
1028
1199
|
* Parses markup template into segments, gap types and placeholder counts
|
|
1029
1200
|
*/
|
|
1030
1201
|
function scanMarkupStructure(markup) {
|
|
@@ -1150,13 +1321,9 @@ function getOrCreate$1(map, key) {
|
|
|
1150
1321
|
* Centralizes access to all markup patterns and their descriptors
|
|
1151
1322
|
*/
|
|
1152
1323
|
var MarkupRegistry = class {
|
|
1153
|
-
markups;
|
|
1154
|
-
descriptors;
|
|
1155
|
-
/** Deduplicated list of unique segment definitions (static strings or dynamic patterns) */
|
|
1156
|
-
segments = [];
|
|
1157
|
-
/** Map from first segment index to descriptors that start with this segment (for O(1) lookup) */
|
|
1158
|
-
firstSegmentIndexMap = /* @__PURE__ */ new Map();
|
|
1159
1324
|
constructor(markups) {
|
|
1325
|
+
this.segments = [];
|
|
1326
|
+
this.firstSegmentIndexMap = /* @__PURE__ */ new Map();
|
|
1160
1327
|
this.markups = markups;
|
|
1161
1328
|
const segmentIndexMap = /* @__PURE__ */ new Map();
|
|
1162
1329
|
this.descriptors = markups.map((markup, index) => {
|
|
@@ -1241,20 +1408,9 @@ function getSegmentIndex(baseIndex, value) {
|
|
|
1241
1408
|
* - isActive: expectedSegmentIndex >= 0 (not NaN and not -1)
|
|
1242
1409
|
*/
|
|
1243
1410
|
var Match = class {
|
|
1244
|
-
gaps = {};
|
|
1245
|
-
/** Captured value from first dynamic segment (for hasTwoValues patterns) */
|
|
1246
|
-
captured;
|
|
1247
|
-
/**
|
|
1248
|
-
* Index of expected next segment:
|
|
1249
|
-
* - >= 0: active, waiting for segment at this index
|
|
1250
|
-
* - NaN: completed successfully
|
|
1251
|
-
* - -1: invalid, should be discarded
|
|
1252
|
-
*/
|
|
1253
|
-
expectedSegmentIndex;
|
|
1254
|
-
start;
|
|
1255
|
-
end;
|
|
1256
1411
|
constructor(descriptor, firstSegment) {
|
|
1257
1412
|
this.descriptor = descriptor;
|
|
1413
|
+
this.gaps = {};
|
|
1258
1414
|
this.expectedSegmentIndex = 1;
|
|
1259
1415
|
this.start = firstSegment.start;
|
|
1260
1416
|
this.end = firstSegment.end;
|
|
@@ -1368,11 +1524,11 @@ function getOrCreate(map, key) {
|
|
|
1368
1524
|
* Optimized parser using state machine approach
|
|
1369
1525
|
*/
|
|
1370
1526
|
var PatternMatcher = class {
|
|
1371
|
-
pendingStates = /* @__PURE__ */ new Map();
|
|
1372
|
-
completingStates = /* @__PURE__ */ new Map();
|
|
1373
|
-
completedStates = [];
|
|
1374
1527
|
constructor(registry) {
|
|
1375
1528
|
this.registry = registry;
|
|
1529
|
+
this.pendingStates = /* @__PURE__ */ new Map();
|
|
1530
|
+
this.completingStates = /* @__PURE__ */ new Map();
|
|
1531
|
+
this.completedStates = [];
|
|
1376
1532
|
}
|
|
1377
1533
|
/** Main method that converts found segments into structured matches */
|
|
1378
1534
|
process(segments) {
|
|
@@ -1497,8 +1653,6 @@ function computeDynamicPattern(before, after, exclusions) {
|
|
|
1497
1653
|
* Segment matcher using dual strategy for optimal performance
|
|
1498
1654
|
*/
|
|
1499
1655
|
var SegmentMatcher = class {
|
|
1500
|
-
static;
|
|
1501
|
-
dynamic;
|
|
1502
1656
|
constructor(segments) {
|
|
1503
1657
|
this.initializeDual(segments);
|
|
1504
1658
|
}
|
|
@@ -1632,8 +1786,6 @@ const createTextToken = (input, start = 0, end = input.length) => {
|
|
|
1632
1786
|
* Memory: O(D) for active parents stack where D is nesting depth (typically 3-5)
|
|
1633
1787
|
*/
|
|
1634
1788
|
var TreeBuilder = class {
|
|
1635
|
-
input;
|
|
1636
|
-
options;
|
|
1637
1789
|
constructor(options) {
|
|
1638
1790
|
this.options = options ?? {};
|
|
1639
1791
|
}
|
|
@@ -1892,11 +2044,6 @@ function toString(tokens) {
|
|
|
1892
2044
|
* ```
|
|
1893
2045
|
*/
|
|
1894
2046
|
var Parser = class Parser {
|
|
1895
|
-
registry;
|
|
1896
|
-
segmentMatcher;
|
|
1897
|
-
patternMatcher;
|
|
1898
|
-
treeBuilder;
|
|
1899
|
-
parseOptions;
|
|
1900
2047
|
/**
|
|
1901
2048
|
* Creates a new Parser instance with the specified markup patterns
|
|
1902
2049
|
*
|
|
@@ -2146,8 +2293,8 @@ function getClosestIndexes(array, target) {
|
|
|
2146
2293
|
//#region ../../core/src/features/parsing/utils/valueParser.ts
|
|
2147
2294
|
function getTokensByUI(store) {
|
|
2148
2295
|
const { focus } = store.nodes;
|
|
2149
|
-
const parser = store.
|
|
2150
|
-
const tokens = store.
|
|
2296
|
+
const parser = store.parsing.parser();
|
|
2297
|
+
const tokens = store.parsing.tokens();
|
|
2151
2298
|
if (!parser) return tokens;
|
|
2152
2299
|
const parsed = parser.parse(focus.content);
|
|
2153
2300
|
if (parsed.length <= 1) return tokens;
|
|
@@ -2155,19 +2302,19 @@ function getTokensByUI(store) {
|
|
|
2155
2302
|
}
|
|
2156
2303
|
function computeTokensFromValue(store) {
|
|
2157
2304
|
const value = store.props.value();
|
|
2158
|
-
const previousValue = store.
|
|
2305
|
+
const previousValue = store.value.last();
|
|
2159
2306
|
const gap = findGap(previousValue, value);
|
|
2160
2307
|
if (!gap.left && !gap.right) {
|
|
2161
|
-
store.
|
|
2162
|
-
return store.
|
|
2308
|
+
store.value.last(value);
|
|
2309
|
+
return store.parsing.tokens();
|
|
2163
2310
|
}
|
|
2164
2311
|
if (gap.left === 0 && previousValue !== void 0 && gap.right !== void 0 && gap.right >= previousValue.length) {
|
|
2165
|
-
store.
|
|
2312
|
+
store.value.last(value);
|
|
2166
2313
|
return parseWithParser(store, value ?? "");
|
|
2167
2314
|
}
|
|
2168
|
-
store.
|
|
2315
|
+
store.value.last(value);
|
|
2169
2316
|
const ranges = getRangeMap(store);
|
|
2170
|
-
const tokens = store.
|
|
2317
|
+
const tokens = store.parsing.tokens();
|
|
2171
2318
|
if (gap.left !== void 0 && ranges.includes(gap.left) && gap.right !== void 0 && Math.abs(gap.left - gap.right) > 1) {
|
|
2172
2319
|
const updatedIndex = ranges.indexOf(gap.left);
|
|
2173
2320
|
if (updatedIndex > 0) {
|
|
@@ -2191,7 +2338,7 @@ function computeTokensFromValue(store) {
|
|
|
2191
2338
|
}
|
|
2192
2339
|
function parseUnionLabels(store, ...indexes) {
|
|
2193
2340
|
let span = "";
|
|
2194
|
-
const tokens = store.
|
|
2341
|
+
const tokens = store.parsing.tokens();
|
|
2195
2342
|
for (const index of indexes) {
|
|
2196
2343
|
const token = tokens[index];
|
|
2197
2344
|
span += token.content;
|
|
@@ -2200,14 +2347,14 @@ function parseUnionLabels(store, ...indexes) {
|
|
|
2200
2347
|
}
|
|
2201
2348
|
function getRangeMap(store) {
|
|
2202
2349
|
let position = 0;
|
|
2203
|
-
return store.
|
|
2350
|
+
return store.parsing.tokens().map((token) => {
|
|
2204
2351
|
const length = token.content.length;
|
|
2205
2352
|
position += length;
|
|
2206
2353
|
return position - length;
|
|
2207
2354
|
});
|
|
2208
2355
|
}
|
|
2209
2356
|
function parseWithParser(store, value) {
|
|
2210
|
-
const parser = store.
|
|
2357
|
+
const parser = store.parsing.parser();
|
|
2211
2358
|
if (!parser) return [{
|
|
2212
2359
|
type: "text",
|
|
2213
2360
|
content: value,
|
|
@@ -2220,10 +2367,18 @@ function parseWithParser(store, value) {
|
|
|
2220
2367
|
}
|
|
2221
2368
|
//#endregion
|
|
2222
2369
|
//#region ../../core/src/features/parsing/ParseFeature.ts
|
|
2223
|
-
var
|
|
2370
|
+
var ParsingFeature = class {
|
|
2224
2371
|
#scope;
|
|
2225
|
-
constructor(
|
|
2226
|
-
this.
|
|
2372
|
+
constructor(_store) {
|
|
2373
|
+
this._store = _store;
|
|
2374
|
+
this.tokens = signal([]);
|
|
2375
|
+
this.parser = computed(() => {
|
|
2376
|
+
if (!this._store.mark.enabled()) return;
|
|
2377
|
+
const markups = this._store.props.options().map((opt) => opt.markup);
|
|
2378
|
+
if (!markups.some(Boolean)) return;
|
|
2379
|
+
return new Parser(markups, this._store.slots.isBlock() ? { skipEmptyText: true } : void 0);
|
|
2380
|
+
});
|
|
2381
|
+
this.reparse = event();
|
|
2227
2382
|
}
|
|
2228
2383
|
enable() {
|
|
2229
2384
|
if (this.#scope) return;
|
|
@@ -2238,163 +2393,26 @@ var ParseFeature = class {
|
|
|
2238
2393
|
this.#scope = void 0;
|
|
2239
2394
|
}
|
|
2240
2395
|
sync() {
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
store.state.previousValue(inputValue);
|
|
2396
|
+
const inputValue = this._store.props.value() ?? this._store.props.defaultValue() ?? "";
|
|
2397
|
+
this.tokens(parseWithParser(this._store, inputValue));
|
|
2398
|
+
this._store.value.last(inputValue);
|
|
2245
2399
|
}
|
|
2246
2400
|
#subscribeParse() {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
store.state.previousValue(text);
|
|
2401
|
+
watch(this.reparse, () => {
|
|
2402
|
+
if (this._store.caret.recovery()) {
|
|
2403
|
+
const text = toString(this.tokens());
|
|
2404
|
+
this.tokens(parseWithParser(this._store, text));
|
|
2405
|
+
this._store.value.last(text);
|
|
2253
2406
|
return;
|
|
2254
2407
|
}
|
|
2255
|
-
|
|
2408
|
+
this.tokens(this._store.nodes.focus.target ? getTokensByUI(this._store) : computeTokensFromValue(this._store));
|
|
2256
2409
|
});
|
|
2257
2410
|
}
|
|
2258
2411
|
#subscribeReactiveParse() {
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
if (!store.state.recovery()) store.event.parse();
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
|
-
};
|
|
2265
|
-
//#endregion
|
|
2266
|
-
//#region ../../core/src/features/navigation/navigation.ts
|
|
2267
|
-
function shiftFocusPrev(store, event) {
|
|
2268
|
-
const { focus } = store.nodes;
|
|
2269
|
-
if (focus.isMark && !focus.isEditable || focus.isCaretAtBeginning) {
|
|
2270
|
-
let prev = focus.prev;
|
|
2271
|
-
while (prev.target && prev.isMark && !prev.isEditable) prev = prev.prev;
|
|
2272
|
-
if (!prev.target) return false;
|
|
2273
|
-
event.preventDefault();
|
|
2274
|
-
prev.target.focus();
|
|
2275
|
-
Caret.setCaretToEnd(prev.target);
|
|
2276
|
-
return true;
|
|
2277
|
-
}
|
|
2278
|
-
return false;
|
|
2279
|
-
}
|
|
2280
|
-
function shiftFocusNext(store, event) {
|
|
2281
|
-
const { focus } = store.nodes;
|
|
2282
|
-
if (focus.isMark && !focus.isEditable || focus.isCaretAtEnd) {
|
|
2283
|
-
let next = focus.next;
|
|
2284
|
-
while (next.target && next.isMark && !next.isEditable) next = next.next;
|
|
2285
|
-
if (!next.target) return false;
|
|
2286
|
-
event.preventDefault();
|
|
2287
|
-
next.target.focus();
|
|
2288
|
-
Caret.trySetIndex(next.target, 0);
|
|
2289
|
-
return true;
|
|
2290
|
-
}
|
|
2291
|
-
return false;
|
|
2292
|
-
}
|
|
2293
|
-
//#endregion
|
|
2294
|
-
//#region ../../core/src/features/selection/selectionHelpers.ts
|
|
2295
|
-
function isFullSelection(store) {
|
|
2296
|
-
const sel = window.getSelection();
|
|
2297
|
-
const container = store.refs.container;
|
|
2298
|
-
if (!sel?.rangeCount || !container?.firstChild || !container.lastChild) return false;
|
|
2299
|
-
try {
|
|
2300
|
-
const range = sel.getRangeAt(0);
|
|
2301
|
-
return container.contains(range.startContainer) && container.contains(range.endContainer) && range.toString().length > 0;
|
|
2302
|
-
} catch {
|
|
2303
|
-
return false;
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
function selectAllText(store, event) {
|
|
2307
|
-
if ((event.ctrlKey || event.metaKey) && event.code === "KeyA") {
|
|
2308
|
-
if (store.computed.isBlock()) return;
|
|
2309
|
-
event.preventDefault();
|
|
2310
|
-
const selection = window.getSelection();
|
|
2311
|
-
const anchorNode = store.refs.container?.firstChild;
|
|
2312
|
-
const focusNode = store.refs.container?.lastChild;
|
|
2313
|
-
if (!selection || !anchorNode || !focusNode) return;
|
|
2314
|
-
selection.setBaseAndExtent(anchorNode, 0, focusNode, 1);
|
|
2315
|
-
store.state.selecting("all");
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
//#endregion
|
|
2319
|
-
//#region ../../core/src/features/selection/TextSelectionFeature.ts
|
|
2320
|
-
var TextSelectionFeature = class {
|
|
2321
|
-
#scope;
|
|
2322
|
-
#pressedNode = null;
|
|
2323
|
-
#isPressed = false;
|
|
2324
|
-
constructor(store) {
|
|
2325
|
-
this.store = store;
|
|
2326
|
-
}
|
|
2327
|
-
enable() {
|
|
2328
|
-
if (this.#scope) return;
|
|
2329
|
-
this.#scope = effectScope(() => {
|
|
2330
|
-
listen(document, "mousedown", (e) => {
|
|
2331
|
-
this.#pressedNode = nodeTarget(e);
|
|
2332
|
-
this.#isPressed = true;
|
|
2333
|
-
});
|
|
2334
|
-
listen(document, "mousemove", (e) => {
|
|
2335
|
-
const container = this.store.refs.container;
|
|
2336
|
-
if (!container) return;
|
|
2337
|
-
const isPressed = this.#isPressed;
|
|
2338
|
-
const isNotInnerSome = !container.contains(this.#pressedNode) || this.#pressedNode !== e.target;
|
|
2339
|
-
const isInside = window.getSelection()?.containsNode(container, true);
|
|
2340
|
-
if (isPressed && isNotInnerSome && isInside) {
|
|
2341
|
-
if (this.store.state.selecting() !== "drag") this.store.state.selecting("drag");
|
|
2342
|
-
}
|
|
2343
|
-
});
|
|
2344
|
-
listen(document, "mouseup", () => {
|
|
2345
|
-
this.#isPressed = false;
|
|
2346
|
-
this.#pressedNode = null;
|
|
2347
|
-
if (this.store.state.selecting() === "drag") {
|
|
2348
|
-
const sel = window.getSelection();
|
|
2349
|
-
if (!sel || sel.isCollapsed) this.store.state.selecting(void 0);
|
|
2350
|
-
}
|
|
2351
|
-
});
|
|
2352
|
-
listen(document, "selectionchange", () => {
|
|
2353
|
-
if (this.store.state.selecting() !== "drag") return;
|
|
2354
|
-
const sel = window.getSelection();
|
|
2355
|
-
if (!sel || sel.isCollapsed) this.store.state.selecting(void 0);
|
|
2356
|
-
});
|
|
2357
|
-
alienEffect(() => {
|
|
2358
|
-
if (this.store.state.selecting() !== "drag") return;
|
|
2359
|
-
const container = this.store.refs.container;
|
|
2360
|
-
if (!container) return;
|
|
2361
|
-
container.querySelectorAll("[contenteditable=\"true\"]").forEach((el) => el.contentEditable = "false");
|
|
2362
|
-
});
|
|
2363
|
-
});
|
|
2364
|
-
}
|
|
2365
|
-
disable() {
|
|
2366
|
-
if (this.store.state.selecting() === "drag") this.store.state.selecting(void 0);
|
|
2367
|
-
this.#scope?.();
|
|
2368
|
-
this.#scope = void 0;
|
|
2369
|
-
this.#pressedNode = null;
|
|
2370
|
-
this.#isPressed = false;
|
|
2371
|
-
}
|
|
2372
|
-
};
|
|
2373
|
-
//#endregion
|
|
2374
|
-
//#region ../../core/src/features/arrownav/ArrowNavFeature.ts
|
|
2375
|
-
var ArrowNavFeature = class {
|
|
2376
|
-
#scope;
|
|
2377
|
-
constructor(store) {
|
|
2378
|
-
this.store = store;
|
|
2379
|
-
}
|
|
2380
|
-
enable() {
|
|
2381
|
-
if (this.#scope) return;
|
|
2382
|
-
const container = this.store.refs.container;
|
|
2383
|
-
if (!container) return;
|
|
2384
|
-
this.#scope = effectScope(() => {
|
|
2385
|
-
listen(container, "keydown", (e) => {
|
|
2386
|
-
if (this.store.computed.isBlock()) return;
|
|
2387
|
-
if (!this.store.nodes.focus.target) return;
|
|
2388
|
-
if (e.key === KEYBOARD.LEFT) shiftFocusPrev(this.store, e);
|
|
2389
|
-
else if (e.key === KEYBOARD.RIGHT) shiftFocusNext(this.store, e);
|
|
2390
|
-
selectAllText(this.store, e);
|
|
2391
|
-
});
|
|
2412
|
+
watch(computed(() => [this._store.props.value(), this.parser()]), () => {
|
|
2413
|
+
if (!this._store.caret.recovery()) this.reparse();
|
|
2392
2414
|
});
|
|
2393
2415
|
}
|
|
2394
|
-
disable() {
|
|
2395
|
-
this.#scope?.();
|
|
2396
|
-
this.#scope = void 0;
|
|
2397
|
-
}
|
|
2398
2416
|
};
|
|
2399
2417
|
//#endregion
|
|
2400
2418
|
//#region ../../core/src/features/clipboard/pasteMarkup.ts
|
|
@@ -2467,13 +2485,13 @@ function getBoundaryOffset(range, child, isStart) {
|
|
|
2467
2485
|
* Returns null if selection is collapsed, empty, or outside the container.
|
|
2468
2486
|
*/
|
|
2469
2487
|
function selectionToTokens(store) {
|
|
2470
|
-
const container = store.
|
|
2488
|
+
const container = store.slots.container();
|
|
2471
2489
|
if (!container) return null;
|
|
2472
2490
|
const sel = window.getSelection();
|
|
2473
2491
|
if (!sel || sel.isCollapsed || !sel.rangeCount) return null;
|
|
2474
2492
|
const range = sel.getRangeAt(0);
|
|
2475
2493
|
if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) return null;
|
|
2476
|
-
const tokens = store.
|
|
2494
|
+
const tokens = store.parsing.tokens();
|
|
2477
2495
|
let startIndex = findContainerChildIndex(range.startContainer, container);
|
|
2478
2496
|
let endIndex = findContainerChildIndex(range.endContainer, container);
|
|
2479
2497
|
if (startIndex === -1 || endIndex === -1) return null;
|
|
@@ -2487,7 +2505,7 @@ function selectionToTokens(store) {
|
|
|
2487
2505
|
};
|
|
2488
2506
|
}
|
|
2489
2507
|
//#endregion
|
|
2490
|
-
//#region ../../core/src/features/clipboard/
|
|
2508
|
+
//#region ../../core/src/features/clipboard/ClipboardFeature.ts
|
|
2491
2509
|
/**
|
|
2492
2510
|
* Trim boundary text tokens to the selected portion.
|
|
2493
2511
|
* Mark tokens are always kept in full — partial mark selection expands to full mark.
|
|
@@ -2518,14 +2536,14 @@ function trimBoundaryTokens({ tokens, startOffset, endOffset }) {
|
|
|
2518
2536
|
return token;
|
|
2519
2537
|
});
|
|
2520
2538
|
}
|
|
2521
|
-
var
|
|
2539
|
+
var ClipboardFeature = class {
|
|
2522
2540
|
#scope;
|
|
2523
2541
|
constructor(store) {
|
|
2524
2542
|
this.store = store;
|
|
2525
2543
|
}
|
|
2526
2544
|
enable() {
|
|
2527
2545
|
if (this.#scope) return;
|
|
2528
|
-
const container = this.store.
|
|
2546
|
+
const container = this.store.slots.container();
|
|
2529
2547
|
if (!container) return;
|
|
2530
2548
|
this.#scope = effectScope(() => {
|
|
2531
2549
|
listen(container, "copy", (e) => {
|
|
@@ -2539,15 +2557,15 @@ var CopyFeature = class {
|
|
|
2539
2557
|
const last = result.tokens[result.tokens.length - 1];
|
|
2540
2558
|
const rawStart = first.type === "text" ? first.position.start + result.startOffset : first.position.start;
|
|
2541
2559
|
const rawEnd = last.type === "text" ? last.position.start + result.endOffset : last.position.end;
|
|
2542
|
-
const value = this.store.
|
|
2560
|
+
const value = this.store.value.current();
|
|
2543
2561
|
if (rawStart === rawEnd) return;
|
|
2544
2562
|
const newValue = value.slice(0, rawStart) + value.slice(rawEnd);
|
|
2545
|
-
this.store.
|
|
2546
|
-
const newTokens = this.store.
|
|
2563
|
+
this.store.value.next(newValue);
|
|
2564
|
+
const newTokens = this.store.parsing.tokens();
|
|
2547
2565
|
let targetIdx = newTokens.findIndex((t) => t.type === "text" && rawStart >= t.position.start && rawStart <= t.position.end);
|
|
2548
2566
|
if (targetIdx === -1) targetIdx = newTokens.length - 1;
|
|
2549
2567
|
const caretWithinToken = rawStart - newTokens[targetIdx].position.start;
|
|
2550
|
-
this.store.
|
|
2568
|
+
this.store.caret.recovery({
|
|
2551
2569
|
anchor: this.store.nodes.focus,
|
|
2552
2570
|
caret: caretWithinToken,
|
|
2553
2571
|
isNext: true,
|
|
@@ -2561,7 +2579,7 @@ var CopyFeature = class {
|
|
|
2561
2579
|
this.#scope = void 0;
|
|
2562
2580
|
}
|
|
2563
2581
|
#handleCopy(e) {
|
|
2564
|
-
const container = this.store.
|
|
2582
|
+
const container = this.store.slots.container();
|
|
2565
2583
|
if (!container) return false;
|
|
2566
2584
|
const sel = window.getSelection();
|
|
2567
2585
|
if (!sel || sel.isCollapsed || !sel.rangeCount) return false;
|
|
@@ -2583,6 +2601,172 @@ var CopyFeature = class {
|
|
|
2583
2601
|
}
|
|
2584
2602
|
};
|
|
2585
2603
|
//#endregion
|
|
2604
|
+
//#region ../../core/src/features/dom/isTextTokenSpan.ts
|
|
2605
|
+
function isTextTokenSpan(el) {
|
|
2606
|
+
return el.tagName === "SPAN" && (el.attributes.length === 0 || el.attributes.length === 1 && el.hasAttribute("contenteditable"));
|
|
2607
|
+
}
|
|
2608
|
+
//#endregion
|
|
2609
|
+
//#region ../../core/src/features/dom/DomFeature.ts
|
|
2610
|
+
var DomFeature = class {
|
|
2611
|
+
#scope;
|
|
2612
|
+
constructor(_store) {
|
|
2613
|
+
this._store = _store;
|
|
2614
|
+
}
|
|
2615
|
+
enable() {
|
|
2616
|
+
if (this.#scope) return;
|
|
2617
|
+
this.#scope = effectScope(() => {
|
|
2618
|
+
alienEffect(() => {
|
|
2619
|
+
this._store.props.readOnly();
|
|
2620
|
+
this.reconcile();
|
|
2621
|
+
});
|
|
2622
|
+
alienEffect(() => {
|
|
2623
|
+
if (this._store.caret.selecting() === void 0) this.reconcile();
|
|
2624
|
+
});
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
disable() {
|
|
2628
|
+
this.#scope?.();
|
|
2629
|
+
this.#scope = void 0;
|
|
2630
|
+
}
|
|
2631
|
+
reconcile() {
|
|
2632
|
+
const container = this._store.slots.container();
|
|
2633
|
+
if (!container) return;
|
|
2634
|
+
const readOnly = this._store.props.readOnly();
|
|
2635
|
+
const value = readOnly ? "false" : "true";
|
|
2636
|
+
const children = container.children;
|
|
2637
|
+
const isBlock = this._store.slots.isBlock();
|
|
2638
|
+
if (isBlock) {
|
|
2639
|
+
const tokens = this._store.parsing.tokens();
|
|
2640
|
+
for (let i = 0; i < tokens.length && i < children.length; i++) {
|
|
2641
|
+
const el = childAt(container, i);
|
|
2642
|
+
if (!el) continue;
|
|
2643
|
+
if (tokens[i].type === "mark") {
|
|
2644
|
+
if (!readOnly) el.tabIndex = 0;
|
|
2645
|
+
} else el.contentEditable = value;
|
|
2646
|
+
}
|
|
2647
|
+
} else for (let i = 0; i < children.length; i += 2) {
|
|
2648
|
+
const el = childAt(container, i);
|
|
2649
|
+
if (el) el.contentEditable = value;
|
|
2650
|
+
}
|
|
2651
|
+
const tokens = this._store.parsing.tokens();
|
|
2652
|
+
if (isBlock) this.#reconcileDragTextContent(tokens, container, readOnly);
|
|
2653
|
+
else this.#reconcileTextContent(tokens, container);
|
|
2654
|
+
}
|
|
2655
|
+
#reconcileTextContent(tokens, parent) {
|
|
2656
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2657
|
+
const token = tokens[i];
|
|
2658
|
+
const el = childAt(parent, i);
|
|
2659
|
+
if (!el) continue;
|
|
2660
|
+
if (token.type === "text") {
|
|
2661
|
+
if (el.textContent !== token.content) el.textContent = token.content;
|
|
2662
|
+
} else if (token.children.length > 0) this.#reconcileMarkChildren(token.children, el);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
#reconcileMarkChildren(tokens, parent, editable) {
|
|
2666
|
+
const children = parent.children;
|
|
2667
|
+
let childIdx = 0;
|
|
2668
|
+
for (const token of tokens) if (token.type === "text") {
|
|
2669
|
+
while (childIdx < children.length) {
|
|
2670
|
+
const el = childAt(parent, childIdx);
|
|
2671
|
+
if (el && isTextTokenSpan(el)) break;
|
|
2672
|
+
childIdx++;
|
|
2673
|
+
}
|
|
2674
|
+
const el = childAt(parent, childIdx);
|
|
2675
|
+
if (el) {
|
|
2676
|
+
if (el.textContent !== token.content) el.textContent = token.content;
|
|
2677
|
+
if (editable !== void 0) el.contentEditable = editable;
|
|
2678
|
+
childIdx++;
|
|
2679
|
+
}
|
|
2680
|
+
} else if (token.children.length > 0) {
|
|
2681
|
+
while (childIdx < children.length) {
|
|
2682
|
+
const el = childAt(parent, childIdx);
|
|
2683
|
+
if (el && !isTextTokenSpan(el)) break;
|
|
2684
|
+
childIdx++;
|
|
2685
|
+
}
|
|
2686
|
+
const el = childAt(parent, childIdx);
|
|
2687
|
+
if (el) {
|
|
2688
|
+
this.#reconcileMarkChildren(token.children, el, editable);
|
|
2689
|
+
childIdx++;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
#reconcileDragTextContent(tokens, container, readOnly) {
|
|
2694
|
+
const editable = readOnly ? "false" : "true";
|
|
2695
|
+
for (let ri = 0; ri < tokens.length; ri++) {
|
|
2696
|
+
const token = tokens[ri];
|
|
2697
|
+
const blockEl = childAt(container, ri);
|
|
2698
|
+
if (!blockEl) continue;
|
|
2699
|
+
if (token.type === "mark") {
|
|
2700
|
+
if (token.children.length > 0) {
|
|
2701
|
+
const markEl = blockEl.hasAttribute("data-testid") ? childAt(blockEl, readOnly ? 0 : 1) : blockEl;
|
|
2702
|
+
if (markEl) this.#reconcileMarkChildren(token.children, markEl, editable);
|
|
2703
|
+
}
|
|
2704
|
+
continue;
|
|
2705
|
+
}
|
|
2706
|
+
const el = childAt(blockEl, readOnly ? 0 : 1);
|
|
2707
|
+
if (!el) continue;
|
|
2708
|
+
if (el.textContent !== token.content) el.textContent = token.content;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
//#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
|
+
//#region ../../core/src/features/editing/createRowContent.ts
|
|
2719
|
+
function createRowContent(options) {
|
|
2720
|
+
const firstOption = options[0];
|
|
2721
|
+
if (!firstOption.markup) return "\n";
|
|
2722
|
+
return annotate(firstOption.markup, {
|
|
2723
|
+
value: "",
|
|
2724
|
+
slot: "",
|
|
2725
|
+
meta: ""
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
//#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
|
|
2586
2770
|
//#region ../../core/src/features/drag/operations.ts
|
|
2587
2771
|
function gapText(value, a, b) {
|
|
2588
2772
|
return value.substring(a.position.end, b.position.start);
|
|
@@ -2670,175 +2854,141 @@ function reorderDragRows(value, rows, sourceIndex, targetIndex) {
|
|
|
2670
2854
|
return parts.join("");
|
|
2671
2855
|
}
|
|
2672
2856
|
//#endregion
|
|
2673
|
-
//#region ../../core/src/features/
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
return annotate(firstOption.markup, {
|
|
2683
|
-
value: "",
|
|
2684
|
-
slot: "",
|
|
2685
|
-
meta: ""
|
|
2686
|
-
});
|
|
2687
|
-
}
|
|
2688
|
-
//#endregion
|
|
2689
|
-
//#region ../../core/src/features/editing/utils/deleteMark.ts
|
|
2690
|
-
function deleteMark(place, store) {
|
|
2691
|
-
const placeIndex = {
|
|
2692
|
-
prev: 2,
|
|
2693
|
-
self: 1,
|
|
2694
|
-
next: 0
|
|
2695
|
-
}[place];
|
|
2696
|
-
const { focus } = store.nodes;
|
|
2697
|
-
const targetIndex = Math.max(0, focus.index - placeIndex);
|
|
2698
|
-
const tokens = store.state.tokens();
|
|
2699
|
-
const spliced = tokens.splice(focus.index - placeIndex, 3);
|
|
2700
|
-
const span1 = spliced.at(0);
|
|
2701
|
-
const span2 = spliced.at(2);
|
|
2702
|
-
const content1 = span1?.content ?? "";
|
|
2703
|
-
const content2 = span2?.content ?? "";
|
|
2704
|
-
store.state.tokens(tokens.toSpliced(focus.index - placeIndex, 0, {
|
|
2705
|
-
type: "text",
|
|
2706
|
-
content: content1 + content2,
|
|
2707
|
-
position: {
|
|
2708
|
-
start: span1?.position.start ?? 0,
|
|
2709
|
-
end: span2?.position.end ?? (content1 + content2).length
|
|
2710
|
-
}
|
|
2711
|
-
}));
|
|
2712
|
-
let caretAnchor = focus;
|
|
2713
|
-
for (let i = 0; i < placeIndex; i++) caretAnchor = caretAnchor.prev;
|
|
2714
|
-
const caret = caretAnchor.length;
|
|
2715
|
-
store.state.recovery({
|
|
2716
|
-
anchor: caretAnchor.prev,
|
|
2717
|
-
caret
|
|
2718
|
-
});
|
|
2719
|
-
store.event.change();
|
|
2720
|
-
queueMicrotask(() => {
|
|
2721
|
-
const target = childAt(store.refs.container, targetIndex);
|
|
2722
|
-
if (!target) return;
|
|
2723
|
-
store.nodes.focus.target = target;
|
|
2724
|
-
target.focus();
|
|
2725
|
-
store.nodes.focus.caret = caret;
|
|
2726
|
-
});
|
|
2727
|
-
}
|
|
2728
|
-
//#endregion
|
|
2729
|
-
//#region ../../core/src/features/editable/isTextTokenSpan.ts
|
|
2730
|
-
function isTextTokenSpan(el) {
|
|
2731
|
-
return el.tagName === "SPAN" && (el.attributes.length === 0 || el.attributes.length === 1 && el.hasAttribute("contenteditable"));
|
|
2732
|
-
}
|
|
2857
|
+
//#region ../../core/src/features/drag/tokens.ts
|
|
2858
|
+
const EMPTY_TEXT_TOKEN = {
|
|
2859
|
+
type: "text",
|
|
2860
|
+
content: "",
|
|
2861
|
+
position: {
|
|
2862
|
+
start: 0,
|
|
2863
|
+
end: 0
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2733
2866
|
//#endregion
|
|
2734
|
-
//#region ../../core/src/features/
|
|
2735
|
-
var
|
|
2736
|
-
#scope;
|
|
2867
|
+
//#region ../../core/src/features/drag/DragFeature.ts
|
|
2868
|
+
var DragFeature = class {
|
|
2737
2869
|
constructor(store) {
|
|
2738
2870
|
this.store = store;
|
|
2871
|
+
this.action = event();
|
|
2739
2872
|
}
|
|
2873
|
+
#unsub;
|
|
2740
2874
|
enable() {
|
|
2741
|
-
if (this.#
|
|
2742
|
-
this.#
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2875
|
+
if (this.#unsub) return;
|
|
2876
|
+
this.#unsub = watch(this.action, (action) => {
|
|
2877
|
+
switch (action.type) {
|
|
2878
|
+
case "reorder":
|
|
2879
|
+
this.#reorder(action.source, action.target);
|
|
2880
|
+
break;
|
|
2881
|
+
case "add":
|
|
2882
|
+
this.#add(action.afterIndex);
|
|
2883
|
+
break;
|
|
2884
|
+
case "delete":
|
|
2885
|
+
this.#delete(action.index);
|
|
2886
|
+
break;
|
|
2887
|
+
case "duplicate":
|
|
2888
|
+
this.#duplicate(action.index);
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2753
2891
|
});
|
|
2754
2892
|
}
|
|
2755
2893
|
disable() {
|
|
2756
|
-
this.#
|
|
2757
|
-
this.#
|
|
2894
|
+
this.#unsub?.();
|
|
2895
|
+
this.#unsub = void 0;
|
|
2758
2896
|
}
|
|
2759
|
-
|
|
2760
|
-
const
|
|
2761
|
-
if (!
|
|
2762
|
-
const
|
|
2763
|
-
|
|
2764
|
-
const children = container.children;
|
|
2765
|
-
const isBlock = this.store.computed.isBlock();
|
|
2766
|
-
if (isBlock) {
|
|
2767
|
-
const tokens = this.store.state.tokens();
|
|
2768
|
-
for (let i = 0; i < tokens.length && i < children.length; i++) {
|
|
2769
|
-
const el = childAt(container, i);
|
|
2770
|
-
if (!el) continue;
|
|
2771
|
-
if (tokens[i].type === "mark") {
|
|
2772
|
-
if (!readOnly) el.tabIndex = 0;
|
|
2773
|
-
} else el.contentEditable = value;
|
|
2774
|
-
}
|
|
2775
|
-
} else for (let i = 0; i < children.length; i += 2) {
|
|
2776
|
-
const el = childAt(container, i);
|
|
2777
|
-
if (el) el.contentEditable = value;
|
|
2778
|
-
}
|
|
2779
|
-
const tokens = this.store.state.tokens();
|
|
2780
|
-
if (isBlock) this.#syncDragTextContent(tokens, container, readOnly);
|
|
2781
|
-
else this.#syncTextContent(tokens, container);
|
|
2897
|
+
#reorder(sourceIndex, targetIndex) {
|
|
2898
|
+
const value = this.store.props.value();
|
|
2899
|
+
if (value == null || !this.store.props.onChange()) return;
|
|
2900
|
+
const newValue = reorderDragRows(value, this.store.parsing.tokens(), sourceIndex, targetIndex);
|
|
2901
|
+
if (newValue !== value) this.store.value.next(newValue);
|
|
2782
2902
|
}
|
|
2783
|
-
#
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2903
|
+
#add(afterIndex) {
|
|
2904
|
+
const value = this.store.props.value();
|
|
2905
|
+
if (value == null || !this.store.props.onChange()) return;
|
|
2906
|
+
const rawRows = this.store.parsing.tokens();
|
|
2907
|
+
const rows = rawRows.length > 0 ? rawRows : [EMPTY_TEXT_TOKEN];
|
|
2908
|
+
const newRowContent = createRowContent(this.store.props.options());
|
|
2909
|
+
this.store.value.next(addDragRow(value, rows, afterIndex, newRowContent));
|
|
2910
|
+
queueMicrotask(() => {
|
|
2911
|
+
const container = this.store.slots.container();
|
|
2912
|
+
if (!container) return;
|
|
2913
|
+
childAt(container, afterIndex + 1)?.focus();
|
|
2914
|
+
});
|
|
2792
2915
|
}
|
|
2793
|
-
#
|
|
2794
|
-
const
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
const el = childAt(parent, childIdx);
|
|
2799
|
-
if (el && isTextTokenSpan(el)) break;
|
|
2800
|
-
childIdx++;
|
|
2801
|
-
}
|
|
2802
|
-
const el = childAt(parent, childIdx);
|
|
2803
|
-
if (el) {
|
|
2804
|
-
if (el.textContent !== token.content) el.textContent = token.content;
|
|
2805
|
-
if (editable !== void 0) el.contentEditable = editable;
|
|
2806
|
-
childIdx++;
|
|
2807
|
-
}
|
|
2808
|
-
} else if (token.children.length > 0) {
|
|
2809
|
-
while (childIdx < children.length) {
|
|
2810
|
-
const el = childAt(parent, childIdx);
|
|
2811
|
-
if (el && !isTextTokenSpan(el)) break;
|
|
2812
|
-
childIdx++;
|
|
2813
|
-
}
|
|
2814
|
-
const el = childAt(parent, childIdx);
|
|
2815
|
-
if (el) {
|
|
2816
|
-
this.#syncMarkChildren(token.children, el, editable);
|
|
2817
|
-
childIdx++;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2916
|
+
#delete(index) {
|
|
2917
|
+
const value = this.store.props.value();
|
|
2918
|
+
if (value == null || !this.store.props.onChange()) return;
|
|
2919
|
+
const rows = this.store.parsing.tokens();
|
|
2920
|
+
this.store.value.next(deleteDragRow(value, rows, index));
|
|
2820
2921
|
}
|
|
2821
|
-
#
|
|
2822
|
-
const
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
if (!blockEl) continue;
|
|
2827
|
-
if (token.type === "mark") {
|
|
2828
|
-
if (token.children.length > 0) {
|
|
2829
|
-
const markEl = blockEl.hasAttribute("data-testid") ? childAt(blockEl, readOnly ? 0 : 1) : blockEl;
|
|
2830
|
-
if (markEl) this.#syncMarkChildren(token.children, markEl, editable);
|
|
2831
|
-
}
|
|
2832
|
-
continue;
|
|
2833
|
-
}
|
|
2834
|
-
const el = childAt(blockEl, readOnly ? 0 : 1);
|
|
2835
|
-
if (!el) continue;
|
|
2836
|
-
if (el.textContent !== token.content) el.textContent = token.content;
|
|
2837
|
-
}
|
|
2922
|
+
#duplicate(index) {
|
|
2923
|
+
const value = this.store.props.value();
|
|
2924
|
+
if (value == null || !this.store.props.onChange()) return;
|
|
2925
|
+
const rows = this.store.parsing.tokens();
|
|
2926
|
+
this.store.value.next(duplicateDragRow(value, rows, index));
|
|
2838
2927
|
}
|
|
2839
2928
|
};
|
|
2840
2929
|
//#endregion
|
|
2841
|
-
//#region ../../core/src/
|
|
2930
|
+
//#region ../../core/src/shared/utils/dragUtils.ts
|
|
2931
|
+
function getDragDropPosition(clientY, rect) {
|
|
2932
|
+
return clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
2933
|
+
}
|
|
2934
|
+
function parseDragSourceIndex(dataTransfer) {
|
|
2935
|
+
const index = parseInt(dataTransfer.getData("text/plain"), 10);
|
|
2936
|
+
return isNaN(index) ? null : index;
|
|
2937
|
+
}
|
|
2938
|
+
function getDragTargetIndex(blockIndex, position) {
|
|
2939
|
+
return position === "before" ? blockIndex : blockIndex + 1;
|
|
2940
|
+
}
|
|
2941
|
+
//#endregion
|
|
2942
|
+
//#region ../../core/src/features/drag/config.ts
|
|
2943
|
+
function getAlwaysShowHandle(draggable) {
|
|
2944
|
+
return typeof draggable === "object" && !!draggable.alwaysShowHandle;
|
|
2945
|
+
}
|
|
2946
|
+
//#endregion
|
|
2947
|
+
//#region ../../core/src/features/navigation/navigation.ts
|
|
2948
|
+
function shiftFocusPrev(store, event) {
|
|
2949
|
+
const { focus } = store.nodes;
|
|
2950
|
+
if (focus.isMark && !focus.isEditable || focus.isCaretAtBeginning) {
|
|
2951
|
+
let prev = focus.prev;
|
|
2952
|
+
while (prev.target && prev.isMark && !prev.isEditable) prev = prev.prev;
|
|
2953
|
+
if (!prev.target) return false;
|
|
2954
|
+
event.preventDefault();
|
|
2955
|
+
prev.target.focus();
|
|
2956
|
+
Caret.setCaretToEnd(prev.target);
|
|
2957
|
+
return true;
|
|
2958
|
+
}
|
|
2959
|
+
return false;
|
|
2960
|
+
}
|
|
2961
|
+
function shiftFocusNext(store, event) {
|
|
2962
|
+
const { focus } = store.nodes;
|
|
2963
|
+
if (focus.isMark && !focus.isEditable || focus.isCaretAtEnd) {
|
|
2964
|
+
let next = focus.next;
|
|
2965
|
+
while (next.target && next.isMark && !next.isEditable) next = next.next;
|
|
2966
|
+
if (!next.target) return false;
|
|
2967
|
+
event.preventDefault();
|
|
2968
|
+
next.target.focus();
|
|
2969
|
+
Caret.trySetIndex(next.target, 0);
|
|
2970
|
+
return true;
|
|
2971
|
+
}
|
|
2972
|
+
return false;
|
|
2973
|
+
}
|
|
2974
|
+
//#endregion
|
|
2975
|
+
//#region ../../core/src/features/keyboard/arrowNav.ts
|
|
2976
|
+
function enableArrowNav(store) {
|
|
2977
|
+
const container = store.slots.container();
|
|
2978
|
+
if (!container) return () => {};
|
|
2979
|
+
const scope = effectScope(() => {
|
|
2980
|
+
listen(container, "keydown", (e) => {
|
|
2981
|
+
if (store.slots.isBlock()) return;
|
|
2982
|
+
if (!store.nodes.focus.target) return;
|
|
2983
|
+
if (e.key === KEYBOARD.LEFT) shiftFocusPrev(store, e);
|
|
2984
|
+
else if (e.key === KEYBOARD.RIGHT) shiftFocusNext(store, e);
|
|
2985
|
+
selectAllText(store, e);
|
|
2986
|
+
});
|
|
2987
|
+
});
|
|
2988
|
+
return () => scope();
|
|
2989
|
+
}
|
|
2990
|
+
//#endregion
|
|
2991
|
+
//#region ../../core/src/features/keyboard/rawPosition.ts
|
|
2842
2992
|
function getCaretRawPosInBlock(blockDiv, token) {
|
|
2843
2993
|
const selection = window.getSelection();
|
|
2844
2994
|
if (!selection?.rangeCount) return token.position.end;
|
|
@@ -2961,196 +3111,172 @@ function setCaretInMarkAtRawPos(markElement, markToken, rawAbsolutePos) {
|
|
|
2961
3111
|
tokenIdx++;
|
|
2962
3112
|
}
|
|
2963
3113
|
}
|
|
2964
|
-
return false;
|
|
2965
|
-
}
|
|
2966
|
-
//#endregion
|
|
2967
|
-
//#region ../../core/src/features/
|
|
2968
|
-
function isTextLikeRow(token) {
|
|
2969
|
-
if (token.type === "text") return true;
|
|
2970
|
-
return token.descriptor.hasSlot && token.descriptor.segments.length === 1;
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
const
|
|
3004
|
-
const
|
|
3005
|
-
if (
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
//#endregion
|
|
3117
|
+
//#region ../../core/src/features/keyboard/blockEdit.ts
|
|
3118
|
+
function isTextLikeRow(token) {
|
|
3119
|
+
if (token.type === "text") return true;
|
|
3120
|
+
return token.descriptor.hasSlot && token.descriptor.segments.length === 1;
|
|
3121
|
+
}
|
|
3122
|
+
function enableBlockEdit(store) {
|
|
3123
|
+
const container = store.slots.container();
|
|
3124
|
+
if (!container) return () => {};
|
|
3125
|
+
const scope = effectScope(() => {
|
|
3126
|
+
listen(container, "keydown", (e) => {
|
|
3127
|
+
if (!store.slots.isBlock()) return;
|
|
3128
|
+
if (e.key === KEYBOARD.LEFT || e.key === KEYBOARD.RIGHT) handleBlockArrowLeftRight(store, e, e.key === KEYBOARD.LEFT ? "left" : "right");
|
|
3129
|
+
else if (e.key === KEYBOARD.UP || e.key === KEYBOARD.DOWN) handleArrowUpDown(store, e);
|
|
3130
|
+
handleDelete$1(store, e);
|
|
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);
|
|
3138
|
+
});
|
|
3139
|
+
return () => scope();
|
|
3140
|
+
}
|
|
3141
|
+
function handleDelete$1(store, event) {
|
|
3142
|
+
const container = store.slots.container();
|
|
3143
|
+
if (!container) return;
|
|
3144
|
+
const blockDivs = htmlChildren(container);
|
|
3145
|
+
const blockIndex = blockDivs.findIndex((div) => div === document.activeElement || div.contains(document.activeElement));
|
|
3146
|
+
if (blockIndex === -1) return;
|
|
3147
|
+
const rows = store.parsing.tokens();
|
|
3148
|
+
if (blockIndex >= rows.length) return;
|
|
3149
|
+
const token = rows[blockIndex];
|
|
3150
|
+
const value = store.value.current();
|
|
3151
|
+
if (!store.props.onChange()) return;
|
|
3152
|
+
if (event.key === KEYBOARD.BACKSPACE) {
|
|
3153
|
+
const blockDiv = blockDivs[blockIndex];
|
|
3154
|
+
const caretAtStart = Caret.getCaretIndex(blockDiv) === 0;
|
|
3155
|
+
if (("content" in token ? token.content : "") === "") {
|
|
3156
|
+
event.preventDefault();
|
|
3157
|
+
const newValue = rows.length <= 1 ? "" : (() => {
|
|
3158
|
+
if (blockIndex >= rows.length - 1) return value.slice(0, rows[blockIndex - 1].position.end);
|
|
3159
|
+
return value.slice(0, rows[blockIndex].position.start) + value.slice(rows[blockIndex + 1].position.start);
|
|
3160
|
+
})();
|
|
3161
|
+
store.value.next(newValue);
|
|
3162
|
+
queueMicrotask(() => {
|
|
3163
|
+
const target = childAt(container, Math.max(0, blockIndex - 1));
|
|
3164
|
+
if (target) {
|
|
3165
|
+
target.focus();
|
|
3166
|
+
Caret.setCaretToEnd(target);
|
|
3167
|
+
}
|
|
3168
|
+
});
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
if (caretAtStart && blockIndex > 0) {
|
|
3172
|
+
const prevToken = rows[blockIndex - 1];
|
|
3173
|
+
const currToken = rows[blockIndex];
|
|
3174
|
+
if (canMergeRows(prevToken, currToken)) {
|
|
3015
3175
|
event.preventDefault();
|
|
3016
|
-
const
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
})();
|
|
3020
|
-
this.store.state.innerValue(newValue);
|
|
3176
|
+
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3177
|
+
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3178
|
+
store.value.next(newValue);
|
|
3021
3179
|
queueMicrotask(() => {
|
|
3022
|
-
const target = childAt(container,
|
|
3180
|
+
const target = childAt(container, blockIndex - 1);
|
|
3023
3181
|
if (target) {
|
|
3024
3182
|
target.focus();
|
|
3025
|
-
|
|
3183
|
+
const updatedToken = store.parsing.tokens()[blockIndex - 1];
|
|
3184
|
+
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3026
3185
|
}
|
|
3027
3186
|
});
|
|
3028
3187
|
return;
|
|
3029
3188
|
}
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
const
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
this.store.state.innerValue(newValue);
|
|
3038
|
-
queueMicrotask(() => {
|
|
3039
|
-
const target = childAt(container, blockIndex - 1);
|
|
3040
|
-
if (target) {
|
|
3041
|
-
target.focus();
|
|
3042
|
-
const updatedToken = this.store.state.tokens()[blockIndex - 1];
|
|
3043
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3044
|
-
}
|
|
3045
|
-
});
|
|
3046
|
-
return;
|
|
3047
|
-
}
|
|
3048
|
-
event.preventDefault();
|
|
3049
|
-
queueMicrotask(() => {
|
|
3050
|
-
const target = blockDivs[blockIndex - 1];
|
|
3051
|
-
target.focus();
|
|
3052
|
-
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3053
|
-
});
|
|
3054
|
-
return;
|
|
3055
|
-
}
|
|
3189
|
+
event.preventDefault();
|
|
3190
|
+
queueMicrotask(() => {
|
|
3191
|
+
const target = blockDivs[blockIndex - 1];
|
|
3192
|
+
target.focus();
|
|
3193
|
+
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3194
|
+
});
|
|
3195
|
+
return;
|
|
3056
3196
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3067
|
-
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3068
|
-
this.store.state.innerValue(newValue);
|
|
3069
|
-
queueMicrotask(() => {
|
|
3070
|
-
const target = childAt(container, blockIndex - 1);
|
|
3071
|
-
if (target) {
|
|
3072
|
-
target.focus();
|
|
3073
|
-
const updatedToken = this.store.state.tokens()[blockIndex - 1];
|
|
3074
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3075
|
-
}
|
|
3076
|
-
});
|
|
3077
|
-
return;
|
|
3078
|
-
}
|
|
3197
|
+
}
|
|
3198
|
+
if (event.key === KEYBOARD.DELETE) {
|
|
3199
|
+
const blockDiv = blockDivs[blockIndex];
|
|
3200
|
+
const caretIndex = Caret.getCaretIndex(blockDiv);
|
|
3201
|
+
const caretAtEnd = caretIndex === blockDiv.textContent.length;
|
|
3202
|
+
if (caretIndex === 0 && blockIndex > 0) {
|
|
3203
|
+
const prevToken = rows[blockIndex - 1];
|
|
3204
|
+
const currToken = rows[blockIndex];
|
|
3205
|
+
if (canMergeRows(prevToken, currToken)) {
|
|
3079
3206
|
event.preventDefault();
|
|
3207
|
+
const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
|
|
3208
|
+
const newValue = mergeDragRows(value, rows, blockIndex);
|
|
3209
|
+
store.value.next(newValue);
|
|
3080
3210
|
queueMicrotask(() => {
|
|
3081
|
-
const target =
|
|
3082
|
-
target
|
|
3083
|
-
|
|
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
|
+
}
|
|
3084
3217
|
});
|
|
3085
3218
|
return;
|
|
3086
3219
|
}
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
const updatedToken = this.store.state.tokens()[blockIndex];
|
|
3100
|
-
setCaretAtRawPos(target, updatedToken, joinPos);
|
|
3101
|
-
}
|
|
3102
|
-
});
|
|
3103
|
-
return;
|
|
3104
|
-
}
|
|
3220
|
+
event.preventDefault();
|
|
3221
|
+
queueMicrotask(() => {
|
|
3222
|
+
const target = blockDivs[blockIndex - 1];
|
|
3223
|
+
target.focus();
|
|
3224
|
+
if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
|
|
3225
|
+
});
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
if (caretAtEnd && blockIndex < rows.length - 1) {
|
|
3229
|
+
const currToken = rows[blockIndex];
|
|
3230
|
+
const nextToken = rows[blockIndex + 1];
|
|
3231
|
+
if (canMergeRows(currToken, nextToken)) {
|
|
3105
3232
|
event.preventDefault();
|
|
3233
|
+
const joinPos = getMergeDragRowJoinPos(rows, blockIndex + 1);
|
|
3234
|
+
const newValue = mergeDragRows(value, rows, blockIndex + 1);
|
|
3235
|
+
store.value.next(newValue);
|
|
3106
3236
|
queueMicrotask(() => {
|
|
3107
|
-
const target =
|
|
3108
|
-
target
|
|
3109
|
-
|
|
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
|
+
}
|
|
3110
3243
|
});
|
|
3111
3244
|
return;
|
|
3112
3245
|
}
|
|
3113
|
-
|
|
3114
|
-
}
|
|
3115
|
-
#handleEnter(event) {
|
|
3116
|
-
if (event.key !== KEYBOARD.ENTER) return;
|
|
3117
|
-
if (event.shiftKey) return;
|
|
3118
|
-
const container = this.store.refs.container;
|
|
3119
|
-
if (!container) return;
|
|
3120
|
-
const activeElement = document.activeElement;
|
|
3121
|
-
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3122
|
-
event.preventDefault();
|
|
3123
|
-
const blockDivs = htmlChildren(container);
|
|
3124
|
-
let blockIndex = -1;
|
|
3125
|
-
for (let i = 0; i < blockDivs.length; i++) if (blockDivs[i] === activeElement || blockDivs[i].contains(activeElement)) {
|
|
3126
|
-
blockIndex = i;
|
|
3127
|
-
break;
|
|
3128
|
-
}
|
|
3129
|
-
if (blockIndex === -1) return;
|
|
3130
|
-
const rows = this.store.state.tokens();
|
|
3131
|
-
const token = rows[blockIndex];
|
|
3132
|
-
const blockDiv = blockDivs[blockIndex];
|
|
3133
|
-
const value = this.store.computed.currentValue();
|
|
3134
|
-
if (!this.store.props.onChange()) return;
|
|
3135
|
-
const newRowContent = createRowContent(this.store.props.options());
|
|
3136
|
-
if (!isTextLikeRow(token)) {
|
|
3137
|
-
const newValue = addDragRow(value, rows, blockIndex, newRowContent);
|
|
3138
|
-
this.store.state.innerValue(newValue);
|
|
3246
|
+
event.preventDefault();
|
|
3139
3247
|
queueMicrotask(() => {
|
|
3140
|
-
const
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
if (newBlockEl) {
|
|
3144
|
-
newBlockEl.focus();
|
|
3145
|
-
Caret.trySetIndex(newBlockEl, 0);
|
|
3146
|
-
}
|
|
3147
|
-
}
|
|
3248
|
+
const target = blockDivs[blockIndex + 1];
|
|
3249
|
+
target.focus();
|
|
3250
|
+
Caret.trySetIndex(target, 0);
|
|
3148
3251
|
});
|
|
3149
3252
|
return;
|
|
3150
3253
|
}
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
function handleEnter(store, event) {
|
|
3257
|
+
if (event.key !== KEYBOARD.ENTER) return;
|
|
3258
|
+
if (event.shiftKey) return;
|
|
3259
|
+
const container = store.slots.container();
|
|
3260
|
+
if (!container) return;
|
|
3261
|
+
const activeElement = document.activeElement;
|
|
3262
|
+
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3263
|
+
event.preventDefault();
|
|
3264
|
+
const blockDivs = htmlChildren(container);
|
|
3265
|
+
let blockIndex = -1;
|
|
3266
|
+
for (let i = 0; i < blockDivs.length; i++) if (blockDivs[i] === activeElement || blockDivs[i].contains(activeElement)) {
|
|
3267
|
+
blockIndex = i;
|
|
3268
|
+
break;
|
|
3269
|
+
}
|
|
3270
|
+
if (blockIndex === -1) return;
|
|
3271
|
+
const rows = store.parsing.tokens();
|
|
3272
|
+
const token = rows[blockIndex];
|
|
3273
|
+
const blockDiv = blockDivs[blockIndex];
|
|
3274
|
+
const value = store.value.current();
|
|
3275
|
+
if (!store.props.onChange()) return;
|
|
3276
|
+
const newRowContent = createRowContent(store.props.options());
|
|
3277
|
+
if (!isTextLikeRow(token)) {
|
|
3278
|
+
const newValue = addDragRow(value, rows, blockIndex, newRowContent);
|
|
3279
|
+
store.value.next(newValue);
|
|
3154
3280
|
queueMicrotask(() => {
|
|
3155
3281
|
const newBlockIndex = blockIndex + 1;
|
|
3156
3282
|
if (newBlockIndex < container.children.length) {
|
|
@@ -3161,456 +3287,218 @@ var BlockEditFeature = class {
|
|
|
3161
3287
|
}
|
|
3162
3288
|
}
|
|
3163
3289
|
});
|
|
3290
|
+
return;
|
|
3164
3291
|
}
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
if (Caret.getCaretIndex(blockDiv) !== 0) return false;
|
|
3176
|
-
if (blockIndex === 0) return true;
|
|
3177
|
-
event.preventDefault();
|
|
3178
|
-
const prevBlock = blockDivs[blockIndex - 1];
|
|
3179
|
-
prevBlock.focus();
|
|
3180
|
-
Caret.setCaretToEnd(prevBlock);
|
|
3181
|
-
return true;
|
|
3182
|
-
}
|
|
3183
|
-
if (Caret.getCaretIndex(blockDiv) !== blockDiv.textContent.length) return false;
|
|
3184
|
-
if (blockIndex >= blockDivs.length - 1) return true;
|
|
3185
|
-
event.preventDefault();
|
|
3186
|
-
const nextBlock = blockDivs[blockIndex + 1];
|
|
3187
|
-
nextBlock.focus();
|
|
3188
|
-
Caret.trySetIndex(nextBlock, 0);
|
|
3189
|
-
return true;
|
|
3190
|
-
}
|
|
3191
|
-
#handleArrowUpDown(event) {
|
|
3192
|
-
const container = this.store.refs.container;
|
|
3193
|
-
if (!container) return;
|
|
3194
|
-
const activeElement = document.activeElement;
|
|
3195
|
-
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3196
|
-
const blockDivs = htmlChildren(container);
|
|
3197
|
-
const blockIndex = blockDivs.findIndex((div) => div === activeElement || div.contains(activeElement));
|
|
3198
|
-
if (blockIndex === -1) return;
|
|
3199
|
-
const blockDiv = blockDivs[blockIndex];
|
|
3200
|
-
if (event.key === KEYBOARD.UP) {
|
|
3201
|
-
if (!Caret.isCaretOnFirstLine(blockDiv)) return;
|
|
3202
|
-
if (blockIndex === 0) return;
|
|
3203
|
-
event.preventDefault();
|
|
3204
|
-
const caretX = Caret.getCaretRect()?.left ?? blockDiv.getBoundingClientRect().left;
|
|
3205
|
-
const prevBlockDiv = blockDivs[blockIndex - 1];
|
|
3206
|
-
prevBlockDiv.focus();
|
|
3207
|
-
const prevRect = prevBlockDiv.getBoundingClientRect();
|
|
3208
|
-
Caret.setAtX(prevBlockDiv, caretX, prevRect.bottom - 4);
|
|
3209
|
-
} else if (event.key === KEYBOARD.DOWN) {
|
|
3210
|
-
if (!Caret.isCaretOnLastLine(blockDiv)) return;
|
|
3211
|
-
if (blockIndex >= blockDivs.length - 1) return;
|
|
3212
|
-
event.preventDefault();
|
|
3213
|
-
const caretX = Caret.getCaretRect()?.left ?? blockDiv.getBoundingClientRect().left;
|
|
3214
|
-
const nextBlockDiv = blockDivs[blockIndex + 1];
|
|
3215
|
-
nextBlockDiv.focus();
|
|
3216
|
-
const nextRect = nextBlockDiv.getBoundingClientRect();
|
|
3217
|
-
Caret.setAtX(nextBlockDiv, caretX, nextRect.top + 4);
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
#handleBlockBeforeInput(event) {
|
|
3221
|
-
const container = this.store.refs.container;
|
|
3222
|
-
if (!container) return;
|
|
3223
|
-
const activeElement = document.activeElement;
|
|
3224
|
-
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3225
|
-
const blockDivs = htmlChildren(container);
|
|
3226
|
-
const blockIndex = blockDivs.findIndex((div) => div === activeElement || div.contains(activeElement));
|
|
3227
|
-
if (blockIndex === -1) return;
|
|
3228
|
-
const blockDiv = blockDivs[blockIndex];
|
|
3229
|
-
const rows = this.store.state.tokens();
|
|
3230
|
-
if (blockIndex >= rows.length) return;
|
|
3231
|
-
const token = rows[blockIndex];
|
|
3232
|
-
const value = this.store.computed.currentValue();
|
|
3233
|
-
const focusAndSetCaret = (newRawPos) => {
|
|
3234
|
-
queueMicrotask(() => {
|
|
3235
|
-
const target = childAt(container, blockIndex);
|
|
3236
|
-
if (!target) return;
|
|
3237
|
-
target.focus();
|
|
3238
|
-
const updatedToken = this.store.state.tokens()[blockIndex];
|
|
3239
|
-
setCaretAtRawPos(target, updatedToken, newRawPos);
|
|
3240
|
-
});
|
|
3241
|
-
};
|
|
3242
|
-
switch (event.inputType) {
|
|
3243
|
-
case "insertText": {
|
|
3244
|
-
event.preventDefault();
|
|
3245
|
-
const data = event.data ?? "";
|
|
3246
|
-
const ranges = event.getTargetRanges();
|
|
3247
|
-
let rawFrom;
|
|
3248
|
-
let rawTo;
|
|
3249
|
-
if (ranges.length > 0) {
|
|
3250
|
-
const rawStart = getDomRawPos(ranges[0].startContainer, ranges[0].startOffset, blockDiv, token);
|
|
3251
|
-
const rawEnd = getDomRawPos(ranges[0].endContainer, ranges[0].endOffset, blockDiv, token);
|
|
3252
|
-
[rawFrom, rawTo] = rawStart <= rawEnd ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
|
3253
|
-
} else rawFrom = rawTo = getCaretRawPosInBlock(blockDiv, token);
|
|
3254
|
-
this.store.state.innerValue(value.slice(0, rawFrom) + data + value.slice(rawTo));
|
|
3255
|
-
focusAndSetCaret(rawFrom + data.length);
|
|
3256
|
-
break;
|
|
3257
|
-
}
|
|
3258
|
-
case "insertFromPaste":
|
|
3259
|
-
case "insertReplacementText": {
|
|
3260
|
-
event.preventDefault();
|
|
3261
|
-
const pasteData = (this.store.refs.container ? consumeMarkupPaste(this.store.refs.container) : void 0) ?? event.dataTransfer?.getData("text/plain") ?? "";
|
|
3262
|
-
const ranges = event.getTargetRanges();
|
|
3263
|
-
let rawFrom;
|
|
3264
|
-
let rawTo;
|
|
3265
|
-
if (ranges.length > 0) {
|
|
3266
|
-
const rawStart = getDomRawPos(ranges[0].startContainer, ranges[0].startOffset, blockDiv, token);
|
|
3267
|
-
const rawEnd = getDomRawPos(ranges[0].endContainer, ranges[0].endOffset, blockDiv, token);
|
|
3268
|
-
[rawFrom, rawTo] = rawStart <= rawEnd ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
|
3269
|
-
} else rawFrom = rawTo = getCaretRawPosInBlock(blockDiv, token);
|
|
3270
|
-
this.store.state.innerValue(value.slice(0, rawFrom) + pasteData + value.slice(rawTo));
|
|
3271
|
-
focusAndSetCaret(rawFrom + pasteData.length);
|
|
3272
|
-
break;
|
|
3273
|
-
}
|
|
3274
|
-
case "deleteContentBackward":
|
|
3275
|
-
case "deleteContentForward":
|
|
3276
|
-
case "deleteWordBackward":
|
|
3277
|
-
case "deleteWordForward":
|
|
3278
|
-
case "deleteSoftLineBackward":
|
|
3279
|
-
case "deleteSoftLineForward": {
|
|
3280
|
-
const ranges = event.getTargetRanges();
|
|
3281
|
-
if (!ranges.length) return;
|
|
3282
|
-
const rawStart = getDomRawPos(ranges[0].startContainer, ranges[0].startOffset, blockDiv, token);
|
|
3283
|
-
const rawEnd = getDomRawPos(ranges[0].endContainer, ranges[0].endOffset, blockDiv, token);
|
|
3284
|
-
const [rawFrom, rawTo] = rawStart <= rawEnd ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
|
3285
|
-
if (rawFrom === rawTo) return;
|
|
3286
|
-
event.preventDefault();
|
|
3287
|
-
this.store.state.innerValue(value.slice(0, rawFrom) + value.slice(rawTo));
|
|
3288
|
-
focusAndSetCaret(rawFrom);
|
|
3289
|
-
break;
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3293
|
-
};
|
|
3294
|
-
//#endregion
|
|
3295
|
-
//#region ../../core/src/features/drag/tokens.ts
|
|
3296
|
-
const EMPTY_TEXT_TOKEN = {
|
|
3297
|
-
type: "text",
|
|
3298
|
-
content: "",
|
|
3299
|
-
position: {
|
|
3300
|
-
start: 0,
|
|
3301
|
-
end: 0
|
|
3302
|
-
}
|
|
3303
|
-
};
|
|
3304
|
-
//#endregion
|
|
3305
|
-
//#region ../../core/src/features/drag/DragFeature.ts
|
|
3306
|
-
var DragFeature = class {
|
|
3307
|
-
constructor(store) {
|
|
3308
|
-
this.store = store;
|
|
3309
|
-
}
|
|
3310
|
-
#unsub;
|
|
3311
|
-
enable() {
|
|
3312
|
-
if (this.#unsub) return;
|
|
3313
|
-
this.#unsub = watch(this.store.event.dragAction, (action) => {
|
|
3314
|
-
switch (action.type) {
|
|
3315
|
-
case "reorder":
|
|
3316
|
-
this.#reorder(action.source, action.target);
|
|
3317
|
-
break;
|
|
3318
|
-
case "add":
|
|
3319
|
-
this.#add(action.afterIndex);
|
|
3320
|
-
break;
|
|
3321
|
-
case "delete":
|
|
3322
|
-
this.#delete(action.index);
|
|
3323
|
-
break;
|
|
3324
|
-
case "duplicate":
|
|
3325
|
-
this.#duplicate(action.index);
|
|
3326
|
-
break;
|
|
3327
|
-
}
|
|
3328
|
-
});
|
|
3329
|
-
}
|
|
3330
|
-
disable() {
|
|
3331
|
-
this.#unsub?.();
|
|
3332
|
-
this.#unsub = void 0;
|
|
3333
|
-
}
|
|
3334
|
-
#reorder(sourceIndex, targetIndex) {
|
|
3335
|
-
const value = this.store.props.value();
|
|
3336
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3337
|
-
const newValue = reorderDragRows(value, this.store.state.tokens(), sourceIndex, targetIndex);
|
|
3338
|
-
if (newValue !== value) this.store.state.innerValue(newValue);
|
|
3339
|
-
}
|
|
3340
|
-
#add(afterIndex) {
|
|
3341
|
-
const value = this.store.props.value();
|
|
3342
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3343
|
-
const rawRows = this.store.state.tokens();
|
|
3344
|
-
const rows = rawRows.length > 0 ? rawRows : [EMPTY_TEXT_TOKEN];
|
|
3345
|
-
const newRowContent = createRowContent(this.store.props.options());
|
|
3346
|
-
this.store.state.innerValue(addDragRow(value, rows, afterIndex, newRowContent));
|
|
3347
|
-
queueMicrotask(() => {
|
|
3348
|
-
const container = this.store.refs.container;
|
|
3349
|
-
if (!container) return;
|
|
3350
|
-
childAt(container, afterIndex + 1)?.focus();
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3353
|
-
#delete(index) {
|
|
3354
|
-
const value = this.store.props.value();
|
|
3355
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3356
|
-
const rows = this.store.state.tokens();
|
|
3357
|
-
this.store.state.innerValue(deleteDragRow(value, rows, index));
|
|
3358
|
-
}
|
|
3359
|
-
#duplicate(index) {
|
|
3360
|
-
const value = this.store.props.value();
|
|
3361
|
-
if (value == null || !this.store.props.onChange()) return;
|
|
3362
|
-
const rows = this.store.state.tokens();
|
|
3363
|
-
this.store.state.innerValue(duplicateDragRow(value, rows, index));
|
|
3364
|
-
}
|
|
3365
|
-
};
|
|
3366
|
-
//#endregion
|
|
3367
|
-
//#region ../../core/src/shared/utils/dragUtils.ts
|
|
3368
|
-
function getDragDropPosition(clientY, rect) {
|
|
3369
|
-
return clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
3370
|
-
}
|
|
3371
|
-
function parseDragSourceIndex(dataTransfer) {
|
|
3372
|
-
const index = parseInt(dataTransfer.getData("text/plain"), 10);
|
|
3373
|
-
return isNaN(index) ? null : index;
|
|
3374
|
-
}
|
|
3375
|
-
function getDragTargetIndex(blockIndex, position) {
|
|
3376
|
-
return position === "before" ? blockIndex : blockIndex + 1;
|
|
3377
|
-
}
|
|
3378
|
-
//#endregion
|
|
3379
|
-
//#region ../../core/src/features/drag/config.ts
|
|
3380
|
-
function getAlwaysShowHandle(draggable) {
|
|
3381
|
-
return typeof draggable === "object" && !!draggable.alwaysShowHandle;
|
|
3382
|
-
}
|
|
3383
|
-
//#endregion
|
|
3384
|
-
//#region ../../core/src/features/events/SystemListenerFeature.ts
|
|
3385
|
-
var SystemListenerFeature = class {
|
|
3386
|
-
#scope;
|
|
3387
|
-
constructor(store) {
|
|
3388
|
-
this.store = store;
|
|
3389
|
-
}
|
|
3390
|
-
enable() {
|
|
3391
|
-
if (this.#scope) return;
|
|
3392
|
-
this.#scope = effectScope(() => {
|
|
3393
|
-
watch(this.store.event.change, () => {
|
|
3394
|
-
const onChange = this.store.props.onChange();
|
|
3395
|
-
const { focus } = this.store.nodes;
|
|
3396
|
-
if (!focus.target || !focus.target.isContentEditable) {
|
|
3397
|
-
const serialized = toString(this.store.state.tokens());
|
|
3398
|
-
onChange?.(serialized);
|
|
3399
|
-
this.store.state.previousValue(serialized);
|
|
3400
|
-
this.store.bumpTokens();
|
|
3401
|
-
return;
|
|
3402
|
-
}
|
|
3403
|
-
const tokens = this.store.state.tokens();
|
|
3404
|
-
if (focus.index >= tokens.length) return;
|
|
3405
|
-
const token = tokens[focus.index];
|
|
3406
|
-
if (token.type === "text") token.content = focus.content;
|
|
3407
|
-
else token.value = focus.content;
|
|
3408
|
-
onChange?.(toString(tokens));
|
|
3409
|
-
this.store.event.parse();
|
|
3410
|
-
});
|
|
3411
|
-
watch(this.store.event.delete, (payload) => {
|
|
3412
|
-
const { token } = payload;
|
|
3413
|
-
const tokens = this.store.state.tokens();
|
|
3414
|
-
if (!findToken(tokens, token)) return;
|
|
3415
|
-
const value = toString(tokens);
|
|
3416
|
-
const nextValue = value.slice(0, token.position.start) + value.slice(token.position.end);
|
|
3417
|
-
this.store.state.innerValue(nextValue);
|
|
3418
|
-
});
|
|
3419
|
-
watch(this.store.state.innerValue, (newValue) => {
|
|
3420
|
-
if (newValue === void 0) return;
|
|
3421
|
-
const newTokens = parseWithParser(this.store, newValue);
|
|
3422
|
-
batch(() => {
|
|
3423
|
-
this.store.state.tokens(newTokens);
|
|
3424
|
-
this.store.state.previousValue(newValue);
|
|
3425
|
-
});
|
|
3426
|
-
this.store.props.onChange()?.(newValue);
|
|
3427
|
-
});
|
|
3428
|
-
watch(this.store.event.select, (event) => {
|
|
3429
|
-
const Mark = this.store.props.Mark();
|
|
3430
|
-
const onChange = this.store.props.onChange();
|
|
3431
|
-
const { mark, match: { option, span, index, source } } = event;
|
|
3432
|
-
const markup = option.markup;
|
|
3433
|
-
if (!markup) return;
|
|
3434
|
-
const annotation = mark.type === "mark" ? annotate(markup, {
|
|
3435
|
-
value: mark.value,
|
|
3436
|
-
meta: mark.meta
|
|
3437
|
-
}) : annotate(markup, { value: mark.content });
|
|
3438
|
-
const newSpan = createNewSpan(span, annotation, index, source);
|
|
3439
|
-
this.store.state.recovery(Mark ? {
|
|
3440
|
-
caret: 0,
|
|
3441
|
-
anchor: this.store.nodes.input.next,
|
|
3442
|
-
isNext: true,
|
|
3443
|
-
childIndex: this.store.nodes.input.index
|
|
3444
|
-
} : {
|
|
3445
|
-
caret: index + annotation.length,
|
|
3446
|
-
anchor: this.store.nodes.input
|
|
3447
|
-
});
|
|
3448
|
-
if (this.store.nodes.input.target) {
|
|
3449
|
-
this.store.nodes.input.content = newSpan;
|
|
3450
|
-
const tokens = this.store.state.tokens();
|
|
3451
|
-
const inputToken = tokens[this.store.nodes.input.index];
|
|
3452
|
-
if (inputToken.type === "text") inputToken.content = newSpan;
|
|
3453
|
-
this.store.nodes.focus.target = this.store.nodes.input.target;
|
|
3454
|
-
this.store.nodes.input.clear();
|
|
3455
|
-
onChange?.(toString(tokens));
|
|
3456
|
-
this.store.event.parse();
|
|
3457
|
-
}
|
|
3458
|
-
});
|
|
3459
|
-
});
|
|
3460
|
-
}
|
|
3461
|
-
disable() {
|
|
3462
|
-
this.#scope?.();
|
|
3463
|
-
this.#scope = void 0;
|
|
3464
|
-
}
|
|
3465
|
-
};
|
|
3466
|
-
//#endregion
|
|
3467
|
-
//#region ../../core/src/features/focus/FocusFeature.ts
|
|
3468
|
-
var FocusFeature = class {
|
|
3469
|
-
#scope;
|
|
3470
|
-
constructor(store) {
|
|
3471
|
-
this.store = store;
|
|
3472
|
-
}
|
|
3473
|
-
enable() {
|
|
3474
|
-
if (this.#scope) return;
|
|
3475
|
-
const container = this.store.refs.container;
|
|
3476
|
-
if (!container) return;
|
|
3477
|
-
this.#scope = effectScope(() => {
|
|
3478
|
-
listen(container, "focusin", (e) => {
|
|
3479
|
-
const target = isHtmlElement(e.target) ? e.target : void 0;
|
|
3480
|
-
this.store.nodes.focus.target = target;
|
|
3481
|
-
});
|
|
3482
|
-
listen(container, "focusout", () => {
|
|
3483
|
-
this.store.nodes.focus.target = void 0;
|
|
3484
|
-
});
|
|
3485
|
-
listen(container, "click", () => {
|
|
3486
|
-
const tokens = this.store.state.tokens();
|
|
3487
|
-
if (tokens.length === 1 && tokens[0].type === "text" && tokens[0].content === "") {
|
|
3488
|
-
const container = this.store.refs.container;
|
|
3489
|
-
(container ? firstHtmlChild(container) : null)?.focus();
|
|
3490
|
-
}
|
|
3491
|
-
});
|
|
3492
|
-
watch(this.store.event.recoverFocus, () => {
|
|
3493
|
-
this.#recover();
|
|
3494
|
-
});
|
|
3495
|
-
watch(this.store.event.afterTokensRendered, () => {
|
|
3496
|
-
this.store.event.sync();
|
|
3497
|
-
if (!this.store.props.Mark()) return;
|
|
3498
|
-
this.store.event.recoverFocus();
|
|
3499
|
-
});
|
|
3500
|
-
});
|
|
3501
|
-
}
|
|
3502
|
-
disable() {
|
|
3503
|
-
this.#scope?.();
|
|
3504
|
-
this.#scope = void 0;
|
|
3505
|
-
this.store.nodes.focus.clear();
|
|
3506
|
-
}
|
|
3507
|
-
#recover() {
|
|
3508
|
-
const recovery = this.store.state.recovery();
|
|
3509
|
-
if (!recovery) return;
|
|
3510
|
-
const { anchor, caret, isNext } = recovery;
|
|
3511
|
-
const isStale = !anchor.target || !anchor.target.isConnected;
|
|
3512
|
-
let target;
|
|
3513
|
-
switch (true) {
|
|
3514
|
-
case isNext && isStale: {
|
|
3515
|
-
const container = this.store.refs.container;
|
|
3516
|
-
target = (recovery.childIndex != null ? childAt(container, recovery.childIndex + 2) : void 0) ?? this.store.nodes.focus.tail ?? void 0;
|
|
3517
|
-
break;
|
|
3292
|
+
const absolutePos = getCaretRawPosInBlock(blockDiv, token);
|
|
3293
|
+
const newValue = value.slice(0, absolutePos) + newRowContent + value.slice(absolutePos);
|
|
3294
|
+
store.value.next(newValue);
|
|
3295
|
+
queueMicrotask(() => {
|
|
3296
|
+
const newBlockIndex = blockIndex + 1;
|
|
3297
|
+
if (newBlockIndex < container.children.length) {
|
|
3298
|
+
const newBlockEl = childAt(container, newBlockIndex);
|
|
3299
|
+
if (newBlockEl) {
|
|
3300
|
+
newBlockEl.focus();
|
|
3301
|
+
Caret.trySetIndex(newBlockEl, 0);
|
|
3518
3302
|
}
|
|
3519
|
-
case isNext:
|
|
3520
|
-
target = anchor.prev.target;
|
|
3521
|
-
break;
|
|
3522
|
-
case isStale:
|
|
3523
|
-
target = this.store.nodes.focus.head ?? void 0;
|
|
3524
|
-
break;
|
|
3525
|
-
default: target = anchor.next.target;
|
|
3526
3303
|
}
|
|
3527
|
-
|
|
3528
|
-
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
function handleBlockArrowLeftRight(store, event, direction) {
|
|
3307
|
+
const container = store.slots.container();
|
|
3308
|
+
if (!container) return false;
|
|
3309
|
+
const activeElement = document.activeElement;
|
|
3310
|
+
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return false;
|
|
3311
|
+
const blockDivs = htmlChildren(container);
|
|
3312
|
+
const blockIndex = blockDivs.findIndex((div) => div === activeElement || div.contains(activeElement));
|
|
3313
|
+
if (blockIndex === -1) return false;
|
|
3314
|
+
const blockDiv = blockDivs[blockIndex];
|
|
3315
|
+
if (direction === "left") {
|
|
3316
|
+
if (Caret.getCaretIndex(blockDiv) !== 0) return false;
|
|
3317
|
+
if (blockIndex === 0) return true;
|
|
3318
|
+
event.preventDefault();
|
|
3319
|
+
const prevBlock = blockDivs[blockIndex - 1];
|
|
3320
|
+
prevBlock.focus();
|
|
3321
|
+
Caret.setCaretToEnd(prevBlock);
|
|
3322
|
+
return true;
|
|
3323
|
+
}
|
|
3324
|
+
if (Caret.getCaretIndex(blockDiv) !== blockDiv.textContent.length) return false;
|
|
3325
|
+
if (blockIndex >= blockDivs.length - 1) return true;
|
|
3326
|
+
event.preventDefault();
|
|
3327
|
+
const nextBlock = blockDivs[blockIndex + 1];
|
|
3328
|
+
nextBlock.focus();
|
|
3329
|
+
Caret.trySetIndex(nextBlock, 0);
|
|
3330
|
+
return true;
|
|
3331
|
+
}
|
|
3332
|
+
function handleArrowUpDown(store, event) {
|
|
3333
|
+
const container = store.slots.container();
|
|
3334
|
+
if (!container) return;
|
|
3335
|
+
const activeElement = document.activeElement;
|
|
3336
|
+
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3337
|
+
const blockDivs = htmlChildren(container);
|
|
3338
|
+
const blockIndex = blockDivs.findIndex((div) => div === activeElement || div.contains(activeElement));
|
|
3339
|
+
if (blockIndex === -1) return;
|
|
3340
|
+
const blockDiv = blockDivs[blockIndex];
|
|
3341
|
+
if (event.key === KEYBOARD.UP) {
|
|
3342
|
+
if (!Caret.isCaretOnFirstLine(blockDiv)) return;
|
|
3343
|
+
if (blockIndex === 0) return;
|
|
3344
|
+
event.preventDefault();
|
|
3345
|
+
const caretX = Caret.getCaretRect()?.left ?? blockDiv.getBoundingClientRect().left;
|
|
3346
|
+
const prevBlockDiv = blockDivs[blockIndex - 1];
|
|
3347
|
+
prevBlockDiv.focus();
|
|
3348
|
+
const prevRect = prevBlockDiv.getBoundingClientRect();
|
|
3349
|
+
Caret.setAtX(prevBlockDiv, caretX, prevRect.bottom - 4);
|
|
3350
|
+
} else if (event.key === KEYBOARD.DOWN) {
|
|
3351
|
+
if (!Caret.isCaretOnLastLine(blockDiv)) return;
|
|
3352
|
+
if (blockIndex >= blockDivs.length - 1) return;
|
|
3353
|
+
event.preventDefault();
|
|
3354
|
+
const caretX = Caret.getCaretRect()?.left ?? blockDiv.getBoundingClientRect().left;
|
|
3355
|
+
const nextBlockDiv = blockDivs[blockIndex + 1];
|
|
3356
|
+
nextBlockDiv.focus();
|
|
3357
|
+
const nextRect = nextBlockDiv.getBoundingClientRect();
|
|
3358
|
+
Caret.setAtX(nextBlockDiv, caretX, nextRect.top + 4);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
function handleBlockBeforeInput(store, event) {
|
|
3362
|
+
const container = store.slots.container();
|
|
3363
|
+
if (!container) return;
|
|
3364
|
+
const activeElement = document.activeElement;
|
|
3365
|
+
if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return;
|
|
3366
|
+
const blockDivs = htmlChildren(container);
|
|
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) => {
|
|
3529
3375
|
queueMicrotask(() => {
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
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);
|
|
3533
3381
|
});
|
|
3534
|
-
|
|
3382
|
+
};
|
|
3383
|
+
switch (event.inputType) {
|
|
3384
|
+
case "insertText": {
|
|
3385
|
+
event.preventDefault();
|
|
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);
|
|
3397
|
+
break;
|
|
3398
|
+
}
|
|
3399
|
+
case "insertFromPaste":
|
|
3400
|
+
case "insertReplacementText": {
|
|
3401
|
+
event.preventDefault();
|
|
3402
|
+
const c = store.slots.container();
|
|
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);
|
|
3414
|
+
break;
|
|
3415
|
+
}
|
|
3416
|
+
case "deleteContentBackward":
|
|
3417
|
+
case "deleteContentForward":
|
|
3418
|
+
case "deleteWordBackward":
|
|
3419
|
+
case "deleteWordForward":
|
|
3420
|
+
case "deleteSoftLineBackward":
|
|
3421
|
+
case "deleteSoftLineForward": {
|
|
3422
|
+
const ranges = event.getTargetRanges();
|
|
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);
|
|
3431
|
+
break;
|
|
3432
|
+
}
|
|
3535
3433
|
}
|
|
3536
|
-
}
|
|
3434
|
+
}
|
|
3537
3435
|
//#endregion
|
|
3538
|
-
//#region ../../core/src/features/input
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
if (this.#scope) return;
|
|
3546
|
-
const container = this.store.refs.container;
|
|
3547
|
-
if (!container) return;
|
|
3548
|
-
this.#scope = effectScope(() => {
|
|
3549
|
-
listen(container, "keydown", (e) => {
|
|
3550
|
-
if (!this.store.computed.isBlock()) this.#handleDelete(e);
|
|
3551
|
-
});
|
|
3552
|
-
listen(container, "paste", (e) => {
|
|
3553
|
-
const c = this.store.refs.container;
|
|
3554
|
-
if (c) captureMarkupPaste(e, c);
|
|
3555
|
-
handlePaste(this.store, e);
|
|
3556
|
-
});
|
|
3557
|
-
listen(container, "beforeinput", (e) => {
|
|
3558
|
-
handleBeforeInput(this.store, e);
|
|
3559
|
-
}, true);
|
|
3436
|
+
//#region ../../core/src/features/keyboard/input.ts
|
|
3437
|
+
function enableInput(store) {
|
|
3438
|
+
const container = store.slots.container();
|
|
3439
|
+
if (!container) return () => {};
|
|
3440
|
+
const scope = effectScope(() => {
|
|
3441
|
+
listen(container, "keydown", (e) => {
|
|
3442
|
+
if (!store.slots.isBlock()) handleDelete(store, e);
|
|
3560
3443
|
});
|
|
3444
|
+
listen(container, "paste", (e) => {
|
|
3445
|
+
const c = store.slots.container();
|
|
3446
|
+
if (c) captureMarkupPaste(e, c);
|
|
3447
|
+
handlePaste(store, e);
|
|
3448
|
+
});
|
|
3449
|
+
listen(container, "beforeinput", (e) => {
|
|
3450
|
+
handleBeforeInput(store, e);
|
|
3451
|
+
}, true);
|
|
3452
|
+
});
|
|
3453
|
+
return () => scope();
|
|
3454
|
+
}
|
|
3455
|
+
function handleDelete(store, event) {
|
|
3456
|
+
const { focus } = store.nodes;
|
|
3457
|
+
if (event.key !== KEYBOARD.DELETE && event.key !== KEYBOARD.BACKSPACE) return;
|
|
3458
|
+
if (focus.isMark) {
|
|
3459
|
+
if (focus.isEditable) {
|
|
3460
|
+
if (event.key === KEYBOARD.BACKSPACE && !focus.isCaretAtBeginning) return;
|
|
3461
|
+
if (event.key === KEYBOARD.DELETE && !focus.isCaretAtEnd) return;
|
|
3462
|
+
}
|
|
3463
|
+
event.preventDefault();
|
|
3464
|
+
deleteMark("self", store);
|
|
3465
|
+
return;
|
|
3561
3466
|
}
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
this.#scope = void 0;
|
|
3565
|
-
}
|
|
3566
|
-
#handleDelete(event) {
|
|
3567
|
-
const { focus } = this.store.nodes;
|
|
3568
|
-
if (event.key !== KEYBOARD.DELETE && event.key !== KEYBOARD.BACKSPACE) return;
|
|
3569
|
-
if (focus.isMark) {
|
|
3570
|
-
if (focus.isEditable) {
|
|
3571
|
-
if (event.key === KEYBOARD.BACKSPACE && !focus.isCaretAtBeginning) return;
|
|
3572
|
-
if (event.key === KEYBOARD.DELETE && !focus.isCaretAtEnd) return;
|
|
3573
|
-
}
|
|
3467
|
+
if (event.key === KEYBOARD.BACKSPACE) {
|
|
3468
|
+
if (focus.isSpan && focus.isCaretAtBeginning && focus.prev.target) {
|
|
3574
3469
|
event.preventDefault();
|
|
3575
|
-
deleteMark("
|
|
3470
|
+
deleteMark("prev", store);
|
|
3576
3471
|
return;
|
|
3577
3472
|
}
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
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;
|
|
3584
3479
|
}
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
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;
|
|
3591
3490
|
}
|
|
3592
|
-
if (
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
focus.caret = caret - 1;
|
|
3599
|
-
this.store.event.change();
|
|
3600
|
-
return;
|
|
3601
|
-
}
|
|
3602
|
-
if (event.key === KEYBOARD.DELETE && caret >= 0 && caret < content.length) {
|
|
3603
|
-
event.preventDefault();
|
|
3604
|
-
focus.content = content.slice(0, caret) + content.slice(caret + 1);
|
|
3605
|
-
focus.caret = caret;
|
|
3606
|
-
this.store.event.change();
|
|
3607
|
-
return;
|
|
3608
|
-
}
|
|
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;
|
|
3609
3497
|
}
|
|
3610
3498
|
}
|
|
3611
|
-
}
|
|
3499
|
+
}
|
|
3612
3500
|
function handleBeforeInput(store, event) {
|
|
3613
|
-
const selecting = store.
|
|
3501
|
+
const selecting = store.caret.selecting();
|
|
3614
3502
|
if (selecting === "all" && isFullSelection(store)) {
|
|
3615
3503
|
if (event.inputType === "insertFromPaste") {
|
|
3616
3504
|
event.preventDefault();
|
|
@@ -3620,22 +3508,22 @@ function handleBeforeInput(store, event) {
|
|
|
3620
3508
|
replaceAllContentWith(store, event.inputType.startsWith("delete") ? "" : event.data ?? "");
|
|
3621
3509
|
return;
|
|
3622
3510
|
}
|
|
3623
|
-
if (selecting === "all") store.
|
|
3624
|
-
if (store.
|
|
3511
|
+
if (selecting === "all") store.caret.selecting(void 0);
|
|
3512
|
+
if (store.slots.isBlock()) return;
|
|
3625
3513
|
const { focus } = store.nodes;
|
|
3626
3514
|
if (!focus.target || !focus.isEditable) return;
|
|
3627
3515
|
if ((event.inputType === "insertFromPaste" || event.inputType === "insertReplacementText") && handleMarkputSpanPaste(store, focus, event)) return;
|
|
3628
|
-
if (applySpanInput(focus, event)) store.
|
|
3516
|
+
if (applySpanInput(focus, event)) store.value.change();
|
|
3629
3517
|
}
|
|
3630
3518
|
function handleMarkputSpanPaste(store, focus, event) {
|
|
3631
|
-
const container = store.
|
|
3519
|
+
const container = store.slots.container();
|
|
3632
3520
|
if (!container) return false;
|
|
3633
3521
|
const markup = consumeMarkupPaste(container);
|
|
3634
3522
|
if (!markup) return false;
|
|
3635
3523
|
event.preventDefault();
|
|
3636
|
-
const token = store.
|
|
3524
|
+
const token = store.parsing.tokens()[focus.index];
|
|
3637
3525
|
const offset = focus.caret;
|
|
3638
|
-
const currentValue = store.
|
|
3526
|
+
const currentValue = store.value.current();
|
|
3639
3527
|
const ranges = event.getTargetRanges();
|
|
3640
3528
|
const childElement = container.children[focus.index];
|
|
3641
3529
|
let rawInsertPos;
|
|
@@ -3651,12 +3539,12 @@ function handleMarkputSpanPaste(store, focus, event) {
|
|
|
3651
3539
|
}
|
|
3652
3540
|
const caretPos = rawInsertPos + markup.length;
|
|
3653
3541
|
const newValue = currentValue.slice(0, rawInsertPos) + markup + currentValue.slice(rawEndPos);
|
|
3654
|
-
store.
|
|
3655
|
-
const newTokens = store.
|
|
3542
|
+
store.value.next(newValue);
|
|
3543
|
+
const newTokens = store.parsing.tokens();
|
|
3656
3544
|
let targetIdx = newTokens.findIndex((t) => t.type === "text" && caretPos >= t.position.start && caretPos <= t.position.end);
|
|
3657
3545
|
if (targetIdx === -1) targetIdx = newTokens.length - 1;
|
|
3658
3546
|
const caretWithinToken = caretPos - newTokens[targetIdx].position.start;
|
|
3659
|
-
store.
|
|
3547
|
+
store.caret.recovery({
|
|
3660
3548
|
anchor: store.nodes.focus,
|
|
3661
3549
|
caret: caretWithinToken,
|
|
3662
3550
|
isNext: true,
|
|
@@ -3719,20 +3607,21 @@ function applySpanInput(focus, event) {
|
|
|
3719
3607
|
return true;
|
|
3720
3608
|
}
|
|
3721
3609
|
function handlePaste(store, event) {
|
|
3722
|
-
const selecting = store.
|
|
3610
|
+
const selecting = store.caret.selecting();
|
|
3723
3611
|
if (selecting !== "all" || !isFullSelection(store)) {
|
|
3724
|
-
if (selecting === "all") store.
|
|
3612
|
+
if (selecting === "all") store.caret.selecting(void 0);
|
|
3725
3613
|
return;
|
|
3726
3614
|
}
|
|
3727
3615
|
event.preventDefault();
|
|
3728
|
-
|
|
3616
|
+
const c = store.slots.container();
|
|
3617
|
+
replaceAllContentWith(store, (c ? consumeMarkupPaste(c) : void 0) ?? event.clipboardData?.getData("text/plain") ?? "");
|
|
3729
3618
|
}
|
|
3730
3619
|
function replaceAllContentWith(store, newContent) {
|
|
3731
3620
|
store.nodes.focus.target = null;
|
|
3732
|
-
store.
|
|
3733
|
-
store.
|
|
3621
|
+
store.caret.selecting(void 0);
|
|
3622
|
+
store.value.last(newContent);
|
|
3734
3623
|
store.props.onChange()?.(newContent);
|
|
3735
|
-
if (store.props.value() === void 0) store.
|
|
3624
|
+
if (store.props.value() === void 0) store.parsing.tokens(store.parsing.parser()?.parse(newContent) ?? [{
|
|
3736
3625
|
type: "text",
|
|
3737
3626
|
content: newContent,
|
|
3738
3627
|
position: {
|
|
@@ -3741,17 +3630,247 @@ function replaceAllContentWith(store, newContent) {
|
|
|
3741
3630
|
}
|
|
3742
3631
|
}]);
|
|
3743
3632
|
queueMicrotask(() => {
|
|
3744
|
-
const rawFirstChild = store.
|
|
3633
|
+
const rawFirstChild = store.slots.container()?.firstChild;
|
|
3745
3634
|
const firstChild = isHtmlElement(rawFirstChild) ? rawFirstChild : null;
|
|
3746
3635
|
if (firstChild) {
|
|
3747
|
-
store.
|
|
3636
|
+
store.caret.recovery({
|
|
3748
3637
|
anchor: store.nodes.focus,
|
|
3749
3638
|
caret: newContent.length
|
|
3750
3639
|
});
|
|
3751
|
-
firstChild.focus();
|
|
3752
|
-
}
|
|
3753
|
-
});
|
|
3754
|
-
}
|
|
3640
|
+
firstChild.focus();
|
|
3641
|
+
}
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
//#endregion
|
|
3645
|
+
//#region ../../core/src/features/keyboard/KeyboardFeature.ts
|
|
3646
|
+
var KeyboardFeature = class {
|
|
3647
|
+
#disposers = [];
|
|
3648
|
+
constructor(_store) {
|
|
3649
|
+
this._store = _store;
|
|
3650
|
+
}
|
|
3651
|
+
enable() {
|
|
3652
|
+
if (this.#disposers.length) return;
|
|
3653
|
+
this.#disposers = [
|
|
3654
|
+
enableInput(this._store),
|
|
3655
|
+
enableBlockEdit(this._store),
|
|
3656
|
+
enableArrowNav(this._store)
|
|
3657
|
+
];
|
|
3658
|
+
}
|
|
3659
|
+
disable() {
|
|
3660
|
+
this.#disposers.forEach((d) => d());
|
|
3661
|
+
this.#disposers = [];
|
|
3662
|
+
}
|
|
3663
|
+
};
|
|
3664
|
+
//#endregion
|
|
3665
|
+
//#region ../../core/src/features/lifecycle/LifecycleFeature.ts
|
|
3666
|
+
var LifecycleFeature = class {
|
|
3667
|
+
constructor(_store) {
|
|
3668
|
+
this._store = _store;
|
|
3669
|
+
this.mounted = event();
|
|
3670
|
+
this.unmounted = event();
|
|
3671
|
+
this.rendered = event();
|
|
3672
|
+
}
|
|
3673
|
+
enable() {}
|
|
3674
|
+
disable() {}
|
|
3675
|
+
};
|
|
3676
|
+
//#endregion
|
|
3677
|
+
//#region ../../core/src/features/mark/MarkHandler.ts
|
|
3678
|
+
var MarkHandler = class {
|
|
3679
|
+
#store;
|
|
3680
|
+
#token;
|
|
3681
|
+
#readOnly;
|
|
3682
|
+
constructor(param) {
|
|
3683
|
+
this.change = (props) => {
|
|
3684
|
+
this.#token.content = props.content;
|
|
3685
|
+
this.#token.value = props.value ?? "";
|
|
3686
|
+
if (props.meta !== void 0) this.#token.meta = props.meta;
|
|
3687
|
+
this.#emitChange();
|
|
3688
|
+
};
|
|
3689
|
+
this.remove = () => this.#store.mark.remove({ token: this.#token });
|
|
3690
|
+
this.ref = param.ref;
|
|
3691
|
+
this.#store = param.store;
|
|
3692
|
+
this.#token = param.token;
|
|
3693
|
+
}
|
|
3694
|
+
get readOnly() {
|
|
3695
|
+
return this.#readOnly;
|
|
3696
|
+
}
|
|
3697
|
+
set readOnly(value) {
|
|
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();
|
|
3706
|
+
}
|
|
3707
|
+
get value() {
|
|
3708
|
+
return this.#token.value;
|
|
3709
|
+
}
|
|
3710
|
+
set value(v) {
|
|
3711
|
+
this.#token.value = v ?? "";
|
|
3712
|
+
this.#emitChange();
|
|
3713
|
+
}
|
|
3714
|
+
get meta() {
|
|
3715
|
+
return this.#token.meta;
|
|
3716
|
+
}
|
|
3717
|
+
set meta(v) {
|
|
3718
|
+
this.#token.meta = v;
|
|
3719
|
+
this.#emitChange();
|
|
3720
|
+
}
|
|
3721
|
+
get slot() {
|
|
3722
|
+
return this.#token.slot?.content;
|
|
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");
|
|
3732
|
+
}
|
|
3733
|
+
get parent() {
|
|
3734
|
+
return this.#tokenInfo?.parent;
|
|
3735
|
+
}
|
|
3736
|
+
get tokens() {
|
|
3737
|
+
return this.#token.children;
|
|
3738
|
+
}
|
|
3739
|
+
#emitChange() {
|
|
3740
|
+
this.#store.value.change();
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
//#endregion
|
|
3744
|
+
//#region ../../core/src/shared/utils/dataAttributes.ts
|
|
3745
|
+
/**
|
|
3746
|
+
* Converts object with camelCase data attribute keys to kebab-case data-* attributes
|
|
3747
|
+
*
|
|
3748
|
+
* Takes keys like 'dataUserId' and converts to 'data-user-id'
|
|
3749
|
+
*
|
|
3750
|
+
* @param obj - Object potentially containing camelCase data attribute keys
|
|
3751
|
+
* @returns New object with converted data attributes
|
|
3752
|
+
*
|
|
3753
|
+
* @example
|
|
3754
|
+
* convertDataAttrs({ dataUserId: '123', dataUserName: 'John', className: 'test' })
|
|
3755
|
+
* // Returns: { 'data-user-id': '123', 'data-user-name': 'John', className: 'test' }
|
|
3756
|
+
*
|
|
3757
|
+
* convertDataAttrs({ dataTestId * @example
|
|
3758
|
+
: 'test', dataFoo: 'bar' })
|
|
3759
|
+
* // Returns: { 'data-test-id': 'test', 'data-foo': 'bar' }
|
|
3760
|
+
*/
|
|
3761
|
+
function convertDataAttrs(obj) {
|
|
3762
|
+
if (!obj) return {};
|
|
3763
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
|
|
3764
|
+
if (key.startsWith("data") && key.length > 4 && key[4] === key[4].toUpperCase()) return [`data-${key.slice(4).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`, value];
|
|
3765
|
+
return [key, value];
|
|
3766
|
+
}));
|
|
3767
|
+
}
|
|
3768
|
+
//#endregion
|
|
3769
|
+
//#region ../../core/src/features/slots/resolveOptionSlot.ts
|
|
3770
|
+
function resolveOptionSlot(optionConfig, baseProps) {
|
|
3771
|
+
if (optionConfig !== void 0) return typeof optionConfig === "function" ? optionConfig(baseProps) : optionConfig;
|
|
3772
|
+
return baseProps;
|
|
3773
|
+
}
|
|
3774
|
+
//#endregion
|
|
3775
|
+
//#region ../../core/src/features/slots/resolveSlot.ts
|
|
3776
|
+
const defaultSlots = {
|
|
3777
|
+
container: "div",
|
|
3778
|
+
block: "div",
|
|
3779
|
+
span: "span"
|
|
3780
|
+
};
|
|
3781
|
+
function resolveSlot(slotName, slots) {
|
|
3782
|
+
return slots?.[slotName] ?? defaultSlots[slotName];
|
|
3783
|
+
}
|
|
3784
|
+
function resolveSlotProps(slotName, slotProps) {
|
|
3785
|
+
const props = slotProps?.[slotName];
|
|
3786
|
+
return props ? convertDataAttrs(props) : void 0;
|
|
3787
|
+
}
|
|
3788
|
+
function resolveOverlaySlot(globalComponent, option, defaultComponent) {
|
|
3789
|
+
const Component = option?.Overlay ?? globalComponent ?? defaultComponent;
|
|
3790
|
+
if (!Component) throw new Error("No overlay component found. Provide either option.Overlay, global Overlay, or a defaultComponent.");
|
|
3791
|
+
return [Component, resolveOptionSlot(option?.overlay, {})];
|
|
3792
|
+
}
|
|
3793
|
+
function resolveMarkSlot(token, tokenOptions, GlobalMark, GlobalSpan) {
|
|
3794
|
+
if (token.type === "text") return [GlobalSpan ?? "span", GlobalSpan ? { value: token.content } : {}];
|
|
3795
|
+
const option = tokenOptions?.[token.descriptor.index];
|
|
3796
|
+
const baseProps = {
|
|
3797
|
+
value: token.value,
|
|
3798
|
+
meta: token.meta
|
|
3799
|
+
};
|
|
3800
|
+
const props = resolveOptionSlot(option?.mark, baseProps);
|
|
3801
|
+
const Component = option?.Mark ?? GlobalMark;
|
|
3802
|
+
if (!Component) throw new Error("No mark component found. Provide either option.Mark or global Mark.");
|
|
3803
|
+
return [Component, props];
|
|
3804
|
+
}
|
|
3805
|
+
//#endregion
|
|
3806
|
+
//#region ../../core/src/features/slots/SlotsFeature.ts
|
|
3807
|
+
const DRAG_HANDLE_WIDTH = 24;
|
|
3808
|
+
function buildContainerProps(isDraggableBlock, readOnly, className, style, slotProps) {
|
|
3809
|
+
const containerSlotProps = slotProps?.container;
|
|
3810
|
+
const baseStyle = merge(style, containerSlotProps?.style);
|
|
3811
|
+
const mergedStyle = isDraggableBlock && !readOnly ? {
|
|
3812
|
+
paddingLeft: DRAG_HANDLE_WIDTH,
|
|
3813
|
+
...baseStyle
|
|
3814
|
+
} : baseStyle;
|
|
3815
|
+
const { className: _, style: __, ...otherSlotProps } = resolveSlotProps("container", slotProps) ?? {};
|
|
3816
|
+
return {
|
|
3817
|
+
className: cx(styles.Container, className, containerSlotProps?.className),
|
|
3818
|
+
style: mergedStyle,
|
|
3819
|
+
...otherSlotProps
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
var SlotsFeature = class {
|
|
3823
|
+
constructor(_store) {
|
|
3824
|
+
this._store = _store;
|
|
3825
|
+
this.container = signal(null);
|
|
3826
|
+
this.isBlock = computed(() => this._store.props.layout() === "block");
|
|
3827
|
+
this.isDraggable = computed(() => !!this._store.props.draggable());
|
|
3828
|
+
this.containerComponent = computed(() => resolveSlot("container", this._store.props.slots()));
|
|
3829
|
+
this.containerProps = computed(() => buildContainerProps(this.isDraggable() && this.isBlock(), this._store.props.readOnly(), this._store.props.className(), this._store.props.style(), this._store.props.slotProps()), { equals: shallow });
|
|
3830
|
+
this.blockComponent = computed(() => resolveSlot("block", this._store.props.slots()));
|
|
3831
|
+
this.blockProps = computed(() => resolveSlotProps("block", this._store.props.slotProps()));
|
|
3832
|
+
this.spanComponent = computed(() => resolveSlot("span", this._store.props.slots()));
|
|
3833
|
+
this.spanProps = computed(() => resolveSlotProps("span", this._store.props.slotProps()));
|
|
3834
|
+
}
|
|
3835
|
+
enable() {}
|
|
3836
|
+
disable() {}
|
|
3837
|
+
};
|
|
3838
|
+
//#endregion
|
|
3839
|
+
//#region ../../core/src/features/mark/MarkFeature.ts
|
|
3840
|
+
var MarkFeature = class {
|
|
3841
|
+
#scope;
|
|
3842
|
+
constructor(_store) {
|
|
3843
|
+
this._store = _store;
|
|
3844
|
+
this.enabled = computed(() => {
|
|
3845
|
+
if (this._store.props.Mark()) return true;
|
|
3846
|
+
return this._store.props.options().some((opt) => "Mark" in opt && opt.Mark != null);
|
|
3847
|
+
});
|
|
3848
|
+
this.slot = computed(() => {
|
|
3849
|
+
const options = this._store.props.options();
|
|
3850
|
+
const Mark = this._store.props.Mark();
|
|
3851
|
+
const Span = this._store.props.Span();
|
|
3852
|
+
return (token) => resolveMarkSlot(token, options, Mark, Span);
|
|
3853
|
+
});
|
|
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
|
+
}
|
|
3873
|
+
};
|
|
3755
3874
|
//#endregion
|
|
3756
3875
|
//#region ../../core/src/features/overlay/filterSuggestions.ts
|
|
3757
3876
|
function filterSuggestions(data, search) {
|
|
@@ -3789,45 +3908,84 @@ function createMarkFromOverlay(match, value, meta) {
|
|
|
3789
3908
|
//#region ../../core/src/features/overlay/OverlayFeature.ts
|
|
3790
3909
|
var OverlayFeature = class {
|
|
3791
3910
|
#scope;
|
|
3792
|
-
constructor(
|
|
3793
|
-
this.
|
|
3911
|
+
constructor(_store) {
|
|
3912
|
+
this._store = _store;
|
|
3913
|
+
this.match = signal(void 0);
|
|
3914
|
+
this.element = signal(null);
|
|
3915
|
+
this.slot = computed(() => {
|
|
3916
|
+
const Overlay = this._store.props.Overlay();
|
|
3917
|
+
return (option, defaultComponent) => resolveOverlaySlot(Overlay, option, defaultComponent);
|
|
3918
|
+
});
|
|
3919
|
+
this.select = event();
|
|
3920
|
+
this.close = event();
|
|
3921
|
+
}
|
|
3922
|
+
#probeTrigger() {
|
|
3923
|
+
const match = TriggerFinder.find(this._store.props.options(), (option) => option.overlay?.trigger);
|
|
3924
|
+
this.match(match);
|
|
3794
3925
|
}
|
|
3795
3926
|
enable() {
|
|
3796
3927
|
if (this.#scope) return;
|
|
3797
3928
|
this.#scope = effectScope(() => {
|
|
3798
|
-
watch(this.
|
|
3799
|
-
this.
|
|
3800
|
-
});
|
|
3801
|
-
watch(this.store.event.checkOverlay, () => {
|
|
3802
|
-
const match = TriggerFinder.find(this.store.props.options(), (option) => option.overlay?.trigger);
|
|
3803
|
-
this.store.state.overlayMatch(match);
|
|
3929
|
+
watch(this.close, () => {
|
|
3930
|
+
this.match(void 0);
|
|
3804
3931
|
});
|
|
3805
|
-
watch(this.
|
|
3806
|
-
const showOverlayOn = this.
|
|
3932
|
+
watch(this._store.value.change, () => {
|
|
3933
|
+
const showOverlayOn = this._store.props.showOverlayOn();
|
|
3807
3934
|
const type = "change";
|
|
3808
|
-
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this
|
|
3935
|
+
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
3809
3936
|
});
|
|
3810
3937
|
alienEffect(() => {
|
|
3811
|
-
if (this.
|
|
3812
|
-
this.
|
|
3938
|
+
if (this.match()) {
|
|
3939
|
+
this._store.nodes.input.target = this._store.nodes.focus.target;
|
|
3813
3940
|
listen(window, "keydown", (e) => {
|
|
3814
|
-
if (e.key === KEYBOARD.ESC) this.
|
|
3941
|
+
if (e.key === KEYBOARD.ESC) this.close();
|
|
3815
3942
|
});
|
|
3816
3943
|
listen(document, "click", (e) => {
|
|
3817
3944
|
const target = e.target instanceof HTMLElement ? e.target : null;
|
|
3818
|
-
if (this.
|
|
3819
|
-
if (this.
|
|
3820
|
-
this.
|
|
3945
|
+
if (this.element()?.contains(target)) return;
|
|
3946
|
+
if (this._store.slots.container()?.contains(target)) return;
|
|
3947
|
+
this.close();
|
|
3821
3948
|
}, true);
|
|
3822
3949
|
}
|
|
3823
3950
|
});
|
|
3824
3951
|
const selectionChangeHandler = () => {
|
|
3825
|
-
if (!this.
|
|
3826
|
-
const showOverlayOn = this.
|
|
3952
|
+
if (!this._store.slots.container()?.contains(document.activeElement)) return;
|
|
3953
|
+
const showOverlayOn = this._store.props.showOverlayOn();
|
|
3827
3954
|
const type = "selectionChange";
|
|
3828
|
-
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this
|
|
3955
|
+
if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
|
|
3829
3956
|
};
|
|
3830
3957
|
listen(document, "selectionchange", selectionChangeHandler);
|
|
3958
|
+
watch(this.select, (overlayEvent) => {
|
|
3959
|
+
const Mark = this._store.props.Mark();
|
|
3960
|
+
const onChange = this._store.props.onChange();
|
|
3961
|
+
const { mark, match: { option, span, index, source } } = overlayEvent;
|
|
3962
|
+
const markup = option.markup;
|
|
3963
|
+
if (!markup) return;
|
|
3964
|
+
const annotation = mark.type === "mark" ? annotate(markup, {
|
|
3965
|
+
value: mark.value,
|
|
3966
|
+
meta: mark.meta
|
|
3967
|
+
}) : annotate(markup, { value: mark.content });
|
|
3968
|
+
const newSpan = createNewSpan(span, annotation, index, source);
|
|
3969
|
+
this._store.caret.recovery(Mark ? {
|
|
3970
|
+
caret: 0,
|
|
3971
|
+
anchor: this._store.nodes.input.next,
|
|
3972
|
+
isNext: true,
|
|
3973
|
+
childIndex: this._store.nodes.input.index
|
|
3974
|
+
} : {
|
|
3975
|
+
caret: index + annotation.length,
|
|
3976
|
+
anchor: this._store.nodes.input
|
|
3977
|
+
});
|
|
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
|
+
});
|
|
3831
3989
|
});
|
|
3832
3990
|
}
|
|
3833
3991
|
disable() {
|
|
@@ -3866,67 +4024,86 @@ function navigateSuggestions(key, activeIndex, length) {
|
|
|
3866
4024
|
}
|
|
3867
4025
|
}
|
|
3868
4026
|
//#endregion
|
|
3869
|
-
//#region ../../core/src/
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
}
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
}
|
|
4027
|
+
//#region ../../core/src/features/props/PropsFeature.ts
|
|
4028
|
+
var PropsFeature = class {
|
|
4029
|
+
constructor(_store) {
|
|
4030
|
+
this._store = _store;
|
|
4031
|
+
this.value = signal(void 0, { readonly: true });
|
|
4032
|
+
this.defaultValue = signal(void 0, { readonly: true });
|
|
4033
|
+
this.onChange = signal(void 0, { readonly: true });
|
|
4034
|
+
this.options = signal(DEFAULT_OPTIONS, { readonly: true });
|
|
4035
|
+
this.readOnly = signal(false, { readonly: true });
|
|
4036
|
+
this.layout = signal("inline", { readonly: true });
|
|
4037
|
+
this.draggable = signal(false, { readonly: true });
|
|
4038
|
+
this.showOverlayOn = signal("change", { readonly: true });
|
|
4039
|
+
this.Span = signal(void 0, { readonly: true });
|
|
4040
|
+
this.Mark = signal(void 0, { readonly: true });
|
|
4041
|
+
this.Overlay = signal(void 0, { readonly: true });
|
|
4042
|
+
this.className = signal(void 0, { readonly: true });
|
|
4043
|
+
this.style = signal(void 0, {
|
|
4044
|
+
equals: shallow,
|
|
4045
|
+
readonly: true
|
|
4046
|
+
});
|
|
4047
|
+
this.slots = signal(void 0, { readonly: true });
|
|
4048
|
+
this.slotProps = signal(void 0, { readonly: true });
|
|
4049
|
+
}
|
|
4050
|
+
set(values) {
|
|
4051
|
+
batch(() => {
|
|
4052
|
+
for (const key of Object.keys(values)) {
|
|
4053
|
+
if (!(key in this)) continue;
|
|
4054
|
+
this[key](values[key]);
|
|
4055
|
+
}
|
|
4056
|
+
}, { mutable: true });
|
|
4057
|
+
}
|
|
4058
|
+
};
|
|
3899
4059
|
//#endregion
|
|
3900
|
-
//#region ../../core/src/features/
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
4060
|
+
//#region ../../core/src/features/value/ValueFeature.ts
|
|
4061
|
+
var ValueFeature = class {
|
|
4062
|
+
#scope;
|
|
4063
|
+
constructor(_store) {
|
|
4064
|
+
this._store = _store;
|
|
4065
|
+
this.last = signal(void 0);
|
|
4066
|
+
this.next = signal(void 0);
|
|
4067
|
+
this.current = computed(() => this.last() ?? this._store.props.value() ?? "");
|
|
4068
|
+
this.change = event();
|
|
4069
|
+
}
|
|
4070
|
+
enable() {
|
|
4071
|
+
if (this.#scope) return;
|
|
4072
|
+
this.#scope = effectScope(() => {
|
|
4073
|
+
watch(this.change, () => {
|
|
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
|
+
});
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
disable() {
|
|
4103
|
+
this.#scope?.();
|
|
4104
|
+
this.#scope = void 0;
|
|
4105
|
+
}
|
|
3905
4106
|
};
|
|
3906
|
-
function resolveSlot(slotName, slots) {
|
|
3907
|
-
return slots?.[slotName] ?? defaultSlots[slotName];
|
|
3908
|
-
}
|
|
3909
|
-
function resolveSlotProps(slotName, slotProps) {
|
|
3910
|
-
const props = slotProps?.[slotName];
|
|
3911
|
-
return props ? convertDataAttrs(props) : void 0;
|
|
3912
|
-
}
|
|
3913
|
-
function resolveOverlaySlot(globalComponent, option, defaultComponent) {
|
|
3914
|
-
const Component = option?.Overlay ?? globalComponent ?? defaultComponent;
|
|
3915
|
-
if (!Component) throw new Error("No overlay component found. Provide either option.Overlay, global Overlay, or a defaultComponent.");
|
|
3916
|
-
return [Component, resolveOptionSlot(option?.overlay, {})];
|
|
3917
|
-
}
|
|
3918
|
-
function resolveMarkSlot(token, tokenOptions, GlobalMark, GlobalSpan) {
|
|
3919
|
-
if (token.type === "text") return [GlobalSpan ?? "span", GlobalSpan ? { value: token.content } : {}];
|
|
3920
|
-
const option = tokenOptions?.[token.descriptor.index];
|
|
3921
|
-
const baseProps = {
|
|
3922
|
-
value: token.value,
|
|
3923
|
-
meta: token.meta
|
|
3924
|
-
};
|
|
3925
|
-
const props = resolveOptionSlot(option?.mark, baseProps);
|
|
3926
|
-
const Component = option?.Mark ?? GlobalMark;
|
|
3927
|
-
if (!Component) throw new Error("No mark component found. Provide either option.Mark or global Mark.");
|
|
3928
|
-
return [Component, props];
|
|
3929
|
-
}
|
|
3930
4107
|
//#endregion
|
|
3931
4108
|
//#region ../../core/src/shared/utils/menuUtils.ts
|
|
3932
4109
|
/**
|
|
@@ -3945,17 +4122,41 @@ function isEscapeKey(e) {
|
|
|
3945
4122
|
//#endregion
|
|
3946
4123
|
//#region ../../core/src/store/BlockStore.ts
|
|
3947
4124
|
var BlockStore = class {
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
4125
|
+
constructor() {
|
|
4126
|
+
this.refs = { container: null };
|
|
4127
|
+
this.state = {
|
|
4128
|
+
isHovered: signal(false),
|
|
4129
|
+
isDragging: signal(false),
|
|
4130
|
+
dropPosition: signal(null),
|
|
4131
|
+
menuOpen: signal(false),
|
|
4132
|
+
menuPosition: signal({
|
|
4133
|
+
top: 0,
|
|
4134
|
+
left: 0
|
|
4135
|
+
})
|
|
4136
|
+
};
|
|
4137
|
+
this.closeMenu = () => this.state.menuOpen(false);
|
|
4138
|
+
this.addBlock = () => {
|
|
4139
|
+
this.#emit({
|
|
4140
|
+
type: "add",
|
|
4141
|
+
afterIndex: this.#blockIndex
|
|
4142
|
+
});
|
|
4143
|
+
this.closeMenu();
|
|
4144
|
+
};
|
|
4145
|
+
this.deleteBlock = () => {
|
|
4146
|
+
this.#emit({
|
|
4147
|
+
type: "delete",
|
|
4148
|
+
index: this.#blockIndex
|
|
4149
|
+
});
|
|
4150
|
+
this.closeMenu();
|
|
4151
|
+
};
|
|
4152
|
+
this.duplicateBlock = () => {
|
|
4153
|
+
this.#emit({
|
|
4154
|
+
type: "duplicate",
|
|
4155
|
+
index: this.#blockIndex
|
|
4156
|
+
});
|
|
4157
|
+
this.closeMenu();
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
3959
4160
|
#blockIndex = 0;
|
|
3960
4161
|
#dragAction = null;
|
|
3961
4162
|
#cleanupContainer;
|
|
@@ -3963,7 +4164,7 @@ var BlockStore = class {
|
|
|
3963
4164
|
#cleanupMenu;
|
|
3964
4165
|
attachContainer(el, blockIndex, actions) {
|
|
3965
4166
|
this.#blockIndex = blockIndex;
|
|
3966
|
-
this.#dragAction = actions.
|
|
4167
|
+
this.#dragAction = actions.action;
|
|
3967
4168
|
if (el === this.refs.container) return;
|
|
3968
4169
|
this.#cleanupContainer?.();
|
|
3969
4170
|
this.refs.container = el;
|
|
@@ -4009,7 +4210,7 @@ var BlockStore = class {
|
|
|
4009
4210
|
}
|
|
4010
4211
|
attachGrip(el, blockIndex, actions) {
|
|
4011
4212
|
this.#blockIndex = blockIndex;
|
|
4012
|
-
this.#dragAction = actions.
|
|
4213
|
+
this.#dragAction = actions.action;
|
|
4013
4214
|
this.#cleanupGrip?.();
|
|
4014
4215
|
if (!el) return;
|
|
4015
4216
|
const onDragStart = (e) => {
|
|
@@ -4057,28 +4258,6 @@ var BlockStore = class {
|
|
|
4057
4258
|
document.removeEventListener("keydown", onKeyDown);
|
|
4058
4259
|
};
|
|
4059
4260
|
}
|
|
4060
|
-
closeMenu = () => this.state.menuOpen(false);
|
|
4061
|
-
addBlock = () => {
|
|
4062
|
-
this.#emit({
|
|
4063
|
-
type: "add",
|
|
4064
|
-
afterIndex: this.#blockIndex
|
|
4065
|
-
});
|
|
4066
|
-
this.closeMenu();
|
|
4067
|
-
};
|
|
4068
|
-
deleteBlock = () => {
|
|
4069
|
-
this.#emit({
|
|
4070
|
-
type: "delete",
|
|
4071
|
-
index: this.#blockIndex
|
|
4072
|
-
});
|
|
4073
|
-
this.closeMenu();
|
|
4074
|
-
};
|
|
4075
|
-
duplicateBlock = () => {
|
|
4076
|
-
this.#emit({
|
|
4077
|
-
type: "duplicate",
|
|
4078
|
-
index: this.#blockIndex
|
|
4079
|
-
});
|
|
4080
|
-
this.closeMenu();
|
|
4081
|
-
};
|
|
4082
4261
|
#emit(action) {
|
|
4083
4262
|
this.#dragAction?.(action);
|
|
4084
4263
|
}
|
|
@@ -4098,218 +4277,53 @@ var BlockRegistry = class {
|
|
|
4098
4277
|
};
|
|
4099
4278
|
//#endregion
|
|
4100
4279
|
//#region ../../core/src/store/Store.ts
|
|
4101
|
-
const DRAG_HANDLE_WIDTH = 24;
|
|
4102
|
-
function buildContainerProps(isDraggableBlock, readOnly, className, style, slotProps) {
|
|
4103
|
-
const containerSlotProps = slotProps?.container;
|
|
4104
|
-
const baseStyle = merge(style, containerSlotProps?.style);
|
|
4105
|
-
const mergedStyle = isDraggableBlock && !readOnly ? {
|
|
4106
|
-
paddingLeft: DRAG_HANDLE_WIDTH,
|
|
4107
|
-
...baseStyle
|
|
4108
|
-
} : baseStyle;
|
|
4109
|
-
const { className: _, style: __, ...otherSlotProps } = resolveSlotProps("container", slotProps) ?? {};
|
|
4110
|
-
return {
|
|
4111
|
-
className: cx(styles.Container, className, containerSlotProps?.className),
|
|
4112
|
-
style: mergedStyle,
|
|
4113
|
-
...otherSlotProps
|
|
4114
|
-
};
|
|
4115
|
-
}
|
|
4116
4280
|
var Store = class {
|
|
4117
|
-
key = new KeyGenerator();
|
|
4118
|
-
blocks = new BlockRegistry();
|
|
4119
|
-
nodes = {
|
|
4120
|
-
focus: new NodeProxy(void 0, this),
|
|
4121
|
-
input: new NodeProxy(void 0, this)
|
|
4122
|
-
};
|
|
4123
|
-
props = {
|
|
4124
|
-
value: signal(void 0, { readonly: true }),
|
|
4125
|
-
defaultValue: signal(void 0, { readonly: true }),
|
|
4126
|
-
onChange: signal(void 0, { readonly: true }),
|
|
4127
|
-
options: signal(DEFAULT_OPTIONS, { readonly: true }),
|
|
4128
|
-
readOnly: signal(false, { readonly: true }),
|
|
4129
|
-
layout: signal("inline", { readonly: true }),
|
|
4130
|
-
draggable: signal(false, { readonly: true }),
|
|
4131
|
-
showOverlayOn: signal("change", { readonly: true }),
|
|
4132
|
-
Span: signal(void 0, { readonly: true }),
|
|
4133
|
-
Mark: signal(void 0, { readonly: true }),
|
|
4134
|
-
Overlay: signal(void 0, { readonly: true }),
|
|
4135
|
-
className: signal(void 0, { readonly: true }),
|
|
4136
|
-
style: signal(void 0, {
|
|
4137
|
-
equals: shallow,
|
|
4138
|
-
readonly: true
|
|
4139
|
-
}),
|
|
4140
|
-
slots: signal(void 0, { readonly: true }),
|
|
4141
|
-
slotProps: signal(void 0, { readonly: true })
|
|
4142
|
-
};
|
|
4143
|
-
state = {
|
|
4144
|
-
tokens: signal([]),
|
|
4145
|
-
previousValue: signal(void 0),
|
|
4146
|
-
innerValue: signal(void 0),
|
|
4147
|
-
recovery: signal(void 0),
|
|
4148
|
-
selecting: signal(void 0),
|
|
4149
|
-
overlayMatch: signal(void 0)
|
|
4150
|
-
};
|
|
4151
|
-
computed = {
|
|
4152
|
-
hasMark: computed(() => {
|
|
4153
|
-
if (this.props.Mark()) return true;
|
|
4154
|
-
return this.props.options().some((opt) => "Mark" in opt && opt.Mark != null);
|
|
4155
|
-
}),
|
|
4156
|
-
isBlock: computed(() => this.props.layout() === "block"),
|
|
4157
|
-
isDraggable: computed(() => !!this.props.draggable()),
|
|
4158
|
-
parser: computed(() => {
|
|
4159
|
-
if (!this.computed.hasMark()) return;
|
|
4160
|
-
const markups = this.props.options().map((opt) => opt.markup);
|
|
4161
|
-
if (!markups.some(Boolean)) return;
|
|
4162
|
-
return new Parser(markups, this.computed.isBlock() ? { skipEmptyText: true } : void 0);
|
|
4163
|
-
}),
|
|
4164
|
-
currentValue: computed(() => this.state.previousValue() ?? this.props.value() ?? ""),
|
|
4165
|
-
containerComponent: computed(() => resolveSlot("container", this.props.slots())),
|
|
4166
|
-
containerProps: computed(() => buildContainerProps(this.computed.isDraggable() && this.computed.isBlock(), this.props.readOnly(), this.props.className(), this.props.style(), this.props.slotProps()), { equals: shallow }),
|
|
4167
|
-
blockComponent: computed(() => resolveSlot("block", this.props.slots())),
|
|
4168
|
-
blockProps: computed(() => resolveSlotProps("block", this.props.slotProps())),
|
|
4169
|
-
spanComponent: computed(() => resolveSlot("span", this.props.slots())),
|
|
4170
|
-
spanProps: computed(() => resolveSlotProps("span", this.props.slotProps())),
|
|
4171
|
-
overlay: computed(() => {
|
|
4172
|
-
const Overlay = this.props.Overlay();
|
|
4173
|
-
return (option, defaultComponent) => resolveOverlaySlot(Overlay, option, defaultComponent);
|
|
4174
|
-
}),
|
|
4175
|
-
mark: computed(() => {
|
|
4176
|
-
const options = this.props.options();
|
|
4177
|
-
const Mark = this.props.Mark();
|
|
4178
|
-
const Span = this.props.Span();
|
|
4179
|
-
return (token) => resolveMarkSlot(token, options, Mark, Span);
|
|
4180
|
-
})
|
|
4181
|
-
};
|
|
4182
|
-
event = {
|
|
4183
|
-
change: event(),
|
|
4184
|
-
parse: event(),
|
|
4185
|
-
delete: event(),
|
|
4186
|
-
select: event(),
|
|
4187
|
-
clearOverlay: event(),
|
|
4188
|
-
checkOverlay: event(),
|
|
4189
|
-
sync: event(),
|
|
4190
|
-
recoverFocus: event(),
|
|
4191
|
-
dragAction: event(),
|
|
4192
|
-
updated: event(),
|
|
4193
|
-
afterTokensRendered: event(),
|
|
4194
|
-
mounted: event(),
|
|
4195
|
-
unmounted: event()
|
|
4196
|
-
};
|
|
4197
|
-
refs = {
|
|
4198
|
-
container: null,
|
|
4199
|
-
overlay: null
|
|
4200
|
-
};
|
|
4201
|
-
handler = new MarkputHandler(this);
|
|
4202
|
-
features = {
|
|
4203
|
-
overlay: new OverlayFeature(this),
|
|
4204
|
-
focus: new FocusFeature(this),
|
|
4205
|
-
input: new InputFeature(this),
|
|
4206
|
-
blockEditing: new BlockEditFeature(this),
|
|
4207
|
-
arrowNav: new ArrowNavFeature(this),
|
|
4208
|
-
system: new SystemListenerFeature(this),
|
|
4209
|
-
textSelection: new TextSelectionFeature(this),
|
|
4210
|
-
contentEditable: new ContentEditableFeature(this),
|
|
4211
|
-
drag: new DragFeature(this),
|
|
4212
|
-
copy: new CopyFeature(this),
|
|
4213
|
-
parse: new ParseFeature(this)
|
|
4214
|
-
};
|
|
4215
4281
|
constructor() {
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
this.
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
this
|
|
4249
|
-
|
|
4250
|
-
get content() {
|
|
4251
|
-
return this.#token.content;
|
|
4252
|
-
}
|
|
4253
|
-
set content(value) {
|
|
4254
|
-
this.#token.content = value;
|
|
4255
|
-
this.#emitChange();
|
|
4256
|
-
}
|
|
4257
|
-
get value() {
|
|
4258
|
-
return this.#token.value;
|
|
4259
|
-
}
|
|
4260
|
-
set value(v) {
|
|
4261
|
-
this.#token.value = v ?? "";
|
|
4262
|
-
this.#emitChange();
|
|
4263
|
-
}
|
|
4264
|
-
get meta() {
|
|
4265
|
-
return this.#token.meta;
|
|
4266
|
-
}
|
|
4267
|
-
set meta(v) {
|
|
4268
|
-
this.#token.meta = v;
|
|
4269
|
-
this.#emitChange();
|
|
4270
|
-
}
|
|
4271
|
-
get slot() {
|
|
4272
|
-
return this.#token.slot?.content;
|
|
4273
|
-
}
|
|
4274
|
-
get #tokenInfo() {
|
|
4275
|
-
return findToken(this.#store.state.tokens(), this.#token);
|
|
4276
|
-
}
|
|
4277
|
-
get depth() {
|
|
4278
|
-
return this.#tokenInfo?.depth ?? 0;
|
|
4279
|
-
}
|
|
4280
|
-
get hasChildren() {
|
|
4281
|
-
return this.#token.children.some((child) => child.type === "mark");
|
|
4282
|
-
}
|
|
4283
|
-
get parent() {
|
|
4284
|
-
return this.#tokenInfo?.parent;
|
|
4285
|
-
}
|
|
4286
|
-
get tokens() {
|
|
4287
|
-
return this.#token.children;
|
|
4288
|
-
}
|
|
4289
|
-
change = (props) => {
|
|
4290
|
-
this.#token.content = props.content;
|
|
4291
|
-
this.#token.value = props.value ?? "";
|
|
4292
|
-
if (props.meta !== void 0) this.#token.meta = props.meta;
|
|
4293
|
-
this.#emitChange();
|
|
4294
|
-
};
|
|
4295
|
-
remove = () => this.#store.event.delete({ token: this.#token });
|
|
4296
|
-
#emitChange() {
|
|
4297
|
-
this.#store.event.change();
|
|
4282
|
+
this.key = new KeyGenerator();
|
|
4283
|
+
this.blocks = new BlockRegistry();
|
|
4284
|
+
this.nodes = {
|
|
4285
|
+
focus: new NodeProxy(void 0, this),
|
|
4286
|
+
input: new NodeProxy(void 0, this)
|
|
4287
|
+
};
|
|
4288
|
+
this.props = new PropsFeature(this);
|
|
4289
|
+
this.handler = new MarkputHandler(this);
|
|
4290
|
+
this.lifecycle = new LifecycleFeature(this);
|
|
4291
|
+
this.value = new ValueFeature(this);
|
|
4292
|
+
this.mark = new MarkFeature(this);
|
|
4293
|
+
this.overlay = new OverlayFeature(this);
|
|
4294
|
+
this.slots = new SlotsFeature(this);
|
|
4295
|
+
this.caret = new CaretFeature(this);
|
|
4296
|
+
this.keyboard = new KeyboardFeature(this);
|
|
4297
|
+
this.dom = new DomFeature(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()));
|
|
4298
4316
|
}
|
|
4299
4317
|
};
|
|
4300
4318
|
//#endregion
|
|
4301
4319
|
//#region src/lib/providers/StoreContext.ts
|
|
4302
4320
|
const StoreContext = createContext(void 0);
|
|
4303
4321
|
StoreContext.displayName = "StoreContext";
|
|
4304
|
-
function useStore() {
|
|
4305
|
-
const store = useContext(StoreContext);
|
|
4306
|
-
if (store === void 0) throw new Error("Store not found. Make sure to wrap component in StoreContext.");
|
|
4307
|
-
return store;
|
|
4308
|
-
}
|
|
4309
4322
|
//#endregion
|
|
4310
4323
|
//#region src/lib/hooks/useMarkput.ts
|
|
4311
4324
|
function useMarkput(selector) {
|
|
4312
|
-
const store =
|
|
4325
|
+
const store = useContext(StoreContext);
|
|
4326
|
+
if (store === void 0) throw new Error("Store not found. Make sure to wrap component in StoreContext.");
|
|
4313
4327
|
const stableRef = useRef(null);
|
|
4314
4328
|
if (stableRef.current === null) {
|
|
4315
4329
|
const target = selector(store);
|
|
@@ -4365,10 +4379,10 @@ const Popup = ({ ref, style, children }) => {
|
|
|
4365
4379
|
//#endregion
|
|
4366
4380
|
//#region src/components/BlockMenu.tsx
|
|
4367
4381
|
const BlockMenu = memo(({ token }) => {
|
|
4368
|
-
const blockStore =
|
|
4369
|
-
|
|
4370
|
-
menuOpen:
|
|
4371
|
-
menuPosition:
|
|
4382
|
+
const { blockStore, menuOpen, menuPosition } = useMarkput((s) => ({
|
|
4383
|
+
blockStore: s.blocks.get(token),
|
|
4384
|
+
menuOpen: s.blocks.get(token).state.menuOpen,
|
|
4385
|
+
menuPosition: s.blocks.get(token).state.menuPosition
|
|
4372
4386
|
}));
|
|
4373
4387
|
if (!menuOpen) return null;
|
|
4374
4388
|
return /* @__PURE__ */ jsx(Popup, {
|
|
@@ -4398,20 +4412,20 @@ BlockMenu.displayName = "BlockMenu";
|
|
|
4398
4412
|
//#region src/components/DragHandle.tsx
|
|
4399
4413
|
const iconGrip = `${styles$1.Icon} ${styles$1.IconGrip}`;
|
|
4400
4414
|
const DragHandle = memo(({ token, blockIndex }) => {
|
|
4401
|
-
const
|
|
4402
|
-
|
|
4403
|
-
|
|
4415
|
+
const { blockStore, action, readOnly, draggable, isDragging, isHovered } = useMarkput((s) => ({
|
|
4416
|
+
blockStore: s.blocks.get(token),
|
|
4417
|
+
action: s.drag.action,
|
|
4404
4418
|
readOnly: s.props.readOnly,
|
|
4405
|
-
draggable: s.props.draggable
|
|
4419
|
+
draggable: s.props.draggable,
|
|
4420
|
+
isDragging: s.blocks.get(token).state.isDragging,
|
|
4421
|
+
isHovered: s.blocks.get(token).state.isHovered
|
|
4406
4422
|
}));
|
|
4407
|
-
const isDragging = useMarkput(() => blockStore.state.isDragging);
|
|
4408
|
-
const isHovered = useMarkput(() => blockStore.state.isHovered);
|
|
4409
4423
|
const alwaysShowHandle = useMemo(() => getAlwaysShowHandle(draggable), [draggable]);
|
|
4410
4424
|
if (readOnly) return null;
|
|
4411
4425
|
return /* @__PURE__ */ jsx("div", {
|
|
4412
4426
|
className: cx(styles$1.SidePanel, alwaysShowHandle ? styles$1.SidePanelAlways : isHovered && !isDragging && styles$1.SidePanelVisible),
|
|
4413
4427
|
children: /* @__PURE__ */ jsx("button", {
|
|
4414
|
-
ref: (el) => blockStore.attachGrip(el, blockIndex,
|
|
4428
|
+
ref: (el) => blockStore.attachGrip(el, blockIndex, { action }),
|
|
4415
4429
|
type: "button",
|
|
4416
4430
|
draggable: true,
|
|
4417
4431
|
className: cx(styles$1.GripButton, isDragging && styles$1.GripButtonDragging),
|
|
@@ -4424,8 +4438,7 @@ DragHandle.displayName = "DragHandle";
|
|
|
4424
4438
|
//#endregion
|
|
4425
4439
|
//#region src/components/DropIndicator.tsx
|
|
4426
4440
|
const DropIndicator = memo(({ token, position }) => {
|
|
4427
|
-
|
|
4428
|
-
if (useMarkput(() => blockStore.state.dropPosition) !== position) return null;
|
|
4441
|
+
if (useMarkput((s) => s.blocks.get(token).state.dropPosition) !== position) return null;
|
|
4429
4442
|
return /* @__PURE__ */ jsx("div", {
|
|
4430
4443
|
className: styles$1.DropIndicator,
|
|
4431
4444
|
style: position === "before" ? { top: -1 } : { bottom: -1 }
|
|
@@ -4444,12 +4457,15 @@ function useToken() {
|
|
|
4444
4457
|
//#endregion
|
|
4445
4458
|
//#region src/components/Token.tsx
|
|
4446
4459
|
const Token = memo(({ mark }) => {
|
|
4447
|
-
const
|
|
4448
|
-
|
|
4460
|
+
const { resolveMarkSlot, key } = useMarkput((s) => ({
|
|
4461
|
+
resolveMarkSlot: s.mark.slot,
|
|
4462
|
+
key: s.key
|
|
4463
|
+
}));
|
|
4464
|
+
const [Component, props] = resolveMarkSlot(mark);
|
|
4449
4465
|
return /* @__PURE__ */ jsx(TokenContext, {
|
|
4450
4466
|
value: mark,
|
|
4451
4467
|
children: /* @__PURE__ */ jsx(Component, {
|
|
4452
|
-
children: mark.type === "mark" && mark.children.length > 0 ? mark.children.map((child) => /* @__PURE__ */ jsx(Token, { mark: child },
|
|
4468
|
+
children: mark.type === "mark" && mark.children.length > 0 ? mark.children.map((child) => /* @__PURE__ */ jsx(Token, { mark: child }, key.get(child))) : void 0,
|
|
4453
4469
|
...props
|
|
4454
4470
|
})
|
|
4455
4471
|
});
|
|
@@ -4457,14 +4473,18 @@ const Token = memo(({ mark }) => {
|
|
|
4457
4473
|
Token.displayName = "Token";
|
|
4458
4474
|
//#endregion
|
|
4459
4475
|
//#region src/components/Block.tsx
|
|
4460
|
-
const Block = memo(({ token
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4476
|
+
const Block = memo(({ token }) => {
|
|
4477
|
+
const { blockStore, action, Component, slotProps, isDragging, tokens } = useMarkput((s) => ({
|
|
4478
|
+
blockStore: s.blocks.get(token),
|
|
4479
|
+
action: s.drag.action,
|
|
4480
|
+
Component: s.slots.blockComponent,
|
|
4481
|
+
slotProps: s.slots.blockProps,
|
|
4482
|
+
isDragging: s.blocks.get(token).state.isDragging,
|
|
4483
|
+
tokens: s.parsing.tokens
|
|
4484
|
+
}));
|
|
4485
|
+
const blockIndex = tokens.indexOf(token);
|
|
4466
4486
|
return /* @__PURE__ */ jsxs(Component, {
|
|
4467
|
-
ref: (el) => blockStore.attachContainer(el, blockIndex,
|
|
4487
|
+
ref: (el) => blockStore.attachContainer(el, blockIndex, { action }),
|
|
4468
4488
|
"data-testid": "block",
|
|
4469
4489
|
...slotProps,
|
|
4470
4490
|
className: cx(styles$1.Block, slotProps?.className),
|
|
@@ -4494,33 +4514,38 @@ Block.displayName = "Block";
|
|
|
4494
4514
|
//#endregion
|
|
4495
4515
|
//#region src/components/Container.tsx
|
|
4496
4516
|
const Container = memo(() => {
|
|
4497
|
-
const
|
|
4498
|
-
|
|
4499
|
-
|
|
4517
|
+
const storeCtx = useContext(StoreContext);
|
|
4518
|
+
if (!storeCtx) throw new Error("Store not found");
|
|
4519
|
+
const store = storeCtx;
|
|
4520
|
+
const { isBlock, tokens, key, lifecycleEmit, Component, props } = useMarkput((s) => ({
|
|
4521
|
+
isBlock: s.slots.isBlock,
|
|
4522
|
+
tokens: s.parsing.tokens,
|
|
4500
4523
|
key: s.key,
|
|
4501
|
-
|
|
4502
|
-
|
|
4524
|
+
lifecycleEmit: s.lifecycle,
|
|
4525
|
+
Component: s.slots.containerComponent,
|
|
4526
|
+
props: s.slots.containerProps
|
|
4503
4527
|
}));
|
|
4504
|
-
const Component = useMarkput((s) => s.computed.containerComponent);
|
|
4505
|
-
const props = useMarkput((s) => s.computed.containerProps);
|
|
4506
4528
|
useLayoutEffect(() => {
|
|
4507
|
-
|
|
4508
|
-
}, [tokens,
|
|
4529
|
+
lifecycleEmit.rendered();
|
|
4530
|
+
}, [tokens, lifecycleEmit]);
|
|
4509
4531
|
return /* @__PURE__ */ jsx(Component, {
|
|
4510
|
-
ref:
|
|
4532
|
+
ref: store.slots.container,
|
|
4511
4533
|
...props,
|
|
4512
|
-
children:
|
|
4513
|
-
token: t,
|
|
4514
|
-
blockIndex: i
|
|
4515
|
-
}, key.get(t))) : tokens.map((t) => /* @__PURE__ */ jsx(Token, { mark: t }, key.get(t)))
|
|
4534
|
+
children: isBlock ? tokens.map((t) => /* @__PURE__ */ jsx(Block, { token: t }, key.get(t))) : tokens.map((t) => /* @__PURE__ */ jsx(Token, { mark: t }, key.get(t)))
|
|
4516
4535
|
});
|
|
4517
4536
|
});
|
|
4518
4537
|
Container.displayName = "Container";
|
|
4519
4538
|
//#endregion
|
|
4520
4539
|
//#region src/lib/hooks/useOverlay.tsx
|
|
4521
4540
|
function useOverlay() {
|
|
4522
|
-
const
|
|
4523
|
-
const
|
|
4541
|
+
const match = useMarkput((s) => s.overlay.match);
|
|
4542
|
+
const storeRef = useRef(null);
|
|
4543
|
+
if (storeRef.current === null) {
|
|
4544
|
+
const ctx = useContext(StoreContext);
|
|
4545
|
+
if (!ctx) throw new Error("Store not found");
|
|
4546
|
+
storeRef.current = ctx;
|
|
4547
|
+
}
|
|
4548
|
+
const store = storeRef.current;
|
|
4524
4549
|
const style = useMemo(() => {
|
|
4525
4550
|
if (!match) return {
|
|
4526
4551
|
left: 0,
|
|
@@ -4528,34 +4553,36 @@ function useOverlay() {
|
|
|
4528
4553
|
};
|
|
4529
4554
|
return Caret.getAbsolutePosition();
|
|
4530
4555
|
}, [match]);
|
|
4531
|
-
const close = useCallback(() => store.
|
|
4556
|
+
const close = useCallback(() => store.overlay.close(), [store]);
|
|
4532
4557
|
return {
|
|
4533
4558
|
match,
|
|
4534
4559
|
style,
|
|
4535
4560
|
select: useCallback((value) => {
|
|
4536
4561
|
if (!match) return;
|
|
4537
4562
|
const mark = createMarkFromOverlay(match, value.value, value.meta);
|
|
4538
|
-
store.
|
|
4563
|
+
store.overlay.select({
|
|
4539
4564
|
mark,
|
|
4540
4565
|
match
|
|
4541
4566
|
});
|
|
4542
|
-
store.
|
|
4543
|
-
}, [match]),
|
|
4567
|
+
store.overlay.close();
|
|
4568
|
+
}, [match, store]),
|
|
4544
4569
|
close,
|
|
4545
4570
|
ref: useMemo(() => ({
|
|
4546
4571
|
get current() {
|
|
4547
|
-
return store.
|
|
4572
|
+
return store.overlay.element();
|
|
4548
4573
|
},
|
|
4549
4574
|
set current(v) {
|
|
4550
|
-
store.
|
|
4575
|
+
store.overlay.element(v);
|
|
4551
4576
|
}
|
|
4552
|
-
}), [])
|
|
4577
|
+
}), [store])
|
|
4553
4578
|
};
|
|
4554
4579
|
}
|
|
4555
4580
|
//#endregion
|
|
4556
4581
|
//#region src/components/Suggestions/Suggestions.tsx
|
|
4557
4582
|
const Suggestions = () => {
|
|
4558
|
-
const
|
|
4583
|
+
const storeCtx = useContext(StoreContext);
|
|
4584
|
+
if (!storeCtx) throw new Error("Store not found");
|
|
4585
|
+
const store = storeCtx;
|
|
4559
4586
|
const { match, select, style, ref } = useOverlay();
|
|
4560
4587
|
const [active, setActive] = useState(NaN);
|
|
4561
4588
|
const data = match?.option.overlay?.data ?? [];
|
|
@@ -4566,7 +4593,7 @@ const Suggestions = () => {
|
|
|
4566
4593
|
const filteredRef = useRef(filtered);
|
|
4567
4594
|
filteredRef.current = filtered;
|
|
4568
4595
|
useEffect(() => {
|
|
4569
|
-
const container = store.
|
|
4596
|
+
const container = store.slots.container();
|
|
4570
4597
|
if (!container) return;
|
|
4571
4598
|
const handler = (event) => {
|
|
4572
4599
|
const result = navigateSuggestions(event.key, activeRef.current, length);
|
|
@@ -4608,10 +4635,13 @@ const Suggestions = () => {
|
|
|
4608
4635
|
//#endregion
|
|
4609
4636
|
//#region src/components/OverlayRenderer.tsx
|
|
4610
4637
|
const OverlayRenderer = memo(() => {
|
|
4611
|
-
const
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4638
|
+
const { match, key: keyGen, resolveOverlay } = useMarkput((s) => ({
|
|
4639
|
+
match: s.overlay.match,
|
|
4640
|
+
key: s.key,
|
|
4641
|
+
resolveOverlay: s.overlay.slot
|
|
4642
|
+
}));
|
|
4643
|
+
const key = useMemo(() => match ? keyGen.get(match.option) : void 0, [match]);
|
|
4644
|
+
const [Overlay, props] = resolveOverlay(match?.option, Suggestions);
|
|
4615
4645
|
if (!key) return;
|
|
4616
4646
|
return /* @__PURE__ */ jsx(Overlay, { ...props }, key);
|
|
4617
4647
|
});
|
|
@@ -4620,12 +4650,11 @@ OverlayRenderer.displayName = "OverlayRenderer";
|
|
|
4620
4650
|
//#region src/components/MarkedInput.tsx
|
|
4621
4651
|
function MarkedInput(props) {
|
|
4622
4652
|
const [store] = useState(() => new Store());
|
|
4623
|
-
store.
|
|
4653
|
+
store.props.set(props);
|
|
4624
4654
|
useLayoutEffect(() => {
|
|
4625
|
-
store.
|
|
4626
|
-
return () => store.
|
|
4655
|
+
store.lifecycle.mounted();
|
|
4656
|
+
return () => store.lifecycle.unmounted();
|
|
4627
4657
|
}, []);
|
|
4628
|
-
useLayoutEffect(() => store.event.updated());
|
|
4629
4658
|
useImperativeHandle(props.ref, () => store.handler, [store]);
|
|
4630
4659
|
return /* @__PURE__ */ jsxs(StoreContext, {
|
|
4631
4660
|
value: store,
|
|
@@ -4635,7 +4664,10 @@ function MarkedInput(props) {
|
|
|
4635
4664
|
//#endregion
|
|
4636
4665
|
//#region src/lib/hooks/useMark.tsx
|
|
4637
4666
|
const useMark = (options = {}) => {
|
|
4638
|
-
const store =
|
|
4667
|
+
const { store, readOnly } = useMarkput((s) => ({
|
|
4668
|
+
store: s,
|
|
4669
|
+
readOnly: s.props.readOnly
|
|
4670
|
+
}));
|
|
4639
4671
|
const token = useToken();
|
|
4640
4672
|
if (token.type !== "mark") throw new Error("useMark must be called within a mark token context");
|
|
4641
4673
|
const ref = useRef(null);
|
|
@@ -4645,7 +4677,6 @@ const useMark = (options = {}) => {
|
|
|
4645
4677
|
token
|
|
4646
4678
|
}), [store, token]);
|
|
4647
4679
|
useUncontrolledInit(ref, options, token);
|
|
4648
|
-
const readOnly = useMarkput((s) => s.props.readOnly);
|
|
4649
4680
|
useEffect(() => {
|
|
4650
4681
|
mark.readOnly = readOnly;
|
|
4651
4682
|
}, [mark, readOnly]);
|