@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.
package/dist/avl/index.js CHANGED
@@ -3311,8 +3311,17 @@ var init_Typography = __esm({
3311
3311
  weight && weightStyles[weight],
3312
3312
  size && typographySizeStyles[size],
3313
3313
  align && `text-${align}`,
3314
- truncate && "truncate overflow-hidden text-ellipsis",
3315
- overflow && overflowStyles[overflow],
3314
+ // `min-w-0` rides along with truncate/wrap/clamp: a flex or grid
3315
+ // item's default `min-width: auto` refuses to shrink below its
3316
+ // content's intrinsic width, so ellipsis/wrap silently does nothing
3317
+ // in the single most common placement (a row next to a fixed-width
3318
+ // control) unless the item can also shrink to zero. (Spelled out as
3319
+ // just "truncate", not "truncate overflow-hidden text-ellipsis" —
3320
+ // tailwind-merge treats those as the same conflict group and drops
3321
+ // "truncate" as the earlier-declared class, silently losing its
3322
+ // `white-space: nowrap`.)
3323
+ truncate && "truncate min-w-0",
3324
+ overflow && cn(overflowStyles[overflow], overflow !== "visible" && "min-w-0"),
3316
3325
  className
3317
3326
  ),
3318
3327
  style
@@ -5178,7 +5187,7 @@ var init_Button = __esm({
5178
5187
  secondary: [
5179
5188
  "bg-transparent text-accent",
5180
5189
  "border border-accent",
5181
- "hover:bg-accent hover:text-white hover:border-accent",
5190
+ "hover:bg-accent hover:text-accent-foreground hover:border-accent",
5182
5191
  "active:scale-[var(--active-scale)]"
5183
5192
  ].join(" "),
5184
5193
  ghost: [
@@ -13706,6 +13715,10 @@ var init_paintDispatch = __esm({
13706
13715
  });
13707
13716
 
13708
13717
  // lib/drawable/hitTest.ts
13718
+ function shapeDrawnItem(n) {
13719
+ if (n.shape !== "rect") return { pos: n.position, id: n.id };
13720
+ return { pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotate };
13721
+ }
13709
13722
  function collectDrawnItems(nodes) {
13710
13723
  const out = [];
13711
13724
  for (const n of nodes) {
@@ -13714,6 +13727,8 @@ function collectDrawnItems(nodes) {
13714
13727
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotation });
13715
13728
  break;
13716
13729
  case "draw-shape":
13730
+ if (isValidScenePos(n.position)) out.push(shapeDrawnItem(n));
13731
+ break;
13717
13732
  case "draw-text":
13718
13733
  case "draw-group":
13719
13734
  case "draw-mesh":
@@ -13725,6 +13740,10 @@ function collectDrawnItems(nodes) {
13725
13740
  }
13726
13741
  break;
13727
13742
  case "draw-shape-layer":
13743
+ for (const it of n.items) {
13744
+ if (isValidScenePos(it.position)) out.push(shapeDrawnItem(it));
13745
+ }
13746
+ break;
13728
13747
  case "draw-text-layer":
13729
13748
  for (const it of n.items) {
13730
13749
  if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
@@ -20029,6 +20048,12 @@ var init_EmptyState = __esm({
20029
20048
  });
20030
20049
 
20031
20050
  // lib/editorMotions.ts
20051
+ function isEditorMotion(value) {
20052
+ return EDITOR_MOTION_SET.has(value);
20053
+ }
20054
+ function isEditorOperator(value) {
20055
+ return EDITOR_OPERATOR_SET.has(value);
20056
+ }
20032
20057
  function clamp(value, min, max) {
20033
20058
  return Math.max(min, Math.min(max, value));
20034
20059
  }
@@ -20096,6 +20121,30 @@ function prevParagraphBoundary(lines, fromLineIdx) {
20096
20121
  }
20097
20122
  return 0;
20098
20123
  }
20124
+ function matchBracket(text, pos) {
20125
+ const ch = text[pos];
20126
+ const partner = ch !== void 0 ? BRACKET_PARTNER[ch] : void 0;
20127
+ if (partner === void 0) return null;
20128
+ let depth = 1;
20129
+ if (OPEN_BRACKETS.has(ch)) {
20130
+ for (let i = pos + 1; i < text.length; i++) {
20131
+ if (text[i] === ch) depth++;
20132
+ else if (text[i] === partner) {
20133
+ depth--;
20134
+ if (depth === 0) return i;
20135
+ }
20136
+ }
20137
+ } else {
20138
+ for (let i = pos - 1; i >= 0; i--) {
20139
+ if (text[i] === ch) depth++;
20140
+ else if (text[i] === partner) {
20141
+ depth--;
20142
+ if (depth === 0) return i;
20143
+ }
20144
+ }
20145
+ }
20146
+ return null;
20147
+ }
20099
20148
  function applyMotion(text, caret, motion, count) {
20100
20149
  const n = Math.max(1, count);
20101
20150
  const lines = computeLines(text);
@@ -20157,6 +20206,20 @@ function applyMotion(text, caret, motion, count) {
20157
20206
  }
20158
20207
  return pos;
20159
20208
  }
20209
+ case "match-bracket": {
20210
+ const chAtCaret = text[caret];
20211
+ if (chAtCaret !== void 0 && BRACKET_PARTNER[chAtCaret] !== void 0) {
20212
+ const target = matchBracket(text, caret);
20213
+ return target === null ? caret : target;
20214
+ }
20215
+ for (let i = caret; i < line.end; i++) {
20216
+ if (BRACKET_PARTNER[text[i]] !== void 0) {
20217
+ const target = matchBracket(text, i);
20218
+ return target === null ? caret : target;
20219
+ }
20220
+ }
20221
+ return caret;
20222
+ }
20160
20223
  case "line":
20161
20224
  case "selection":
20162
20225
  return caret;
@@ -20184,8 +20247,8 @@ function motionRange(text, caret, motion, count, selection) {
20184
20247
  const newCaret = applyMotion(text, caret, motion, count);
20185
20248
  let start = Math.min(caret, newCaret);
20186
20249
  let end = Math.max(caret, newCaret);
20187
- if (motion === "word-end" || motion === "line-end") {
20188
- end = Math.min(Math.max(start, newCaret) + 1, text.length);
20250
+ if ((motion === "word-end" || motion === "line-end" || motion === "match-bracket") && newCaret !== caret) {
20251
+ end = Math.min(Math.max(caret, newCaret) + 1, text.length);
20189
20252
  } else if (motion === "word-forward" && newCaret > caret) {
20190
20253
  const startLine = lineIndexAt(lines, caret);
20191
20254
  const endLine = lineIndexAt(lines, newCaret);
@@ -20195,17 +20258,198 @@ function motionRange(text, caret, motion, count, selection) {
20195
20258
  }
20196
20259
  return [start, end];
20197
20260
  }
20198
- function applyOperator(text, range, operator, register) {
20199
- const start = clamp(range[0], 0, text.length);
20200
- const end = clamp(range[1], start, text.length);
20201
- const removed = text.slice(start, end);
20202
- if (operator === "yank") {
20203
- return { text, caret: start, register: removed };
20261
+ function toggleCase(s) {
20262
+ return s.replace(/[a-zA-Z]/g, (c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase());
20263
+ }
20264
+ function applyJoin(text, caret, count, register, registerLinewise) {
20265
+ const lines = computeLines(text);
20266
+ const startIdx = lineIndexAt(lines, caret);
20267
+ const n = Math.max(2, count);
20268
+ const endIdx = clamp(startIdx + n - 1, 0, lines.length - 1);
20269
+ if (endIdx <= startIdx) {
20270
+ return { text, caret, register, registerLinewise };
20271
+ }
20272
+ const joinCaret = lines[startIdx].end;
20273
+ let joined = text.slice(lines[startIdx].start, lines[startIdx].end);
20274
+ for (let i = startIdx + 1; i <= endIdx; i++) {
20275
+ const raw = text.slice(lines[i].start, lines[i].end);
20276
+ joined += " " + raw.replace(/^[ \t]+/, "");
20277
+ }
20278
+ const newText = text.slice(0, lines[startIdx].start) + joined + text.slice(lines[endIdx].end);
20279
+ return { text: newText, caret: joinCaret, register, registerLinewise };
20280
+ }
20281
+ function applyIndent(text, start, end, operator, register, registerLinewise) {
20282
+ const lines = computeLines(text);
20283
+ const startLineIdx = lineIndexAt(lines, start);
20284
+ const lastTouchedPos = end > start ? end - 1 : start;
20285
+ const endLineIdx = lineIndexAt(lines, lastTouchedPos);
20286
+ let result = text;
20287
+ for (let i = endLineIdx; i >= startLineIdx; i--) {
20288
+ const lineStart = lines[i].start;
20289
+ if (operator === "indent") {
20290
+ result = result.slice(0, lineStart) + INDENT_UNIT + result.slice(lineStart);
20291
+ } else if (result[lineStart] === " ") {
20292
+ result = result.slice(0, lineStart) + result.slice(lineStart + 1);
20293
+ } else {
20294
+ let removeCount = 0;
20295
+ while (removeCount < INDENT_UNIT.length && result[lineStart + removeCount] === " ") removeCount++;
20296
+ result = result.slice(0, lineStart) + result.slice(lineStart + removeCount);
20297
+ }
20298
+ }
20299
+ const newLines = computeLines(result);
20300
+ const caret = firstNonBlank(result, newLines[startLineIdx]);
20301
+ return { text: result, caret, register, registerLinewise };
20302
+ }
20303
+ function applyPut(text, caret, operator, register, registerLinewise, count) {
20304
+ if (register.length === 0) {
20305
+ return { text, caret, register, registerLinewise };
20306
+ }
20307
+ const content = register.repeat(count);
20308
+ const lines = computeLines(text);
20309
+ const line = lines[lineIndexAt(lines, caret)];
20310
+ if (registerLinewise) {
20311
+ if (operator === "put-before") {
20312
+ const insertPos3 = line.start;
20313
+ return {
20314
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
20315
+ caret: insertPos3,
20316
+ register,
20317
+ registerLinewise
20318
+ };
20319
+ }
20320
+ const nextLineIdx = lineIndexAt(lines, caret) + 1;
20321
+ if (nextLineIdx < lines.length) {
20322
+ const insertPos3 = lines[nextLineIdx].start;
20323
+ return {
20324
+ text: text.slice(0, insertPos3) + content + text.slice(insertPos3),
20325
+ caret: insertPos3,
20326
+ register,
20327
+ registerLinewise
20328
+ };
20329
+ }
20330
+ const insertPos2 = text.length;
20331
+ return {
20332
+ text: text.slice(0, insertPos2) + "\n" + content,
20333
+ caret: insertPos2 + 1,
20334
+ register,
20335
+ registerLinewise
20336
+ };
20337
+ }
20338
+ const insertPos = operator === "put-before" ? clamp(caret, 0, text.length) : clamp(caret + 1, line.start, line.end);
20339
+ return {
20340
+ text: text.slice(0, insertPos) + content + text.slice(insertPos),
20341
+ caret: insertPos + content.length - 1,
20342
+ register,
20343
+ registerLinewise
20344
+ };
20345
+ }
20346
+ function applyOperator(input) {
20347
+ const { text, caret, operator, motion, register, registerLinewise } = input;
20348
+ const count = Math.max(1, input.count);
20349
+ const start = clamp(input.range[0], 0, text.length);
20350
+ const end = clamp(input.range[1], start, text.length);
20351
+ switch (operator) {
20352
+ case "yank":
20353
+ return { text, caret: start, register: text.slice(start, end), registerLinewise: motion === "line" };
20354
+ case "delete":
20355
+ case "replace": {
20356
+ const removed = text.slice(start, end);
20357
+ return {
20358
+ text: text.slice(0, start) + text.slice(end),
20359
+ caret: start,
20360
+ register: removed,
20361
+ registerLinewise: motion === "line"
20362
+ };
20363
+ }
20364
+ case "change": {
20365
+ if (motion === "line") {
20366
+ const hasTrailingNewline = end > start && text[end - 1] === "\n";
20367
+ const removeEnd = hasTrailingNewline ? end - 1 : end;
20368
+ const removed2 = text.slice(start, removeEnd);
20369
+ return {
20370
+ text: text.slice(0, start) + text.slice(removeEnd),
20371
+ caret: start,
20372
+ register: removed2,
20373
+ registerLinewise: true
20374
+ };
20375
+ }
20376
+ const removed = text.slice(start, end);
20377
+ return {
20378
+ text: text.slice(0, start) + text.slice(end),
20379
+ caret: start,
20380
+ register: removed,
20381
+ registerLinewise: false
20382
+ };
20383
+ }
20384
+ case "put":
20385
+ case "put-before":
20386
+ return applyPut(text, caret, operator, register, registerLinewise, count);
20387
+ case "join":
20388
+ return applyJoin(text, caret, count, register, registerLinewise);
20389
+ case "toggle-case": {
20390
+ const toggled = toggleCase(text.slice(start, end));
20391
+ return { text: text.slice(0, start) + toggled + text.slice(end), caret: end, register, registerLinewise };
20392
+ }
20393
+ case "indent":
20394
+ case "dedent":
20395
+ return applyIndent(text, start, end, operator, register, registerLinewise);
20396
+ case "undo":
20397
+ case "redo":
20398
+ return { text, caret, register, registerLinewise };
20399
+ default: {
20400
+ const _exhaustive = operator;
20401
+ return _exhaustive;
20402
+ }
20204
20403
  }
20205
- return { text: text.slice(0, start) + text.slice(end), caret: start, register: removed };
20206
20404
  }
20405
+ var EDITOR_MOTIONS, EDITOR_OPERATORS, EDITOR_MOTION_SET, EDITOR_OPERATOR_SET, INDENT_UNIT, BRACKET_PARTNER, OPEN_BRACKETS;
20207
20406
  var init_editorMotions = __esm({
20208
20407
  "lib/editorMotions.ts"() {
20408
+ EDITOR_MOTIONS = [
20409
+ "left",
20410
+ "right",
20411
+ "up",
20412
+ "down",
20413
+ "word-forward",
20414
+ "word-back",
20415
+ "word-end",
20416
+ "line-start",
20417
+ "line-end",
20418
+ "first-nonblank",
20419
+ "doc-start",
20420
+ "doc-end",
20421
+ "paragraph-forward",
20422
+ "paragraph-back",
20423
+ "line",
20424
+ "selection",
20425
+ "match-bracket"
20426
+ ];
20427
+ EDITOR_OPERATORS = [
20428
+ "delete",
20429
+ "yank",
20430
+ "change",
20431
+ "put",
20432
+ "put-before",
20433
+ "undo",
20434
+ "redo",
20435
+ "join",
20436
+ "toggle-case",
20437
+ "indent",
20438
+ "dedent",
20439
+ "replace"
20440
+ ];
20441
+ EDITOR_MOTION_SET = new Set(EDITOR_MOTIONS);
20442
+ EDITOR_OPERATOR_SET = new Set(EDITOR_OPERATORS);
20443
+ INDENT_UNIT = " ";
20444
+ BRACKET_PARTNER = {
20445
+ "(": ")",
20446
+ ")": "(",
20447
+ "[": "]",
20448
+ "]": "[",
20449
+ "{": "}",
20450
+ "}": "{"
20451
+ };
20452
+ OPEN_BRACKETS = /* @__PURE__ */ new Set(["(", "[", "{"]);
20209
20453
  }
20210
20454
  });
20211
20455
  function isMotionPayload(payload) {
@@ -20220,14 +20464,103 @@ function isInsertTextPayload(payload) {
20220
20464
  function isSetModePayload(payload) {
20221
20465
  return !!payload && typeof payload.editorId === "string" && typeof payload.mode === "string" && typeof payload.caret === "string";
20222
20466
  }
20467
+ function typedDelta(prev, next) {
20468
+ const maxPrefix = Math.min(prev.length, next.length);
20469
+ let prefixLen = 0;
20470
+ while (prefixLen < maxPrefix && prev[prefixLen] === next[prefixLen]) prefixLen++;
20471
+ const maxSuffix = Math.min(prev.length - prefixLen, next.length - prefixLen);
20472
+ let suffixLen = 0;
20473
+ while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
20474
+ suffixLen++;
20475
+ }
20476
+ const removed = prev.slice(prefixLen, prev.length - suffixLen);
20477
+ const inserted = next.slice(prefixLen, next.length - suffixLen);
20478
+ return removed + inserted;
20479
+ }
20223
20480
  function useEditorCapabilities(args) {
20224
20481
  const [caretMode, setCaretMode] = useState("bar");
20225
20482
  const registerRef = useRef("");
20483
+ const registerLinewiseRef = useRef(false);
20484
+ const pastRef = useRef([]);
20485
+ const futureRef = useRef([]);
20486
+ const openTypingStepRef = useRef(false);
20487
+ const insertSessionOpenRef = useRef(false);
20488
+ const closeOpenTypingStep = useCallback(() => {
20489
+ openTypingStepRef.current = false;
20490
+ }, []);
20491
+ const pushHistoryStep = useCallback(
20492
+ (text, caret) => {
20493
+ closeOpenTypingStep();
20494
+ pastRef.current.push({ text, caret });
20495
+ futureRef.current = [];
20496
+ },
20497
+ [closeOpenTypingStep]
20498
+ );
20499
+ const recordKeystroke = useCallback(
20500
+ (prevText, prevCaret, nextText) => {
20501
+ if (!openTypingStepRef.current) {
20502
+ pastRef.current.push({ text: prevText, caret: prevCaret });
20503
+ futureRef.current = [];
20504
+ openTypingStepRef.current = true;
20505
+ }
20506
+ if (!insertSessionOpenRef.current && /\s/.test(typedDelta(prevText, nextText))) {
20507
+ openTypingStepRef.current = false;
20508
+ }
20509
+ },
20510
+ []
20511
+ );
20512
+ const performUndo = useCallback(
20513
+ (count) => {
20514
+ const ta = args.textareaRef.current;
20515
+ if (!ta) return;
20516
+ closeOpenTypingStep();
20517
+ let moved = false;
20518
+ for (let i = 0; i < Math.max(1, count) && pastRef.current.length > 0; i++) {
20519
+ const prev = pastRef.current.pop();
20520
+ futureRef.current.push({ text: ta.value, caret: ta.selectionStart });
20521
+ ta.value = prev.text;
20522
+ ta.setSelectionRange(prev.caret, prev.caret);
20523
+ moved = true;
20524
+ }
20525
+ if (moved) args.applyChange(ta.value, "capability");
20526
+ },
20527
+ [args, closeOpenTypingStep]
20528
+ );
20529
+ const performRedo = useCallback(
20530
+ (count) => {
20531
+ const ta = args.textareaRef.current;
20532
+ if (!ta) return;
20533
+ closeOpenTypingStep();
20534
+ let moved = false;
20535
+ for (let i = 0; i < Math.max(1, count) && futureRef.current.length > 0; i++) {
20536
+ const next = futureRef.current.pop();
20537
+ pastRef.current.push({ text: ta.value, caret: ta.selectionStart });
20538
+ ta.value = next.text;
20539
+ ta.setSelectionRange(next.caret, next.caret);
20540
+ moved = true;
20541
+ }
20542
+ if (moved) args.applyChange(ta.value, "capability");
20543
+ },
20544
+ [args, closeOpenTypingStep]
20545
+ );
20546
+ const undo = useCallback(() => performUndo(1), [performUndo]);
20547
+ const redo = useCallback(() => performRedo(1), [performRedo]);
20548
+ const wasFocusedRef = useRef(args.focused);
20549
+ useEffect(() => {
20550
+ if (wasFocusedRef.current && !args.focused) {
20551
+ setCaretMode("bar");
20552
+ }
20553
+ wasFocusedRef.current = args.focused;
20554
+ }, [args.focused]);
20226
20555
  useEventListener(`UI:${args.events.onMotion}`, (evt) => {
20227
20556
  if (!args.editorId || !isMotionPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20557
+ const { motion, count } = evt.payload;
20558
+ if (!isEditorMotion(motion)) {
20559
+ console.warn(`useEditorCapabilities: ignoring MOTION with unknown motion "${motion}"`);
20560
+ return;
20561
+ }
20228
20562
  const ta = args.textareaRef.current;
20229
20563
  if (!ta) return;
20230
- const { motion, count } = evt.payload;
20231
20564
  const newCaret = applyMotion(ta.value, ta.selectionStart, motion, count);
20232
20565
  if (ta.selectionStart !== ta.selectionEnd) {
20233
20566
  ta.setSelectionRange(ta.selectionStart, newCaret);
@@ -20237,32 +20570,63 @@ function useEditorCapabilities(args) {
20237
20570
  });
20238
20571
  useEventListener(`UI:${args.events.onOperate}`, (evt) => {
20239
20572
  if (!args.editorId || !isOperatePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20573
+ const { operator, motion, count } = evt.payload;
20574
+ if (!isEditorOperator(operator)) {
20575
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown operator "${operator}"`);
20576
+ return;
20577
+ }
20578
+ if (!isEditorMotion(motion)) {
20579
+ console.warn(`useEditorCapabilities: ignoring OPERATE with unknown motion "${motion}"`);
20580
+ return;
20581
+ }
20240
20582
  const ta = args.textareaRef.current;
20241
20583
  if (!ta) return;
20242
- const { operator, motion, count } = evt.payload;
20584
+ if (operator === "undo") {
20585
+ performUndo(count);
20586
+ return;
20587
+ }
20588
+ if (operator === "redo") {
20589
+ performRedo(count);
20590
+ return;
20591
+ }
20592
+ pushHistoryStep(ta.value, ta.selectionStart);
20243
20593
  const selection = motion === "selection" ? [ta.selectionStart, ta.selectionEnd] : void 0;
20244
20594
  const range = motionRange(ta.value, ta.selectionStart, motion, count, selection);
20245
- const result = applyOperator(ta.value, range, operator, registerRef.current);
20595
+ const result = applyOperator({
20596
+ text: ta.value,
20597
+ caret: ta.selectionStart,
20598
+ range,
20599
+ operator,
20600
+ motion,
20601
+ count,
20602
+ register: registerRef.current,
20603
+ registerLinewise: registerLinewiseRef.current
20604
+ });
20246
20605
  registerRef.current = result.register;
20606
+ registerLinewiseRef.current = result.registerLinewise;
20247
20607
  if (operator === "yank") {
20248
- ta.setSelectionRange(range[0], range[0]);
20608
+ ta.setSelectionRange(result.caret, result.caret);
20249
20609
  } else {
20250
- ta.setRangeText("", range[0], range[1], "end");
20610
+ ta.value = result.text;
20611
+ ta.setSelectionRange(result.caret, result.caret);
20251
20612
  }
20252
- args.applyChange(ta.value);
20613
+ args.applyChange(ta.value, "capability");
20253
20614
  });
20254
20615
  useEventListener(`UI:${args.events.onInsertText}`, (evt) => {
20255
20616
  if (!args.editorId || !isInsertTextPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20256
20617
  const ta = args.textareaRef.current;
20257
20618
  if (!ta) return;
20619
+ pushHistoryStep(ta.value, ta.selectionStart);
20258
20620
  ta.setRangeText(evt.payload.text, ta.selectionStart, ta.selectionEnd, "end");
20259
- args.applyChange(ta.value);
20621
+ args.applyChange(ta.value, "capability");
20260
20622
  });
20261
20623
  useEventListener(`UI:${args.events.onSetMode}`, (evt) => {
20262
20624
  if (!args.editorId || !isSetModePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20625
+ closeOpenTypingStep();
20626
+ insertSessionOpenRef.current = evt.payload.mode === "INSERT";
20263
20627
  setCaretMode(evt.payload.caret);
20264
20628
  });
20265
- return { caretMode };
20629
+ return { caretMode, recordKeystroke, undo, redo };
20266
20630
  }
20267
20631
  var init_useEditorCapabilities = __esm({
20268
20632
  "components/core/molecules/markdown/useEditorCapabilities.ts"() {
@@ -20678,9 +21042,23 @@ var init_CodeBlock = __esm({
20678
21042
  "paragraph-forward",
20679
21043
  "paragraph-back",
20680
21044
  "line",
20681
- "selection"
21045
+ "selection",
21046
+ "match-bracket"
20682
21047
  ],
20683
- operators = ["delete", "yank", "change"]
21048
+ operators = [
21049
+ "delete",
21050
+ "yank",
21051
+ "change",
21052
+ "put",
21053
+ "put-before",
21054
+ "undo",
21055
+ "redo",
21056
+ "join",
21057
+ "toggle-case",
21058
+ "indent",
21059
+ "dedent",
21060
+ "replace"
21061
+ ]
20684
21062
  }) => {
20685
21063
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
20686
21064
  const activeStyle = resolveHighlightStyle(language);
@@ -20714,6 +21092,12 @@ var init_CodeBlock = __esm({
20714
21092
  const lastPropCodeRef = useRef(code);
20715
21093
  const editableTextareaRef = useRef(null);
20716
21094
  const editableOverlayRef = useRef(null);
21095
+ const [isFocused, setIsFocused] = useState(false);
21096
+ const prevCaretRef = useRef(0);
21097
+ const [caretIndex, setCaretIndex] = useState(0);
21098
+ const caretMirrorRef = useRef(null);
21099
+ const caretMarkerRef = useRef(null);
21100
+ const [caretGeometry, setCaretGeometry] = useState(null);
20717
21101
  useEffect(() => {
20718
21102
  if (code !== lastPropCodeRef.current) {
20719
21103
  lastPropCodeRef.current = code;
@@ -20729,23 +21113,77 @@ var init_CodeBlock = __esm({
20729
21113
  ov.scrollLeft = ta.scrollLeft;
20730
21114
  }
20731
21115
  }, []);
20732
- const handleEditableChange = useCallback((v) => {
21116
+ const handleEditableChange = useCallback((v, _origin) => {
20733
21117
  lastPropCodeRef.current = v;
20734
21118
  setEditableValue(v);
21119
+ const ta = editableTextareaRef.current;
21120
+ if (ta) setCaretIndex(ta.selectionStart);
20735
21121
  onChange?.(v);
20736
21122
  }, [onChange]);
20737
- const { caretMode } = useEditorCapabilities({
21123
+ const { caretMode, recordKeystroke, undo, redo } = useEditorCapabilities({
20738
21124
  editorId: editable ? editorId : void 0,
20739
21125
  textareaRef: editableTextareaRef,
20740
21126
  events: { onMotion, onOperate, onInsertText, onSetMode },
21127
+ focused: isFocused,
20741
21128
  applyChange: handleEditableChange
20742
21129
  });
20743
- const [caretIndex, setCaretIndex] = useState(0);
20744
- const caretRowCol = useMemo(() => {
20745
- const before = editableValue.slice(0, Math.min(caretIndex, editableValue.length));
20746
- const lines = before.split("\n");
20747
- return { row: lines.length - 1, col: lines[lines.length - 1].length };
20748
- }, [editableValue, caretIndex]);
21130
+ const handleEditableKeyDown = useCallback(
21131
+ (e) => {
21132
+ const ta = editableTextareaRef.current;
21133
+ if (ta) prevCaretRef.current = ta.selectionStart;
21134
+ const mod = e.metaKey || e.ctrlKey;
21135
+ if (!mod) return;
21136
+ const key = e.key.toLowerCase();
21137
+ if (key === "z" && !e.shiftKey) {
21138
+ e.preventDefault();
21139
+ undo();
21140
+ } else if (key === "z" && e.shiftKey || key === "y") {
21141
+ e.preventDefault();
21142
+ redo();
21143
+ }
21144
+ },
21145
+ [undo, redo]
21146
+ );
21147
+ const showBlockCaret = isFocused && caretMode !== "bar";
21148
+ useLayoutEffect(() => {
21149
+ if (!showBlockCaret) return;
21150
+ const ta = editableTextareaRef.current;
21151
+ const mirror = caretMirrorRef.current;
21152
+ const marker = caretMarkerRef.current;
21153
+ if (!ta || !mirror || !marker) return;
21154
+ const computed = window.getComputedStyle(ta);
21155
+ const MIRRORED_PROPS = [
21156
+ "font-family",
21157
+ "font-size",
21158
+ "font-weight",
21159
+ "font-style",
21160
+ "letter-spacing",
21161
+ "line-height",
21162
+ "padding-top",
21163
+ "padding-right",
21164
+ "padding-bottom",
21165
+ "padding-left",
21166
+ "border-top-width",
21167
+ "border-right-width",
21168
+ "border-bottom-width",
21169
+ "border-left-width",
21170
+ "box-sizing",
21171
+ "width",
21172
+ "white-space",
21173
+ "word-break",
21174
+ "overflow-wrap",
21175
+ "tab-size"
21176
+ ];
21177
+ for (const prop of MIRRORED_PROPS) {
21178
+ mirror.style.setProperty(prop, computed.getPropertyValue(prop));
21179
+ }
21180
+ const lineHeight = parseFloat(computed.getPropertyValue("line-height"));
21181
+ setCaretGeometry({
21182
+ top: marker.offsetTop,
21183
+ left: marker.offsetLeft,
21184
+ lineHeight: Number.isFinite(lineHeight) ? lineHeight : 0
21185
+ });
21186
+ }, [showBlockCaret, editableValue, caretIndex]);
20749
21187
  const errorLineProps = useMemo(() => buildLineProps(errorLines), [errorLines]);
20750
21188
  const viewerLineProps = useMemo(
20751
21189
  () => buildLineProps(errorLines, "px-4 py-0.5 hover:bg-muted/50"),
@@ -21269,11 +21707,24 @@ var init_CodeBlock = __esm({
21269
21707
  {
21270
21708
  ref: editableTextareaRef,
21271
21709
  defaultValue: code,
21272
- onChange: (e) => handleEditableChange(e.target.value),
21710
+ onChange: (e) => {
21711
+ const next = e.target.value;
21712
+ recordKeystroke(editableValue, prevCaretRef.current, next);
21713
+ handleEditableChange(next, "keystroke");
21714
+ },
21273
21715
  onScroll: handleEditableScroll,
21274
21716
  onSelect: (e) => setCaretIndex(e.currentTarget.selectionStart),
21275
- onFocus: editorId ? () => eventBus.emit(`UI:${onEditorFocus}`, { editorId }) : void 0,
21276
- onBlur: editorId ? () => eventBus.emit(`UI:${onEditorBlur}`, { editorId }) : void 0,
21717
+ onKeyUp: (e) => setCaretIndex(e.currentTarget.selectionStart),
21718
+ onClick: (e) => setCaretIndex(e.currentTarget.selectionStart),
21719
+ onKeyDown: handleEditableKeyDown,
21720
+ onFocus: () => {
21721
+ setIsFocused(true);
21722
+ if (editorId) eventBus.emit(`UI:${onEditorFocus}`, { editorId });
21723
+ },
21724
+ onBlur: () => {
21725
+ setIsFocused(false);
21726
+ if (editorId) eventBus.emit(`UI:${onEditorBlur}`, { editorId });
21727
+ },
21277
21728
  spellCheck: false,
21278
21729
  style: {
21279
21730
  position: "absolute",
@@ -21300,16 +21751,39 @@ var init_CodeBlock = __esm({
21300
21751
  },
21301
21752
  editableTextareaKey
21302
21753
  ),
21303
- caretMode !== "bar" && /* @__PURE__ */ jsx(
21754
+ showBlockCaret && /* @__PURE__ */ jsxs(
21755
+ "div",
21756
+ {
21757
+ ref: caretMirrorRef,
21758
+ "aria-hidden": true,
21759
+ "data-testid": "editor-caret-mirror",
21760
+ style: {
21761
+ position: "absolute",
21762
+ top: 0,
21763
+ left: 0,
21764
+ padding: "1rem",
21765
+ margin: 0,
21766
+ border: "none",
21767
+ visibility: "hidden",
21768
+ pointerEvents: "none"
21769
+ },
21770
+ children: [
21771
+ editableValue.slice(0, caretIndex),
21772
+ /* @__PURE__ */ jsx("span", { ref: caretMarkerRef, "data-testid": "editor-caret-marker", children: "\u200B" })
21773
+ ]
21774
+ }
21775
+ ),
21776
+ showBlockCaret && caretGeometry && /* @__PURE__ */ jsx(
21304
21777
  "span",
21305
21778
  {
21306
21779
  "aria-hidden": true,
21780
+ "data-testid": "editor-caret",
21307
21781
  style: {
21308
21782
  position: "absolute",
21309
- top: `calc(1rem + ${caretRowCol.row * 19.5}px)`,
21310
- left: `calc(1rem + ${caretRowCol.col}ch)`,
21783
+ top: caretGeometry.top,
21784
+ left: caretGeometry.left,
21311
21785
  width: "1ch",
21312
- height: caretMode === "block" ? "19.5px" : "2px",
21786
+ height: caretMode === "block" ? caretGeometry.lineHeight || "1.2em" : "2px",
21313
21787
  backgroundColor: caretMode === "block" ? "rgba(230, 230, 230, 0.5)" : void 0,
21314
21788
  borderBottom: caretMode === "underline" ? "2px solid #e6e6e6" : void 0,
21315
21789
  pointerEvents: "none"
@@ -29051,6 +29525,7 @@ function SubMenu({
29051
29525
  item.onClick?.();
29052
29526
  },
29053
29527
  "aria-disabled": item.disabled || void 0,
29528
+ title: item.title,
29054
29529
  "data-testid": item.event ? `action-${item.event}` : void 0,
29055
29530
  className: cn(
29056
29531
  "w-full flex items-center gap-3 px-4 py-2 text-start",
@@ -29099,6 +29574,7 @@ function MenuItemRow({
29099
29574
  as: "button",
29100
29575
  onClick: () => onItemClick({ ...item, id: itemId }, itemId),
29101
29576
  "aria-disabled": item.disabled || void 0,
29577
+ title: item.title,
29102
29578
  onMouseEnter: (e) => {
29103
29579
  if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
29104
29580
  },
@@ -36145,13 +36621,13 @@ var init_MapView = __esm({
36145
36621
  shadowSize: [41, 41]
36146
36622
  });
36147
36623
  L.Marker.prototype.options.icon = defaultIcon;
36148
- const { useEffect: useEffect70, useRef: useRef70, useCallback: useCallback100, useState: useState109 } = React96__default;
36624
+ const { useEffect: useEffect71, useRef: useRef70, useCallback: useCallback101, useState: useState109 } = React96__default;
36149
36625
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
36150
36626
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
36151
36627
  function MapUpdater({ centerLat, centerLng, zoom }) {
36152
36628
  const map = useMap();
36153
36629
  const prevRef = useRef70({ centerLat, centerLng, zoom });
36154
- useEffect70(() => {
36630
+ useEffect71(() => {
36155
36631
  const prev = prevRef.current;
36156
36632
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
36157
36633
  map.setView([centerLat, centerLng], zoom);
@@ -36162,7 +36638,7 @@ var init_MapView = __esm({
36162
36638
  }
36163
36639
  function MapClickHandler({ onMapClick }) {
36164
36640
  const map = useMap();
36165
- useEffect70(() => {
36641
+ useEffect71(() => {
36166
36642
  if (!onMapClick) return;
36167
36643
  const handler = (e) => {
36168
36644
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -36191,7 +36667,7 @@ var init_MapView = __esm({
36191
36667
  }) {
36192
36668
  const eventBus = useEventBus2();
36193
36669
  const [clickedPosition, setClickedPosition] = useState109(null);
36194
- const handleMapClick = useCallback100((lat, lng) => {
36670
+ const handleMapClick = useCallback101((lat, lng) => {
36195
36671
  if (showClickedPin) {
36196
36672
  setClickedPosition({ lat, lng });
36197
36673
  }
@@ -36200,7 +36676,7 @@ var init_MapView = __esm({
36200
36676
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
36201
36677
  }
36202
36678
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
36203
- const handleMarkerClick = useCallback100((marker) => {
36679
+ const handleMarkerClick = useCallback101((marker) => {
36204
36680
  onMarkerClick?.(marker);
36205
36681
  if (markerClickEvent) {
36206
36682
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -45538,7 +46014,10 @@ var init_FloatingToolbar = __esm({
45538
46014
  positionClasses = {
45539
46015
  "bottom-center": "bottom-6 left-1/2 -translate-x-1/2",
45540
46016
  "bottom-left": "bottom-6 left-6",
45541
- "bottom-right": "bottom-6 right-6"
46017
+ "bottom-right": "bottom-6 right-6",
46018
+ "top-center": "top-6 left-1/2 -translate-x-1/2",
46019
+ "top-left": "top-6 left-6",
46020
+ "top-right": "top-6 right-6"
45542
46021
  };
45543
46022
  FloatingToolbar = ({
45544
46023
  items,
@@ -53199,6 +53678,7 @@ var init_component_registry_generated = __esm({
53199
53678
  "TrendIndicator": TrendIndicator,
53200
53679
  "TypewriterText": TypewriterText,
53201
53680
  "Typography": Typography,
53681
+ "UISlotComponent": UISlotComponent,
53202
53682
  "UISlotRenderer": UISlotRenderer,
53203
53683
  "UploadDropZone": UploadDropZone,
53204
53684
  "VStack": VStack,
@@ -53504,6 +53984,7 @@ function UISlotComponentInner({
53504
53984
  const contained = useContext(SlotContainedContext);
53505
53985
  const schemaCtx = useEntitySchemaOptional();
53506
53986
  const rawContent = slots[slot];
53987
+ const regionClassName = fallback !== void 0 ? cn("contents", className) : className;
53507
53988
  const binding = useEntityBindingSnapshot(rawContent?.sourceTrait);
53508
53989
  const content = useMemo(() => {
53509
53990
  if (!rawContent) return rawContent;
@@ -53552,7 +54033,7 @@ function UISlotComponentInner({
53552
54033
  Box,
53553
54034
  {
53554
54035
  id: `slot-${slot}`,
53555
- className: cn("ui-slot", `ui-slot-${slot}`, className),
54036
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
53556
54037
  "data-testid": `ui-slot-${slot}`,
53557
54038
  "data-slot-mode": "fallback",
53558
54039
  children: fallback
@@ -53587,7 +54068,7 @@ function UISlotComponentInner({
53587
54068
  Box,
53588
54069
  {
53589
54070
  id: `slot-${slot}-fallback`,
53590
- className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
54071
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, regionClassName),
53591
54072
  "data-testid": `ui-slot-${slot}-fallback`,
53592
54073
  "data-slot-mode": "append",
53593
54074
  children: fallback
@@ -53619,7 +54100,7 @@ function UISlotComponentInner({
53619
54100
  Box,
53620
54101
  {
53621
54102
  id: `slot-${slot}`,
53622
- className: cn("ui-slot", `ui-slot-${slot}`, className),
54103
+ className: cn("ui-slot", `ui-slot-${slot}`, regionClassName),
53623
54104
  "data-pattern": content.pattern,
53624
54105
  "data-source-trait": content.sourceTrait,
53625
54106
  "data-testid": `ui-slot-${slot}`,