@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.js CHANGED
@@ -58,10 +58,10 @@ var MarkputHandler = class {
58
58
  this.store = store;
59
59
  }
60
60
  get container() {
61
- return this.store.refs.container;
61
+ return this.store.slots.container();
62
62
  }
63
63
  get overlay() {
64
- return this.store.refs.overlay;
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/parsing/parser/constants.ts
961
- /**
962
- * Constants for ParserV2 - modern markup parser with nesting support
963
- *
964
- * This module contains the placeholder constants used by ParserV2.
965
- * Unlike the legacy Parser, ParserV2 supports:
966
- * - `__value__` - main content (replaces `__label__`)
967
- * - `__meta__` - metadata (replaces `__value__`)
968
- * - `__slot__` - nested content (new feature)
969
- *
970
- * For legacy Parser compatibility, see ../Parser/constants.ts
971
- * For Markup types, see ./types.ts
972
- */
973
- const PLACEHOLDER = {
974
- Value: "__value__",
975
- Meta: "__meta__",
976
- Slot: "__slot__"
977
- };
978
- /**
979
- * Gap types used in markup descriptors
980
- * Represents the content type in gaps between segments
981
- */
982
- const GAP_TYPE = {
983
- Value: "value",
984
- Meta: "meta",
985
- Slot: "slot"
986
- };
987
- //#endregion
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
- * Maps placeholder types to their text representations
1021
- */
1022
- const PLACEHOLDER_TEXT = {
1023
- [GAP_TYPE.Value]: PLACEHOLDER.Value,
1024
- [GAP_TYPE.Meta]: PLACEHOLDER.Meta,
1025
- [GAP_TYPE.Slot]: PLACEHOLDER.Slot
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.computed.parser();
2150
- const tokens = store.state.tokens();
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.state.previousValue();
2305
+ const previousValue = store.value.last();
2159
2306
  const gap = findGap(previousValue, value);
2160
2307
  if (!gap.left && !gap.right) {
2161
- store.state.previousValue(value);
2162
- return store.state.tokens();
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.state.previousValue(value);
2312
+ store.value.last(value);
2166
2313
  return parseWithParser(store, value ?? "");
2167
2314
  }
2168
- store.state.previousValue(value);
2315
+ store.value.last(value);
2169
2316
  const ranges = getRangeMap(store);
2170
- const tokens = store.state.tokens();
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.state.tokens();
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.state.tokens().map((token) => {
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.computed.parser();
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 ParseFeature = class {
2370
+ var ParsingFeature = class {
2224
2371
  #scope;
2225
- constructor(store) {
2226
- this.store = store;
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 { store } = this;
2242
- const inputValue = store.props.value() ?? store.props.defaultValue() ?? "";
2243
- store.state.tokens(parseWithParser(store, inputValue));
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
- const { store } = this;
2248
- watch(store.event.parse, () => {
2249
- if (store.state.recovery()) {
2250
- const text = toString(store.state.tokens());
2251
- store.state.tokens(parseWithParser(store, text));
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
- store.state.tokens(store.nodes.focus.target ? getTokensByUI(store) : computeTokensFromValue(store));
2408
+ this.tokens(this._store.nodes.focus.target ? getTokensByUI(this._store) : computeTokensFromValue(this._store));
2256
2409
  });
2257
2410
  }
2258
2411
  #subscribeReactiveParse() {
2259
- const { store } = this;
2260
- watch(computed(() => [store.props.value(), store.computed.parser()]), () => {
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.refs.container;
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.state.tokens();
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/CopyFeature.ts
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 CopyFeature = class {
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.refs.container;
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.computed.currentValue();
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.state.innerValue(newValue);
2546
- const newTokens = this.store.state.tokens();
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.state.recovery({
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.refs.container;
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/editing/utils/createNewSpan.ts
2674
- function createNewSpan(span, annotation, index, source) {
2675
- return span.slice(0, index) + annotation + span.slice(index + source.length);
2676
- }
2677
- //#endregion
2678
- //#region ../../core/src/features/editing/createRowContent.ts
2679
- function createRowContent(options) {
2680
- const firstOption = options[0];
2681
- if (!firstOption.markup) return "\n";
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/editable/ContentEditableFeature.ts
2735
- var ContentEditableFeature = class {
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.#scope) return;
2742
- this.#scope = effectScope(() => {
2743
- alienEffect(() => {
2744
- this.store.props.readOnly();
2745
- this.sync();
2746
- });
2747
- alienEffect(() => {
2748
- if (this.store.state.selecting() === void 0) this.sync();
2749
- });
2750
- watch(this.store.event.sync, () => {
2751
- this.sync();
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.#scope?.();
2757
- this.#scope = void 0;
2894
+ this.#unsub?.();
2895
+ this.#unsub = void 0;
2758
2896
  }
2759
- sync() {
2760
- const container = this.store.refs.container;
2761
- if (!container) return;
2762
- const readOnly = this.store.props.readOnly();
2763
- const value = readOnly ? "false" : "true";
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
- #syncTextContent(tokens, parent) {
2784
- for (let i = 0; i < tokens.length; i++) {
2785
- const token = tokens[i];
2786
- const el = childAt(parent, i);
2787
- if (!el) continue;
2788
- if (token.type === "text") {
2789
- if (el.textContent !== token.content) el.textContent = token.content;
2790
- } else if (token.children.length > 0) this.#syncMarkChildren(token.children, el);
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
- #syncMarkChildren(tokens, parent, editable) {
2794
- const children = parent.children;
2795
- let childIdx = 0;
2796
- for (const token of tokens) if (token.type === "text") {
2797
- while (childIdx < children.length) {
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
- #syncDragTextContent(tokens, container, readOnly) {
2822
- const editable = readOnly ? "false" : "true";
2823
- for (let ri = 0; ri < tokens.length; ri++) {
2824
- const token = tokens[ri];
2825
- const blockEl = childAt(container, ri);
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/features/block-editing/rawPosition.ts
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/block-editing/BlockEditFeature.ts
2968
- function isTextLikeRow(token) {
2969
- if (token.type === "text") return true;
2970
- return token.descriptor.hasSlot && token.descriptor.segments.length === 1;
2971
- }
2972
- var BlockEditFeature = class {
2973
- #scope;
2974
- constructor(store) {
2975
- this.store = store;
2976
- }
2977
- enable() {
2978
- if (this.#scope) return;
2979
- const container = this.store.refs.container;
2980
- if (!container) return;
2981
- this.#scope = effectScope(() => {
2982
- listen(container, "keydown", (e) => {
2983
- if (!this.store.computed.isBlock()) return;
2984
- if (e.key === KEYBOARD.LEFT || e.key === KEYBOARD.RIGHT) this.#handleBlockArrowLeftRight(e, e.key === KEYBOARD.LEFT ? "left" : "right");
2985
- else if (e.key === KEYBOARD.UP || e.key === KEYBOARD.DOWN) this.#handleArrowUpDown(e);
2986
- this.#handleDelete(e);
2987
- this.#handleEnter(e);
2988
- });
2989
- listen(container, "beforeinput", (e) => {
2990
- if (!this.store.computed.isBlock()) return;
2991
- if (e.defaultPrevented) return;
2992
- this.#handleBlockBeforeInput(e);
2993
- }, true);
2994
- });
2995
- }
2996
- disable() {
2997
- this.#scope?.();
2998
- this.#scope = void 0;
2999
- }
3000
- #handleDelete(event) {
3001
- const container = this.store.refs.container;
3002
- if (!container) return;
3003
- const blockDivs = htmlChildren(container);
3004
- const blockIndex = blockDivs.findIndex((div) => div === document.activeElement || div.contains(document.activeElement));
3005
- if (blockIndex === -1) return;
3006
- const rows = this.store.state.tokens();
3007
- if (blockIndex >= rows.length) return;
3008
- const token = rows[blockIndex];
3009
- const value = this.store.computed.currentValue();
3010
- if (!this.store.props.onChange()) return;
3011
- if (event.key === KEYBOARD.BACKSPACE) {
3012
- const blockDiv = blockDivs[blockIndex];
3013
- const caretAtStart = Caret.getCaretIndex(blockDiv) === 0;
3014
- if (("content" in token ? token.content : "") === "") {
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 newValue = rows.length <= 1 ? "" : (() => {
3017
- if (blockIndex >= rows.length - 1) return value.slice(0, rows[blockIndex - 1].position.end);
3018
- return value.slice(0, rows[blockIndex].position.start) + value.slice(rows[blockIndex + 1].position.start);
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, Math.max(0, blockIndex - 1));
3180
+ const target = childAt(container, blockIndex - 1);
3023
3181
  if (target) {
3024
3182
  target.focus();
3025
- Caret.setCaretToEnd(target);
3183
+ const updatedToken = store.parsing.tokens()[blockIndex - 1];
3184
+ setCaretAtRawPos(target, updatedToken, joinPos);
3026
3185
  }
3027
3186
  });
3028
3187
  return;
3029
3188
  }
3030
- if (caretAtStart && blockIndex > 0) {
3031
- const prevToken = rows[blockIndex - 1];
3032
- const currToken = rows[blockIndex];
3033
- if (canMergeRows(prevToken, currToken)) {
3034
- event.preventDefault();
3035
- const joinPos = getMergeDragRowJoinPos(rows, blockIndex);
3036
- const newValue = mergeDragRows(value, rows, blockIndex);
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
- if (event.key === KEYBOARD.DELETE) {
3058
- const blockDiv = blockDivs[blockIndex];
3059
- const caretIndex = Caret.getCaretIndex(blockDiv);
3060
- const caretAtEnd = caretIndex === blockDiv.textContent.length;
3061
- if (caretIndex === 0 && blockIndex > 0) {
3062
- const prevToken = rows[blockIndex - 1];
3063
- const currToken = rows[blockIndex];
3064
- if (canMergeRows(prevToken, currToken)) {
3065
- event.preventDefault();
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 = blockDivs[blockIndex - 1];
3082
- target.focus();
3083
- if (prevToken.type !== "mark") Caret.setCaretToEnd(target);
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
- if (caretAtEnd && blockIndex < rows.length - 1) {
3088
- const currToken = rows[blockIndex];
3089
- const nextToken = rows[blockIndex + 1];
3090
- if (canMergeRows(currToken, nextToken)) {
3091
- event.preventDefault();
3092
- const joinPos = getMergeDragRowJoinPos(rows, blockIndex + 1);
3093
- const newValue = mergeDragRows(value, rows, blockIndex + 1);
3094
- this.store.state.innerValue(newValue);
3095
- queueMicrotask(() => {
3096
- const target = childAt(container, blockIndex);
3097
- if (target) {
3098
- target.focus();
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 = blockDivs[blockIndex + 1];
3108
- target.focus();
3109
- Caret.trySetIndex(target, 0);
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 newBlockIndex = blockIndex + 1;
3141
- if (newBlockIndex < container.children.length) {
3142
- const newBlockEl = childAt(container, newBlockIndex);
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
- const absolutePos = getCaretRawPosInBlock(blockDiv, token);
3152
- const newValue = value.slice(0, absolutePos) + newRowContent + value.slice(absolutePos);
3153
- this.store.state.innerValue(newValue);
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
- #handleBlockArrowLeftRight(event, direction) {
3166
- const container = this.store.refs.container;
3167
- if (!container) return false;
3168
- const activeElement = document.activeElement;
3169
- if (!isHtmlElement(activeElement) || !container.contains(activeElement)) return false;
3170
- const blockDivs = htmlChildren(container);
3171
- const blockIndex = blockDivs.findIndex((div) => div === activeElement || div.contains(activeElement));
3172
- if (blockIndex === -1) return false;
3173
- const blockDiv = blockDivs[blockIndex];
3174
- if (direction === "left") {
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
- this.store.nodes.focus.target = target;
3528
- target?.focus();
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
- if (!target?.isConnected) return;
3531
- this.store.nodes.focus.target = target;
3532
- this.store.nodes.focus.caret = caret;
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
- this.store.state.recovery(void 0);
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/InputFeature.ts
3539
- var InputFeature = class {
3540
- #scope;
3541
- constructor(store) {
3542
- this.store = store;
3543
- }
3544
- enable() {
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
- disable() {
3563
- this.#scope?.();
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("self", this.store);
3470
+ deleteMark("prev", store);
3576
3471
  return;
3577
3472
  }
3578
- if (event.key === KEYBOARD.BACKSPACE) {
3579
- if (focus.isSpan && focus.isCaretAtBeginning && focus.prev.target) {
3580
- event.preventDefault();
3581
- deleteMark("prev", this.store);
3582
- return;
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
- if (event.key === KEYBOARD.DELETE) {
3586
- if (focus.isSpan && focus.isCaretAtEnd && focus.next.target) {
3587
- event.preventDefault();
3588
- deleteMark("next", this.store);
3589
- return;
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 (focus.isSpan && focus.isEditable && window.getSelection()?.isCollapsed) {
3593
- const content = focus.content;
3594
- const caret = focus.caret;
3595
- if (event.key === KEYBOARD.BACKSPACE && caret > 0) {
3596
- event.preventDefault();
3597
- focus.content = content.slice(0, caret - 1) + content.slice(caret);
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.state.selecting();
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.state.selecting(void 0);
3624
- if (store.computed.isBlock()) return;
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.event.change();
3516
+ if (applySpanInput(focus, event)) store.value.change();
3629
3517
  }
3630
3518
  function handleMarkputSpanPaste(store, focus, event) {
3631
- const container = store.refs.container;
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.state.tokens()[focus.index];
3524
+ const token = store.parsing.tokens()[focus.index];
3637
3525
  const offset = focus.caret;
3638
- const currentValue = store.computed.currentValue();
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.state.innerValue(newValue);
3655
- const newTokens = store.state.tokens();
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.state.recovery({
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.state.selecting();
3610
+ const selecting = store.caret.selecting();
3723
3611
  if (selecting !== "all" || !isFullSelection(store)) {
3724
- if (selecting === "all") store.state.selecting(void 0);
3612
+ if (selecting === "all") store.caret.selecting(void 0);
3725
3613
  return;
3726
3614
  }
3727
3615
  event.preventDefault();
3728
- replaceAllContentWith(store, (store.refs.container ? consumeMarkupPaste(store.refs.container) : void 0) ?? event.clipboardData?.getData("text/plain") ?? "");
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.state.selecting(void 0);
3733
- store.state.previousValue(newContent);
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.state.tokens(store.computed.parser()?.parse(newContent) ?? [{
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.refs.container?.firstChild;
3633
+ const rawFirstChild = store.slots.container()?.firstChild;
3745
3634
  const firstChild = isHtmlElement(rawFirstChild) ? rawFirstChild : null;
3746
3635
  if (firstChild) {
3747
- store.state.recovery({
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(store) {
3793
- this.store = store;
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.store.event.clearOverlay, () => {
3799
- this.store.state.overlayMatch(void 0);
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.store.event.change, () => {
3806
- const showOverlayOn = this.store.props.showOverlayOn();
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.store.event.checkOverlay();
3935
+ if (showOverlayOn === type || Array.isArray(showOverlayOn) && showOverlayOn.includes(type)) this.#probeTrigger();
3809
3936
  });
3810
3937
  alienEffect(() => {
3811
- if (this.store.state.overlayMatch()) {
3812
- this.store.nodes.input.target = this.store.nodes.focus.target;
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.store.event.clearOverlay();
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.store.refs.overlay?.contains(target)) return;
3819
- if (this.store.refs.container?.contains(target)) return;
3820
- this.store.event.clearOverlay();
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.store.refs.container?.contains(document.activeElement)) return;
3826
- const showOverlayOn = this.store.props.showOverlayOn();
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.store.event.checkOverlay();
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/shared/utils/dataAttributes.ts
3870
- /**
3871
- * Converts object with camelCase data attribute keys to kebab-case data-* attributes
3872
- *
3873
- * Takes keys like 'dataUserId' and converts to 'data-user-id'
3874
- *
3875
- * @param obj - Object potentially containing camelCase data attribute keys
3876
- * @returns New object with converted data attributes
3877
- *
3878
- * @example
3879
- * convertDataAttrs({ dataUserId: '123', dataUserName: 'John', className: 'test' })
3880
- * // Returns: { 'data-user-id': '123', 'data-user-name': 'John', className: 'test' }
3881
- *
3882
- * convertDataAttrs({ dataTestId * @example
3883
- : 'test', dataFoo: 'bar' })
3884
- * // Returns: { 'data-test-id': 'test', 'data-foo': 'bar' }
3885
- */
3886
- function convertDataAttrs(obj) {
3887
- if (!obj) return {};
3888
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
3889
- 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];
3890
- return [key, value];
3891
- }));
3892
- }
3893
- //#endregion
3894
- //#region ../../core/src/features/slots/resolveOptionSlot.ts
3895
- function resolveOptionSlot(optionConfig, baseProps) {
3896
- if (optionConfig !== void 0) return typeof optionConfig === "function" ? optionConfig(baseProps) : optionConfig;
3897
- return baseProps;
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/slots/resolveSlot.ts
3901
- const defaultSlots = {
3902
- container: "div",
3903
- block: "div",
3904
- span: "span"
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
- refs = { container: null };
3949
- state = {
3950
- isHovered: signal(false),
3951
- isDragging: signal(false),
3952
- dropPosition: signal(null),
3953
- menuOpen: signal(false),
3954
- menuPosition: signal({
3955
- top: 0,
3956
- left: 0
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.dragAction;
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.dragAction;
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
- watch(this.event.mounted, () => Object.values(this.features).forEach((f) => f.enable()));
4217
- watch(this.event.unmounted, () => Object.values(this.features).forEach((f) => f.disable()));
4218
- }
4219
- setProps(values) {
4220
- batch(() => {
4221
- const props = this.props;
4222
- for (const key of Object.keys(values)) {
4223
- if (!(key in props)) continue;
4224
- props[key](values[key]);
4225
- }
4226
- }, { mutable: true });
4227
- }
4228
- bumpTokens() {
4229
- this.state.tokens([...this.state.tokens()]);
4230
- }
4231
- };
4232
- //#endregion
4233
- //#region ../../core/src/features/mark/MarkHandler.ts
4234
- var MarkHandler = class {
4235
- ref;
4236
- #store;
4237
- #token;
4238
- #readOnly;
4239
- constructor(param) {
4240
- this.ref = param.ref;
4241
- this.#store = param.store;
4242
- this.#token = param.token;
4243
- }
4244
- get readOnly() {
4245
- return this.#readOnly;
4246
- }
4247
- set readOnly(value) {
4248
- this.#readOnly = value;
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 = useStore();
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 = useStore().blocks.get(token);
4369
- const { menuOpen, menuPosition } = useMarkput(() => ({
4370
- menuOpen: blockStore.state.menuOpen,
4371
- menuPosition: blockStore.state.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 store = useStore();
4402
- const blockStore = store.blocks.get(token);
4403
- const { readOnly, draggable } = useMarkput((s) => ({
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, store.event),
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
- const blockStore = useStore().blocks.get(token);
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 store = useStore();
4448
- const [Component, props] = useMarkput((s) => s.computed.mark)(mark);
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 }, store.key.get(child))) : void 0,
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, blockIndex }) => {
4461
- const store = useStore();
4462
- const blockStore = store.blocks.get(token);
4463
- const Component = useMarkput((s) => s.computed.blockComponent);
4464
- const slotProps = useMarkput((s) => s.computed.blockProps);
4465
- const isDragging = useMarkput(() => blockStore.state.isDragging);
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, store.event),
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 { layout, tokens, key, refs, event } = useMarkput((s) => ({
4498
- layout: s.props.layout,
4499
- tokens: s.state.tokens,
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
- refs: s.refs,
4502
- event: s.event
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
- event.afterTokensRendered();
4508
- }, [tokens, event]);
4529
+ lifecycleEmit.rendered();
4530
+ }, [tokens, lifecycleEmit]);
4509
4531
  return /* @__PURE__ */ jsx(Component, {
4510
- ref: (el) => refs.container = el,
4532
+ ref: store.slots.container,
4511
4533
  ...props,
4512
- children: layout === "block" ? tokens.map((t, i) => /* @__PURE__ */ jsx(Block, {
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 store = useStore();
4523
- const match = useMarkput((s) => s.state.overlayMatch);
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.event.clearOverlay(), []);
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.event.select({
4563
+ store.overlay.select({
4539
4564
  mark,
4540
4565
  match
4541
4566
  });
4542
- store.event.clearOverlay();
4543
- }, [match]),
4567
+ store.overlay.close();
4568
+ }, [match, store]),
4544
4569
  close,
4545
4570
  ref: useMemo(() => ({
4546
4571
  get current() {
4547
- return store.refs.overlay;
4572
+ return store.overlay.element();
4548
4573
  },
4549
4574
  set current(v) {
4550
- store.refs.overlay = v;
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 store = useStore();
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.refs.container;
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 store = useStore();
4612
- const overlayMatch = useMarkput((s) => s.state.overlayMatch);
4613
- const key = useMemo(() => overlayMatch ? store.key.get(overlayMatch.option) : void 0, [overlayMatch]);
4614
- const [Overlay, props] = useMarkput((s) => s.computed.overlay)(overlayMatch?.option, Suggestions);
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.setProps(props);
4653
+ store.props.set(props);
4624
4654
  useLayoutEffect(() => {
4625
- store.event.mounted();
4626
- return () => store.event.unmounted();
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 = useStore();
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]);