@ryuu-64/dsh-find-all 0.1.8 → 0.1.10

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/lib/client.js +225 -34
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.10
4
+
5
+ - Keep the find bar below the conversation header and allow moving it within the conversation body with a drag handle or directional buttons.
6
+ - Preserve its manual position while the same conversation stays open, with a control to restore the default position; reset it when the bar reopens or the conversation changes.
7
+ - Constrain the bar to the available conversation area as the window, sidebar or input area changes, and reduce unused space in its default layout.
8
+ - Keep the current search match clear of the find bar, including matches in nested scrolling areas and when navigation feedback expands the bar.
9
+
10
+ The supported host-version list and return-to-reading-position behavior are unchanged.
11
+
12
+ ## 0.1.9
13
+
14
+ - Reveal host-owned `hidden="until-found"` content before scrolling to a search match, instead of treating its layout geometry as proof that the match is visible.
15
+ - Wait for concealed matches to become visible before marking navigation as successful.
16
+
17
+ The supported host-version list is unchanged.
18
+
3
19
  ## 0.1.8
4
20
 
5
21
  - Keep Ctrl+F and the header search button bound to the current main conversation across sidebar focus changes and main-view navigation.
package/lib/client.js CHANGED
@@ -76,6 +76,7 @@ window.__ModuleLoader__.load({
76
76
  prev: "上一个匹配",
77
77
  next: "下一个匹配",
78
78
  close: "关闭查找",
79
+ move: "拖动查找栏;点击展开移动按钮", moveLeft: "向左移动", moveRight: "向右移动", moveUp: "向上移动", moveDown: "向下移动", resetPosition: "恢复默认位置",
79
80
  cap: "仅显示前 {n} 处匹配",
80
81
  resultCount: "{n} 个结果",
81
82
  scopeWhole: "包含更早内容",
@@ -106,6 +107,7 @@ window.__ModuleLoader__.load({
106
107
  prev: "Previous match",
107
108
  next: "Next match",
108
109
  close: "Close find",
110
+ move: "Drag find bar; click for move controls", moveLeft: "Move left", moveRight: "Move right", moveUp: "Move up", moveDown: "Move down", resetPosition: "Reset position",
109
111
  cap: "Showing only the first {n} matches",
110
112
  resultCount: "{n} results",
111
113
  scopeWhole: "Include earlier content",
@@ -234,14 +236,19 @@ window.__ModuleLoader__.load({
234
236
 
235
237
  function cssText() {
236
238
  return "" +
237
- "#" + BAR_ID + "{--dsh-find-all-anchor-gap:4px;--dsh-find-all-panel-inset:16px;--dsh-find-all-max-width:760px;position:fixed;top:calc(var(--dsh-find-all-header-bottom) + var(--dsh-find-all-anchor-gap));right:calc(var(--dsh-find-all-panel-right-space) + var(--dsh-find-all-panel-inset));z-index:2147483000;visibility:hidden;isolation:isolate;box-sizing:border-box;align-items:stretch;gap:4px;padding:6px 8px;display:flex;flex-direction:column;width:min(var(--dsh-find-all-max-width),calc(var(--dsh-find-all-panel-width) - var(--dsh-find-all-panel-inset) - var(--dsh-find-all-panel-inset)));max-width:calc(100vw - var(--dsh-find-all-panel-inset) - var(--dsh-find-all-panel-inset));background:transparent;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);border:0;border-radius:var(--dsw-radius-md,8px);box-shadow:var(--dsw-elevation-panel,var(--dsw-shadow-lv2))}" +
239
+ "#" + BAR_ID + "{--dsh-find-all-max-width:600px;position:fixed;top:var(--dsh-find-all-bar-top);right:var(--dsh-find-all-bar-right);z-index:2147483000;visibility:hidden;isolation:isolate;box-sizing:border-box;align-items:stretch;gap:4px;padding:6px 8px;display:flex;flex-direction:column;width:min(var(--dsh-find-all-max-width),var(--dsh-find-all-panel-width));max-width:calc(100vw - 16px);max-height:var(--dsh-find-all-max-height);overflow:auto;background:transparent;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);border:0;border-radius:var(--dsw-radius-md,8px);box-shadow:var(--dsw-elevation-panel,var(--dsw-shadow-lv2))}" +
238
240
  "#" + BAR_ID + "[data-positioned]{visibility:visible}" +
239
241
  "#" + BAR_ID + ":before{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));-webkit-backdrop-filter:var(--dsw-menu-backdrop-filter,blur(40px) saturate(150%));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(40px) saturate(150%))}" +
240
242
  "#" + BAR_ID + " .controls{display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-width:0}" +
241
- "#" + BAR_ID + " input{flex:1 1 210px;min-width:120px;width:auto;height:28px;color:var(--dsw-alias-label-primary);font:13px/1 var(--dsw-font-family);background:var(--dsw-alias-bg-base);border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm,6px);outline:none;padding:0 8px}" +
243
+ "#" + BAR_ID + " input{box-sizing:border-box;flex:1 1 160px;min-width:0;max-width:100%;width:auto;height:28px;color:var(--dsw-alias-label-primary);font:13px/1 var(--dsw-font-family);background:var(--dsw-alias-bg-base);border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm,6px);outline:none;padding:0 8px}" +
242
244
  "#" + BAR_ID + " input:focus-visible{border-color:var(--dsw-alias-state-business-primary)}" +
243
245
  "#" + BAR_ID + " .count{min-width:52px;color:var(--dsw-alias-label-tertiary);font:12px/1 var(--dsw-font-family);font-variant-numeric:tabular-nums;text-align:center}" +
244
- "#" + BAR_ID + " .status{width:100%;min-height:15px;color:var(--dsw-alias-label-tertiary);font:12px/1.25 var(--dsw-font-family);text-align:left;white-space:normal;overflow-wrap:anywhere}" +
246
+ "#" + BAR_ID + " .status{width:100%;color:var(--dsw-alias-label-tertiary);font:12px/1.25 var(--dsw-font-family);text-align:left;white-space:normal;overflow-wrap:anywhere}" +
247
+ "#" + BAR_ID + " .status:empty{display:none}" +
248
+ "#" + BAR_ID + " .position-controls{display:flex;flex-wrap:wrap;align-items:center;gap:4px}" +
249
+ "#" + BAR_ID + " .position-controls[hidden]{display:none}" +
250
+ "#" + BAR_ID + " button.drag-handle{touch-action:none;user-select:none;cursor:grab;flex:none}" +
251
+ "#" + BAR_ID + "[data-dragging] button.drag-handle{cursor:grabbing}" +
245
252
  "#" + BAR_ID + " .status[data-busy]{cursor:pointer;color:var(--dsw-alias-state-business-primary)}" +
246
253
  "#" + BAR_ID + " .scope{width:auto;padding:0 9px;color:var(--dsw-alias-label-secondary);font:12px/1 var(--dsw-font-family);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:999px}" +
247
254
  "#" + BAR_ID + " .scope[data-whole]{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-business-primary)}" +
@@ -423,13 +430,78 @@ window.__ModuleLoader__.load({
423
430
  return highlight;
424
431
  }
425
432
 
433
+ function matchBarRect() {
434
+ return isOpen() && state.bar.hasAttribute("data-positioned") ? state.bar.getBoundingClientRect() : null;
435
+ }
436
+
437
+ // Match visibility is deliberately independent of reading-origin capture
438
+ // and return. Range geometry alone does not account for floating UI.
439
+ function visibleMatchRect(range, viewport) {
440
+ if (!viewport || !rangeRect(range, viewport)) return null;
441
+ var clip = clippedViewport(range.startContainer.parentElement, viewport), bar = matchBarRect();
442
+ var rects = range.getClientRects();
443
+ for (var i = 0; i < rects.length; i++) {
444
+ var rect = rects[i], left = Math.max(rect.left, clip.left), right = Math.min(rect.right, clip.right);
445
+ var top = Math.max(rect.top, clip.top), bottom = Math.min(rect.bottom, clip.bottom);
446
+ if (right <= left || bottom <= top) continue;
447
+ if (!bar || right <= bar.left || left >= bar.right || bottom <= bar.top || top >= bar.bottom) return rect;
448
+ }
449
+ return null;
450
+ }
451
+
452
+ function scrollRangeWithin(range, scroll, viewport, bar) {
453
+ var rect = rangeRect(range);
454
+ if (!rect) return;
455
+ var current = scroll.scrollTop, maximum = Math.max(0, scroll.scrollHeight - scroll.clientHeight);
456
+ var bands = [[viewport.top, viewport.bottom]];
457
+ if (bar && rect.right > bar.left && rect.left < bar.right && bar.bottom > viewport.top && bar.top < viewport.bottom) {
458
+ bands = [[viewport.top, Math.min(viewport.bottom, bar.top - 8)],
459
+ [Math.max(viewport.top, bar.bottom + 8), viewport.bottom]];
460
+ }
461
+ var best = null;
462
+ bands.forEach(function (band) {
463
+ if (band[1] <= band[0]) return;
464
+ var delta = rect.top < band[0] ? rect.top - band[0] :
465
+ rect.bottom > band[1] ? Math.min(rect.top - band[0], rect.bottom - band[1]) : 0;
466
+ delta = Math.max(0, Math.min(maximum, current + delta)) - current;
467
+ var shown = Math.max(0, Math.min(rect.bottom - delta, band[1]) - Math.max(rect.top - delta, band[0]));
468
+ // At either history boundary, prefer the region actually reachable
469
+ // after clamping rather than assuming the larger region is reachable.
470
+ if (!best || shown > best.shown || (shown === best.shown && Math.abs(delta) < Math.abs(best.delta))) best = { delta: delta, shown: shown };
471
+ });
472
+ var x = rect.left < viewport.left ? rect.left - viewport.left :
473
+ rect.right > viewport.right ? Math.min(rect.left - viewport.left, rect.right - viewport.right) : 0;
474
+ if ((best && best.delta) || x) {
475
+ try { scroll.scrollBy({ top: best ? best.delta : 0, left: x, behavior: "instant" }); } catch (error) {}
476
+ }
477
+ }
478
+
426
479
  function scrollToRange(range) {
427
480
  var viewport = readingViewport(state.target);
428
- if (viewport && rangeRect(range, viewport)) return;
429
- var el = range.startContainer.parentElement;
430
- if (el && el.scrollIntoView) {
431
- try { el.scrollIntoView({ block: "center", behavior: "instant" }); } catch (error) {}
481
+ if (!viewport || visibleMatchRect(range, viewport)) return;
482
+ // Reveal the exact Range, not its potentially very tall <pre>/<p>.
483
+ // Work from inner scroll clips outwards, without scrolling the window
484
+ // or unrelated panels. Re-read geometry after every scroll.
485
+ for (var node = range.startContainer.parentElement; node && node !== viewport.scroll; node = node.parentElement) {
486
+ var style = window.getComputedStyle(node);
487
+ var clipsY = /^(auto|scroll|hidden)$/.test(style.overflowY) && node.scrollHeight > node.clientHeight;
488
+ var clipsX = /^(auto|scroll|hidden)$/.test(style.overflowX) && node.scrollWidth > node.clientWidth;
489
+ if (!clipsX && !clipsY) continue;
490
+ var box = node.getBoundingClientRect(), top = box.top + node.clientTop, left = box.left + node.clientLeft;
491
+ var rect = rangeRect(range);
492
+ if (!rect) return;
493
+ var clip = clippedViewport(node, viewport);
494
+ var safeTop = Math.max(top, clip.top), safeBottom = Math.min(top + node.clientHeight, clip.bottom);
495
+ var shown = safeBottom > safeTop;
496
+ // A code scroller may still have room when the conversation is at
497
+ // its boundary. Let it avoid the overlay within its visible clip.
498
+ scrollRangeWithin(range, node, { top: clipsY ? (shown ? safeTop : top) : rect.top,
499
+ bottom: clipsY ? (shown ? safeBottom : top + node.clientHeight) : rect.bottom,
500
+ left: clipsX ? left : rect.left, right: clipsX ? left + node.clientWidth : rect.right },
501
+ clipsY && shown ? matchBarRect() : null);
432
502
  }
503
+ viewport = readingViewport(state.target);
504
+ if (viewport && !visibleMatchRect(range, viewport)) scrollRangeWithin(range, viewport.scroll, viewport, matchBarRect());
433
505
  }
434
506
 
435
507
  //#region host adapter
@@ -727,6 +799,16 @@ window.__ModuleLoader__.load({
727
799
  return null;
728
800
  }
729
801
 
802
+ function hiddenUntilFoundAncestor(node, root) {
803
+ var element = node && (node.nodeType === 1 ? node : node.parentElement);
804
+ for (; element && root && root.contains(element); element = element.parentElement) {
805
+ var hidden = element.getAttribute && element.getAttribute('hidden');
806
+ if (typeof hidden === 'string' && hidden.toLowerCase() === 'until-found') return element;
807
+ if (element === root) break;
808
+ }
809
+ return null;
810
+ }
811
+
730
812
  function captureReadingOrigin() {
731
813
  var target = state.target, viewport = readingViewport(target);
732
814
  var store = state.adapter && state.adapter.nodes(target);
@@ -844,19 +926,22 @@ window.__ModuleLoader__.load({
844
926
  if (state.target !== target || !state.adapter.valid(target) || state.adapter.face(target) !== face) return;
845
927
  }
846
928
  var origin = state.origin, token = state.navigationToken, started = Date.now();
847
- var activated = new Set(), scrolledRange = null;
929
+ var activated = new Set(), announceReturn = false;
848
930
  function check() {
849
931
  state.navigationTimer = 0;
850
932
  if (token !== state.navigationToken || !originValid(origin)) return;
933
+ positionBar();
851
934
  var viewport = readingViewport(origin.target);
852
935
  var current = state.ranges[state.index];
853
- var geometry = current && origin.target.root.contains(current.startContainer) && rangeRect(current);
854
- if (current && !geometry) revealMatchRange(origin.target, current, activated);
855
- if (geometry && scrolledRange !== current) {
856
- scrollToRange(current);
857
- scrolledRange = current;
858
- }
859
- if (viewport && current && origin.target.root.contains(current.startContainer) && rangeRect(current, viewport)) {
936
+ var attached = current && origin.target.root.contains(current.startContainer);
937
+ // hidden=until-found uses content-visibility:hidden, so Chromium may
938
+ // report layout geometry for descendants that are not painted. Reveal
939
+ // the host-owned disclosure before trusting Range geometry.
940
+ var concealed = attached && hiddenUntilFoundAncestor(current.startContainer, origin.target.root);
941
+ var geometry = attached && !concealed && rangeRect(current);
942
+ if (current && (!geometry || concealed)) revealMatchRange(origin.target, current, activated);
943
+ if (geometry && !visibleMatchRect(current, viewport)) scrollToRange(current);
944
+ if (viewport && attached && !concealed && visibleMatchRect(current, viewport)) {
860
945
  var firstNavigation = !origin.navigated;
861
946
  origin.navigated = true;
862
947
  updateReturnButton();
@@ -866,8 +951,13 @@ window.__ModuleLoader__.load({
866
951
  var priorStatus = state.jumpStatus;
867
952
  setStatus(priorStatus, false);
868
953
  } else setStatus(L.returnAvailable, false);
869
- if (state.announcement) state.announcement.textContent = L.returnAvailable;
954
+ announceReturn = true;
955
+ // Return/status controls can wrap and grow the overlay. Verify
956
+ // the resulting layout before declaring the match visible.
957
+ state.navigationTimer = setTimeout(check, 0);
958
+ return;
870
959
  }
960
+ if (announceReturn && state.announcement) state.announcement.textContent = L.returnAvailable;
871
961
  return;
872
962
  }
873
963
  if (Date.now() - started < 1500) {
@@ -1098,7 +1188,8 @@ window.__ModuleLoader__.load({
1098
1188
  origin: null, restoring: null, returnBtn: null, announcement: null, navigationTimer: 0, navigationToken: 0,
1099
1189
  jumpStatus: null,
1100
1190
  positionObserver: null,
1101
- positionFrame: 0
1191
+ positionFrame: null,
1192
+ positionOwner: null, manualPosition: null, drag: null, dragHandle: null, positionControls: null, suppressMoveClick: false
1102
1193
  };
1103
1194
 
1104
1195
  function barAnchor(target) {
@@ -1114,39 +1205,115 @@ window.__ModuleLoader__.load({
1114
1205
 
1115
1206
  function positionBar() {
1116
1207
  if (!isOpen() || !state.bar) return false;
1117
- if (!state.target) { state.bar.removeAttribute("data-positioned"); return false; }
1118
- var header = barAnchor(state.target);
1119
- if (!header || !header.isConnected || !state.target.panel.isConnected) {
1208
+ var target = state.target, header = barAnchor(target), viewport = readingViewport(target);
1209
+ if (!target || !header || !header.isConnected || !target.panel.isConnected || !viewport) {
1210
+ state.bar.removeAttribute("data-positioned");
1211
+ return false;
1212
+ }
1213
+ var panel = target.panel.getBoundingClientRect(), headerRect = header.getBoundingClientRect();
1214
+ var leftEdge = Math.max(panel.left, viewport.left) + 8;
1215
+ var rightEdge = Math.min(panel.right, viewport.right) - 8;
1216
+ var topEdge = Math.max(panel.top, viewport.top, headerRect.bottom) + 8;
1217
+ var bottomEdge = Math.min(panel.bottom, viewport.bottom) - 8;
1218
+ if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
1120
1219
  state.bar.removeAttribute("data-positioned");
1121
1220
  return false;
1122
1221
  }
1123
- var headerRect = header.getBoundingClientRect();
1124
- var panelRect = state.target.panel.getBoundingClientRect();
1125
- var viewportWidth = document.documentElement.clientWidth || window.innerWidth;
1126
- var panelLeft = Math.max(0, panelRect.left);
1127
- var panelRight = Math.min(viewportWidth, panelRect.right);
1128
1222
  var dpr = window.devicePixelRatio || 1;
1129
1223
  function cssPixel(value) { return Math.round(value * dpr) / dpr + "px"; }
1130
- state.bar.style.setProperty("--dsh-find-all-header-bottom", cssPixel(headerRect.bottom));
1131
- state.bar.style.setProperty("--dsh-find-all-panel-right-space", cssPixel(Math.max(0, viewportWidth - panelRight)));
1132
- state.bar.style.setProperty("--dsh-find-all-panel-width", cssPixel(Math.max(0, panelRight - panelLeft)));
1224
+ // This is the only position writer, including pointer, resize, scroll
1225
+ // and polling updates. Manual placement never falls back to the anchor.
1226
+ state.bar.style.setProperty("--dsh-find-all-panel-width", cssPixel(rightEdge - leftEdge));
1227
+ state.bar.style.setProperty("--dsh-find-all-max-height", cssPixel(bottomEdge - topEdge));
1228
+ var box = state.bar.getBoundingClientRect(), manual = state.manualPosition;
1229
+ var left = manual ? manual.left : rightEdge - box.width;
1230
+ var top = manual ? manual.top : topEdge;
1231
+ left = Math.max(leftEdge, Math.min(left, rightEdge - box.width));
1232
+ top = Math.max(topEdge, Math.min(top, bottomEdge - box.height));
1233
+ if (manual) { manual.left = left; manual.top = top; }
1234
+ state.bar.style.setProperty("--dsh-find-all-bar-top", cssPixel(top));
1235
+ state.bar.style.setProperty("--dsh-find-all-bar-right", cssPixel(window.innerWidth - left - box.width));
1133
1236
  state.bar.setAttribute("data-positioned", "");
1134
1237
  return true;
1135
1238
  }
1136
1239
 
1240
+ function finishBarDrag(event) {
1241
+ var drag = state.drag;
1242
+ if (!drag || (event && event.pointerId !== drag.id)) return;
1243
+ state.drag = null;
1244
+ state.bar.removeAttribute("data-dragging");
1245
+ try { if (drag.handle.hasPointerCapture(drag.id)) drag.handle.releasePointerCapture(drag.id); } catch (error) {}
1246
+ }
1247
+
1248
+ function resetBarPosition() {
1249
+ finishBarDrag();
1250
+ state.manualPosition = null;
1251
+ scheduleBarPosition();
1252
+ }
1253
+
1254
+ function moveBarBy(x, y) {
1255
+ if (!positionBar()) return;
1256
+ var box = state.bar.getBoundingClientRect();
1257
+ state.manualPosition = { left: box.left + x, top: box.top + y };
1258
+ scheduleBarPosition();
1259
+ }
1260
+
1261
+ function buildPositionControls(controls, bar) {
1262
+ var moves = document.createElement("div");
1263
+ moves.className = "position-controls"; moves.id = BAR_ID + "-position"; moves.hidden = true;
1264
+ var handle = button(L.move, "M9 5h.01M15 5h.01M9 12h.01M15 12h.01M9 19h.01M15 19h.01", function (event) {
1265
+ if (event.detail && state.suppressMoveClick) { state.suppressMoveClick = false; return; }
1266
+ moves.hidden = !moves.hidden;
1267
+ handle.setAttribute("aria-expanded", String(!moves.hidden));
1268
+ scheduleBarPosition();
1269
+ });
1270
+ handle.className = "drag-handle";
1271
+ handle.setAttribute("aria-expanded", "false"); handle.setAttribute("aria-controls", moves.id);
1272
+ handle.addEventListener("pointerdown", function (event) {
1273
+ if (!event.isPrimary || event.button !== 0 || state.drag || !positionBar()) return;
1274
+ state.suppressMoveClick = false;
1275
+ try { handle.setPointerCapture(event.pointerId); } catch (error) { return; }
1276
+ var box = state.bar.getBoundingClientRect();
1277
+ state.drag = { id: event.pointerId, handle: handle, x: event.clientX, y: event.clientY, left: box.left, top: box.top };
1278
+ // Dragging the handle must not steal focus/selection from the query.
1279
+ event.preventDefault();
1280
+ });
1281
+ handle.addEventListener("pointermove", function (event) {
1282
+ var drag = state.drag;
1283
+ if (!drag || event.pointerId !== drag.id) return;
1284
+ var x = event.clientX - drag.x, y = event.clientY - drag.y;
1285
+ if (!state.suppressMoveClick && Math.abs(x) + Math.abs(y) < 4) return;
1286
+ state.suppressMoveClick = true;
1287
+ state.bar.setAttribute("data-dragging", "");
1288
+ state.manualPosition = { left: drag.left + x, top: drag.top + y };
1289
+ scheduleBarPosition();
1290
+ });
1291
+ ["pointerup", "pointercancel", "lostpointercapture"].forEach(function (type) { handle.addEventListener(type, finishBarDrag); });
1292
+ moves.appendChild(button(L.moveLeft, "m14 6-6 6 6 6", function () { moveBarBy(-32, 0); }));
1293
+ moves.appendChild(button(L.moveRight, "m10 6 6 6-6 6", function () { moveBarBy(32, 0); }));
1294
+ moves.appendChild(button(L.moveUp, "m6 14 6-6 6 6", function () { moveBarBy(0, -32); }));
1295
+ moves.appendChild(button(L.moveDown, "m6 10 6 6 6-6", function () { moveBarBy(0, 32); }));
1296
+ var reset = button(L.resetPosition, "M3 10a9 9 0 1 1 2 8M3 4v6h6", resetBarPosition);
1297
+ moves.appendChild(reset);
1298
+ state.dragHandle = handle; state.positionControls = moves;
1299
+ controls.appendChild(handle);
1300
+ bar.appendChild(moves);
1301
+ }
1302
+
1137
1303
  function scheduleBarPosition() {
1138
- if (!isOpen() || state.positionFrame) return;
1304
+ if (!isOpen() || state.positionFrame !== null) return;
1139
1305
  if (typeof requestAnimationFrame !== "function") { positionBar(); return; }
1140
1306
  state.positionFrame = requestAnimationFrame(function () {
1141
- state.positionFrame = 0;
1307
+ state.positionFrame = null;
1142
1308
  positionBar();
1143
1309
  });
1144
1310
  }
1145
1311
 
1146
1312
  function stopBarPositioning() {
1313
+ finishBarDrag();
1147
1314
  if (state.positionObserver) { state.positionObserver.disconnect(); state.positionObserver = null; }
1148
- if (state.positionFrame && typeof cancelAnimationFrame === "function") cancelAnimationFrame(state.positionFrame);
1149
- state.positionFrame = 0;
1315
+ if (state.positionFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(state.positionFrame);
1316
+ state.positionFrame = null;
1150
1317
  }
1151
1318
 
1152
1319
  function startBarPositioning() {
@@ -1156,6 +1323,13 @@ window.__ModuleLoader__.load({
1156
1323
  if (!header || typeof ResizeObserver === "undefined") return;
1157
1324
  state.positionObserver = new ResizeObserver(scheduleBarPosition);
1158
1325
  state.positionObserver.observe(header);
1326
+ var viewport = readingViewport(state.target);
1327
+ if (viewport) {
1328
+ state.positionObserver.observe(viewport.scroll);
1329
+ var composer = viewport.scroll.querySelector("[data-composer-seat]");
1330
+ if (composer) state.positionObserver.observe(composer);
1331
+ }
1332
+ state.positionObserver.observe(state.bar);
1159
1333
  if (state.target.panel !== header) state.positionObserver.observe(state.target.panel);
1160
1334
  }
1161
1335
 
@@ -1438,6 +1612,15 @@ window.__ModuleLoader__.load({
1438
1612
  function syncTarget(target, passive) {
1439
1613
  if (!state.adapter) return;
1440
1614
  var selected = passive ? state.adapter.refresh(state.target) : state.adapter.select(target || document.activeElement, state.target);
1615
+ if (selected) {
1616
+ var owner = state.positionOwner;
1617
+ if (owner && (owner.id !== selected.id || owner.panel !== selected.panel)) {
1618
+ resetBarPosition();
1619
+ if (state.positionControls) state.positionControls.hidden = true;
1620
+ if (state.dragHandle) state.dragHandle.setAttribute("aria-expanded", "false");
1621
+ }
1622
+ state.positionOwner = { id: selected.id, panel: selected.panel };
1623
+ }
1441
1624
  if (selected && state.target && selected.id === state.target.id && selected.anchor === state.target.anchor && selected.root === state.target.root && selected.panel === state.target.panel && selected.registration === state.target.registration) {
1442
1625
  if (state.origin && !originValid(state.origin)) { discardOrigin(); setStatus(returnFailure('unavailable'), false); }
1443
1626
  scheduleBarPosition();
@@ -1554,6 +1737,7 @@ window.__ModuleLoader__.load({
1554
1737
  setStatus(L.incomplete, false);
1555
1738
  });
1556
1739
 
1740
+ buildPositionControls(controls, bar);
1557
1741
  controls.appendChild(input);
1558
1742
  controls.appendChild(scopeBtn);
1559
1743
  controls.appendChild(count);
@@ -1566,7 +1750,7 @@ window.__ModuleLoader__.load({
1566
1750
  state.returnBtn = returnBtn;
1567
1751
  controls.appendChild(returnBtn);
1568
1752
  controls.appendChild(button(L.close, "M18 6 6 18M6 6l12 12", close));
1569
- bar.appendChild(controls);
1753
+ bar.insertBefore(controls, bar.firstChild);
1570
1754
  bar.appendChild(status);
1571
1755
 
1572
1756
  state.bar = bar;
@@ -1616,6 +1800,10 @@ window.__ModuleLoader__.load({
1616
1800
  cancelPageIn();
1617
1801
  stopWatchingSession();
1618
1802
  stopBarPositioning();
1803
+ state.manualPosition = state.positionOwner = null;
1804
+ state.suppressMoveClick = false;
1805
+ if (state.positionControls) state.positionControls.hidden = true;
1806
+ if (state.dragHandle) state.dragHandle.setAttribute("aria-expanded", "false");
1619
1807
  if (state.bar) { state.bar.style.display = "none"; state.bar.removeAttribute("data-positioned"); }
1620
1808
  state.ranges = [];
1621
1809
  state.index = -1;
@@ -1692,6 +1880,8 @@ window.__ModuleLoader__.load({
1692
1880
  window.addEventListener("focusin", focus, true);
1693
1881
  window.addEventListener("pointerdown", focus, true);
1694
1882
  window.addEventListener("wheel", readingFocus, true);
1883
+ function blur() { finishBarDrag(); }
1884
+ window.addEventListener("blur", blur);
1695
1885
  window.addEventListener("resize", scheduleBarPosition);
1696
1886
  window.addEventListener("scroll", scheduleBarPosition, true);
1697
1887
  return function () {
@@ -1700,13 +1890,14 @@ window.__ModuleLoader__.load({
1700
1890
  window.removeEventListener("focusin", focus, true);
1701
1891
  window.removeEventListener("pointerdown", focus, true);
1702
1892
  window.removeEventListener("wheel", readingFocus, true);
1893
+ window.removeEventListener("blur", blur);
1703
1894
  window.removeEventListener("resize", scheduleBarPosition);
1704
1895
  window.removeEventListener("scroll", scheduleBarPosition, true);
1705
1896
  close();
1706
1897
  state.query = "";
1707
1898
  if (state.bar) state.bar.remove();
1708
1899
  if (state.announcement) state.announcement.remove();
1709
- state.announcement = state.returnBtn = null;
1900
+ state.announcement = state.returnBtn = state.dragHandle = state.positionControls = null;
1710
1901
  var style = document.querySelector('style[data-plugin-css="dsh-find-all/bar.css"]');
1711
1902
  if (style) style.remove();
1712
1903
  state.bar = state.input = state.count = state.status = state.scopeBtn = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuu-64/dsh-find-all",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Ctrl+F find bar for the DSH desktop app: it searches the whole conversation, not just the messages already on screen.",
5
5
  "type": "module",
6
6
  "license": "MIT",