@almadar/ui 6.8.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1740,7 +1740,7 @@ var init_Button = __esm({
1740
1740
  secondary: [
1741
1741
  "bg-transparent text-accent",
1742
1742
  "border border-accent",
1743
- "hover:bg-accent hover:text-white hover:border-accent",
1743
+ "hover:bg-accent hover:text-accent-foreground hover:border-accent",
1744
1744
  "active:scale-[var(--active-scale)]"
1745
1745
  ].join(" "),
1746
1746
  ghost: [
@@ -2080,8 +2080,17 @@ var init_Typography = __esm({
2080
2080
  weight && weightStyles[weight],
2081
2081
  size && typographySizeStyles[size],
2082
2082
  align && `text-${align}`,
2083
- truncate && "truncate overflow-hidden text-ellipsis",
2084
- overflow && overflowStyles2[overflow],
2083
+ // `min-w-0` rides along with truncate/wrap/clamp: a flex or grid
2084
+ // item's default `min-width: auto` refuses to shrink below its
2085
+ // content's intrinsic width, so ellipsis/wrap silently does nothing
2086
+ // in the single most common placement (a row next to a fixed-width
2087
+ // control) unless the item can also shrink to zero. (Spelled out as
2088
+ // just "truncate", not "truncate overflow-hidden text-ellipsis" —
2089
+ // tailwind-merge treats those as the same conflict group and drops
2090
+ // "truncate" as the earlier-declared class, silently losing its
2091
+ // `white-space: nowrap`.)
2092
+ truncate && "truncate min-w-0",
2093
+ overflow && cn(overflowStyles2[overflow], overflow !== "visible" && "min-w-0"),
2085
2094
  className
2086
2095
  ),
2087
2096
  style
@@ -10422,6 +10431,10 @@ var init_paintDispatch = __esm({
10422
10431
  });
10423
10432
 
10424
10433
  // lib/drawable/hitTest.ts
10434
+ function shapeDrawnItem(n) {
10435
+ if (n.shape !== "rect") return { pos: n.position, id: n.id };
10436
+ return { pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotate };
10437
+ }
10425
10438
  function collectDrawnItems(nodes) {
10426
10439
  const out = [];
10427
10440
  for (const n of nodes) {
@@ -10430,6 +10443,8 @@ function collectDrawnItems(nodes) {
10430
10443
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotation });
10431
10444
  break;
10432
10445
  case "draw-shape":
10446
+ if (isValidScenePos(n.position)) out.push(shapeDrawnItem(n));
10447
+ break;
10433
10448
  case "draw-text":
10434
10449
  case "draw-group":
10435
10450
  case "draw-mesh":
@@ -10441,6 +10456,10 @@ function collectDrawnItems(nodes) {
10441
10456
  }
10442
10457
  break;
10443
10458
  case "draw-shape-layer":
10459
+ for (const it of n.items) {
10460
+ if (isValidScenePos(it.position)) out.push(shapeDrawnItem(it));
10461
+ }
10462
+ break;
10444
10463
  case "draw-text-layer":
10445
10464
  for (const it of n.items) {
10446
10465
  if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
@@ -17559,6 +17578,12 @@ var init_EmptyState = __esm({
17559
17578
  });
17560
17579
 
17561
17580
  // lib/editorMotions.ts
17581
+ function isEditorMotion(value) {
17582
+ return EDITOR_MOTION_SET.has(value);
17583
+ }
17584
+ function isEditorOperator(value) {
17585
+ return EDITOR_OPERATOR_SET.has(value);
17586
+ }
17562
17587
  function clamp(value, min, max) {
17563
17588
  return Math.max(min, Math.min(max, value));
17564
17589
  }
@@ -17626,6 +17651,30 @@ function prevParagraphBoundary(lines, fromLineIdx) {
17626
17651
  }
17627
17652
  return 0;
17628
17653
  }
17654
+ function matchBracket(text, pos) {
17655
+ const ch = text[pos];
17656
+ const partner = ch !== void 0 ? BRACKET_PARTNER[ch] : void 0;
17657
+ if (partner === void 0) return null;
17658
+ let depth = 1;
17659
+ if (OPEN_BRACKETS.has(ch)) {
17660
+ for (let i = pos + 1; i < text.length; i++) {
17661
+ if (text[i] === ch) depth++;
17662
+ else if (text[i] === partner) {
17663
+ depth--;
17664
+ if (depth === 0) return i;
17665
+ }
17666
+ }
17667
+ } else {
17668
+ for (let i = pos - 1; i >= 0; i--) {
17669
+ if (text[i] === ch) depth++;
17670
+ else if (text[i] === partner) {
17671
+ depth--;
17672
+ if (depth === 0) return i;
17673
+ }
17674
+ }
17675
+ }
17676
+ return null;
17677
+ }
17629
17678
  function applyMotion(text, caret, motion, count) {
17630
17679
  const n = Math.max(1, count);
17631
17680
  const lines = computeLines(text);
@@ -17687,6 +17736,20 @@ function applyMotion(text, caret, motion, count) {
17687
17736
  }
17688
17737
  return pos;
17689
17738
  }
17739
+ case "match-bracket": {
17740
+ const chAtCaret = text[caret];
17741
+ if (chAtCaret !== void 0 && BRACKET_PARTNER[chAtCaret] !== void 0) {
17742
+ const target = matchBracket(text, caret);
17743
+ return target === null ? caret : target;
17744
+ }
17745
+ for (let i = caret; i < line.end; i++) {
17746
+ if (BRACKET_PARTNER[text[i]] !== void 0) {
17747
+ const target = matchBracket(text, i);
17748
+ return target === null ? caret : target;
17749
+ }
17750
+ }
17751
+ return caret;
17752
+ }
17690
17753
  case "line":
17691
17754
  case "selection":
17692
17755
  return caret;
@@ -17714,8 +17777,8 @@ function motionRange(text, caret, motion, count, selection) {
17714
17777
  const newCaret = applyMotion(text, caret, motion, count);
17715
17778
  let start = Math.min(caret, newCaret);
17716
17779
  let end = Math.max(caret, newCaret);
17717
- if (motion === "word-end" || motion === "line-end") {
17718
- end = Math.min(Math.max(start, newCaret) + 1, text.length);
17780
+ if ((motion === "word-end" || motion === "line-end" || motion === "match-bracket") && newCaret !== caret) {
17781
+ end = Math.min(Math.max(caret, newCaret) + 1, text.length);
17719
17782
  } else if (motion === "word-forward" && newCaret > caret) {
17720
17783
  const startLine = lineIndexAt(lines, caret);
17721
17784
  const endLine = lineIndexAt(lines, newCaret);
@@ -17725,17 +17788,198 @@ function motionRange(text, caret, motion, count, selection) {
17725
17788
  }
17726
17789
  return [start, end];
17727
17790
  }
17728
- function applyOperator(text, range, operator, register) {
17729
- const start = clamp(range[0], 0, text.length);
17730
- const end = clamp(range[1], start, text.length);
17731
- const removed = text.slice(start, end);
17732
- if (operator === "yank") {
17733
- return { text, caret: start, register: removed };
17791
+ function toggleCase(s) {
17792
+ return s.replace(/[a-zA-Z]/g, (c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase());
17793
+ }
17794
+ function applyJoin(text, caret, count, register, registerLinewise) {
17795
+ const lines = computeLines(text);
17796
+ const startIdx = lineIndexAt(lines, caret);
17797
+ const n = Math.max(2, count);
17798
+ const endIdx = clamp(startIdx + n - 1, 0, lines.length - 1);
17799
+ if (endIdx <= startIdx) {
17800
+ return { text, caret, register, registerLinewise };
17801
+ }
17802
+ const joinCaret = lines[startIdx].end;
17803
+ let joined = text.slice(lines[startIdx].start, lines[startIdx].end);
17804
+ for (let i = startIdx + 1; i <= endIdx; i++) {
17805
+ const raw = text.slice(lines[i].start, lines[i].end);
17806
+ joined += " " + raw.replace(/^[ \t]+/, "");
17807
+ }
17808
+ const newText = text.slice(0, lines[startIdx].start) + joined + text.slice(lines[endIdx].end);
17809
+ return { text: newText, caret: joinCaret, register, registerLinewise };
17810
+ }
17811
+ function applyIndent(text, start, end, operator, register, registerLinewise) {
17812
+ const lines = computeLines(text);
17813
+ const startLineIdx = lineIndexAt(lines, start);
17814
+ const lastTouchedPos = end > start ? end - 1 : start;
17815
+ const endLineIdx = lineIndexAt(lines, lastTouchedPos);
17816
+ let result = text;
17817
+ for (let i = endLineIdx; i >= startLineIdx; i--) {
17818
+ const lineStart = lines[i].start;
17819
+ if (operator === "indent") {
17820
+ result = result.slice(0, lineStart) + INDENT_UNIT + result.slice(lineStart);
17821
+ } else if (result[lineStart] === " ") {
17822
+ result = result.slice(0, lineStart) + result.slice(lineStart + 1);
17823
+ } else {
17824
+ let removeCount = 0;
17825
+ while (removeCount < INDENT_UNIT.length && result[lineStart + removeCount] === " ") removeCount++;
17826
+ result = result.slice(0, lineStart) + result.slice(lineStart + removeCount);
17827
+ }
17828
+ }
17829
+ const newLines = computeLines(result);
17830
+ const caret = firstNonBlank(result, newLines[startLineIdx]);
17831
+ return { text: result, caret, register, registerLinewise };
17832
+ }
17833
+ function applyPut(text, caret, operator, register, registerLinewise, count) {
17834
+ if (register.length === 0) {
17835
+ return { text, caret, register, registerLinewise };
17836
+ }
17837
+ const content = register.repeat(count);
17838
+ const lines = computeLines(text);
17839
+ const line = lines[lineIndexAt(lines, caret)];
17840
+ if (registerLinewise) {
17841
+ if (operator === "put-before") {
17842
+ const insertPos3 = line.start;
17843
+ return {
17844
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
17845
+ caret: insertPos3,
17846
+ register,
17847
+ registerLinewise
17848
+ };
17849
+ }
17850
+ const nextLineIdx = lineIndexAt(lines, caret) + 1;
17851
+ if (nextLineIdx < lines.length) {
17852
+ const insertPos3 = lines[nextLineIdx].start;
17853
+ return {
17854
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
17855
+ caret: insertPos3,
17856
+ register,
17857
+ registerLinewise
17858
+ };
17859
+ }
17860
+ const insertPos2 = text.length;
17861
+ return {
17862
+ text: text.slice(0, insertPos2) + "\n" + content,
17863
+ caret: insertPos2 + 1,
17864
+ register,
17865
+ registerLinewise
17866
+ };
17867
+ }
17868
+ const insertPos = operator === "put-before" ? clamp(caret, 0, text.length) : clamp(caret + 1, line.start, line.end);
17869
+ return {
17870
+ text: text.slice(0, insertPos) + content + text.slice(insertPos),
17871
+ caret: insertPos + content.length - 1,
17872
+ register,
17873
+ registerLinewise
17874
+ };
17875
+ }
17876
+ function applyOperator(input) {
17877
+ const { text, caret, operator, motion, register, registerLinewise } = input;
17878
+ const count = Math.max(1, input.count);
17879
+ const start = clamp(input.range[0], 0, text.length);
17880
+ const end = clamp(input.range[1], start, text.length);
17881
+ switch (operator) {
17882
+ case "yank":
17883
+ return { text, caret: start, register: text.slice(start, end), registerLinewise: motion === "line" };
17884
+ case "delete":
17885
+ case "replace": {
17886
+ const removed = text.slice(start, end);
17887
+ return {
17888
+ text: text.slice(0, start) + text.slice(end),
17889
+ caret: start,
17890
+ register: removed,
17891
+ registerLinewise: motion === "line"
17892
+ };
17893
+ }
17894
+ case "change": {
17895
+ if (motion === "line") {
17896
+ const hasTrailingNewline = end > start && text[end - 1] === "\n";
17897
+ const removeEnd = hasTrailingNewline ? end - 1 : end;
17898
+ const removed2 = text.slice(start, removeEnd);
17899
+ return {
17900
+ text: text.slice(0, start) + text.slice(removeEnd),
17901
+ caret: start,
17902
+ register: removed2,
17903
+ registerLinewise: true
17904
+ };
17905
+ }
17906
+ const removed = text.slice(start, end);
17907
+ return {
17908
+ text: text.slice(0, start) + text.slice(end),
17909
+ caret: start,
17910
+ register: removed,
17911
+ registerLinewise: false
17912
+ };
17913
+ }
17914
+ case "put":
17915
+ case "put-before":
17916
+ return applyPut(text, caret, operator, register, registerLinewise, count);
17917
+ case "join":
17918
+ return applyJoin(text, caret, count, register, registerLinewise);
17919
+ case "toggle-case": {
17920
+ const toggled = toggleCase(text.slice(start, end));
17921
+ return { text: text.slice(0, start) + toggled + text.slice(end), caret: end, register, registerLinewise };
17922
+ }
17923
+ case "indent":
17924
+ case "dedent":
17925
+ return applyIndent(text, start, end, operator, register, registerLinewise);
17926
+ case "undo":
17927
+ case "redo":
17928
+ return { text, caret, register, registerLinewise };
17929
+ default: {
17930
+ const _exhaustive = operator;
17931
+ return _exhaustive;
17932
+ }
17734
17933
  }
17735
- return { text: text.slice(0, start) + text.slice(end), caret: start, register: removed };
17736
17934
  }
17935
+ var EDITOR_MOTIONS, EDITOR_OPERATORS, EDITOR_MOTION_SET, EDITOR_OPERATOR_SET, INDENT_UNIT, BRACKET_PARTNER, OPEN_BRACKETS;
17737
17936
  var init_editorMotions = __esm({
17738
17937
  "lib/editorMotions.ts"() {
17938
+ EDITOR_MOTIONS = [
17939
+ "left",
17940
+ "right",
17941
+ "up",
17942
+ "down",
17943
+ "word-forward",
17944
+ "word-back",
17945
+ "word-end",
17946
+ "line-start",
17947
+ "line-end",
17948
+ "first-nonblank",
17949
+ "doc-start",
17950
+ "doc-end",
17951
+ "paragraph-forward",
17952
+ "paragraph-back",
17953
+ "line",
17954
+ "selection",
17955
+ "match-bracket"
17956
+ ];
17957
+ EDITOR_OPERATORS = [
17958
+ "delete",
17959
+ "yank",
17960
+ "change",
17961
+ "put",
17962
+ "put-before",
17963
+ "undo",
17964
+ "redo",
17965
+ "join",
17966
+ "toggle-case",
17967
+ "indent",
17968
+ "dedent",
17969
+ "replace"
17970
+ ];
17971
+ EDITOR_MOTION_SET = new Set(EDITOR_MOTIONS);
17972
+ EDITOR_OPERATOR_SET = new Set(EDITOR_OPERATORS);
17973
+ INDENT_UNIT = " ";
17974
+ BRACKET_PARTNER = {
17975
+ "(": ")",
17976
+ ")": "(",
17977
+ "[": "]",
17978
+ "]": "[",
17979
+ "{": "}",
17980
+ "}": "{"
17981
+ };
17982
+ OPEN_BRACKETS = /* @__PURE__ */ new Set(["(", "[", "{"]);
17739
17983
  }
17740
17984
  });
17741
17985
  function isMotionPayload(payload) {
@@ -17750,14 +17994,103 @@ function isInsertTextPayload(payload) {
17750
17994
  function isSetModePayload(payload) {
17751
17995
  return !!payload && typeof payload.editorId === "string" && typeof payload.mode === "string" && typeof payload.caret === "string";
17752
17996
  }
17997
+ function typedDelta(prev, next) {
17998
+ const maxPrefix = Math.min(prev.length, next.length);
17999
+ let prefixLen = 0;
18000
+ while (prefixLen < maxPrefix && prev[prefixLen] === next[prefixLen]) prefixLen++;
18001
+ const maxSuffix = Math.min(prev.length - prefixLen, next.length - prefixLen);
18002
+ let suffixLen = 0;
18003
+ while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
18004
+ suffixLen++;
18005
+ }
18006
+ const removed = prev.slice(prefixLen, prev.length - suffixLen);
18007
+ const inserted = next.slice(prefixLen, next.length - suffixLen);
18008
+ return removed + inserted;
18009
+ }
17753
18010
  function useEditorCapabilities(args) {
17754
18011
  const [caretMode, setCaretMode] = useState("bar");
17755
18012
  const registerRef = useRef("");
18013
+ const registerLinewiseRef = useRef(false);
18014
+ const pastRef = useRef([]);
18015
+ const futureRef = useRef([]);
18016
+ const openTypingStepRef = useRef(false);
18017
+ const insertSessionOpenRef = useRef(false);
18018
+ const closeOpenTypingStep = useCallback(() => {
18019
+ openTypingStepRef.current = false;
18020
+ }, []);
18021
+ const pushHistoryStep = useCallback(
18022
+ (text, caret) => {
18023
+ closeOpenTypingStep();
18024
+ pastRef.current.push({ text, caret });
18025
+ futureRef.current = [];
18026
+ },
18027
+ [closeOpenTypingStep]
18028
+ );
18029
+ const recordKeystroke = useCallback(
18030
+ (prevText, prevCaret, nextText) => {
18031
+ if (!openTypingStepRef.current) {
18032
+ pastRef.current.push({ text: prevText, caret: prevCaret });
18033
+ futureRef.current = [];
18034
+ openTypingStepRef.current = true;
18035
+ }
18036
+ if (!insertSessionOpenRef.current && /\s/.test(typedDelta(prevText, nextText))) {
18037
+ openTypingStepRef.current = false;
18038
+ }
18039
+ },
18040
+ []
18041
+ );
18042
+ const performUndo = useCallback(
18043
+ (count) => {
18044
+ const ta = args.textareaRef.current;
18045
+ if (!ta) return;
18046
+ closeOpenTypingStep();
18047
+ let moved = false;
18048
+ for (let i = 0; i < Math.max(1, count) && pastRef.current.length > 0; i++) {
18049
+ const prev = pastRef.current.pop();
18050
+ futureRef.current.push({ text: ta.value, caret: ta.selectionStart });
18051
+ ta.value = prev.text;
18052
+ ta.setSelectionRange(prev.caret, prev.caret);
18053
+ moved = true;
18054
+ }
18055
+ if (moved) args.applyChange(ta.value, "capability");
18056
+ },
18057
+ [args, closeOpenTypingStep]
18058
+ );
18059
+ const performRedo = useCallback(
18060
+ (count) => {
18061
+ const ta = args.textareaRef.current;
18062
+ if (!ta) return;
18063
+ closeOpenTypingStep();
18064
+ let moved = false;
18065
+ for (let i = 0; i < Math.max(1, count) && futureRef.current.length > 0; i++) {
18066
+ const next = futureRef.current.pop();
18067
+ pastRef.current.push({ text: ta.value, caret: ta.selectionStart });
18068
+ ta.value = next.text;
18069
+ ta.setSelectionRange(next.caret, next.caret);
18070
+ moved = true;
18071
+ }
18072
+ if (moved) args.applyChange(ta.value, "capability");
18073
+ },
18074
+ [args, closeOpenTypingStep]
18075
+ );
18076
+ const undo = useCallback(() => performUndo(1), [performUndo]);
18077
+ const redo = useCallback(() => performRedo(1), [performRedo]);
18078
+ const wasFocusedRef = useRef(args.focused);
18079
+ useEffect(() => {
18080
+ if (wasFocusedRef.current && !args.focused) {
18081
+ setCaretMode("bar");
18082
+ }
18083
+ wasFocusedRef.current = args.focused;
18084
+ }, [args.focused]);
17756
18085
  useEventListener(`UI:${args.events.onMotion}`, (evt) => {
17757
18086
  if (!args.editorId || !isMotionPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18087
+ const { motion, count } = evt.payload;
18088
+ if (!isEditorMotion(motion)) {
18089
+ console.warn(`useEditorCapabilities: ignoring MOTION with unknown motion "${motion}"`);
18090
+ return;
18091
+ }
17758
18092
  const ta = args.textareaRef.current;
17759
18093
  if (!ta) return;
17760
- const { motion, count } = evt.payload;
17761
18094
  const newCaret = applyMotion(ta.value, ta.selectionStart, motion, count);
17762
18095
  if (ta.selectionStart !== ta.selectionEnd) {
17763
18096
  ta.setSelectionRange(ta.selectionStart, newCaret);
@@ -17767,32 +18100,63 @@ function useEditorCapabilities(args) {
17767
18100
  });
17768
18101
  useEventListener(`UI:${args.events.onOperate}`, (evt) => {
17769
18102
  if (!args.editorId || !isOperatePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18103
+ const { operator, motion, count } = evt.payload;
18104
+ if (!isEditorOperator(operator)) {
18105
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown operator "${operator}"`);
18106
+ return;
18107
+ }
18108
+ if (!isEditorMotion(motion)) {
18109
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown motion "${motion}"`);
18110
+ return;
18111
+ }
17770
18112
  const ta = args.textareaRef.current;
17771
18113
  if (!ta) return;
17772
- const { operator, motion, count } = evt.payload;
18114
+ if (operator === "undo") {
18115
+ performUndo(count);
18116
+ return;
18117
+ }
18118
+ if (operator === "redo") {
18119
+ performRedo(count);
18120
+ return;
18121
+ }
18122
+ pushHistoryStep(ta.value, ta.selectionStart);
17773
18123
  const selection = motion === "selection" ? [ta.selectionStart, ta.selectionEnd] : void 0;
17774
18124
  const range = motionRange(ta.value, ta.selectionStart, motion, count, selection);
17775
- const result = applyOperator(ta.value, range, operator, registerRef.current);
18125
+ const result = applyOperator({
18126
+ text: ta.value,
18127
+ caret: ta.selectionStart,
18128
+ range,
18129
+ operator,
18130
+ motion,
18131
+ count,
18132
+ register: registerRef.current,
18133
+ registerLinewise: registerLinewiseRef.current
18134
+ });
17776
18135
  registerRef.current = result.register;
18136
+ registerLinewiseRef.current = result.registerLinewise;
17777
18137
  if (operator === "yank") {
17778
- ta.setSelectionRange(range[0], range[0]);
18138
+ ta.setSelectionRange(result.caret, result.caret);
17779
18139
  } else {
17780
- ta.setRangeText("", range[0], range[1], "end");
18140
+ ta.value = result.text;
18141
+ ta.setSelectionRange(result.caret, result.caret);
17781
18142
  }
17782
- args.applyChange(ta.value);
18143
+ args.applyChange(ta.value, "capability");
17783
18144
  });
17784
18145
  useEventListener(`UI:${args.events.onInsertText}`, (evt) => {
17785
18146
  if (!args.editorId || !isInsertTextPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
17786
18147
  const ta = args.textareaRef.current;
17787
18148
  if (!ta) return;
18149
+ pushHistoryStep(ta.value, ta.selectionStart);
17788
18150
  ta.setRangeText(evt.payload.text, ta.selectionStart, ta.selectionEnd, "end");
17789
- args.applyChange(ta.value);
18151
+ args.applyChange(ta.value, "capability");
17790
18152
  });
17791
18153
  useEventListener(`UI:${args.events.onSetMode}`, (evt) => {
17792
18154
  if (!args.editorId || !isSetModePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18155
+ closeOpenTypingStep();
18156
+ insertSessionOpenRef.current = evt.payload.mode === "INSERT";
17793
18157
  setCaretMode(evt.payload.caret);
17794
18158
  });
17795
- return { caretMode };
18159
+ return { caretMode, recordKeystroke, undo, redo };
17796
18160
  }
17797
18161
  var init_useEditorCapabilities = __esm({
17798
18162
  "components/core/molecules/markdown/useEditorCapabilities.ts"() {
@@ -18208,9 +18572,23 @@ var init_CodeBlock = __esm({
18208
18572
  "paragraph-forward",
18209
18573
  "paragraph-back",
18210
18574
  "line",
18211
- "selection"
18575
+ "selection",
18576
+ "match-bracket"
18212
18577
  ],
18213
- operators = ["delete", "yank", "change"]
18578
+ operators = [
18579
+ "delete",
18580
+ "yank",
18581
+ "change",
18582
+ "put",
18583
+ "put-before",
18584
+ "undo",
18585
+ "redo",
18586
+ "join",
18587
+ "toggle-case",
18588
+ "indent",
18589
+ "dedent",
18590
+ "replace"
18591
+ ]
18214
18592
  }) => {
18215
18593
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
18216
18594
  const activeStyle = resolveHighlightStyle(language);
@@ -18244,6 +18622,12 @@ var init_CodeBlock = __esm({
18244
18622
  const lastPropCodeRef = useRef(code);
18245
18623
  const editableTextareaRef = useRef(null);
18246
18624
  const editableOverlayRef = useRef(null);
18625
+ const [isFocused, setIsFocused] = useState(false);
18626
+ const prevCaretRef = useRef(0);
18627
+ const [caretIndex, setCaretIndex] = useState(0);
18628
+ const caretMirrorRef = useRef(null);
18629
+ const caretMarkerRef = useRef(null);
18630
+ const [caretGeometry, setCaretGeometry] = useState(null);
18247
18631
  useEffect(() => {
18248
18632
  if (code !== lastPropCodeRef.current) {
18249
18633
  lastPropCodeRef.current = code;
@@ -18259,23 +18643,77 @@ var init_CodeBlock = __esm({
18259
18643
  ov.scrollLeft = ta.scrollLeft;
18260
18644
  }
18261
18645
  }, []);
18262
- const handleEditableChange = useCallback((v) => {
18646
+ const handleEditableChange = useCallback((v, _origin) => {
18263
18647
  lastPropCodeRef.current = v;
18264
18648
  setEditableValue(v);
18649
+ const ta = editableTextareaRef.current;
18650
+ if (ta) setCaretIndex(ta.selectionStart);
18265
18651
  onChange?.(v);
18266
18652
  }, [onChange]);
18267
- const { caretMode } = useEditorCapabilities({
18653
+ const { caretMode, recordKeystroke, undo, redo } = useEditorCapabilities({
18268
18654
  editorId: editable ? editorId : void 0,
18269
18655
  textareaRef: editableTextareaRef,
18270
18656
  events: { onMotion, onOperate, onInsertText, onSetMode },
18657
+ focused: isFocused,
18271
18658
  applyChange: handleEditableChange
18272
18659
  });
18273
- const [caretIndex, setCaretIndex] = useState(0);
18274
- const caretRowCol = useMemo(() => {
18275
- const before = editableValue.slice(0, Math.min(caretIndex, editableValue.length));
18276
- const lines = before.split("\n");
18277
- return { row: lines.length - 1, col: lines[lines.length - 1].length };
18278
- }, [editableValue, caretIndex]);
18660
+ const handleEditableKeyDown = useCallback(
18661
+ (e) => {
18662
+ const ta = editableTextareaRef.current;
18663
+ if (ta) prevCaretRef.current = ta.selectionStart;
18664
+ const mod = e.metaKey || e.ctrlKey;
18665
+ if (!mod) return;
18666
+ const key = e.key.toLowerCase();
18667
+ if (key === "z" && !e.shiftKey) {
18668
+ e.preventDefault();
18669
+ undo();
18670
+ } else if (key === "z" && e.shiftKey || key === "y") {
18671
+ e.preventDefault();
18672
+ redo();
18673
+ }
18674
+ },
18675
+ [undo, redo]
18676
+ );
18677
+ const showBlockCaret = isFocused && caretMode !== "bar";
18678
+ useLayoutEffect(() => {
18679
+ if (!showBlockCaret) return;
18680
+ const ta = editableTextareaRef.current;
18681
+ const mirror = caretMirrorRef.current;
18682
+ const marker = caretMarkerRef.current;
18683
+ if (!ta || !mirror || !marker) return;
18684
+ const computed = window.getComputedStyle(ta);
18685
+ const MIRRORED_PROPS = [
18686
+ "font-family",
18687
+ "font-size",
18688
+ "font-weight",
18689
+ "font-style",
18690
+ "letter-spacing",
18691
+ "line-height",
18692
+ "padding-top",
18693
+ "padding-right",
18694
+ "padding-bottom",
18695
+ "padding-left",
18696
+ "border-top-width",
18697
+ "border-right-width",
18698
+ "border-bottom-width",
18699
+ "border-left-width",
18700
+ "box-sizing",
18701
+ "width",
18702
+ "white-space",
18703
+ "word-break",
18704
+ "overflow-wrap",
18705
+ "tab-size"
18706
+ ];
18707
+ for (const prop of MIRRORED_PROPS) {
18708
+ mirror.style.setProperty(prop, computed.getPropertyValue(prop));
18709
+ }
18710
+ const lineHeight = parseFloat(computed.getPropertyValue("line-height"));
18711
+ setCaretGeometry({
18712
+ top: marker.offsetTop,
18713
+ left: marker.offsetLeft,
18714
+ lineHeight: Number.isFinite(lineHeight) ? lineHeight : 0
18715
+ });
18716
+ }, [showBlockCaret, editableValue, caretIndex]);
18279
18717
  const errorLineProps = useMemo(() => buildLineProps(errorLines), [errorLines]);
18280
18718
  const viewerLineProps = useMemo(
18281
18719
  () => buildLineProps(errorLines, "px-4 py-0.5 hover:bg-muted/50"),
@@ -18799,11 +19237,24 @@ var init_CodeBlock = __esm({
18799
19237
  {
18800
19238
  ref: editableTextareaRef,
18801
19239
  defaultValue: code,
18802
- onChange: (e) => handleEditableChange(e.target.value),
19240
+ onChange: (e) => {
19241
+ const next = e.target.value;
19242
+ recordKeystroke(editableValue, prevCaretRef.current, next);
19243
+ handleEditableChange(next, "keystroke");
19244
+ },
18803
19245
  onScroll: handleEditableScroll,
18804
19246
  onSelect: (e) => setCaretIndex(e.currentTarget.selectionStart),
18805
- onFocus: editorId ? () => eventBus.emit(`UI:${onEditorFocus}`, { editorId }) : void 0,
18806
- onBlur: editorId ? () => eventBus.emit(`UI:${onEditorBlur}`, { editorId }) : void 0,
19247
+ onKeyUp: (e) => setCaretIndex(e.currentTarget.selectionStart),
19248
+ onClick: (e) => setCaretIndex(e.currentTarget.selectionStart),
19249
+ onKeyDown: handleEditableKeyDown,
19250
+ onFocus: () => {
19251
+ setIsFocused(true);
19252
+ if (editorId) eventBus.emit(`UI:${onEditorFocus}`, { editorId });
19253
+ },
19254
+ onBlur: () => {
19255
+ setIsFocused(false);
19256
+ if (editorId) eventBus.emit(`UI:${onEditorBlur}`, { editorId });
19257
+ },
18807
19258
  spellCheck: false,
18808
19259
  style: {
18809
19260
  position: "absolute",
@@ -18830,16 +19281,39 @@ var init_CodeBlock = __esm({
18830
19281
  },
18831
19282
  editableTextareaKey
18832
19283
  ),
18833
- caretMode !== "bar" && /* @__PURE__ */ jsx(
19284
+ showBlockCaret && /* @__PURE__ */ jsxs(
19285
+ "div",
19286
+ {
19287
+ ref: caretMirrorRef,
19288
+ "aria-hidden": true,
19289
+ "data-testid": "editor-caret-mirror",
19290
+ style: {
19291
+ position: "absolute",
19292
+ top: 0,
19293
+ left: 0,
19294
+ padding: "1rem",
19295
+ margin: 0,
19296
+ border: "none",
19297
+ visibility: "hidden",
19298
+ pointerEvents: "none"
19299
+ },
19300
+ children: [
19301
+ editableValue.slice(0, caretIndex),
19302
+ /* @__PURE__ */ jsx("span", { ref: caretMarkerRef, "data-testid": "editor-caret-marker", children: "\u200B" })
19303
+ ]
19304
+ }
19305
+ ),
19306
+ showBlockCaret && caretGeometry && /* @__PURE__ */ jsx(
18834
19307
  "span",
18835
19308
  {
18836
19309
  "aria-hidden": true,
19310
+ "data-testid": "editor-caret",
18837
19311
  style: {
18838
19312
  position: "absolute",
18839
- top: `calc(1rem + ${caretRowCol.row * 19.5}px)`,
18840
- left: `calc(1rem + ${caretRowCol.col}ch)`,
19313
+ top: caretGeometry.top,
19314
+ left: caretGeometry.left,
18841
19315
  width: "1ch",
18842
- height: caretMode === "block" ? "19.5px" : "2px",
19316
+ height: caretMode === "block" ? caretGeometry.lineHeight || "1.2em" : "2px",
18843
19317
  backgroundColor: caretMode === "block" ? "rgba(230, 230, 230, 0.5)" : void 0,
18844
19318
  borderBottom: caretMode === "underline" ? "2px solid #e6e6e6" : void 0,
18845
19319
  pointerEvents: "none"
@@ -26450,6 +26924,7 @@ function SubMenu({
26450
26924
  item.onClick?.();
26451
26925
  },
26452
26926
  "aria-disabled": item.disabled || void 0,
26927
+ title: item.title,
26453
26928
  "data-testid": item.event ? `action-${item.event}` : void 0,
26454
26929
  className: cn(
26455
26930
  "w-full flex items-center gap-3 px-4 py-2 text-start",
@@ -26498,6 +26973,7 @@ function MenuItemRow({
26498
26973
  as: "button",
26499
26974
  onClick: () => onItemClick({ ...item, id: itemId }, itemId),
26500
26975
  "aria-disabled": item.disabled || void 0,
26976
+ title: item.title,
26501
26977
  onMouseEnter: (e) => {
26502
26978
  if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
26503
26979
  },
@@ -33374,13 +33850,13 @@ var init_MapView = __esm({
33374
33850
  shadowSize: [41, 41]
33375
33851
  });
33376
33852
  L.Marker.prototype.options.icon = defaultIcon;
33377
- const { useEffect: useEffect67, useRef: useRef69, useCallback: useCallback95, useState: useState102 } = React87__default;
33853
+ const { useEffect: useEffect68, useRef: useRef69, useCallback: useCallback96, useState: useState102 } = React87__default;
33378
33854
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33379
33855
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33380
33856
  function MapUpdater({ centerLat, centerLng, zoom }) {
33381
33857
  const map = useMap();
33382
33858
  const prevRef = useRef69({ centerLat, centerLng, zoom });
33383
- useEffect67(() => {
33859
+ useEffect68(() => {
33384
33860
  const prev = prevRef.current;
33385
33861
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
33386
33862
  map.setView([centerLat, centerLng], zoom);
@@ -33391,7 +33867,7 @@ var init_MapView = __esm({
33391
33867
  }
33392
33868
  function MapClickHandler({ onMapClick }) {
33393
33869
  const map = useMap();
33394
- useEffect67(() => {
33870
+ useEffect68(() => {
33395
33871
  if (!onMapClick) return;
33396
33872
  const handler = (e) => {
33397
33873
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -33420,7 +33896,7 @@ var init_MapView = __esm({
33420
33896
  }) {
33421
33897
  const eventBus = useEventBus2();
33422
33898
  const [clickedPosition, setClickedPosition] = useState102(null);
33423
- const handleMapClick = useCallback95((lat, lng) => {
33899
+ const handleMapClick = useCallback96((lat, lng) => {
33424
33900
  if (showClickedPin) {
33425
33901
  setClickedPosition({ lat, lng });
33426
33902
  }
@@ -33429,7 +33905,7 @@ var init_MapView = __esm({
33429
33905
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
33430
33906
  }
33431
33907
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
33432
- const handleMarkerClick = useCallback95((marker) => {
33908
+ const handleMarkerClick = useCallback96((marker) => {
33433
33909
  onMarkerClick?.(marker);
33434
33910
  if (markerClickEvent) {
33435
33911
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -43038,7 +43514,10 @@ var init_FloatingToolbar = __esm({
43038
43514
  positionClasses = {
43039
43515
  "bottom-center": "bottom-6 left-1/2 -translate-x-1/2",
43040
43516
  "bottom-left": "bottom-6 left-6",
43041
- "bottom-right": "bottom-6 right-6"
43517
+ "bottom-right": "bottom-6 right-6",
43518
+ "top-center": "top-6 left-1/2 -translate-x-1/2",
43519
+ "top-left": "top-6 left-6",
43520
+ "top-right": "top-6 right-6"
43042
43521
  };
43043
43522
  FloatingToolbar = ({
43044
43523
  items,
@@ -50699,6 +51178,7 @@ var init_component_registry_generated = __esm({
50699
51178
  "TrendIndicator": TrendIndicator,
50700
51179
  "TypewriterText": TypewriterText,
50701
51180
  "Typography": Typography,
51181
+ "UISlotComponent": UISlotComponent,
50702
51182
  "UISlotRenderer": UISlotRenderer,
50703
51183
  "UploadDropZone": UploadDropZone,
50704
51184
  "VStack": VStack,
@@ -51004,6 +51484,7 @@ function UISlotComponentInner({
51004
51484
  const contained = useContext(SlotContainedContext);
51005
51485
  const schemaCtx = useEntitySchemaOptional();
51006
51486
  const rawContent = slots[slot];
51487
+ const regionClassName = fallback !== void 0 ? cn("contents", className) : className;
51007
51488
  const binding = useEntityBindingSnapshot(rawContent?.sourceTrait);
51008
51489
  const content = useMemo(() => {
51009
51490
  if (!rawContent) return rawContent;
@@ -51052,7 +51533,7 @@ function UISlotComponentInner({
51052
51533
  Box,
51053
51534
  {
51054
51535
  id: `slot-${slot}`,
51055
- className: cn("ui-slot", `ui-slot-${slot}`, className),
51536
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
51056
51537
  "data-testid": `ui-slot-${slot}`,
51057
51538
  "data-slot-mode": "fallback",
51058
51539
  children: fallback
@@ -51087,7 +51568,7 @@ function UISlotComponentInner({
51087
51568
  Box,
51088
51569
  {
51089
51570
  id: `slot-${slot}-fallback`,
51090
- className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
51571
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, regionClassName),
51091
51572
  "data-testid": `ui-slot-${slot}-fallback`,
51092
51573
  "data-slot-mode": "append",
51093
51574
  children: fallback
@@ -51119,7 +51600,7 @@ function UISlotComponentInner({
51119
51600
  Box,
51120
51601
  {
51121
51602
  id: `slot-${slot}`,
51122
- className: cn("ui-slot", `ui-slot-${slot}`, className),
51603
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
51123
51604
  "data-pattern": content.pattern,
51124
51605
  "data-source-trait": content.sourceTrait,
51125
51606
  "data-testid": `ui-slot-${slot}`,
@@ -54097,7 +54578,11 @@ function useDeclaredCaptureTable(opts) {
54097
54578
  const keymap = readKeymapConfig(inlineTrait, keymapKnob);
54098
54579
  const currentState = ctx.getState(pluginId, orbital, trait)?.currentState;
54099
54580
  const keys = currentState !== void 0 ? keymap[currentState] ?? [] : [];
54100
- return { [target]: { mode: currentState ?? "unknown", keys: new Set(keys) } };
54581
+ const entry = { mode: currentState ?? "unknown", keys: new Set(keys) };
54582
+ const targets = Array.isArray(target) ? target : [target];
54583
+ const table = {};
54584
+ for (const t of targets) table[t] = entry;
54585
+ return table;
54101
54586
  }, [ctx, pluginId, orbital, trait, keymapKnob, target]);
54102
54587
  }
54103
54588
  function useOrbitalPluginHost() {
@@ -54105,7 +54590,7 @@ function useOrbitalPluginHost() {
54105
54590
  return { getState: ctx.getState, lastEvent: ctx.lastEvent, errors: ctx.errors };
54106
54591
  }
54107
54592
  function buildMockEffectHandlers(opts) {
54108
- const { pluginId, denySet, slotsRef, navigateRef, notifyRef } = opts;
54593
+ const { pluginId, denySet, slotsRef, renderedSlotsRef, navigateRef, notifyRef } = opts;
54109
54594
  const handlers = {
54110
54595
  renderUI: (slot, pattern, props, priority) => {
54111
54596
  if (!isUISlot(slot)) {
@@ -54113,9 +54598,10 @@ function buildMockEffectHandlers(opts) {
54113
54598
  return;
54114
54599
  }
54115
54600
  if (pattern === null) {
54116
- slotsRef.current.clear(slot);
54601
+ slotsRef.current.clearBySource(slot, pluginId);
54117
54602
  return;
54118
54603
  }
54604
+ renderedSlotsRef.current.add(slot);
54119
54605
  slotsRef.current.render({ target: slot, pattern: pattern.type, props, priority, sourceTrait: pluginId });
54120
54606
  }
54121
54607
  };
@@ -54158,6 +54644,7 @@ function PluginRuntimeMount({
54158
54644
  const slots = useUISlots();
54159
54645
  const slotsRef = useRef(slots);
54160
54646
  slotsRef.current = slots;
54647
+ const renderedSlotsRef = useRef(/* @__PURE__ */ new Set());
54161
54648
  const navigateRef = useRef(navigate);
54162
54649
  navigateRef.current = navigate;
54163
54650
  const notifyRef = useRef(notify);
@@ -54171,6 +54658,7 @@ function PluginRuntimeMount({
54171
54658
  pluginId: plugin.id,
54172
54659
  denySet: new Set(deny ?? []),
54173
54660
  slotsRef,
54661
+ renderedSlotsRef,
54174
54662
  navigateRef,
54175
54663
  notifyRef
54176
54664
  })
@@ -54200,9 +54688,12 @@ function PluginRuntimeMount({
54200
54688
  } else if (mode === "server" && transport) {
54201
54689
  void transport.unregister();
54202
54690
  }
54691
+ for (const slot of renderedSlotsRef.current) {
54692
+ slotsRef.current.clearBySource(slot, plugin.id);
54693
+ }
54203
54694
  }, 0);
54204
54695
  };
54205
- }, [mockRuntime, transport, mode]);
54696
+ }, [mockRuntime, transport, mode, plugin.id]);
54206
54697
  const ownOrbitals = useMemo(
54207
54698
  () => new Set(plugin.schema.orbitals.map((o) => o.name)),
54208
54699
  [plugin.schema]