@happy-nut/monacori 0.1.13 → 0.1.15

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.
@@ -164,7 +164,7 @@ function setupCustomSelect(id, getOptions, getCurrent, onPick) {
164
164
  var r = el.getBoundingClientRect(), cur = getCurrent();
165
165
  showCustomDropdown(r.left, r.bottom + 4, getOptions().map(function (o) {
166
166
  return { label: (o.value === cur ? '✓ ' : '  ') + o.label, onSelect: function () { onPick(o.value); render(); } };
167
- }));
167
+ }), r.top - 4);
168
168
  });
169
169
  render();
170
170
  return { render: render };
@@ -2091,7 +2091,28 @@ function saveMergePrompt(kind, text) {
2091
2091
 
2092
2092
  // Reusable custom dropdown (keyboard + mouse). options: [{ label, onSelect }]. First item is pre-selected;
2093
2093
  // Arrow keys move, Enter chooses, Esc / click-outside dismiss. Replaces native <select>/menus everywhere.
2094
- function showCustomDropdown(x, y, options) {
2094
+ // Approximate the caret's pixel position in the (monospace) merged textarea so the dropdown can open right
2095
+ // under it — and flip above when there isn't room below. Returns { x, top (caret line top), below }.
2096
+ function mergedCaretXY(area) {
2097
+ var pos = area.selectionStart || 0;
2098
+ var nl = area.value.slice(0, pos).split('\n');
2099
+ var lineNum = nl.length - 1;
2100
+ var col = nl[lineNum].length;
2101
+ var cs = getComputedStyle(area);
2102
+ var lineH = parseFloat(cs.lineHeight) || 18;
2103
+ var rect = area.getBoundingClientRect();
2104
+ var span = document.createElement('span');
2105
+ span.style.cssText = 'position:absolute;visibility:hidden;white-space:pre';
2106
+ span.style.font = cs.font;
2107
+ span.textContent = 'MMMMMMMMMMMMMMMMMMMM';
2108
+ document.body.appendChild(span);
2109
+ var charW = span.getBoundingClientRect().width / 20;
2110
+ span.remove();
2111
+ var caretTop = rect.top + (parseFloat(cs.paddingTop) || 0) + lineNum * lineH - area.scrollTop;
2112
+ var x = Math.min(rect.left + (parseFloat(cs.paddingLeft) || 0) + col * charW, rect.right - 24);
2113
+ return { x: x, top: caretTop, below: caretTop + lineH + 2 };
2114
+ }
2115
+ function showCustomDropdown(x, y, options, flipTop) {
2095
2116
  var existing = document.getElementById('mc-dropdown');
2096
2117
  if (existing) existing.remove();
2097
2118
  var dd = document.createElement('div');
@@ -2116,9 +2137,16 @@ function showCustomDropdown(x, y, options) {
2116
2137
  item.addEventListener('mousemove', function () { setActive(i); });
2117
2138
  dd.appendChild(item);
2118
2139
  });
2119
- dd.style.left = Math.round(x) + 'px';
2120
- dd.style.top = Math.round(y) + 'px';
2121
2140
  document.body.appendChild(dd);
2141
+ // Position after measuring: open at (x, y); flip above (flipTop) when it would overflow the bottom,
2142
+ // and nudge in from the right/bottom edges so it never clips offscreen.
2143
+ var ddr = dd.getBoundingClientRect();
2144
+ var top = y, left = x;
2145
+ if (typeof flipTop === 'number' && top + ddr.height > window.innerHeight - 8) top = Math.max(8, flipTop - ddr.height);
2146
+ else if (top + ddr.height > window.innerHeight - 8) top = Math.max(8, window.innerHeight - ddr.height - 8);
2147
+ if (left + ddr.width > window.innerWidth - 8) left = Math.max(8, window.innerWidth - ddr.width - 8);
2148
+ dd.style.left = Math.round(left) + 'px';
2149
+ dd.style.top = Math.round(top) + 'px';
2122
2150
  document.addEventListener('keydown', onKey, true);
2123
2151
  document.addEventListener('mousedown', onOutside, true);
2124
2152
  }
@@ -2221,20 +2249,20 @@ function openMergedView(kind) {
2221
2249
  e.stopPropagation();
2222
2250
  var seqs = mergedCommentSeqs(kind, area.selectionStart, area.selectionEnd);
2223
2251
  if (!seqs.length) return;
2224
- var rect = area.getBoundingClientRect();
2225
- var x = rect.left + 24, y = rect.top + 48;
2252
+ var cxy = mergedCaretXY(area);
2253
+ var x = cxy.x, y = cxy.below, flipTop = cxy.top;
2226
2254
  var rerender = function () {
2227
2255
  if (!reviewComments.filter(function (c) { return c.kind === kind; }).length) { modal.remove(); return; }
2228
2256
  area.value = buildMergedText(kind);
2229
2257
  };
2230
2258
  if (area.selectionStart !== area.selectionEnd || seqs.length > 1) {
2231
- showCustomDropdown(x, y, [{ label: t('dropdown.remove'), onSelect: function () { seqs.forEach(deleteComment); rerender(); } }]);
2259
+ showCustomDropdown(x, y, [{ label: t('dropdown.remove'), onSelect: function () { seqs.forEach(deleteComment); rerender(); } }], flipTop);
2232
2260
  } else {
2233
2261
  var seq = seqs[0];
2234
2262
  showCustomDropdown(x, y, [
2235
2263
  { label: t('dropdown.navigate'), onSelect: function () { modal.remove(); navigateToComment(seq); } },
2236
2264
  { label: t('dropdown.remove'), onSelect: function () { deleteComment(seq); rerender(); } },
2237
- ]);
2265
+ ], flipTop);
2238
2266
  }
2239
2267
  });
2240
2268
  closeBtn.addEventListener('click', function () { modal.remove(); });
@@ -2264,12 +2292,12 @@ function openMergedView(kind) {
2264
2292
  document.body.appendChild(modal);
2265
2293
  // Focus the send button (Enter starts pane-pick) when present, else the read-only text. Electron
2266
2294
  // async-restores focus to <body>, so retry briefly (same as the composer).
2267
- var modalFocusTarget = sendBtn || area;
2295
+ var modalFocusTarget = area; // focus the text (not the send button) so the caret is visible and Opt+Arrow/Enter work; Send-to-terminal is a click
2268
2296
  var modalFocusTries = 0;
2269
2297
  var tryFocusModal = function () {
2270
2298
  if (!document.getElementById('mc-modal')) return true;
2271
2299
  if (document.activeElement === modalFocusTarget) return true;
2272
- try { modalFocusTarget.focus(); if (modalFocusTarget === area) modalFocusTarget.select(); } catch (e) {}
2300
+ try { modalFocusTarget.focus(); modalFocusTarget.selectionStart = modalFocusTarget.selectionEnd = 0; } catch (e) {}
2273
2301
  return document.activeElement === modalFocusTarget;
2274
2302
  };
2275
2303
  if (!tryFocusModal()) {
package/dist/viewer.css CHANGED
@@ -685,6 +685,10 @@ h1 { margin: 0; font-size: 18px; }
685
685
  font-size: 14px; line-height: 1; padding: 0 3px; cursor: pointer; border-radius: 3px;
686
686
  }
687
687
  .source-tab-close:hover { background: var(--line); color: var(--text); }
688
+ /* Scrolloff: keep the caret / focused row near the vertical middle while navigating, so holding an arrow
689
+ key scrolls the view CONTINUOUSLY instead of leaving it still until the caret reaches the viewport edge
690
+ (the "stutter every ~viewport" the user reported). Applies to the source body, the diff, and the sidebar. */
691
+ .source-body, #diff2html-container, .sidebar-scroll { scroll-padding-block: 35vh; }
688
692
  .source-body {
689
693
  border: 1px solid var(--border);
690
694
  overflow: auto;
@@ -860,8 +864,8 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
860
864
  .mc-modal-head span { margin-right: auto; }
861
865
  .mc-modal-text { width: 100%; height: 100%; box-sizing: border-box; resize: none; border: 0; padding: 12px; background: var(--bg); color: var(--text); caret-color: var(--text); font: 12px/1.55 Monaco, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
862
866
  .mc-modal-text:focus { outline: none; }
863
- .mc-dropdown { position: fixed; z-index: 70; min-width: 150px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45); padding: 4px; }
864
- .mc-dropdown-item { display: block; width: 100%; text-align: left; padding: 7px 12px; border: 0; background: transparent; color: var(--text); border-radius: 6px; font-size: 13px; cursor: pointer; white-space: nowrap; }
867
+ .mc-dropdown { position: fixed; z-index: 70; min-width: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4); padding: 3px; }
868
+ .mc-dropdown-item { display: block; width: 100%; text-align: left; padding: 4px 10px; border: 0; background: transparent; color: var(--text); border-radius: 4px; font-size: 12px; line-height: 1.5; cursor: pointer; white-space: nowrap; }
865
869
  .mc-dropdown-item.active, .mc-dropdown-item:hover { background: var(--active); color: #fff; }
866
870
  #mc-toasts { position: fixed; left: 16px; bottom: 16px; z-index: 80; display: flex; flex-direction: column; gap: 8px; max-width: 360px; pointer-events: none; }
867
871
  .mc-toast { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-left: 3px solid var(--active); border-radius: 8px; padding: 10px 14px; font-size: 13px; line-height: 1.45; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); opacity: 0; transform: translateY(8px); transition: opacity .25s ease, transform .25s ease; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happy-nut/monacori",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Validation control plane for AI-generated code changes.",
5
5
  "type": "module",
6
6
  "repository": {