@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.
@@ -1774,7 +1774,7 @@ var init_Button = __esm({
1774
1774
  secondary: [
1775
1775
  "bg-transparent text-accent",
1776
1776
  "border border-accent",
1777
- "hover:bg-accent hover:text-white hover:border-accent",
1777
+ "hover:bg-accent hover:text-accent-foreground hover:border-accent",
1778
1778
  "active:scale-[var(--active-scale)]"
1779
1779
  ].join(" "),
1780
1780
  ghost: [
@@ -4225,8 +4225,17 @@ var init_Typography = __esm({
4225
4225
  weight && weightStyles[weight],
4226
4226
  size && typographySizeStyles[size],
4227
4227
  align && `text-${align}`,
4228
- truncate && "truncate overflow-hidden text-ellipsis",
4229
- overflow && overflowStyles2[overflow],
4228
+ // `min-w-0` rides along with truncate/wrap/clamp: a flex or grid
4229
+ // item's default `min-width: auto` refuses to shrink below its
4230
+ // content's intrinsic width, so ellipsis/wrap silently does nothing
4231
+ // in the single most common placement (a row next to a fixed-width
4232
+ // control) unless the item can also shrink to zero. (Spelled out as
4233
+ // just "truncate", not "truncate overflow-hidden text-ellipsis" —
4234
+ // tailwind-merge treats those as the same conflict group and drops
4235
+ // "truncate" as the earlier-declared class, silently losing its
4236
+ // `white-space: nowrap`.)
4237
+ truncate && "truncate min-w-0",
4238
+ overflow && cn(overflowStyles2[overflow], overflow !== "visible" && "min-w-0"),
4230
4239
  className
4231
4240
  ),
4232
4241
  style
@@ -13401,6 +13410,12 @@ var init_EmptyState = __esm({
13401
13410
  });
13402
13411
 
13403
13412
  // lib/editorMotions.ts
13413
+ function isEditorMotion(value) {
13414
+ return EDITOR_MOTION_SET.has(value);
13415
+ }
13416
+ function isEditorOperator(value) {
13417
+ return EDITOR_OPERATOR_SET.has(value);
13418
+ }
13404
13419
  function clamp(value, min, max) {
13405
13420
  return Math.max(min, Math.min(max, value));
13406
13421
  }
@@ -13468,6 +13483,30 @@ function prevParagraphBoundary(lines, fromLineIdx) {
13468
13483
  }
13469
13484
  return 0;
13470
13485
  }
13486
+ function matchBracket(text, pos) {
13487
+ const ch = text[pos];
13488
+ const partner = ch !== void 0 ? BRACKET_PARTNER[ch] : void 0;
13489
+ if (partner === void 0) return null;
13490
+ let depth = 1;
13491
+ if (OPEN_BRACKETS.has(ch)) {
13492
+ for (let i = pos + 1; i < text.length; i++) {
13493
+ if (text[i] === ch) depth++;
13494
+ else if (text[i] === partner) {
13495
+ depth--;
13496
+ if (depth === 0) return i;
13497
+ }
13498
+ }
13499
+ } else {
13500
+ for (let i = pos - 1; i >= 0; i--) {
13501
+ if (text[i] === ch) depth++;
13502
+ else if (text[i] === partner) {
13503
+ depth--;
13504
+ if (depth === 0) return i;
13505
+ }
13506
+ }
13507
+ }
13508
+ return null;
13509
+ }
13471
13510
  function applyMotion(text, caret, motion, count) {
13472
13511
  const n = Math.max(1, count);
13473
13512
  const lines = computeLines(text);
@@ -13529,6 +13568,20 @@ function applyMotion(text, caret, motion, count) {
13529
13568
  }
13530
13569
  return pos;
13531
13570
  }
13571
+ case "match-bracket": {
13572
+ const chAtCaret = text[caret];
13573
+ if (chAtCaret !== void 0 && BRACKET_PARTNER[chAtCaret] !== void 0) {
13574
+ const target = matchBracket(text, caret);
13575
+ return target === null ? caret : target;
13576
+ }
13577
+ for (let i = caret; i < line.end; i++) {
13578
+ if (BRACKET_PARTNER[text[i]] !== void 0) {
13579
+ const target = matchBracket(text, i);
13580
+ return target === null ? caret : target;
13581
+ }
13582
+ }
13583
+ return caret;
13584
+ }
13532
13585
  case "line":
13533
13586
  case "selection":
13534
13587
  return caret;
@@ -13556,8 +13609,8 @@ function motionRange(text, caret, motion, count, selection) {
13556
13609
  const newCaret = applyMotion(text, caret, motion, count);
13557
13610
  let start = Math.min(caret, newCaret);
13558
13611
  let end = Math.max(caret, newCaret);
13559
- if (motion === "word-end" || motion === "line-end") {
13560
- end = Math.min(Math.max(start, newCaret) + 1, text.length);
13612
+ if ((motion === "word-end" || motion === "line-end" || motion === "match-bracket") && newCaret !== caret) {
13613
+ end = Math.min(Math.max(caret, newCaret) + 1, text.length);
13561
13614
  } else if (motion === "word-forward" && newCaret > caret) {
13562
13615
  const startLine = lineIndexAt(lines, caret);
13563
13616
  const endLine = lineIndexAt(lines, newCaret);
@@ -13567,17 +13620,198 @@ function motionRange(text, caret, motion, count, selection) {
13567
13620
  }
13568
13621
  return [start, end];
13569
13622
  }
13570
- function applyOperator(text, range, operator, register) {
13571
- const start = clamp(range[0], 0, text.length);
13572
- const end = clamp(range[1], start, text.length);
13573
- const removed = text.slice(start, end);
13574
- if (operator === "yank") {
13575
- return { text, caret: start, register: removed };
13623
+ function toggleCase(s) {
13624
+ return s.replace(/[a-zA-Z]/g, (c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase());
13625
+ }
13626
+ function applyJoin(text, caret, count, register, registerLinewise) {
13627
+ const lines = computeLines(text);
13628
+ const startIdx = lineIndexAt(lines, caret);
13629
+ const n = Math.max(2, count);
13630
+ const endIdx = clamp(startIdx + n - 1, 0, lines.length - 1);
13631
+ if (endIdx <= startIdx) {
13632
+ return { text, caret, register, registerLinewise };
13633
+ }
13634
+ const joinCaret = lines[startIdx].end;
13635
+ let joined = text.slice(lines[startIdx].start, lines[startIdx].end);
13636
+ for (let i = startIdx + 1; i <= endIdx; i++) {
13637
+ const raw = text.slice(lines[i].start, lines[i].end);
13638
+ joined += " " + raw.replace(/^[ \t]+/, "");
13639
+ }
13640
+ const newText = text.slice(0, lines[startIdx].start) + joined + text.slice(lines[endIdx].end);
13641
+ return { text: newText, caret: joinCaret, register, registerLinewise };
13642
+ }
13643
+ function applyIndent(text, start, end, operator, register, registerLinewise) {
13644
+ const lines = computeLines(text);
13645
+ const startLineIdx = lineIndexAt(lines, start);
13646
+ const lastTouchedPos = end > start ? end - 1 : start;
13647
+ const endLineIdx = lineIndexAt(lines, lastTouchedPos);
13648
+ let result = text;
13649
+ for (let i = endLineIdx; i >= startLineIdx; i--) {
13650
+ const lineStart = lines[i].start;
13651
+ if (operator === "indent") {
13652
+ result = result.slice(0, lineStart) + INDENT_UNIT + result.slice(lineStart);
13653
+ } else if (result[lineStart] === " ") {
13654
+ result = result.slice(0, lineStart) + result.slice(lineStart + 1);
13655
+ } else {
13656
+ let removeCount = 0;
13657
+ while (removeCount < INDENT_UNIT.length && result[lineStart + removeCount] === " ") removeCount++;
13658
+ result = result.slice(0, lineStart) + result.slice(lineStart + removeCount);
13659
+ }
13576
13660
  }
13577
- return { text: text.slice(0, start) + text.slice(end), caret: start, register: removed };
13661
+ const newLines = computeLines(result);
13662
+ const caret = firstNonBlank(result, newLines[startLineIdx]);
13663
+ return { text: result, caret, register, registerLinewise };
13578
13664
  }
13665
+ function applyPut(text, caret, operator, register, registerLinewise, count) {
13666
+ if (register.length === 0) {
13667
+ return { text, caret, register, registerLinewise };
13668
+ }
13669
+ const content = register.repeat(count);
13670
+ const lines = computeLines(text);
13671
+ const line = lines[lineIndexAt(lines, caret)];
13672
+ if (registerLinewise) {
13673
+ if (operator === "put-before") {
13674
+ const insertPos3 = line.start;
13675
+ return {
13676
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
13677
+ caret: insertPos3,
13678
+ register,
13679
+ registerLinewise
13680
+ };
13681
+ }
13682
+ const nextLineIdx = lineIndexAt(lines, caret) + 1;
13683
+ if (nextLineIdx < lines.length) {
13684
+ const insertPos3 = lines[nextLineIdx].start;
13685
+ return {
13686
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
13687
+ caret: insertPos3,
13688
+ register,
13689
+ registerLinewise
13690
+ };
13691
+ }
13692
+ const insertPos2 = text.length;
13693
+ return {
13694
+ text: text.slice(0, insertPos2) + "\n" + content,
13695
+ caret: insertPos2 + 1,
13696
+ register,
13697
+ registerLinewise
13698
+ };
13699
+ }
13700
+ const insertPos = operator === "put-before" ? clamp(caret, 0, text.length) : clamp(caret + 1, line.start, line.end);
13701
+ return {
13702
+ text: text.slice(0, insertPos) + content + text.slice(insertPos),
13703
+ caret: insertPos + content.length - 1,
13704
+ register,
13705
+ registerLinewise
13706
+ };
13707
+ }
13708
+ function applyOperator(input) {
13709
+ const { text, caret, operator, motion, register, registerLinewise } = input;
13710
+ const count = Math.max(1, input.count);
13711
+ const start = clamp(input.range[0], 0, text.length);
13712
+ const end = clamp(input.range[1], start, text.length);
13713
+ switch (operator) {
13714
+ case "yank":
13715
+ return { text, caret: start, register: text.slice(start, end), registerLinewise: motion === "line" };
13716
+ case "delete":
13717
+ case "replace": {
13718
+ const removed = text.slice(start, end);
13719
+ return {
13720
+ text: text.slice(0, start) + text.slice(end),
13721
+ caret: start,
13722
+ register: removed,
13723
+ registerLinewise: motion === "line"
13724
+ };
13725
+ }
13726
+ case "change": {
13727
+ if (motion === "line") {
13728
+ const hasTrailingNewline = end > start && text[end - 1] === "\n";
13729
+ const removeEnd = hasTrailingNewline ? end - 1 : end;
13730
+ const removed2 = text.slice(start, removeEnd);
13731
+ return {
13732
+ text: text.slice(0, start) + text.slice(removeEnd),
13733
+ caret: start,
13734
+ register: removed2,
13735
+ registerLinewise: true
13736
+ };
13737
+ }
13738
+ const removed = text.slice(start, end);
13739
+ return {
13740
+ text: text.slice(0, start) + text.slice(end),
13741
+ caret: start,
13742
+ register: removed,
13743
+ registerLinewise: false
13744
+ };
13745
+ }
13746
+ case "put":
13747
+ case "put-before":
13748
+ return applyPut(text, caret, operator, register, registerLinewise, count);
13749
+ case "join":
13750
+ return applyJoin(text, caret, count, register, registerLinewise);
13751
+ case "toggle-case": {
13752
+ const toggled = toggleCase(text.slice(start, end));
13753
+ return { text: text.slice(0, start) + toggled + text.slice(end), caret: end, register, registerLinewise };
13754
+ }
13755
+ case "indent":
13756
+ case "dedent":
13757
+ return applyIndent(text, start, end, operator, register, registerLinewise);
13758
+ case "undo":
13759
+ case "redo":
13760
+ return { text, caret, register, registerLinewise };
13761
+ default: {
13762
+ const _exhaustive = operator;
13763
+ return _exhaustive;
13764
+ }
13765
+ }
13766
+ }
13767
+ var EDITOR_MOTIONS, EDITOR_OPERATORS, EDITOR_MOTION_SET, EDITOR_OPERATOR_SET, INDENT_UNIT, BRACKET_PARTNER, OPEN_BRACKETS;
13579
13768
  var init_editorMotions = __esm({
13580
13769
  "lib/editorMotions.ts"() {
13770
+ EDITOR_MOTIONS = [
13771
+ "left",
13772
+ "right",
13773
+ "up",
13774
+ "down",
13775
+ "word-forward",
13776
+ "word-back",
13777
+ "word-end",
13778
+ "line-start",
13779
+ "line-end",
13780
+ "first-nonblank",
13781
+ "doc-start",
13782
+ "doc-end",
13783
+ "paragraph-forward",
13784
+ "paragraph-back",
13785
+ "line",
13786
+ "selection",
13787
+ "match-bracket"
13788
+ ];
13789
+ EDITOR_OPERATORS = [
13790
+ "delete",
13791
+ "yank",
13792
+ "change",
13793
+ "put",
13794
+ "put-before",
13795
+ "undo",
13796
+ "redo",
13797
+ "join",
13798
+ "toggle-case",
13799
+ "indent",
13800
+ "dedent",
13801
+ "replace"
13802
+ ];
13803
+ EDITOR_MOTION_SET = new Set(EDITOR_MOTIONS);
13804
+ EDITOR_OPERATOR_SET = new Set(EDITOR_OPERATORS);
13805
+ INDENT_UNIT = " ";
13806
+ BRACKET_PARTNER = {
13807
+ "(": ")",
13808
+ ")": "(",
13809
+ "[": "]",
13810
+ "]": "[",
13811
+ "{": "}",
13812
+ "}": "{"
13813
+ };
13814
+ OPEN_BRACKETS = /* @__PURE__ */ new Set(["(", "[", "{"]);
13581
13815
  }
13582
13816
  });
13583
13817
  function isMotionPayload(payload) {
@@ -13592,14 +13826,103 @@ function isInsertTextPayload(payload) {
13592
13826
  function isSetModePayload(payload) {
13593
13827
  return !!payload && typeof payload.editorId === "string" && typeof payload.mode === "string" && typeof payload.caret === "string";
13594
13828
  }
13829
+ function typedDelta(prev, next) {
13830
+ const maxPrefix = Math.min(prev.length, next.length);
13831
+ let prefixLen = 0;
13832
+ while (prefixLen < maxPrefix && prev[prefixLen] === next[prefixLen]) prefixLen++;
13833
+ const maxSuffix = Math.min(prev.length - prefixLen, next.length - prefixLen);
13834
+ let suffixLen = 0;
13835
+ while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
13836
+ suffixLen++;
13837
+ }
13838
+ const removed = prev.slice(prefixLen, prev.length - suffixLen);
13839
+ const inserted = next.slice(prefixLen, next.length - suffixLen);
13840
+ return removed + inserted;
13841
+ }
13595
13842
  function useEditorCapabilities(args) {
13596
13843
  const [caretMode, setCaretMode] = React79.useState("bar");
13597
13844
  const registerRef = React79.useRef("");
13845
+ const registerLinewiseRef = React79.useRef(false);
13846
+ const pastRef = React79.useRef([]);
13847
+ const futureRef = React79.useRef([]);
13848
+ const openTypingStepRef = React79.useRef(false);
13849
+ const insertSessionOpenRef = React79.useRef(false);
13850
+ const closeOpenTypingStep = React79.useCallback(() => {
13851
+ openTypingStepRef.current = false;
13852
+ }, []);
13853
+ const pushHistoryStep = React79.useCallback(
13854
+ (text, caret) => {
13855
+ closeOpenTypingStep();
13856
+ pastRef.current.push({ text, caret });
13857
+ futureRef.current = [];
13858
+ },
13859
+ [closeOpenTypingStep]
13860
+ );
13861
+ const recordKeystroke = React79.useCallback(
13862
+ (prevText, prevCaret, nextText) => {
13863
+ if (!openTypingStepRef.current) {
13864
+ pastRef.current.push({ text: prevText, caret: prevCaret });
13865
+ futureRef.current = [];
13866
+ openTypingStepRef.current = true;
13867
+ }
13868
+ if (!insertSessionOpenRef.current && /\s/.test(typedDelta(prevText, nextText))) {
13869
+ openTypingStepRef.current = false;
13870
+ }
13871
+ },
13872
+ []
13873
+ );
13874
+ const performUndo = React79.useCallback(
13875
+ (count) => {
13876
+ const ta = args.textareaRef.current;
13877
+ if (!ta) return;
13878
+ closeOpenTypingStep();
13879
+ let moved = false;
13880
+ for (let i = 0; i < Math.max(1, count) && pastRef.current.length > 0; i++) {
13881
+ const prev = pastRef.current.pop();
13882
+ futureRef.current.push({ text: ta.value, caret: ta.selectionStart });
13883
+ ta.value = prev.text;
13884
+ ta.setSelectionRange(prev.caret, prev.caret);
13885
+ moved = true;
13886
+ }
13887
+ if (moved) args.applyChange(ta.value, "capability");
13888
+ },
13889
+ [args, closeOpenTypingStep]
13890
+ );
13891
+ const performRedo = React79.useCallback(
13892
+ (count) => {
13893
+ const ta = args.textareaRef.current;
13894
+ if (!ta) return;
13895
+ closeOpenTypingStep();
13896
+ let moved = false;
13897
+ for (let i = 0; i < Math.max(1, count) && futureRef.current.length > 0; i++) {
13898
+ const next = futureRef.current.pop();
13899
+ pastRef.current.push({ text: ta.value, caret: ta.selectionStart });
13900
+ ta.value = next.text;
13901
+ ta.setSelectionRange(next.caret, next.caret);
13902
+ moved = true;
13903
+ }
13904
+ if (moved) args.applyChange(ta.value, "capability");
13905
+ },
13906
+ [args, closeOpenTypingStep]
13907
+ );
13908
+ const undo = React79.useCallback(() => performUndo(1), [performUndo]);
13909
+ const redo = React79.useCallback(() => performRedo(1), [performRedo]);
13910
+ const wasFocusedRef = React79.useRef(args.focused);
13911
+ React79.useEffect(() => {
13912
+ if (wasFocusedRef.current && !args.focused) {
13913
+ setCaretMode("bar");
13914
+ }
13915
+ wasFocusedRef.current = args.focused;
13916
+ }, [args.focused]);
13598
13917
  useEventListener(`UI:${args.events.onMotion}`, (evt) => {
13599
13918
  if (!args.editorId || !isMotionPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
13919
+ const { motion, count } = evt.payload;
13920
+ if (!isEditorMotion(motion)) {
13921
+ console.warn(`useEditorCapabilities: ignoring MOTION with unknown motion "${motion}"`);
13922
+ return;
13923
+ }
13600
13924
  const ta = args.textareaRef.current;
13601
13925
  if (!ta) return;
13602
- const { motion, count } = evt.payload;
13603
13926
  const newCaret = applyMotion(ta.value, ta.selectionStart, motion, count);
13604
13927
  if (ta.selectionStart !== ta.selectionEnd) {
13605
13928
  ta.setSelectionRange(ta.selectionStart, newCaret);
@@ -13609,32 +13932,63 @@ function useEditorCapabilities(args) {
13609
13932
  });
13610
13933
  useEventListener(`UI:${args.events.onOperate}`, (evt) => {
13611
13934
  if (!args.editorId || !isOperatePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
13935
+ const { operator, motion, count } = evt.payload;
13936
+ if (!isEditorOperator(operator)) {
13937
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown operator "${operator}"`);
13938
+ return;
13939
+ }
13940
+ if (!isEditorMotion(motion)) {
13941
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown motion "${motion}"`);
13942
+ return;
13943
+ }
13612
13944
  const ta = args.textareaRef.current;
13613
13945
  if (!ta) return;
13614
- const { operator, motion, count } = evt.payload;
13946
+ if (operator === "undo") {
13947
+ performUndo(count);
13948
+ return;
13949
+ }
13950
+ if (operator === "redo") {
13951
+ performRedo(count);
13952
+ return;
13953
+ }
13954
+ pushHistoryStep(ta.value, ta.selectionStart);
13615
13955
  const selection = motion === "selection" ? [ta.selectionStart, ta.selectionEnd] : void 0;
13616
13956
  const range = motionRange(ta.value, ta.selectionStart, motion, count, selection);
13617
- const result = applyOperator(ta.value, range, operator, registerRef.current);
13957
+ const result = applyOperator({
13958
+ text: ta.value,
13959
+ caret: ta.selectionStart,
13960
+ range,
13961
+ operator,
13962
+ motion,
13963
+ count,
13964
+ register: registerRef.current,
13965
+ registerLinewise: registerLinewiseRef.current
13966
+ });
13618
13967
  registerRef.current = result.register;
13968
+ registerLinewiseRef.current = result.registerLinewise;
13619
13969
  if (operator === "yank") {
13620
- ta.setSelectionRange(range[0], range[0]);
13970
+ ta.setSelectionRange(result.caret, result.caret);
13621
13971
  } else {
13622
- ta.setRangeText("", range[0], range[1], "end");
13972
+ ta.value = result.text;
13973
+ ta.setSelectionRange(result.caret, result.caret);
13623
13974
  }
13624
- args.applyChange(ta.value);
13975
+ args.applyChange(ta.value, "capability");
13625
13976
  });
13626
13977
  useEventListener(`UI:${args.events.onInsertText}`, (evt) => {
13627
13978
  if (!args.editorId || !isInsertTextPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
13628
13979
  const ta = args.textareaRef.current;
13629
13980
  if (!ta) return;
13981
+ pushHistoryStep(ta.value, ta.selectionStart);
13630
13982
  ta.setRangeText(evt.payload.text, ta.selectionStart, ta.selectionEnd, "end");
13631
- args.applyChange(ta.value);
13983
+ args.applyChange(ta.value, "capability");
13632
13984
  });
13633
13985
  useEventListener(`UI:${args.events.onSetMode}`, (evt) => {
13634
13986
  if (!args.editorId || !isSetModePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
13987
+ closeOpenTypingStep();
13988
+ insertSessionOpenRef.current = evt.payload.mode === "INSERT";
13635
13989
  setCaretMode(evt.payload.caret);
13636
13990
  });
13637
- return { caretMode };
13991
+ return { caretMode, recordKeystroke, undo, redo };
13638
13992
  }
13639
13993
  var init_useEditorCapabilities = __esm({
13640
13994
  "components/core/molecules/markdown/useEditorCapabilities.ts"() {
@@ -14053,9 +14407,23 @@ var init_CodeBlock = __esm({
14053
14407
  "paragraph-forward",
14054
14408
  "paragraph-back",
14055
14409
  "line",
14056
- "selection"
14410
+ "selection",
14411
+ "match-bracket"
14057
14412
  ],
14058
- operators = ["delete", "yank", "change"]
14413
+ operators = [
14414
+ "delete",
14415
+ "yank",
14416
+ "change",
14417
+ "put",
14418
+ "put-before",
14419
+ "undo",
14420
+ "redo",
14421
+ "join",
14422
+ "toggle-case",
14423
+ "indent",
14424
+ "dedent",
14425
+ "replace"
14426
+ ]
14059
14427
  }) => {
14060
14428
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
14061
14429
  const activeStyle = resolveHighlightStyle(language);
@@ -14089,6 +14457,12 @@ var init_CodeBlock = __esm({
14089
14457
  const lastPropCodeRef = React79.useRef(code);
14090
14458
  const editableTextareaRef = React79.useRef(null);
14091
14459
  const editableOverlayRef = React79.useRef(null);
14460
+ const [isFocused, setIsFocused] = React79.useState(false);
14461
+ const prevCaretRef = React79.useRef(0);
14462
+ const [caretIndex, setCaretIndex] = React79.useState(0);
14463
+ const caretMirrorRef = React79.useRef(null);
14464
+ const caretMarkerRef = React79.useRef(null);
14465
+ const [caretGeometry, setCaretGeometry] = React79.useState(null);
14092
14466
  React79.useEffect(() => {
14093
14467
  if (code !== lastPropCodeRef.current) {
14094
14468
  lastPropCodeRef.current = code;
@@ -14104,23 +14478,77 @@ var init_CodeBlock = __esm({
14104
14478
  ov.scrollLeft = ta.scrollLeft;
14105
14479
  }
14106
14480
  }, []);
14107
- const handleEditableChange = React79.useCallback((v) => {
14481
+ const handleEditableChange = React79.useCallback((v, _origin) => {
14108
14482
  lastPropCodeRef.current = v;
14109
14483
  setEditableValue(v);
14484
+ const ta = editableTextareaRef.current;
14485
+ if (ta) setCaretIndex(ta.selectionStart);
14110
14486
  onChange?.(v);
14111
14487
  }, [onChange]);
14112
- const { caretMode } = useEditorCapabilities({
14488
+ const { caretMode, recordKeystroke, undo, redo } = useEditorCapabilities({
14113
14489
  editorId: editable ? editorId : void 0,
14114
14490
  textareaRef: editableTextareaRef,
14115
14491
  events: { onMotion, onOperate, onInsertText, onSetMode },
14492
+ focused: isFocused,
14116
14493
  applyChange: handleEditableChange
14117
14494
  });
14118
- const [caretIndex, setCaretIndex] = React79.useState(0);
14119
- const caretRowCol = React79.useMemo(() => {
14120
- const before = editableValue.slice(0, Math.min(caretIndex, editableValue.length));
14121
- const lines = before.split("\n");
14122
- return { row: lines.length - 1, col: lines[lines.length - 1].length };
14123
- }, [editableValue, caretIndex]);
14495
+ const handleEditableKeyDown = React79.useCallback(
14496
+ (e) => {
14497
+ const ta = editableTextareaRef.current;
14498
+ if (ta) prevCaretRef.current = ta.selectionStart;
14499
+ const mod = e.metaKey || e.ctrlKey;
14500
+ if (!mod) return;
14501
+ const key = e.key.toLowerCase();
14502
+ if (key === "z" && !e.shiftKey) {
14503
+ e.preventDefault();
14504
+ undo();
14505
+ } else if (key === "z" && e.shiftKey || key === "y") {
14506
+ e.preventDefault();
14507
+ redo();
14508
+ }
14509
+ },
14510
+ [undo, redo]
14511
+ );
14512
+ const showBlockCaret = isFocused && caretMode !== "bar";
14513
+ React79.useLayoutEffect(() => {
14514
+ if (!showBlockCaret) return;
14515
+ const ta = editableTextareaRef.current;
14516
+ const mirror = caretMirrorRef.current;
14517
+ const marker = caretMarkerRef.current;
14518
+ if (!ta || !mirror || !marker) return;
14519
+ const computed = window.getComputedStyle(ta);
14520
+ const MIRRORED_PROPS = [
14521
+ "font-family",
14522
+ "font-size",
14523
+ "font-weight",
14524
+ "font-style",
14525
+ "letter-spacing",
14526
+ "line-height",
14527
+ "padding-top",
14528
+ "padding-right",
14529
+ "padding-bottom",
14530
+ "padding-left",
14531
+ "border-top-width",
14532
+ "border-right-width",
14533
+ "border-bottom-width",
14534
+ "border-left-width",
14535
+ "box-sizing",
14536
+ "width",
14537
+ "white-space",
14538
+ "word-break",
14539
+ "overflow-wrap",
14540
+ "tab-size"
14541
+ ];
14542
+ for (const prop of MIRRORED_PROPS) {
14543
+ mirror.style.setProperty(prop, computed.getPropertyValue(prop));
14544
+ }
14545
+ const lineHeight = parseFloat(computed.getPropertyValue("line-height"));
14546
+ setCaretGeometry({
14547
+ top: marker.offsetTop,
14548
+ left: marker.offsetLeft,
14549
+ lineHeight: Number.isFinite(lineHeight) ? lineHeight : 0
14550
+ });
14551
+ }, [showBlockCaret, editableValue, caretIndex]);
14124
14552
  const errorLineProps = React79.useMemo(() => buildLineProps(errorLines), [errorLines]);
14125
14553
  const viewerLineProps = React79.useMemo(
14126
14554
  () => buildLineProps(errorLines, "px-4 py-0.5 hover:bg-muted/50"),
@@ -14644,11 +15072,24 @@ var init_CodeBlock = __esm({
14644
15072
  {
14645
15073
  ref: editableTextareaRef,
14646
15074
  defaultValue: code,
14647
- onChange: (e) => handleEditableChange(e.target.value),
15075
+ onChange: (e) => {
15076
+ const next = e.target.value;
15077
+ recordKeystroke(editableValue, prevCaretRef.current, next);
15078
+ handleEditableChange(next, "keystroke");
15079
+ },
14648
15080
  onScroll: handleEditableScroll,
14649
15081
  onSelect: (e) => setCaretIndex(e.currentTarget.selectionStart),
14650
- onFocus: editorId ? () => eventBus.emit(`UI:${onEditorFocus}`, { editorId }) : void 0,
14651
- onBlur: editorId ? () => eventBus.emit(`UI:${onEditorBlur}`, { editorId }) : void 0,
15082
+ onKeyUp: (e) => setCaretIndex(e.currentTarget.selectionStart),
15083
+ onClick: (e) => setCaretIndex(e.currentTarget.selectionStart),
15084
+ onKeyDown: handleEditableKeyDown,
15085
+ onFocus: () => {
15086
+ setIsFocused(true);
15087
+ if (editorId) eventBus.emit(`UI:${onEditorFocus}`, { editorId });
15088
+ },
15089
+ onBlur: () => {
15090
+ setIsFocused(false);
15091
+ if (editorId) eventBus.emit(`UI:${onEditorBlur}`, { editorId });
15092
+ },
14652
15093
  spellCheck: false,
14653
15094
  style: {
14654
15095
  position: "absolute",
@@ -14675,16 +15116,39 @@ var init_CodeBlock = __esm({
14675
15116
  },
14676
15117
  editableTextareaKey
14677
15118
  ),
14678
- caretMode !== "bar" && /* @__PURE__ */ jsxRuntime.jsx(
15119
+ showBlockCaret && /* @__PURE__ */ jsxRuntime.jsxs(
15120
+ "div",
15121
+ {
15122
+ ref: caretMirrorRef,
15123
+ "aria-hidden": true,
15124
+ "data-testid": "editor-caret-mirror",
15125
+ style: {
15126
+ position: "absolute",
15127
+ top: 0,
15128
+ left: 0,
15129
+ padding: "1rem",
15130
+ margin: 0,
15131
+ border: "none",
15132
+ visibility: "hidden",
15133
+ pointerEvents: "none"
15134
+ },
15135
+ children: [
15136
+ editableValue.slice(0, caretIndex),
15137
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ref: caretMarkerRef, "data-testid": "editor-caret-marker", children: "\u200B" })
15138
+ ]
15139
+ }
15140
+ ),
15141
+ showBlockCaret && caretGeometry && /* @__PURE__ */ jsxRuntime.jsx(
14679
15142
  "span",
14680
15143
  {
14681
15144
  "aria-hidden": true,
15145
+ "data-testid": "editor-caret",
14682
15146
  style: {
14683
15147
  position: "absolute",
14684
- top: `calc(1rem + ${caretRowCol.row * 19.5}px)`,
14685
- left: `calc(1rem + ${caretRowCol.col}ch)`,
15148
+ top: caretGeometry.top,
15149
+ left: caretGeometry.left,
14686
15150
  width: "1ch",
14687
- height: caretMode === "block" ? "19.5px" : "2px",
15151
+ height: caretMode === "block" ? caretGeometry.lineHeight || "1.2em" : "2px",
14688
15152
  backgroundColor: caretMode === "block" ? "rgba(230, 230, 230, 0.5)" : void 0,
14689
15153
  borderBottom: caretMode === "underline" ? "2px solid #e6e6e6" : void 0,
14690
15154
  pointerEvents: "none"
@@ -19400,6 +19864,10 @@ var init_projector = __esm({
19400
19864
  });
19401
19865
 
19402
19866
  // lib/drawable/hitTest.ts
19867
+ function shapeDrawnItem(n) {
19868
+ if (n.shape !== "rect") return { pos: n.position, id: n.id };
19869
+ return { pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotate };
19870
+ }
19403
19871
  function collectDrawnItems(nodes) {
19404
19872
  const out = [];
19405
19873
  for (const n of nodes) {
@@ -19408,6 +19876,8 @@ function collectDrawnItems(nodes) {
19408
19876
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotation });
19409
19877
  break;
19410
19878
  case "draw-shape":
19879
+ if (isValidScenePos(n.position)) out.push(shapeDrawnItem(n));
19880
+ break;
19411
19881
  case "draw-text":
19412
19882
  case "draw-group":
19413
19883
  case "draw-mesh":
@@ -19419,6 +19889,10 @@ function collectDrawnItems(nodes) {
19419
19889
  }
19420
19890
  break;
19421
19891
  case "draw-shape-layer":
19892
+ for (const it of n.items) {
19893
+ if (isValidScenePos(it.position)) out.push(shapeDrawnItem(it));
19894
+ }
19895
+ break;
19422
19896
  case "draw-text-layer":
19423
19897
  for (const it of n.items) {
19424
19898
  if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
@@ -24210,6 +24684,7 @@ function SubMenu({
24210
24684
  item.onClick?.();
24211
24685
  },
24212
24686
  "aria-disabled": item.disabled || void 0,
24687
+ title: item.title,
24213
24688
  "data-testid": item.event ? `action-${item.event}` : void 0,
24214
24689
  className: cn(
24215
24690
  "w-full flex items-center gap-3 px-4 py-2 text-start",
@@ -24258,6 +24733,7 @@ function MenuItemRow({
24258
24733
  as: "button",
24259
24734
  onClick: () => onItemClick({ ...item, id: itemId }, itemId),
24260
24735
  "aria-disabled": item.disabled || void 0,
24736
+ title: item.title,
24261
24737
  onMouseEnter: (e) => {
24262
24738
  if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
24263
24739
  },
@@ -34722,13 +35198,13 @@ var init_MapView = __esm({
34722
35198
  shadowSize: [41, 41]
34723
35199
  });
34724
35200
  L.Marker.prototype.options.icon = defaultIcon;
34725
- const { useEffect: useEffect72, useRef: useRef70, useCallback: useCallback109, useState: useState110 } = React79__namespace.default;
35201
+ const { useEffect: useEffect73, useRef: useRef70, useCallback: useCallback110, useState: useState110 } = React79__namespace.default;
34726
35202
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
34727
35203
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
34728
35204
  function MapUpdater({ centerLat, centerLng, zoom }) {
34729
35205
  const map = useMap();
34730
35206
  const prevRef = useRef70({ centerLat, centerLng, zoom });
34731
- useEffect72(() => {
35207
+ useEffect73(() => {
34732
35208
  const prev = prevRef.current;
34733
35209
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
34734
35210
  map.setView([centerLat, centerLng], zoom);
@@ -34739,7 +35215,7 @@ var init_MapView = __esm({
34739
35215
  }
34740
35216
  function MapClickHandler({ onMapClick }) {
34741
35217
  const map = useMap();
34742
- useEffect72(() => {
35218
+ useEffect73(() => {
34743
35219
  if (!onMapClick) return;
34744
35220
  const handler = (e) => {
34745
35221
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -34768,7 +35244,7 @@ var init_MapView = __esm({
34768
35244
  }) {
34769
35245
  const eventBus = useEventBus2();
34770
35246
  const [clickedPosition, setClickedPosition] = useState110(null);
34771
- const handleMapClick = useCallback109((lat, lng) => {
35247
+ const handleMapClick = useCallback110((lat, lng) => {
34772
35248
  if (showClickedPin) {
34773
35249
  setClickedPosition({ lat, lng });
34774
35250
  }
@@ -34777,7 +35253,7 @@ var init_MapView = __esm({
34777
35253
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
34778
35254
  }
34779
35255
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
34780
- const handleMarkerClick = useCallback109((marker) => {
35256
+ const handleMarkerClick = useCallback110((marker) => {
34781
35257
  onMarkerClick?.(marker);
34782
35258
  if (markerClickEvent) {
34783
35259
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -44609,7 +45085,10 @@ var init_FloatingToolbar = __esm({
44609
45085
  positionClasses = {
44610
45086
  "bottom-center": "bottom-6 left-1/2 -translate-x-1/2",
44611
45087
  "bottom-left": "bottom-6 left-6",
44612
- "bottom-right": "bottom-6 right-6"
45088
+ "bottom-right": "bottom-6 right-6",
45089
+ "top-center": "top-6 left-1/2 -translate-x-1/2",
45090
+ "top-left": "top-6 left-6",
45091
+ "top-right": "top-6 right-6"
44613
45092
  };
44614
45093
  exports.FloatingToolbar = ({
44615
45094
  items,
@@ -52395,6 +52874,7 @@ var init_component_registry_generated = __esm({
52395
52874
  "TrendIndicator": exports.TrendIndicator,
52396
52875
  "TypewriterText": exports.TypewriterText,
52397
52876
  "Typography": exports.Typography,
52877
+ "UISlotComponent": UISlotComponent,
52398
52878
  "UISlotRenderer": UISlotRenderer,
52399
52879
  "UploadDropZone": exports.UploadDropZone,
52400
52880
  "VStack": exports.VStack,
@@ -52700,6 +53180,7 @@ function UISlotComponentInner({
52700
53180
  const contained = React79.useContext(SlotContainedContext);
52701
53181
  const schemaCtx = providers.useEntitySchemaOptional();
52702
53182
  const rawContent = slots[slot];
53183
+ const regionClassName = fallback !== void 0 ? cn("contents", className) : className;
52703
53184
  const binding = providers.useEntityBindingSnapshot(rawContent?.sourceTrait);
52704
53185
  const content = React79.useMemo(() => {
52705
53186
  if (!rawContent) return rawContent;
@@ -52748,7 +53229,7 @@ function UISlotComponentInner({
52748
53229
  exports.Box,
52749
53230
  {
52750
53231
  id: `slot-${slot}`,
52751
- className: cn("ui-slot", `ui-slot-${slot}`, className),
53232
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
52752
53233
  "data-testid": `ui-slot-${slot}`,
52753
53234
  "data-slot-mode": "fallback",
52754
53235
  children: fallback
@@ -52783,7 +53264,7 @@ function UISlotComponentInner({
52783
53264
  exports.Box,
52784
53265
  {
52785
53266
  id: `slot-${slot}-fallback`,
52786
- className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
53267
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, regionClassName),
52787
53268
  "data-testid": `ui-slot-${slot}-fallback`,
52788
53269
  "data-slot-mode": "append",
52789
53270
  children: fallback
@@ -52815,7 +53296,7 @@ function UISlotComponentInner({
52815
53296
  exports.Box,
52816
53297
  {
52817
53298
  id: `slot-${slot}`,
52818
- className: cn("ui-slot", `ui-slot-${slot}`, className),
53299
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
52819
53300
  "data-pattern": content.pattern,
52820
53301
  "data-source-trait": content.sourceTrait,
52821
53302
  "data-testid": `ui-slot-${slot}`,
@@ -54641,6 +55122,33 @@ init_useEventBus();
54641
55122
 
54642
55123
  // hooks/useKeyboardRouter.ts
54643
55124
  init_useEventBus();
55125
+ function mergeCaptureTables(tables) {
55126
+ const merged = {};
55127
+ for (const table of tables) {
55128
+ for (const [target, entry] of Object.entries(table)) {
55129
+ const current = merged[target];
55130
+ if (!current) {
55131
+ merged[target] = { mode: entry.mode, keys: new Set(entry.keys) };
55132
+ continue;
55133
+ }
55134
+ for (const key of entry.keys) current.keys.add(key);
55135
+ if (current.mode === "any" || entry.mode === "any") current.mode = "any";
55136
+ else if (current.mode !== entry.mode) {
55137
+ current.mode = Array.from(/* @__PURE__ */ new Set([...current.mode.split("|"), entry.mode])).sort().join("|");
55138
+ }
55139
+ }
55140
+ }
55141
+ return merged;
55142
+ }
55143
+ function keyChord(event) {
55144
+ const parts = [];
55145
+ if (event.ctrlKey) parts.push("Control");
55146
+ if (event.altKey) parts.push("Alt");
55147
+ if (event.shiftKey) parts.push("Shift");
55148
+ if (event.metaKey) parts.push("Meta");
55149
+ parts.push(event.key);
55150
+ return parts.join("+");
55151
+ }
54644
55152
  function useKeyboardRouter(options) {
54645
55153
  const {
54646
55154
  captureTable,
@@ -54677,7 +55185,7 @@ function useKeyboardRouter(options) {
54677
55185
  if (event.isComposing) return;
54678
55186
  const target = focusedEditorIdRef.current ?? "shell";
54679
55187
  const entry = captureTableRef.current[target];
54680
- const captured = entry !== void 0 && (entry.mode === "any" || entry.keys.has(event.key));
55188
+ const captured = entry !== void 0 && (entry.keys.has(event.key) || entry.keys.has(keyChord(event)));
54681
55189
  if (captured) {
54682
55190
  event.preventDefault();
54683
55191
  }
@@ -56142,9 +56650,11 @@ exports.getCurrentFrame = getCurrentFrame;
56142
56650
  exports.getTileDimensions = getTileDimensions;
56143
56651
  exports.inferDirection = inferDirection;
56144
56652
  exports.isoToScreen = isoToScreen;
56653
+ exports.keyChord = keyChord;
56145
56654
  exports.makeAsset = makeAsset;
56146
56655
  exports.makeAssetMap = makeAssetMap;
56147
56656
  exports.mapBookData = mapBookData;
56657
+ exports.mergeCaptureTables = mergeCaptureTables;
56148
56658
  exports.meshSphere = meshSphere;
56149
56659
  exports.num = num;
56150
56660
  exports.objAvailableActions = objAvailableActions;