@almadar/ui 6.7.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.
@@ -1215,7 +1215,7 @@ var init_Button = __esm({
1215
1215
  secondary: [
1216
1216
  "bg-transparent text-accent",
1217
1217
  "border border-accent",
1218
- "hover:bg-accent hover:text-white hover:border-accent",
1218
+ "hover:bg-accent hover:text-accent-foreground hover:border-accent",
1219
1219
  "active:scale-[var(--active-scale)]"
1220
1220
  ].join(" "),
1221
1221
  ghost: [
@@ -1555,8 +1555,17 @@ var init_Typography = __esm({
1555
1555
  weight && weightStyles[weight],
1556
1556
  size && typographySizeStyles[size],
1557
1557
  align && `text-${align}`,
1558
- truncate && "truncate overflow-hidden text-ellipsis",
1559
- overflow && overflowStyles2[overflow],
1558
+ // `min-w-0` rides along with truncate/wrap/clamp: a flex or grid
1559
+ // item's default `min-width: auto` refuses to shrink below its
1560
+ // content's intrinsic width, so ellipsis/wrap silently does nothing
1561
+ // in the single most common placement (a row next to a fixed-width
1562
+ // control) unless the item can also shrink to zero. (Spelled out as
1563
+ // just "truncate", not "truncate overflow-hidden text-ellipsis" —
1564
+ // tailwind-merge treats those as the same conflict group and drops
1565
+ // "truncate" as the earlier-declared class, silently losing its
1566
+ // `white-space: nowrap`.)
1567
+ truncate && "truncate min-w-0",
1568
+ overflow && cn(overflowStyles2[overflow], overflow !== "visible" && "min-w-0"),
1560
1569
  className
1561
1570
  ),
1562
1571
  style
@@ -10040,6 +10049,10 @@ var init_paintDispatch = __esm({
10040
10049
  });
10041
10050
 
10042
10051
  // lib/drawable/hitTest.ts
10052
+ function shapeDrawnItem(n) {
10053
+ if (n.shape !== "rect") return { pos: n.position, id: n.id };
10054
+ return { pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotate };
10055
+ }
10043
10056
  function collectDrawnItems(nodes) {
10044
10057
  const out = [];
10045
10058
  for (const n of nodes) {
@@ -10048,6 +10061,8 @@ function collectDrawnItems(nodes) {
10048
10061
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotation });
10049
10062
  break;
10050
10063
  case "draw-shape":
10064
+ if (isValidScenePos(n.position)) out.push(shapeDrawnItem(n));
10065
+ break;
10051
10066
  case "draw-text":
10052
10067
  case "draw-group":
10053
10068
  case "draw-mesh":
@@ -10059,6 +10074,10 @@ function collectDrawnItems(nodes) {
10059
10074
  }
10060
10075
  break;
10061
10076
  case "draw-shape-layer":
10077
+ for (const it of n.items) {
10078
+ if (isValidScenePos(it.position)) out.push(shapeDrawnItem(it));
10079
+ }
10080
+ break;
10062
10081
  case "draw-text-layer":
10063
10082
  for (const it of n.items) {
10064
10083
  if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
@@ -17564,6 +17583,12 @@ var init_EmptyState = __esm({
17564
17583
  });
17565
17584
 
17566
17585
  // lib/editorMotions.ts
17586
+ function isEditorMotion(value) {
17587
+ return EDITOR_MOTION_SET.has(value);
17588
+ }
17589
+ function isEditorOperator(value) {
17590
+ return EDITOR_OPERATOR_SET.has(value);
17591
+ }
17567
17592
  function clamp(value, min, max) {
17568
17593
  return Math.max(min, Math.min(max, value));
17569
17594
  }
@@ -17631,6 +17656,30 @@ function prevParagraphBoundary(lines, fromLineIdx) {
17631
17656
  }
17632
17657
  return 0;
17633
17658
  }
17659
+ function matchBracket(text, pos) {
17660
+ const ch = text[pos];
17661
+ const partner = ch !== void 0 ? BRACKET_PARTNER[ch] : void 0;
17662
+ if (partner === void 0) return null;
17663
+ let depth = 1;
17664
+ if (OPEN_BRACKETS.has(ch)) {
17665
+ for (let i = pos + 1; i < text.length; i++) {
17666
+ if (text[i] === ch) depth++;
17667
+ else if (text[i] === partner) {
17668
+ depth--;
17669
+ if (depth === 0) return i;
17670
+ }
17671
+ }
17672
+ } else {
17673
+ for (let i = pos - 1; i >= 0; i--) {
17674
+ if (text[i] === ch) depth++;
17675
+ else if (text[i] === partner) {
17676
+ depth--;
17677
+ if (depth === 0) return i;
17678
+ }
17679
+ }
17680
+ }
17681
+ return null;
17682
+ }
17634
17683
  function applyMotion(text, caret, motion, count) {
17635
17684
  const n = Math.max(1, count);
17636
17685
  const lines = computeLines(text);
@@ -17692,6 +17741,20 @@ function applyMotion(text, caret, motion, count) {
17692
17741
  }
17693
17742
  return pos;
17694
17743
  }
17744
+ case "match-bracket": {
17745
+ const chAtCaret = text[caret];
17746
+ if (chAtCaret !== void 0 && BRACKET_PARTNER[chAtCaret] !== void 0) {
17747
+ const target = matchBracket(text, caret);
17748
+ return target === null ? caret : target;
17749
+ }
17750
+ for (let i = caret; i < line.end; i++) {
17751
+ if (BRACKET_PARTNER[text[i]] !== void 0) {
17752
+ const target = matchBracket(text, i);
17753
+ return target === null ? caret : target;
17754
+ }
17755
+ }
17756
+ return caret;
17757
+ }
17695
17758
  case "line":
17696
17759
  case "selection":
17697
17760
  return caret;
@@ -17719,8 +17782,8 @@ function motionRange(text, caret, motion, count, selection) {
17719
17782
  const newCaret = applyMotion(text, caret, motion, count);
17720
17783
  let start = Math.min(caret, newCaret);
17721
17784
  let end = Math.max(caret, newCaret);
17722
- if (motion === "word-end" || motion === "line-end") {
17723
- end = Math.min(Math.max(start, newCaret) + 1, text.length);
17785
+ if ((motion === "word-end" || motion === "line-end" || motion === "match-bracket") && newCaret !== caret) {
17786
+ end = Math.min(Math.max(caret, newCaret) + 1, text.length);
17724
17787
  } else if (motion === "word-forward" && newCaret > caret) {
17725
17788
  const startLine = lineIndexAt(lines, caret);
17726
17789
  const endLine = lineIndexAt(lines, newCaret);
@@ -17730,17 +17793,198 @@ function motionRange(text, caret, motion, count, selection) {
17730
17793
  }
17731
17794
  return [start, end];
17732
17795
  }
17733
- function applyOperator(text, range, operator, register) {
17734
- const start = clamp(range[0], 0, text.length);
17735
- const end = clamp(range[1], start, text.length);
17736
- const removed = text.slice(start, end);
17737
- if (operator === "yank") {
17738
- return { text, caret: start, register: removed };
17796
+ function toggleCase(s) {
17797
+ return s.replace(/[a-zA-Z]/g, (c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase());
17798
+ }
17799
+ function applyJoin(text, caret, count, register, registerLinewise) {
17800
+ const lines = computeLines(text);
17801
+ const startIdx = lineIndexAt(lines, caret);
17802
+ const n = Math.max(2, count);
17803
+ const endIdx = clamp(startIdx + n - 1, 0, lines.length - 1);
17804
+ if (endIdx <= startIdx) {
17805
+ return { text, caret, register, registerLinewise };
17806
+ }
17807
+ const joinCaret = lines[startIdx].end;
17808
+ let joined = text.slice(lines[startIdx].start, lines[startIdx].end);
17809
+ for (let i = startIdx + 1; i <= endIdx; i++) {
17810
+ const raw = text.slice(lines[i].start, lines[i].end);
17811
+ joined += " " + raw.replace(/^[ \t]+/, "");
17812
+ }
17813
+ const newText = text.slice(0, lines[startIdx].start) + joined + text.slice(lines[endIdx].end);
17814
+ return { text: newText, caret: joinCaret, register, registerLinewise };
17815
+ }
17816
+ function applyIndent(text, start, end, operator, register, registerLinewise) {
17817
+ const lines = computeLines(text);
17818
+ const startLineIdx = lineIndexAt(lines, start);
17819
+ const lastTouchedPos = end > start ? end - 1 : start;
17820
+ const endLineIdx = lineIndexAt(lines, lastTouchedPos);
17821
+ let result = text;
17822
+ for (let i = endLineIdx; i >= startLineIdx; i--) {
17823
+ const lineStart = lines[i].start;
17824
+ if (operator === "indent") {
17825
+ result = result.slice(0, lineStart) + INDENT_UNIT + result.slice(lineStart);
17826
+ } else if (result[lineStart] === " ") {
17827
+ result = result.slice(0, lineStart) + result.slice(lineStart + 1);
17828
+ } else {
17829
+ let removeCount = 0;
17830
+ while (removeCount < INDENT_UNIT.length && result[lineStart + removeCount] === " ") removeCount++;
17831
+ result = result.slice(0, lineStart) + result.slice(lineStart + removeCount);
17832
+ }
17739
17833
  }
17740
- return { text: text.slice(0, start) + text.slice(end), caret: start, register: removed };
17834
+ const newLines = computeLines(result);
17835
+ const caret = firstNonBlank(result, newLines[startLineIdx]);
17836
+ return { text: result, caret, register, registerLinewise };
17741
17837
  }
17838
+ function applyPut(text, caret, operator, register, registerLinewise, count) {
17839
+ if (register.length === 0) {
17840
+ return { text, caret, register, registerLinewise };
17841
+ }
17842
+ const content = register.repeat(count);
17843
+ const lines = computeLines(text);
17844
+ const line = lines[lineIndexAt(lines, caret)];
17845
+ if (registerLinewise) {
17846
+ if (operator === "put-before") {
17847
+ const insertPos3 = line.start;
17848
+ return {
17849
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
17850
+ caret: insertPos3,
17851
+ register,
17852
+ registerLinewise
17853
+ };
17854
+ }
17855
+ const nextLineIdx = lineIndexAt(lines, caret) + 1;
17856
+ if (nextLineIdx < lines.length) {
17857
+ const insertPos3 = lines[nextLineIdx].start;
17858
+ return {
17859
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
17860
+ caret: insertPos3,
17861
+ register,
17862
+ registerLinewise
17863
+ };
17864
+ }
17865
+ const insertPos2 = text.length;
17866
+ return {
17867
+ text: text.slice(0, insertPos2) + "\n" + content,
17868
+ caret: insertPos2 + 1,
17869
+ register,
17870
+ registerLinewise
17871
+ };
17872
+ }
17873
+ const insertPos = operator === "put-before" ? clamp(caret, 0, text.length) : clamp(caret + 1, line.start, line.end);
17874
+ return {
17875
+ text: text.slice(0, insertPos) + content + text.slice(insertPos),
17876
+ caret: insertPos + content.length - 1,
17877
+ register,
17878
+ registerLinewise
17879
+ };
17880
+ }
17881
+ function applyOperator(input) {
17882
+ const { text, caret, operator, motion, register, registerLinewise } = input;
17883
+ const count = Math.max(1, input.count);
17884
+ const start = clamp(input.range[0], 0, text.length);
17885
+ const end = clamp(input.range[1], start, text.length);
17886
+ switch (operator) {
17887
+ case "yank":
17888
+ return { text, caret: start, register: text.slice(start, end), registerLinewise: motion === "line" };
17889
+ case "delete":
17890
+ case "replace": {
17891
+ const removed = text.slice(start, end);
17892
+ return {
17893
+ text: text.slice(0, start) + text.slice(end),
17894
+ caret: start,
17895
+ register: removed,
17896
+ registerLinewise: motion === "line"
17897
+ };
17898
+ }
17899
+ case "change": {
17900
+ if (motion === "line") {
17901
+ const hasTrailingNewline = end > start && text[end - 1] === "\n";
17902
+ const removeEnd = hasTrailingNewline ? end - 1 : end;
17903
+ const removed2 = text.slice(start, removeEnd);
17904
+ return {
17905
+ text: text.slice(0, start) + text.slice(removeEnd),
17906
+ caret: start,
17907
+ register: removed2,
17908
+ registerLinewise: true
17909
+ };
17910
+ }
17911
+ const removed = text.slice(start, end);
17912
+ return {
17913
+ text: text.slice(0, start) + text.slice(end),
17914
+ caret: start,
17915
+ register: removed,
17916
+ registerLinewise: false
17917
+ };
17918
+ }
17919
+ case "put":
17920
+ case "put-before":
17921
+ return applyPut(text, caret, operator, register, registerLinewise, count);
17922
+ case "join":
17923
+ return applyJoin(text, caret, count, register, registerLinewise);
17924
+ case "toggle-case": {
17925
+ const toggled = toggleCase(text.slice(start, end));
17926
+ return { text: text.slice(0, start) + toggled + text.slice(end), caret: end, register, registerLinewise };
17927
+ }
17928
+ case "indent":
17929
+ case "dedent":
17930
+ return applyIndent(text, start, end, operator, register, registerLinewise);
17931
+ case "undo":
17932
+ case "redo":
17933
+ return { text, caret, register, registerLinewise };
17934
+ default: {
17935
+ const _exhaustive = operator;
17936
+ return _exhaustive;
17937
+ }
17938
+ }
17939
+ }
17940
+ var EDITOR_MOTIONS, EDITOR_OPERATORS, EDITOR_MOTION_SET, EDITOR_OPERATOR_SET, INDENT_UNIT, BRACKET_PARTNER, OPEN_BRACKETS;
17742
17941
  var init_editorMotions = __esm({
17743
17942
  "lib/editorMotions.ts"() {
17943
+ EDITOR_MOTIONS = [
17944
+ "left",
17945
+ "right",
17946
+ "up",
17947
+ "down",
17948
+ "word-forward",
17949
+ "word-back",
17950
+ "word-end",
17951
+ "line-start",
17952
+ "line-end",
17953
+ "first-nonblank",
17954
+ "doc-start",
17955
+ "doc-end",
17956
+ "paragraph-forward",
17957
+ "paragraph-back",
17958
+ "line",
17959
+ "selection",
17960
+ "match-bracket"
17961
+ ];
17962
+ EDITOR_OPERATORS = [
17963
+ "delete",
17964
+ "yank",
17965
+ "change",
17966
+ "put",
17967
+ "put-before",
17968
+ "undo",
17969
+ "redo",
17970
+ "join",
17971
+ "toggle-case",
17972
+ "indent",
17973
+ "dedent",
17974
+ "replace"
17975
+ ];
17976
+ EDITOR_MOTION_SET = new Set(EDITOR_MOTIONS);
17977
+ EDITOR_OPERATOR_SET = new Set(EDITOR_OPERATORS);
17978
+ INDENT_UNIT = " ";
17979
+ BRACKET_PARTNER = {
17980
+ "(": ")",
17981
+ ")": "(",
17982
+ "[": "]",
17983
+ "]": "[",
17984
+ "{": "}",
17985
+ "}": "{"
17986
+ };
17987
+ OPEN_BRACKETS = /* @__PURE__ */ new Set(["(", "[", "{"]);
17744
17988
  }
17745
17989
  });
17746
17990
  function isMotionPayload(payload) {
@@ -17755,14 +17999,103 @@ function isInsertTextPayload(payload) {
17755
17999
  function isSetModePayload(payload) {
17756
18000
  return !!payload && typeof payload.editorId === "string" && typeof payload.mode === "string" && typeof payload.caret === "string";
17757
18001
  }
18002
+ function typedDelta(prev, next) {
18003
+ const maxPrefix = Math.min(prev.length, next.length);
18004
+ let prefixLen = 0;
18005
+ while (prefixLen < maxPrefix && prev[prefixLen] === next[prefixLen]) prefixLen++;
18006
+ const maxSuffix = Math.min(prev.length - prefixLen, next.length - prefixLen);
18007
+ let suffixLen = 0;
18008
+ while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
18009
+ suffixLen++;
18010
+ }
18011
+ const removed = prev.slice(prefixLen, prev.length - suffixLen);
18012
+ const inserted = next.slice(prefixLen, next.length - suffixLen);
18013
+ return removed + inserted;
18014
+ }
17758
18015
  function useEditorCapabilities(args) {
17759
18016
  const [caretMode, setCaretMode] = React89.useState("bar");
17760
18017
  const registerRef = React89.useRef("");
18018
+ const registerLinewiseRef = React89.useRef(false);
18019
+ const pastRef = React89.useRef([]);
18020
+ const futureRef = React89.useRef([]);
18021
+ const openTypingStepRef = React89.useRef(false);
18022
+ const insertSessionOpenRef = React89.useRef(false);
18023
+ const closeOpenTypingStep = React89.useCallback(() => {
18024
+ openTypingStepRef.current = false;
18025
+ }, []);
18026
+ const pushHistoryStep = React89.useCallback(
18027
+ (text, caret) => {
18028
+ closeOpenTypingStep();
18029
+ pastRef.current.push({ text, caret });
18030
+ futureRef.current = [];
18031
+ },
18032
+ [closeOpenTypingStep]
18033
+ );
18034
+ const recordKeystroke = React89.useCallback(
18035
+ (prevText, prevCaret, nextText) => {
18036
+ if (!openTypingStepRef.current) {
18037
+ pastRef.current.push({ text: prevText, caret: prevCaret });
18038
+ futureRef.current = [];
18039
+ openTypingStepRef.current = true;
18040
+ }
18041
+ if (!insertSessionOpenRef.current && /\s/.test(typedDelta(prevText, nextText))) {
18042
+ openTypingStepRef.current = false;
18043
+ }
18044
+ },
18045
+ []
18046
+ );
18047
+ const performUndo = React89.useCallback(
18048
+ (count) => {
18049
+ const ta = args.textareaRef.current;
18050
+ if (!ta) return;
18051
+ closeOpenTypingStep();
18052
+ let moved = false;
18053
+ for (let i = 0; i < Math.max(1, count) && pastRef.current.length > 0; i++) {
18054
+ const prev = pastRef.current.pop();
18055
+ futureRef.current.push({ text: ta.value, caret: ta.selectionStart });
18056
+ ta.value = prev.text;
18057
+ ta.setSelectionRange(prev.caret, prev.caret);
18058
+ moved = true;
18059
+ }
18060
+ if (moved) args.applyChange(ta.value, "capability");
18061
+ },
18062
+ [args, closeOpenTypingStep]
18063
+ );
18064
+ const performRedo = React89.useCallback(
18065
+ (count) => {
18066
+ const ta = args.textareaRef.current;
18067
+ if (!ta) return;
18068
+ closeOpenTypingStep();
18069
+ let moved = false;
18070
+ for (let i = 0; i < Math.max(1, count) && futureRef.current.length > 0; i++) {
18071
+ const next = futureRef.current.pop();
18072
+ pastRef.current.push({ text: ta.value, caret: ta.selectionStart });
18073
+ ta.value = next.text;
18074
+ ta.setSelectionRange(next.caret, next.caret);
18075
+ moved = true;
18076
+ }
18077
+ if (moved) args.applyChange(ta.value, "capability");
18078
+ },
18079
+ [args, closeOpenTypingStep]
18080
+ );
18081
+ const undo = React89.useCallback(() => performUndo(1), [performUndo]);
18082
+ const redo = React89.useCallback(() => performRedo(1), [performRedo]);
18083
+ const wasFocusedRef = React89.useRef(args.focused);
18084
+ React89.useEffect(() => {
18085
+ if (wasFocusedRef.current && !args.focused) {
18086
+ setCaretMode("bar");
18087
+ }
18088
+ wasFocusedRef.current = args.focused;
18089
+ }, [args.focused]);
17761
18090
  useEventListener(`UI:${args.events.onMotion}`, (evt) => {
17762
18091
  if (!args.editorId || !isMotionPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18092
+ const { motion, count } = evt.payload;
18093
+ if (!isEditorMotion(motion)) {
18094
+ console.warn(`useEditorCapabilities: ignoring MOTION with unknown motion "${motion}"`);
18095
+ return;
18096
+ }
17763
18097
  const ta = args.textareaRef.current;
17764
18098
  if (!ta) return;
17765
- const { motion, count } = evt.payload;
17766
18099
  const newCaret = applyMotion(ta.value, ta.selectionStart, motion, count);
17767
18100
  if (ta.selectionStart !== ta.selectionEnd) {
17768
18101
  ta.setSelectionRange(ta.selectionStart, newCaret);
@@ -17772,32 +18105,63 @@ function useEditorCapabilities(args) {
17772
18105
  });
17773
18106
  useEventListener(`UI:${args.events.onOperate}`, (evt) => {
17774
18107
  if (!args.editorId || !isOperatePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18108
+ const { operator, motion, count } = evt.payload;
18109
+ if (!isEditorOperator(operator)) {
18110
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown operator "${operator}"`);
18111
+ return;
18112
+ }
18113
+ if (!isEditorMotion(motion)) {
18114
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown motion "${motion}"`);
18115
+ return;
18116
+ }
17775
18117
  const ta = args.textareaRef.current;
17776
18118
  if (!ta) return;
17777
- const { operator, motion, count } = evt.payload;
18119
+ if (operator === "undo") {
18120
+ performUndo(count);
18121
+ return;
18122
+ }
18123
+ if (operator === "redo") {
18124
+ performRedo(count);
18125
+ return;
18126
+ }
18127
+ pushHistoryStep(ta.value, ta.selectionStart);
17778
18128
  const selection = motion === "selection" ? [ta.selectionStart, ta.selectionEnd] : void 0;
17779
18129
  const range = motionRange(ta.value, ta.selectionStart, motion, count, selection);
17780
- const result = applyOperator(ta.value, range, operator, registerRef.current);
18130
+ const result = applyOperator({
18131
+ text: ta.value,
18132
+ caret: ta.selectionStart,
18133
+ range,
18134
+ operator,
18135
+ motion,
18136
+ count,
18137
+ register: registerRef.current,
18138
+ registerLinewise: registerLinewiseRef.current
18139
+ });
17781
18140
  registerRef.current = result.register;
18141
+ registerLinewiseRef.current = result.registerLinewise;
17782
18142
  if (operator === "yank") {
17783
- ta.setSelectionRange(range[0], range[0]);
18143
+ ta.setSelectionRange(result.caret, result.caret);
17784
18144
  } else {
17785
- ta.setRangeText("", range[0], range[1], "end");
18145
+ ta.value = result.text;
18146
+ ta.setSelectionRange(result.caret, result.caret);
17786
18147
  }
17787
- args.applyChange(ta.value);
18148
+ args.applyChange(ta.value, "capability");
17788
18149
  });
17789
18150
  useEventListener(`UI:${args.events.onInsertText}`, (evt) => {
17790
18151
  if (!args.editorId || !isInsertTextPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
17791
18152
  const ta = args.textareaRef.current;
17792
18153
  if (!ta) return;
18154
+ pushHistoryStep(ta.value, ta.selectionStart);
17793
18155
  ta.setRangeText(evt.payload.text, ta.selectionStart, ta.selectionEnd, "end");
17794
- args.applyChange(ta.value);
18156
+ args.applyChange(ta.value, "capability");
17795
18157
  });
17796
18158
  useEventListener(`UI:${args.events.onSetMode}`, (evt) => {
17797
18159
  if (!args.editorId || !isSetModePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18160
+ closeOpenTypingStep();
18161
+ insertSessionOpenRef.current = evt.payload.mode === "INSERT";
17798
18162
  setCaretMode(evt.payload.caret);
17799
18163
  });
17800
- return { caretMode };
18164
+ return { caretMode, recordKeystroke, undo, redo };
17801
18165
  }
17802
18166
  var init_useEditorCapabilities = __esm({
17803
18167
  "components/core/molecules/markdown/useEditorCapabilities.ts"() {
@@ -18213,9 +18577,23 @@ var init_CodeBlock = __esm({
18213
18577
  "paragraph-forward",
18214
18578
  "paragraph-back",
18215
18579
  "line",
18216
- "selection"
18580
+ "selection",
18581
+ "match-bracket"
18217
18582
  ],
18218
- operators = ["delete", "yank", "change"]
18583
+ operators = [
18584
+ "delete",
18585
+ "yank",
18586
+ "change",
18587
+ "put",
18588
+ "put-before",
18589
+ "undo",
18590
+ "redo",
18591
+ "join",
18592
+ "toggle-case",
18593
+ "indent",
18594
+ "dedent",
18595
+ "replace"
18596
+ ]
18219
18597
  }) => {
18220
18598
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
18221
18599
  const activeStyle = resolveHighlightStyle(language);
@@ -18249,6 +18627,12 @@ var init_CodeBlock = __esm({
18249
18627
  const lastPropCodeRef = React89.useRef(code);
18250
18628
  const editableTextareaRef = React89.useRef(null);
18251
18629
  const editableOverlayRef = React89.useRef(null);
18630
+ const [isFocused, setIsFocused] = React89.useState(false);
18631
+ const prevCaretRef = React89.useRef(0);
18632
+ const [caretIndex, setCaretIndex] = React89.useState(0);
18633
+ const caretMirrorRef = React89.useRef(null);
18634
+ const caretMarkerRef = React89.useRef(null);
18635
+ const [caretGeometry, setCaretGeometry] = React89.useState(null);
18252
18636
  React89.useEffect(() => {
18253
18637
  if (code !== lastPropCodeRef.current) {
18254
18638
  lastPropCodeRef.current = code;
@@ -18264,23 +18648,77 @@ var init_CodeBlock = __esm({
18264
18648
  ov.scrollLeft = ta.scrollLeft;
18265
18649
  }
18266
18650
  }, []);
18267
- const handleEditableChange = React89.useCallback((v) => {
18651
+ const handleEditableChange = React89.useCallback((v, _origin) => {
18268
18652
  lastPropCodeRef.current = v;
18269
18653
  setEditableValue(v);
18654
+ const ta = editableTextareaRef.current;
18655
+ if (ta) setCaretIndex(ta.selectionStart);
18270
18656
  onChange?.(v);
18271
18657
  }, [onChange]);
18272
- const { caretMode } = useEditorCapabilities({
18658
+ const { caretMode, recordKeystroke, undo, redo } = useEditorCapabilities({
18273
18659
  editorId: editable ? editorId : void 0,
18274
18660
  textareaRef: editableTextareaRef,
18275
18661
  events: { onMotion, onOperate, onInsertText, onSetMode },
18662
+ focused: isFocused,
18276
18663
  applyChange: handleEditableChange
18277
18664
  });
18278
- const [caretIndex, setCaretIndex] = React89.useState(0);
18279
- const caretRowCol = React89.useMemo(() => {
18280
- const before = editableValue.slice(0, Math.min(caretIndex, editableValue.length));
18281
- const lines = before.split("\n");
18282
- return { row: lines.length - 1, col: lines[lines.length - 1].length };
18283
- }, [editableValue, caretIndex]);
18665
+ const handleEditableKeyDown = React89.useCallback(
18666
+ (e) => {
18667
+ const ta = editableTextareaRef.current;
18668
+ if (ta) prevCaretRef.current = ta.selectionStart;
18669
+ const mod = e.metaKey || e.ctrlKey;
18670
+ if (!mod) return;
18671
+ const key = e.key.toLowerCase();
18672
+ if (key === "z" && !e.shiftKey) {
18673
+ e.preventDefault();
18674
+ undo();
18675
+ } else if (key === "z" && e.shiftKey || key === "y") {
18676
+ e.preventDefault();
18677
+ redo();
18678
+ }
18679
+ },
18680
+ [undo, redo]
18681
+ );
18682
+ const showBlockCaret = isFocused && caretMode !== "bar";
18683
+ React89.useLayoutEffect(() => {
18684
+ if (!showBlockCaret) return;
18685
+ const ta = editableTextareaRef.current;
18686
+ const mirror = caretMirrorRef.current;
18687
+ const marker = caretMarkerRef.current;
18688
+ if (!ta || !mirror || !marker) return;
18689
+ const computed = window.getComputedStyle(ta);
18690
+ const MIRRORED_PROPS = [
18691
+ "font-family",
18692
+ "font-size",
18693
+ "font-weight",
18694
+ "font-style",
18695
+ "letter-spacing",
18696
+ "line-height",
18697
+ "padding-top",
18698
+ "padding-right",
18699
+ "padding-bottom",
18700
+ "padding-left",
18701
+ "border-top-width",
18702
+ "border-right-width",
18703
+ "border-bottom-width",
18704
+ "border-left-width",
18705
+ "box-sizing",
18706
+ "width",
18707
+ "white-space",
18708
+ "word-break",
18709
+ "overflow-wrap",
18710
+ "tab-size"
18711
+ ];
18712
+ for (const prop of MIRRORED_PROPS) {
18713
+ mirror.style.setProperty(prop, computed.getPropertyValue(prop));
18714
+ }
18715
+ const lineHeight = parseFloat(computed.getPropertyValue("line-height"));
18716
+ setCaretGeometry({
18717
+ top: marker.offsetTop,
18718
+ left: marker.offsetLeft,
18719
+ lineHeight: Number.isFinite(lineHeight) ? lineHeight : 0
18720
+ });
18721
+ }, [showBlockCaret, editableValue, caretIndex]);
18284
18722
  const errorLineProps = React89.useMemo(() => buildLineProps(errorLines), [errorLines]);
18285
18723
  const viewerLineProps = React89.useMemo(
18286
18724
  () => buildLineProps(errorLines, "px-4 py-0.5 hover:bg-muted/50"),
@@ -18804,11 +19242,24 @@ var init_CodeBlock = __esm({
18804
19242
  {
18805
19243
  ref: editableTextareaRef,
18806
19244
  defaultValue: code,
18807
- onChange: (e) => handleEditableChange(e.target.value),
19245
+ onChange: (e) => {
19246
+ const next = e.target.value;
19247
+ recordKeystroke(editableValue, prevCaretRef.current, next);
19248
+ handleEditableChange(next, "keystroke");
19249
+ },
18808
19250
  onScroll: handleEditableScroll,
18809
19251
  onSelect: (e) => setCaretIndex(e.currentTarget.selectionStart),
18810
- onFocus: editorId ? () => eventBus.emit(`UI:${onEditorFocus}`, { editorId }) : void 0,
18811
- onBlur: editorId ? () => eventBus.emit(`UI:${onEditorBlur}`, { editorId }) : void 0,
19252
+ onKeyUp: (e) => setCaretIndex(e.currentTarget.selectionStart),
19253
+ onClick: (e) => setCaretIndex(e.currentTarget.selectionStart),
19254
+ onKeyDown: handleEditableKeyDown,
19255
+ onFocus: () => {
19256
+ setIsFocused(true);
19257
+ if (editorId) eventBus.emit(`UI:${onEditorFocus}`, { editorId });
19258
+ },
19259
+ onBlur: () => {
19260
+ setIsFocused(false);
19261
+ if (editorId) eventBus.emit(`UI:${onEditorBlur}`, { editorId });
19262
+ },
18812
19263
  spellCheck: false,
18813
19264
  style: {
18814
19265
  position: "absolute",
@@ -18835,16 +19286,39 @@ var init_CodeBlock = __esm({
18835
19286
  },
18836
19287
  editableTextareaKey
18837
19288
  ),
18838
- caretMode !== "bar" && /* @__PURE__ */ jsxRuntime.jsx(
19289
+ showBlockCaret && /* @__PURE__ */ jsxRuntime.jsxs(
19290
+ "div",
19291
+ {
19292
+ ref: caretMirrorRef,
19293
+ "aria-hidden": true,
19294
+ "data-testid": "editor-caret-mirror",
19295
+ style: {
19296
+ position: "absolute",
19297
+ top: 0,
19298
+ left: 0,
19299
+ padding: "1rem",
19300
+ margin: 0,
19301
+ border: "none",
19302
+ visibility: "hidden",
19303
+ pointerEvents: "none"
19304
+ },
19305
+ children: [
19306
+ editableValue.slice(0, caretIndex),
19307
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ref: caretMarkerRef, "data-testid": "editor-caret-marker", children: "\u200B" })
19308
+ ]
19309
+ }
19310
+ ),
19311
+ showBlockCaret && caretGeometry && /* @__PURE__ */ jsxRuntime.jsx(
18839
19312
  "span",
18840
19313
  {
18841
19314
  "aria-hidden": true,
19315
+ "data-testid": "editor-caret",
18842
19316
  style: {
18843
19317
  position: "absolute",
18844
- top: `calc(1rem + ${caretRowCol.row * 19.5}px)`,
18845
- left: `calc(1rem + ${caretRowCol.col}ch)`,
19318
+ top: caretGeometry.top,
19319
+ left: caretGeometry.left,
18846
19320
  width: "1ch",
18847
- height: caretMode === "block" ? "19.5px" : "2px",
19321
+ height: caretMode === "block" ? caretGeometry.lineHeight || "1.2em" : "2px",
18848
19322
  backgroundColor: caretMode === "block" ? "rgba(230, 230, 230, 0.5)" : void 0,
18849
19323
  borderBottom: caretMode === "underline" ? "2px solid #e6e6e6" : void 0,
18850
19324
  pointerEvents: "none"
@@ -26586,6 +27060,7 @@ function SubMenu({
26586
27060
  item.onClick?.();
26587
27061
  },
26588
27062
  "aria-disabled": item.disabled || void 0,
27063
+ title: item.title,
26589
27064
  "data-testid": item.event ? `action-${item.event}` : void 0,
26590
27065
  className: cn(
26591
27066
  "w-full flex items-center gap-3 px-4 py-2 text-start",
@@ -26634,6 +27109,7 @@ function MenuItemRow({
26634
27109
  as: "button",
26635
27110
  onClick: () => onItemClick({ ...item, id: itemId }, itemId),
26636
27111
  "aria-disabled": item.disabled || void 0,
27112
+ title: item.title,
26637
27113
  onMouseEnter: (e) => {
26638
27114
  if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
26639
27115
  },
@@ -33680,13 +34156,13 @@ var init_MapView = __esm({
33680
34156
  shadowSize: [41, 41]
33681
34157
  });
33682
34158
  L.Marker.prototype.options.icon = defaultIcon;
33683
- const { useEffect: useEffect69, useRef: useRef69, useCallback: useCallback100, useState: useState103 } = React89__namespace.default;
34159
+ const { useEffect: useEffect70, useRef: useRef69, useCallback: useCallback101, useState: useState103 } = React89__namespace.default;
33684
34160
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33685
34161
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33686
34162
  function MapUpdater({ centerLat, centerLng, zoom }) {
33687
34163
  const map = useMap();
33688
34164
  const prevRef = useRef69({ centerLat, centerLng, zoom });
33689
- useEffect69(() => {
34165
+ useEffect70(() => {
33690
34166
  const prev = prevRef.current;
33691
34167
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
33692
34168
  map.setView([centerLat, centerLng], zoom);
@@ -33697,7 +34173,7 @@ var init_MapView = __esm({
33697
34173
  }
33698
34174
  function MapClickHandler({ onMapClick }) {
33699
34175
  const map = useMap();
33700
- useEffect69(() => {
34176
+ useEffect70(() => {
33701
34177
  if (!onMapClick) return;
33702
34178
  const handler = (e) => {
33703
34179
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -33726,7 +34202,7 @@ var init_MapView = __esm({
33726
34202
  }) {
33727
34203
  const eventBus = useEventBus2();
33728
34204
  const [clickedPosition, setClickedPosition] = useState103(null);
33729
- const handleMapClick = useCallback100((lat, lng) => {
34205
+ const handleMapClick = useCallback101((lat, lng) => {
33730
34206
  if (showClickedPin) {
33731
34207
  setClickedPosition({ lat, lng });
33732
34208
  }
@@ -33735,7 +34211,7 @@ var init_MapView = __esm({
33735
34211
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
33736
34212
  }
33737
34213
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
33738
- const handleMarkerClick = useCallback100((marker) => {
34214
+ const handleMarkerClick = useCallback101((marker) => {
33739
34215
  onMarkerClick?.(marker);
33740
34216
  if (markerClickEvent) {
33741
34217
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -43482,7 +43958,10 @@ var init_FloatingToolbar = __esm({
43482
43958
  positionClasses = {
43483
43959
  "bottom-center": "bottom-6 left-1/2 -translate-x-1/2",
43484
43960
  "bottom-left": "bottom-6 left-6",
43485
- "bottom-right": "bottom-6 right-6"
43961
+ "bottom-right": "bottom-6 right-6",
43962
+ "top-center": "top-6 left-1/2 -translate-x-1/2",
43963
+ "top-left": "top-6 left-6",
43964
+ "top-right": "top-6 right-6"
43486
43965
  };
43487
43966
  FloatingToolbar = ({
43488
43967
  items,
@@ -51124,6 +51603,7 @@ var init_component_registry_generated = __esm({
51124
51603
  "TrendIndicator": TrendIndicator,
51125
51604
  "TypewriterText": TypewriterText,
51126
51605
  "Typography": Typography,
51606
+ "UISlotComponent": UISlotComponent,
51127
51607
  "UISlotRenderer": UISlotRenderer,
51128
51608
  "UploadDropZone": UploadDropZone,
51129
51609
  "VStack": VStack,
@@ -51429,6 +51909,7 @@ function UISlotComponentInner({
51429
51909
  const contained = React89.useContext(SlotContainedContext);
51430
51910
  const schemaCtx = providers.useEntitySchemaOptional();
51431
51911
  const rawContent = slots[slot];
51912
+ const regionClassName = fallback !== void 0 ? cn("contents", className) : className;
51432
51913
  const binding = providers.useEntityBindingSnapshot(rawContent?.sourceTrait);
51433
51914
  const content = React89.useMemo(() => {
51434
51915
  if (!rawContent) return rawContent;
@@ -51477,7 +51958,7 @@ function UISlotComponentInner({
51477
51958
  Box,
51478
51959
  {
51479
51960
  id: `slot-${slot}`,
51480
- className: cn("ui-slot", `ui-slot-${slot}`, className),
51961
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
51481
51962
  "data-testid": `ui-slot-${slot}`,
51482
51963
  "data-slot-mode": "fallback",
51483
51964
  children: fallback
@@ -51512,7 +51993,7 @@ function UISlotComponentInner({
51512
51993
  Box,
51513
51994
  {
51514
51995
  id: `slot-${slot}-fallback`,
51515
- className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
51996
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, regionClassName),
51516
51997
  "data-testid": `ui-slot-${slot}-fallback`,
51517
51998
  "data-slot-mode": "append",
51518
51999
  children: fallback
@@ -51544,7 +52025,7 @@ function UISlotComponentInner({
51544
52025
  Box,
51545
52026
  {
51546
52027
  id: `slot-${slot}`,
51547
- className: cn("ui-slot", `ui-slot-${slot}`, className),
52028
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
51548
52029
  "data-pattern": content.pattern,
51549
52030
  "data-source-trait": content.sourceTrait,
51550
52031
  "data-testid": `ui-slot-${slot}`,