@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.
@@ -1814,7 +1814,7 @@ var init_Button = __esm({
1814
1814
  secondary: [
1815
1815
  "bg-transparent text-accent",
1816
1816
  "border border-accent",
1817
- "hover:bg-accent hover:text-white hover:border-accent",
1817
+ "hover:bg-accent hover:text-accent-foreground hover:border-accent",
1818
1818
  "active:scale-[var(--active-scale)]"
1819
1819
  ].join(" "),
1820
1820
  ghost: [
@@ -2154,8 +2154,17 @@ var init_Typography = __esm({
2154
2154
  weight && weightStyles[weight],
2155
2155
  size && typographySizeStyles[size],
2156
2156
  align && `text-${align}`,
2157
- truncate && "truncate overflow-hidden text-ellipsis",
2158
- overflow && overflowStyles2[overflow],
2157
+ // `min-w-0` rides along with truncate/wrap/clamp: a flex or grid
2158
+ // item's default `min-width: auto` refuses to shrink below its
2159
+ // content's intrinsic width, so ellipsis/wrap silently does nothing
2160
+ // in the single most common placement (a row next to a fixed-width
2161
+ // control) unless the item can also shrink to zero. (Spelled out as
2162
+ // just "truncate", not "truncate overflow-hidden text-ellipsis" —
2163
+ // tailwind-merge treats those as the same conflict group and drops
2164
+ // "truncate" as the earlier-declared class, silently losing its
2165
+ // `white-space: nowrap`.)
2166
+ truncate && "truncate min-w-0",
2167
+ overflow && cn(overflowStyles2[overflow], overflow !== "visible" && "min-w-0"),
2159
2168
  className
2160
2169
  ),
2161
2170
  style
@@ -10496,6 +10505,10 @@ var init_paintDispatch = __esm({
10496
10505
  });
10497
10506
 
10498
10507
  // lib/drawable/hitTest.ts
10508
+ function shapeDrawnItem(n) {
10509
+ if (n.shape !== "rect") return { pos: n.position, id: n.id };
10510
+ return { pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotate };
10511
+ }
10499
10512
  function collectDrawnItems(nodes) {
10500
10513
  const out = [];
10501
10514
  for (const n of nodes) {
@@ -10504,6 +10517,8 @@ function collectDrawnItems(nodes) {
10504
10517
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotation });
10505
10518
  break;
10506
10519
  case "draw-shape":
10520
+ if (isValidScenePos(n.position)) out.push(shapeDrawnItem(n));
10521
+ break;
10507
10522
  case "draw-text":
10508
10523
  case "draw-group":
10509
10524
  case "draw-mesh":
@@ -10515,6 +10530,10 @@ function collectDrawnItems(nodes) {
10515
10530
  }
10516
10531
  break;
10517
10532
  case "draw-shape-layer":
10533
+ for (const it of n.items) {
10534
+ if (isValidScenePos(it.position)) out.push(shapeDrawnItem(it));
10535
+ }
10536
+ break;
10518
10537
  case "draw-text-layer":
10519
10538
  for (const it of n.items) {
10520
10539
  if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
@@ -17633,6 +17652,12 @@ var init_EmptyState = __esm({
17633
17652
  });
17634
17653
 
17635
17654
  // lib/editorMotions.ts
17655
+ function isEditorMotion(value) {
17656
+ return EDITOR_MOTION_SET.has(value);
17657
+ }
17658
+ function isEditorOperator(value) {
17659
+ return EDITOR_OPERATOR_SET.has(value);
17660
+ }
17636
17661
  function clamp(value, min, max) {
17637
17662
  return Math.max(min, Math.min(max, value));
17638
17663
  }
@@ -17700,6 +17725,30 @@ function prevParagraphBoundary(lines, fromLineIdx) {
17700
17725
  }
17701
17726
  return 0;
17702
17727
  }
17728
+ function matchBracket(text, pos) {
17729
+ const ch = text[pos];
17730
+ const partner = ch !== void 0 ? BRACKET_PARTNER[ch] : void 0;
17731
+ if (partner === void 0) return null;
17732
+ let depth = 1;
17733
+ if (OPEN_BRACKETS.has(ch)) {
17734
+ for (let i = pos + 1; i < text.length; i++) {
17735
+ if (text[i] === ch) depth++;
17736
+ else if (text[i] === partner) {
17737
+ depth--;
17738
+ if (depth === 0) return i;
17739
+ }
17740
+ }
17741
+ } else {
17742
+ for (let i = pos - 1; i >= 0; i--) {
17743
+ if (text[i] === ch) depth++;
17744
+ else if (text[i] === partner) {
17745
+ depth--;
17746
+ if (depth === 0) return i;
17747
+ }
17748
+ }
17749
+ }
17750
+ return null;
17751
+ }
17703
17752
  function applyMotion(text, caret, motion, count) {
17704
17753
  const n = Math.max(1, count);
17705
17754
  const lines = computeLines(text);
@@ -17761,6 +17810,20 @@ function applyMotion(text, caret, motion, count) {
17761
17810
  }
17762
17811
  return pos;
17763
17812
  }
17813
+ case "match-bracket": {
17814
+ const chAtCaret = text[caret];
17815
+ if (chAtCaret !== void 0 && BRACKET_PARTNER[chAtCaret] !== void 0) {
17816
+ const target = matchBracket(text, caret);
17817
+ return target === null ? caret : target;
17818
+ }
17819
+ for (let i = caret; i < line.end; i++) {
17820
+ if (BRACKET_PARTNER[text[i]] !== void 0) {
17821
+ const target = matchBracket(text, i);
17822
+ return target === null ? caret : target;
17823
+ }
17824
+ }
17825
+ return caret;
17826
+ }
17764
17827
  case "line":
17765
17828
  case "selection":
17766
17829
  return caret;
@@ -17788,8 +17851,8 @@ function motionRange(text, caret, motion, count, selection) {
17788
17851
  const newCaret = applyMotion(text, caret, motion, count);
17789
17852
  let start = Math.min(caret, newCaret);
17790
17853
  let end = Math.max(caret, newCaret);
17791
- if (motion === "word-end" || motion === "line-end") {
17792
- end = Math.min(Math.max(start, newCaret) + 1, text.length);
17854
+ if ((motion === "word-end" || motion === "line-end" || motion === "match-bracket") && newCaret !== caret) {
17855
+ end = Math.min(Math.max(caret, newCaret) + 1, text.length);
17793
17856
  } else if (motion === "word-forward" && newCaret > caret) {
17794
17857
  const startLine = lineIndexAt(lines, caret);
17795
17858
  const endLine = lineIndexAt(lines, newCaret);
@@ -17799,17 +17862,198 @@ function motionRange(text, caret, motion, count, selection) {
17799
17862
  }
17800
17863
  return [start, end];
17801
17864
  }
17802
- function applyOperator(text, range, operator, register) {
17803
- const start = clamp(range[0], 0, text.length);
17804
- const end = clamp(range[1], start, text.length);
17805
- const removed = text.slice(start, end);
17806
- if (operator === "yank") {
17807
- return { text, caret: start, register: removed };
17865
+ function toggleCase(s) {
17866
+ return s.replace(/[a-zA-Z]/g, (c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase());
17867
+ }
17868
+ function applyJoin(text, caret, count, register, registerLinewise) {
17869
+ const lines = computeLines(text);
17870
+ const startIdx = lineIndexAt(lines, caret);
17871
+ const n = Math.max(2, count);
17872
+ const endIdx = clamp(startIdx + n - 1, 0, lines.length - 1);
17873
+ if (endIdx <= startIdx) {
17874
+ return { text, caret, register, registerLinewise };
17875
+ }
17876
+ const joinCaret = lines[startIdx].end;
17877
+ let joined = text.slice(lines[startIdx].start, lines[startIdx].end);
17878
+ for (let i = startIdx + 1; i <= endIdx; i++) {
17879
+ const raw = text.slice(lines[i].start, lines[i].end);
17880
+ joined += " " + raw.replace(/^[ \t]+/, "");
17881
+ }
17882
+ const newText = text.slice(0, lines[startIdx].start) + joined + text.slice(lines[endIdx].end);
17883
+ return { text: newText, caret: joinCaret, register, registerLinewise };
17884
+ }
17885
+ function applyIndent(text, start, end, operator, register, registerLinewise) {
17886
+ const lines = computeLines(text);
17887
+ const startLineIdx = lineIndexAt(lines, start);
17888
+ const lastTouchedPos = end > start ? end - 1 : start;
17889
+ const endLineIdx = lineIndexAt(lines, lastTouchedPos);
17890
+ let result = text;
17891
+ for (let i = endLineIdx; i >= startLineIdx; i--) {
17892
+ const lineStart = lines[i].start;
17893
+ if (operator === "indent") {
17894
+ result = result.slice(0, lineStart) + INDENT_UNIT + result.slice(lineStart);
17895
+ } else if (result[lineStart] === " ") {
17896
+ result = result.slice(0, lineStart) + result.slice(lineStart + 1);
17897
+ } else {
17898
+ let removeCount = 0;
17899
+ while (removeCount < INDENT_UNIT.length && result[lineStart + removeCount] === " ") removeCount++;
17900
+ result = result.slice(0, lineStart) + result.slice(lineStart + removeCount);
17901
+ }
17902
+ }
17903
+ const newLines = computeLines(result);
17904
+ const caret = firstNonBlank(result, newLines[startLineIdx]);
17905
+ return { text: result, caret, register, registerLinewise };
17906
+ }
17907
+ function applyPut(text, caret, operator, register, registerLinewise, count) {
17908
+ if (register.length === 0) {
17909
+ return { text, caret, register, registerLinewise };
17910
+ }
17911
+ const content = register.repeat(count);
17912
+ const lines = computeLines(text);
17913
+ const line = lines[lineIndexAt(lines, caret)];
17914
+ if (registerLinewise) {
17915
+ if (operator === "put-before") {
17916
+ const insertPos3 = line.start;
17917
+ return {
17918
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
17919
+ caret: insertPos3,
17920
+ register,
17921
+ registerLinewise
17922
+ };
17923
+ }
17924
+ const nextLineIdx = lineIndexAt(lines, caret) + 1;
17925
+ if (nextLineIdx < lines.length) {
17926
+ const insertPos3 = lines[nextLineIdx].start;
17927
+ return {
17928
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
17929
+ caret: insertPos3,
17930
+ register,
17931
+ registerLinewise
17932
+ };
17933
+ }
17934
+ const insertPos2 = text.length;
17935
+ return {
17936
+ text: text.slice(0, insertPos2) + "\n" + content,
17937
+ caret: insertPos2 + 1,
17938
+ register,
17939
+ registerLinewise
17940
+ };
17941
+ }
17942
+ const insertPos = operator === "put-before" ? clamp(caret, 0, text.length) : clamp(caret + 1, line.start, line.end);
17943
+ return {
17944
+ text: text.slice(0, insertPos) + content + text.slice(insertPos),
17945
+ caret: insertPos + content.length - 1,
17946
+ register,
17947
+ registerLinewise
17948
+ };
17949
+ }
17950
+ function applyOperator(input) {
17951
+ const { text, caret, operator, motion, register, registerLinewise } = input;
17952
+ const count = Math.max(1, input.count);
17953
+ const start = clamp(input.range[0], 0, text.length);
17954
+ const end = clamp(input.range[1], start, text.length);
17955
+ switch (operator) {
17956
+ case "yank":
17957
+ return { text, caret: start, register: text.slice(start, end), registerLinewise: motion === "line" };
17958
+ case "delete":
17959
+ case "replace": {
17960
+ const removed = text.slice(start, end);
17961
+ return {
17962
+ text: text.slice(0, start) + text.slice(end),
17963
+ caret: start,
17964
+ register: removed,
17965
+ registerLinewise: motion === "line"
17966
+ };
17967
+ }
17968
+ case "change": {
17969
+ if (motion === "line") {
17970
+ const hasTrailingNewline = end > start && text[end - 1] === "\n";
17971
+ const removeEnd = hasTrailingNewline ? end - 1 : end;
17972
+ const removed2 = text.slice(start, removeEnd);
17973
+ return {
17974
+ text: text.slice(0, start) + text.slice(removeEnd),
17975
+ caret: start,
17976
+ register: removed2,
17977
+ registerLinewise: true
17978
+ };
17979
+ }
17980
+ const removed = text.slice(start, end);
17981
+ return {
17982
+ text: text.slice(0, start) + text.slice(end),
17983
+ caret: start,
17984
+ register: removed,
17985
+ registerLinewise: false
17986
+ };
17987
+ }
17988
+ case "put":
17989
+ case "put-before":
17990
+ return applyPut(text, caret, operator, register, registerLinewise, count);
17991
+ case "join":
17992
+ return applyJoin(text, caret, count, register, registerLinewise);
17993
+ case "toggle-case": {
17994
+ const toggled = toggleCase(text.slice(start, end));
17995
+ return { text: text.slice(0, start) + toggled + text.slice(end), caret: end, register, registerLinewise };
17996
+ }
17997
+ case "indent":
17998
+ case "dedent":
17999
+ return applyIndent(text, start, end, operator, register, registerLinewise);
18000
+ case "undo":
18001
+ case "redo":
18002
+ return { text, caret, register, registerLinewise };
18003
+ default: {
18004
+ const _exhaustive = operator;
18005
+ return _exhaustive;
18006
+ }
17808
18007
  }
17809
- return { text: text.slice(0, start) + text.slice(end), caret: start, register: removed };
17810
18008
  }
18009
+ var EDITOR_MOTIONS, EDITOR_OPERATORS, EDITOR_MOTION_SET, EDITOR_OPERATOR_SET, INDENT_UNIT, BRACKET_PARTNER, OPEN_BRACKETS;
17811
18010
  var init_editorMotions = __esm({
17812
18011
  "lib/editorMotions.ts"() {
18012
+ EDITOR_MOTIONS = [
18013
+ "left",
18014
+ "right",
18015
+ "up",
18016
+ "down",
18017
+ "word-forward",
18018
+ "word-back",
18019
+ "word-end",
18020
+ "line-start",
18021
+ "line-end",
18022
+ "first-nonblank",
18023
+ "doc-start",
18024
+ "doc-end",
18025
+ "paragraph-forward",
18026
+ "paragraph-back",
18027
+ "line",
18028
+ "selection",
18029
+ "match-bracket"
18030
+ ];
18031
+ EDITOR_OPERATORS = [
18032
+ "delete",
18033
+ "yank",
18034
+ "change",
18035
+ "put",
18036
+ "put-before",
18037
+ "undo",
18038
+ "redo",
18039
+ "join",
18040
+ "toggle-case",
18041
+ "indent",
18042
+ "dedent",
18043
+ "replace"
18044
+ ];
18045
+ EDITOR_MOTION_SET = new Set(EDITOR_MOTIONS);
18046
+ EDITOR_OPERATOR_SET = new Set(EDITOR_OPERATORS);
18047
+ INDENT_UNIT = " ";
18048
+ BRACKET_PARTNER = {
18049
+ "(": ")",
18050
+ ")": "(",
18051
+ "[": "]",
18052
+ "]": "[",
18053
+ "{": "}",
18054
+ "}": "{"
18055
+ };
18056
+ OPEN_BRACKETS = /* @__PURE__ */ new Set(["(", "[", "{"]);
17813
18057
  }
17814
18058
  });
17815
18059
  function isMotionPayload(payload) {
@@ -17824,14 +18068,103 @@ function isInsertTextPayload(payload) {
17824
18068
  function isSetModePayload(payload) {
17825
18069
  return !!payload && typeof payload.editorId === "string" && typeof payload.mode === "string" && typeof payload.caret === "string";
17826
18070
  }
18071
+ function typedDelta(prev, next) {
18072
+ const maxPrefix = Math.min(prev.length, next.length);
18073
+ let prefixLen = 0;
18074
+ while (prefixLen < maxPrefix && prev[prefixLen] === next[prefixLen]) prefixLen++;
18075
+ const maxSuffix = Math.min(prev.length - prefixLen, next.length - prefixLen);
18076
+ let suffixLen = 0;
18077
+ while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
18078
+ suffixLen++;
18079
+ }
18080
+ const removed = prev.slice(prefixLen, prev.length - suffixLen);
18081
+ const inserted = next.slice(prefixLen, next.length - suffixLen);
18082
+ return removed + inserted;
18083
+ }
17827
18084
  function useEditorCapabilities(args) {
17828
18085
  const [caretMode, setCaretMode] = React87.useState("bar");
17829
18086
  const registerRef = React87.useRef("");
18087
+ const registerLinewiseRef = React87.useRef(false);
18088
+ const pastRef = React87.useRef([]);
18089
+ const futureRef = React87.useRef([]);
18090
+ const openTypingStepRef = React87.useRef(false);
18091
+ const insertSessionOpenRef = React87.useRef(false);
18092
+ const closeOpenTypingStep = React87.useCallback(() => {
18093
+ openTypingStepRef.current = false;
18094
+ }, []);
18095
+ const pushHistoryStep = React87.useCallback(
18096
+ (text, caret) => {
18097
+ closeOpenTypingStep();
18098
+ pastRef.current.push({ text, caret });
18099
+ futureRef.current = [];
18100
+ },
18101
+ [closeOpenTypingStep]
18102
+ );
18103
+ const recordKeystroke = React87.useCallback(
18104
+ (prevText, prevCaret, nextText) => {
18105
+ if (!openTypingStepRef.current) {
18106
+ pastRef.current.push({ text: prevText, caret: prevCaret });
18107
+ futureRef.current = [];
18108
+ openTypingStepRef.current = true;
18109
+ }
18110
+ if (!insertSessionOpenRef.current && /\s/.test(typedDelta(prevText, nextText))) {
18111
+ openTypingStepRef.current = false;
18112
+ }
18113
+ },
18114
+ []
18115
+ );
18116
+ const performUndo = React87.useCallback(
18117
+ (count) => {
18118
+ const ta = args.textareaRef.current;
18119
+ if (!ta) return;
18120
+ closeOpenTypingStep();
18121
+ let moved = false;
18122
+ for (let i = 0; i < Math.max(1, count) && pastRef.current.length > 0; i++) {
18123
+ const prev = pastRef.current.pop();
18124
+ futureRef.current.push({ text: ta.value, caret: ta.selectionStart });
18125
+ ta.value = prev.text;
18126
+ ta.setSelectionRange(prev.caret, prev.caret);
18127
+ moved = true;
18128
+ }
18129
+ if (moved) args.applyChange(ta.value, "capability");
18130
+ },
18131
+ [args, closeOpenTypingStep]
18132
+ );
18133
+ const performRedo = React87.useCallback(
18134
+ (count) => {
18135
+ const ta = args.textareaRef.current;
18136
+ if (!ta) return;
18137
+ closeOpenTypingStep();
18138
+ let moved = false;
18139
+ for (let i = 0; i < Math.max(1, count) && futureRef.current.length > 0; i++) {
18140
+ const next = futureRef.current.pop();
18141
+ pastRef.current.push({ text: ta.value, caret: ta.selectionStart });
18142
+ ta.value = next.text;
18143
+ ta.setSelectionRange(next.caret, next.caret);
18144
+ moved = true;
18145
+ }
18146
+ if (moved) args.applyChange(ta.value, "capability");
18147
+ },
18148
+ [args, closeOpenTypingStep]
18149
+ );
18150
+ const undo = React87.useCallback(() => performUndo(1), [performUndo]);
18151
+ const redo = React87.useCallback(() => performRedo(1), [performRedo]);
18152
+ const wasFocusedRef = React87.useRef(args.focused);
18153
+ React87.useEffect(() => {
18154
+ if (wasFocusedRef.current && !args.focused) {
18155
+ setCaretMode("bar");
18156
+ }
18157
+ wasFocusedRef.current = args.focused;
18158
+ }, [args.focused]);
17830
18159
  useEventListener(`UI:${args.events.onMotion}`, (evt) => {
17831
18160
  if (!args.editorId || !isMotionPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18161
+ const { motion, count } = evt.payload;
18162
+ if (!isEditorMotion(motion)) {
18163
+ console.warn(`useEditorCapabilities: ignoring MOTION with unknown motion "${motion}"`);
18164
+ return;
18165
+ }
17832
18166
  const ta = args.textareaRef.current;
17833
18167
  if (!ta) return;
17834
- const { motion, count } = evt.payload;
17835
18168
  const newCaret = applyMotion(ta.value, ta.selectionStart, motion, count);
17836
18169
  if (ta.selectionStart !== ta.selectionEnd) {
17837
18170
  ta.setSelectionRange(ta.selectionStart, newCaret);
@@ -17841,32 +18174,63 @@ function useEditorCapabilities(args) {
17841
18174
  });
17842
18175
  useEventListener(`UI:${args.events.onOperate}`, (evt) => {
17843
18176
  if (!args.editorId || !isOperatePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18177
+ const { operator, motion, count } = evt.payload;
18178
+ if (!isEditorOperator(operator)) {
18179
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown operator "${operator}"`);
18180
+ return;
18181
+ }
18182
+ if (!isEditorMotion(motion)) {
18183
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown motion "${motion}"`);
18184
+ return;
18185
+ }
17844
18186
  const ta = args.textareaRef.current;
17845
18187
  if (!ta) return;
17846
- const { operator, motion, count } = evt.payload;
18188
+ if (operator === "undo") {
18189
+ performUndo(count);
18190
+ return;
18191
+ }
18192
+ if (operator === "redo") {
18193
+ performRedo(count);
18194
+ return;
18195
+ }
18196
+ pushHistoryStep(ta.value, ta.selectionStart);
17847
18197
  const selection = motion === "selection" ? [ta.selectionStart, ta.selectionEnd] : void 0;
17848
18198
  const range = motionRange(ta.value, ta.selectionStart, motion, count, selection);
17849
- const result = applyOperator(ta.value, range, operator, registerRef.current);
18199
+ const result = applyOperator({
18200
+ text: ta.value,
18201
+ caret: ta.selectionStart,
18202
+ range,
18203
+ operator,
18204
+ motion,
18205
+ count,
18206
+ register: registerRef.current,
18207
+ registerLinewise: registerLinewiseRef.current
18208
+ });
17850
18209
  registerRef.current = result.register;
18210
+ registerLinewiseRef.current = result.registerLinewise;
17851
18211
  if (operator === "yank") {
17852
- ta.setSelectionRange(range[0], range[0]);
18212
+ ta.setSelectionRange(result.caret, result.caret);
17853
18213
  } else {
17854
- ta.setRangeText("", range[0], range[1], "end");
18214
+ ta.value = result.text;
18215
+ ta.setSelectionRange(result.caret, result.caret);
17855
18216
  }
17856
- args.applyChange(ta.value);
18217
+ args.applyChange(ta.value, "capability");
17857
18218
  });
17858
18219
  useEventListener(`UI:${args.events.onInsertText}`, (evt) => {
17859
18220
  if (!args.editorId || !isInsertTextPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
17860
18221
  const ta = args.textareaRef.current;
17861
18222
  if (!ta) return;
18223
+ pushHistoryStep(ta.value, ta.selectionStart);
17862
18224
  ta.setRangeText(evt.payload.text, ta.selectionStart, ta.selectionEnd, "end");
17863
- args.applyChange(ta.value);
18225
+ args.applyChange(ta.value, "capability");
17864
18226
  });
17865
18227
  useEventListener(`UI:${args.events.onSetMode}`, (evt) => {
17866
18228
  if (!args.editorId || !isSetModePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
18229
+ closeOpenTypingStep();
18230
+ insertSessionOpenRef.current = evt.payload.mode === "INSERT";
17867
18231
  setCaretMode(evt.payload.caret);
17868
18232
  });
17869
- return { caretMode };
18233
+ return { caretMode, recordKeystroke, undo, redo };
17870
18234
  }
17871
18235
  var init_useEditorCapabilities = __esm({
17872
18236
  "components/core/molecules/markdown/useEditorCapabilities.ts"() {
@@ -18282,9 +18646,23 @@ var init_CodeBlock = __esm({
18282
18646
  "paragraph-forward",
18283
18647
  "paragraph-back",
18284
18648
  "line",
18285
- "selection"
18649
+ "selection",
18650
+ "match-bracket"
18286
18651
  ],
18287
- operators = ["delete", "yank", "change"]
18652
+ operators = [
18653
+ "delete",
18654
+ "yank",
18655
+ "change",
18656
+ "put",
18657
+ "put-before",
18658
+ "undo",
18659
+ "redo",
18660
+ "join",
18661
+ "toggle-case",
18662
+ "indent",
18663
+ "dedent",
18664
+ "replace"
18665
+ ]
18288
18666
  }) => {
18289
18667
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
18290
18668
  const activeStyle = resolveHighlightStyle(language);
@@ -18318,6 +18696,12 @@ var init_CodeBlock = __esm({
18318
18696
  const lastPropCodeRef = React87.useRef(code);
18319
18697
  const editableTextareaRef = React87.useRef(null);
18320
18698
  const editableOverlayRef = React87.useRef(null);
18699
+ const [isFocused, setIsFocused] = React87.useState(false);
18700
+ const prevCaretRef = React87.useRef(0);
18701
+ const [caretIndex, setCaretIndex] = React87.useState(0);
18702
+ const caretMirrorRef = React87.useRef(null);
18703
+ const caretMarkerRef = React87.useRef(null);
18704
+ const [caretGeometry, setCaretGeometry] = React87.useState(null);
18321
18705
  React87.useEffect(() => {
18322
18706
  if (code !== lastPropCodeRef.current) {
18323
18707
  lastPropCodeRef.current = code;
@@ -18333,23 +18717,77 @@ var init_CodeBlock = __esm({
18333
18717
  ov.scrollLeft = ta.scrollLeft;
18334
18718
  }
18335
18719
  }, []);
18336
- const handleEditableChange = React87.useCallback((v) => {
18720
+ const handleEditableChange = React87.useCallback((v, _origin) => {
18337
18721
  lastPropCodeRef.current = v;
18338
18722
  setEditableValue(v);
18723
+ const ta = editableTextareaRef.current;
18724
+ if (ta) setCaretIndex(ta.selectionStart);
18339
18725
  onChange?.(v);
18340
18726
  }, [onChange]);
18341
- const { caretMode } = useEditorCapabilities({
18727
+ const { caretMode, recordKeystroke, undo, redo } = useEditorCapabilities({
18342
18728
  editorId: editable ? editorId : void 0,
18343
18729
  textareaRef: editableTextareaRef,
18344
18730
  events: { onMotion, onOperate, onInsertText, onSetMode },
18731
+ focused: isFocused,
18345
18732
  applyChange: handleEditableChange
18346
18733
  });
18347
- const [caretIndex, setCaretIndex] = React87.useState(0);
18348
- const caretRowCol = React87.useMemo(() => {
18349
- const before = editableValue.slice(0, Math.min(caretIndex, editableValue.length));
18350
- const lines = before.split("\n");
18351
- return { row: lines.length - 1, col: lines[lines.length - 1].length };
18352
- }, [editableValue, caretIndex]);
18734
+ const handleEditableKeyDown = React87.useCallback(
18735
+ (e) => {
18736
+ const ta = editableTextareaRef.current;
18737
+ if (ta) prevCaretRef.current = ta.selectionStart;
18738
+ const mod = e.metaKey || e.ctrlKey;
18739
+ if (!mod) return;
18740
+ const key = e.key.toLowerCase();
18741
+ if (key === "z" && !e.shiftKey) {
18742
+ e.preventDefault();
18743
+ undo();
18744
+ } else if (key === "z" && e.shiftKey || key === "y") {
18745
+ e.preventDefault();
18746
+ redo();
18747
+ }
18748
+ },
18749
+ [undo, redo]
18750
+ );
18751
+ const showBlockCaret = isFocused && caretMode !== "bar";
18752
+ React87.useLayoutEffect(() => {
18753
+ if (!showBlockCaret) return;
18754
+ const ta = editableTextareaRef.current;
18755
+ const mirror = caretMirrorRef.current;
18756
+ const marker = caretMarkerRef.current;
18757
+ if (!ta || !mirror || !marker) return;
18758
+ const computed = window.getComputedStyle(ta);
18759
+ const MIRRORED_PROPS = [
18760
+ "font-family",
18761
+ "font-size",
18762
+ "font-weight",
18763
+ "font-style",
18764
+ "letter-spacing",
18765
+ "line-height",
18766
+ "padding-top",
18767
+ "padding-right",
18768
+ "padding-bottom",
18769
+ "padding-left",
18770
+ "border-top-width",
18771
+ "border-right-width",
18772
+ "border-bottom-width",
18773
+ "border-left-width",
18774
+ "box-sizing",
18775
+ "width",
18776
+ "white-space",
18777
+ "word-break",
18778
+ "overflow-wrap",
18779
+ "tab-size"
18780
+ ];
18781
+ for (const prop of MIRRORED_PROPS) {
18782
+ mirror.style.setProperty(prop, computed.getPropertyValue(prop));
18783
+ }
18784
+ const lineHeight = parseFloat(computed.getPropertyValue("line-height"));
18785
+ setCaretGeometry({
18786
+ top: marker.offsetTop,
18787
+ left: marker.offsetLeft,
18788
+ lineHeight: Number.isFinite(lineHeight) ? lineHeight : 0
18789
+ });
18790
+ }, [showBlockCaret, editableValue, caretIndex]);
18353
18791
  const errorLineProps = React87.useMemo(() => buildLineProps(errorLines), [errorLines]);
18354
18792
  const viewerLineProps = React87.useMemo(
18355
18793
  () => buildLineProps(errorLines, "px-4 py-0.5 hover:bg-muted/50"),
@@ -18873,11 +19311,24 @@ var init_CodeBlock = __esm({
18873
19311
  {
18874
19312
  ref: editableTextareaRef,
18875
19313
  defaultValue: code,
18876
- onChange: (e) => handleEditableChange(e.target.value),
19314
+ onChange: (e) => {
19315
+ const next = e.target.value;
19316
+ recordKeystroke(editableValue, prevCaretRef.current, next);
19317
+ handleEditableChange(next, "keystroke");
19318
+ },
18877
19319
  onScroll: handleEditableScroll,
18878
19320
  onSelect: (e) => setCaretIndex(e.currentTarget.selectionStart),
18879
- onFocus: editorId ? () => eventBus.emit(`UI:${onEditorFocus}`, { editorId }) : void 0,
18880
- onBlur: editorId ? () => eventBus.emit(`UI:${onEditorBlur}`, { editorId }) : void 0,
19321
+ onKeyUp: (e) => setCaretIndex(e.currentTarget.selectionStart),
19322
+ onClick: (e) => setCaretIndex(e.currentTarget.selectionStart),
19323
+ onKeyDown: handleEditableKeyDown,
19324
+ onFocus: () => {
19325
+ setIsFocused(true);
19326
+ if (editorId) eventBus.emit(`UI:${onEditorFocus}`, { editorId });
19327
+ },
19328
+ onBlur: () => {
19329
+ setIsFocused(false);
19330
+ if (editorId) eventBus.emit(`UI:${onEditorBlur}`, { editorId });
19331
+ },
18881
19332
  spellCheck: false,
18882
19333
  style: {
18883
19334
  position: "absolute",
@@ -18904,16 +19355,39 @@ var init_CodeBlock = __esm({
18904
19355
  },
18905
19356
  editableTextareaKey
18906
19357
  ),
18907
- caretMode !== "bar" && /* @__PURE__ */ jsxRuntime.jsx(
19358
+ showBlockCaret && /* @__PURE__ */ jsxRuntime.jsxs(
19359
+ "div",
19360
+ {
19361
+ ref: caretMirrorRef,
19362
+ "aria-hidden": true,
19363
+ "data-testid": "editor-caret-mirror",
19364
+ style: {
19365
+ position: "absolute",
19366
+ top: 0,
19367
+ left: 0,
19368
+ padding: "1rem",
19369
+ margin: 0,
19370
+ border: "none",
19371
+ visibility: "hidden",
19372
+ pointerEvents: "none"
19373
+ },
19374
+ children: [
19375
+ editableValue.slice(0, caretIndex),
19376
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ref: caretMarkerRef, "data-testid": "editor-caret-marker", children: "\u200B" })
19377
+ ]
19378
+ }
19379
+ ),
19380
+ showBlockCaret && caretGeometry && /* @__PURE__ */ jsxRuntime.jsx(
18908
19381
  "span",
18909
19382
  {
18910
19383
  "aria-hidden": true,
19384
+ "data-testid": "editor-caret",
18911
19385
  style: {
18912
19386
  position: "absolute",
18913
- top: `calc(1rem + ${caretRowCol.row * 19.5}px)`,
18914
- left: `calc(1rem + ${caretRowCol.col}ch)`,
19387
+ top: caretGeometry.top,
19388
+ left: caretGeometry.left,
18915
19389
  width: "1ch",
18916
- height: caretMode === "block" ? "19.5px" : "2px",
19390
+ height: caretMode === "block" ? caretGeometry.lineHeight || "1.2em" : "2px",
18917
19391
  backgroundColor: caretMode === "block" ? "rgba(230, 230, 230, 0.5)" : void 0,
18918
19392
  borderBottom: caretMode === "underline" ? "2px solid #e6e6e6" : void 0,
18919
19393
  pointerEvents: "none"
@@ -26524,6 +26998,7 @@ function SubMenu({
26524
26998
  item.onClick?.();
26525
26999
  },
26526
27000
  "aria-disabled": item.disabled || void 0,
27001
+ title: item.title,
26527
27002
  "data-testid": item.event ? `action-${item.event}` : void 0,
26528
27003
  className: cn(
26529
27004
  "w-full flex items-center gap-3 px-4 py-2 text-start",
@@ -26572,6 +27047,7 @@ function MenuItemRow({
26572
27047
  as: "button",
26573
27048
  onClick: () => onItemClick({ ...item, id: itemId }, itemId),
26574
27049
  "aria-disabled": item.disabled || void 0,
27050
+ title: item.title,
26575
27051
  onMouseEnter: (e) => {
26576
27052
  if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
26577
27053
  },
@@ -33448,13 +33924,13 @@ var init_MapView = __esm({
33448
33924
  shadowSize: [41, 41]
33449
33925
  });
33450
33926
  L.Marker.prototype.options.icon = defaultIcon;
33451
- const { useEffect: useEffect67, useRef: useRef69, useCallback: useCallback95, useState: useState102 } = React87__namespace.default;
33927
+ const { useEffect: useEffect68, useRef: useRef69, useCallback: useCallback96, useState: useState102 } = React87__namespace.default;
33452
33928
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33453
33929
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33454
33930
  function MapUpdater({ centerLat, centerLng, zoom }) {
33455
33931
  const map = useMap();
33456
33932
  const prevRef = useRef69({ centerLat, centerLng, zoom });
33457
- useEffect67(() => {
33933
+ useEffect68(() => {
33458
33934
  const prev = prevRef.current;
33459
33935
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
33460
33936
  map.setView([centerLat, centerLng], zoom);
@@ -33465,7 +33941,7 @@ var init_MapView = __esm({
33465
33941
  }
33466
33942
  function MapClickHandler({ onMapClick }) {
33467
33943
  const map = useMap();
33468
- useEffect67(() => {
33944
+ useEffect68(() => {
33469
33945
  if (!onMapClick) return;
33470
33946
  const handler = (e) => {
33471
33947
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -33494,7 +33970,7 @@ var init_MapView = __esm({
33494
33970
  }) {
33495
33971
  const eventBus = useEventBus2();
33496
33972
  const [clickedPosition, setClickedPosition] = useState102(null);
33497
- const handleMapClick = useCallback95((lat, lng) => {
33973
+ const handleMapClick = useCallback96((lat, lng) => {
33498
33974
  if (showClickedPin) {
33499
33975
  setClickedPosition({ lat, lng });
33500
33976
  }
@@ -33503,7 +33979,7 @@ var init_MapView = __esm({
33503
33979
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
33504
33980
  }
33505
33981
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
33506
- const handleMarkerClick = useCallback95((marker) => {
33982
+ const handleMarkerClick = useCallback96((marker) => {
33507
33983
  onMarkerClick?.(marker);
33508
33984
  if (markerClickEvent) {
33509
33985
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -43112,7 +43588,10 @@ var init_FloatingToolbar = __esm({
43112
43588
  positionClasses = {
43113
43589
  "bottom-center": "bottom-6 left-1/2 -translate-x-1/2",
43114
43590
  "bottom-left": "bottom-6 left-6",
43115
- "bottom-right": "bottom-6 right-6"
43591
+ "bottom-right": "bottom-6 right-6",
43592
+ "top-center": "top-6 left-1/2 -translate-x-1/2",
43593
+ "top-left": "top-6 left-6",
43594
+ "top-right": "top-6 right-6"
43116
43595
  };
43117
43596
  FloatingToolbar = ({
43118
43597
  items,
@@ -50773,6 +51252,7 @@ var init_component_registry_generated = __esm({
50773
51252
  "TrendIndicator": TrendIndicator,
50774
51253
  "TypewriterText": TypewriterText,
50775
51254
  "Typography": Typography,
51255
+ "UISlotComponent": UISlotComponent,
50776
51256
  "UISlotRenderer": UISlotRenderer,
50777
51257
  "UploadDropZone": UploadDropZone,
50778
51258
  "VStack": VStack,
@@ -51078,6 +51558,7 @@ function UISlotComponentInner({
51078
51558
  const contained = React87.useContext(SlotContainedContext);
51079
51559
  const schemaCtx = providers.useEntitySchemaOptional();
51080
51560
  const rawContent = slots[slot];
51561
+ const regionClassName = fallback !== void 0 ? cn("contents", className) : className;
51081
51562
  const binding = providers.useEntityBindingSnapshot(rawContent?.sourceTrait);
51082
51563
  const content = React87.useMemo(() => {
51083
51564
  if (!rawContent) return rawContent;
@@ -51126,7 +51607,7 @@ function UISlotComponentInner({
51126
51607
  Box,
51127
51608
  {
51128
51609
  id: `slot-${slot}`,
51129
- className: cn("ui-slot", `ui-slot-${slot}`, className),
51610
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
51130
51611
  "data-testid": `ui-slot-${slot}`,
51131
51612
  "data-slot-mode": "fallback",
51132
51613
  children: fallback
@@ -51161,7 +51642,7 @@ function UISlotComponentInner({
51161
51642
  Box,
51162
51643
  {
51163
51644
  id: `slot-${slot}-fallback`,
51164
- className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
51645
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, regionClassName),
51165
51646
  "data-testid": `ui-slot-${slot}-fallback`,
51166
51647
  "data-slot-mode": "append",
51167
51648
  children: fallback
@@ -51193,7 +51674,7 @@ function UISlotComponentInner({
51193
51674
  Box,
51194
51675
  {
51195
51676
  id: `slot-${slot}`,
51196
- className: cn("ui-slot", `ui-slot-${slot}`, className),
51677
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
51197
51678
  "data-pattern": content.pattern,
51198
51679
  "data-source-trait": content.sourceTrait,
51199
51680
  "data-testid": `ui-slot-${slot}`,
@@ -54171,7 +54652,11 @@ function useDeclaredCaptureTable(opts) {
54171
54652
  const keymap = readKeymapConfig(inlineTrait, keymapKnob);
54172
54653
  const currentState = ctx.getState(pluginId, orbital, trait)?.currentState;
54173
54654
  const keys = currentState !== void 0 ? keymap[currentState] ?? [] : [];
54174
- return { [target]: { mode: currentState ?? "unknown", keys: new Set(keys) } };
54655
+ const entry = { mode: currentState ?? "unknown", keys: new Set(keys) };
54656
+ const targets = Array.isArray(target) ? target : [target];
54657
+ const table = {};
54658
+ for (const t of targets) table[t] = entry;
54659
+ return table;
54175
54660
  }, [ctx, pluginId, orbital, trait, keymapKnob, target]);
54176
54661
  }
54177
54662
  function useOrbitalPluginHost() {
@@ -54179,7 +54664,7 @@ function useOrbitalPluginHost() {
54179
54664
  return { getState: ctx.getState, lastEvent: ctx.lastEvent, errors: ctx.errors };
54180
54665
  }
54181
54666
  function buildMockEffectHandlers(opts) {
54182
- const { pluginId, denySet, slotsRef, navigateRef, notifyRef } = opts;
54667
+ const { pluginId, denySet, slotsRef, renderedSlotsRef, navigateRef, notifyRef } = opts;
54183
54668
  const handlers = {
54184
54669
  renderUI: (slot, pattern, props, priority) => {
54185
54670
  if (!isUISlot(slot)) {
@@ -54187,9 +54672,10 @@ function buildMockEffectHandlers(opts) {
54187
54672
  return;
54188
54673
  }
54189
54674
  if (pattern === null) {
54190
- slotsRef.current.clear(slot);
54675
+ slotsRef.current.clearBySource(slot, pluginId);
54191
54676
  return;
54192
54677
  }
54678
+ renderedSlotsRef.current.add(slot);
54193
54679
  slotsRef.current.render({ target: slot, pattern: pattern.type, props, priority, sourceTrait: pluginId });
54194
54680
  }
54195
54681
  };
@@ -54232,6 +54718,7 @@ function PluginRuntimeMount({
54232
54718
  const slots = context.useUISlots();
54233
54719
  const slotsRef = React87.useRef(slots);
54234
54720
  slotsRef.current = slots;
54721
+ const renderedSlotsRef = React87.useRef(/* @__PURE__ */ new Set());
54235
54722
  const navigateRef = React87.useRef(navigate);
54236
54723
  navigateRef.current = navigate;
54237
54724
  const notifyRef = React87.useRef(notify);
@@ -54245,6 +54732,7 @@ function PluginRuntimeMount({
54245
54732
  pluginId: plugin.id,
54246
54733
  denySet: new Set(deny ?? []),
54247
54734
  slotsRef,
54735
+ renderedSlotsRef,
54248
54736
  navigateRef,
54249
54737
  notifyRef
54250
54738
  })
@@ -54274,9 +54762,12 @@ function PluginRuntimeMount({
54274
54762
  } else if (mode === "server" && transport) {
54275
54763
  void transport.unregister();
54276
54764
  }
54765
+ for (const slot of renderedSlotsRef.current) {
54766
+ slotsRef.current.clearBySource(slot, plugin.id);
54767
+ }
54277
54768
  }, 0);
54278
54769
  };
54279
- }, [mockRuntime, transport, mode]);
54770
+ }, [mockRuntime, transport, mode, plugin.id]);
54280
54771
  const ownOrbitals = React87.useMemo(
54281
54772
  () => new Set(plugin.schema.orbitals.map((o) => o.name)),
54282
54773
  [plugin.schema]