@cowwo/dsh-lookout 0.1.1 → 0.1.2

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 (2) hide show
  1. package/client/client.js +53 -8
  2. package/package.json +1 -1
package/client/client.js CHANGED
@@ -7339,6 +7339,15 @@ window.__ModuleLoader__.load({
7339
7339
  const ROW_MENU_QUIET_MS = 250;
7340
7340
  const ROW_MENU_FEEDBACK_MS = 1500;
7341
7341
  const ROW_MENU_TOAST_MS = 3000;
7342
+ /**
7343
+ * How long after opening the menu a scroll/resize counts as part of the tap.
7344
+ *
7345
+ * Phones fire scroll and resize while the tap is still settling (visual viewport
7346
+ * shifts, focus-driven scrolling, the browser chrome collapsing). Closing on those
7347
+ * made the menu flash and vanish on mobile, so inside this window the menu only
7348
+ * repositions; a later, real scroll closes it as usual.
7349
+ */
7350
+ const ROW_MENU_SCROLL_GRACE_MS = 700;
7342
7351
  const ROW_MENU_BUTTON_PADDING = "32px";
7343
7352
  const ROW_MENU_ENABLED_KEY = "dsh-lookout:file-row-menu:enabled";
7344
7353
  const ROW_MENU_DISMISSED_KEY = "dsh-lookout:file-row-menu:gave-up-dismissed";
@@ -7386,12 +7395,12 @@ window.__ModuleLoader__.load({
7386
7395
  * No literal colour is written here, so light/dark follows the host.
7387
7396
  */
7388
7397
  const ROW_MENU_CSS = [
7389
- ".dsh-lookout-rowmenu{position:absolute;top:4px;right:4px;z-index:2;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;line-height:1;}",
7398
+ ".dsh-lookout-rowmenu{position:absolute;top:4px;right:4px;z-index:2;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;line-height:1;touch-action:manipulation;-webkit-tap-highlight-color:transparent;}",
7390
7399
  ".dsh-lookout-rowmenu:hover,.dsh-lookout-rowmenu:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);outline:none;}",
7391
7400
  ".dsh-lookout-rowmenu svg{display:block;width:16px;height:16px;pointer-events:none;}",
7392
7401
  ".dsh-lookout-rowmenu[data-lookout-copied]{color:var(--dsw-alias-state-success-primary);}",
7393
- ".dsh-lookout-rowmenu-pop{position:fixed;z-index:80;min-width:176px;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-drop);}",
7394
- ".dsh-lookout-rowmenu-item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer;}",
7402
+ ".dsh-lookout-rowmenu-pop{position:fixed;z-index:2147482000;min-width:176px;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-drop);}",
7403
+ ".dsh-lookout-rowmenu-item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;}",
7395
7404
  ".dsh-lookout-rowmenu-item:hover,.dsh-lookout-rowmenu-item:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none;}",
7396
7405
  ".dsh-lookout-rowmenu-item[aria-disabled=\"true\"]{color:var(--dsw-alias-label-dimmed);cursor:not-allowed;}",
7397
7406
  ".dsh-lookout-rowmenu-item[aria-disabled=\"true\"]:hover{background:transparent;}",
@@ -7483,6 +7492,7 @@ window.__ModuleLoader__.load({
7483
7492
  let menuAnchor = null;
7484
7493
  let menuRow = null;
7485
7494
  let menuPath = "";
7495
+ let menuOpenedAt = 0;
7486
7496
  let toast = null;
7487
7497
  let toastTimer = 0;
7488
7498
  const feedbackTimers = new Map();
@@ -7899,16 +7909,19 @@ window.__ModuleLoader__.load({
7899
7909
  menuAnchor = anchor;
7900
7910
  menuRow = anchor.closest("li");
7901
7911
  menuPath = path;
7912
+ menuOpenedAt = Date.now();
7902
7913
  anchor.setAttribute("aria-expanded", "true");
7903
7914
  paintedAt = Date.now();
7904
7915
  document.addEventListener("pointerdown", onDocumentPointerDown, true);
7905
7916
  document.addEventListener("keydown", onDocumentKeyDown, true);
7906
- window.addEventListener("scroll", closeMenu, true);
7907
- window.addEventListener("resize", closeMenu, true);
7917
+ window.addEventListener("scroll", onDocumentScroll, true);
7918
+ window.addEventListener("resize", repositionMenu, true);
7919
+ watchVisualViewport(true);
7908
7920
  window.setTimeout(() => {
7909
7921
  if (menu !== popup) return;
7910
7922
  const first = popup.querySelector('[role="menuitem"]:not([disabled])');
7911
- if (first !== null) { try { first.focus(); } catch { /* best effort */ } }
7923
+ if (first === null) return;
7924
+ try { first.focus({ preventScroll: true }); } catch { try { first.focus(); } catch { /* best effort */ } }
7912
7925
  }, 0);
7913
7926
  }
7914
7927
 
@@ -7942,8 +7955,40 @@ window.__ModuleLoader__.load({
7942
7955
  if (anchor !== null) { try { anchor.setAttribute("aria-expanded", "false"); } catch { /* gone */ } }
7943
7956
  document.removeEventListener("pointerdown", onDocumentPointerDown, true);
7944
7957
  document.removeEventListener("keydown", onDocumentKeyDown, true);
7945
- window.removeEventListener("scroll", closeMenu, true);
7946
- window.removeEventListener("resize", closeMenu, true);
7958
+ window.removeEventListener("scroll", onDocumentScroll, true);
7959
+ window.removeEventListener("resize", repositionMenu, true);
7960
+ watchVisualViewport(false);
7961
+ }
7962
+
7963
+ /** Move the open menu back inside the viewport without closing it. */
7964
+ function repositionMenu() {
7965
+ if (menu === null || menuAnchor === null) return;
7966
+ positionMenu(menu, menuAnchor);
7967
+ }
7968
+
7969
+ /**
7970
+ * A scroll normally means the anchor moved, but a phone fires one while the tap
7971
+ * settles; that churn must not close the menu the tap just opened.
7972
+ */
7973
+ function onDocumentScroll() {
7974
+ if (menu === null) return;
7975
+ if (Date.now() - menuOpenedAt < ROW_MENU_SCROLL_GRACE_MS) { repositionMenu(); return; }
7976
+ closeMenu();
7977
+ }
7978
+
7979
+ /** Follow the visual viewport (URL bar, on-screen keyboard) instead of dying with it. */
7980
+ function watchVisualViewport(on) {
7981
+ try {
7982
+ const viewport = window.visualViewport;
7983
+ if (viewport === null || viewport === undefined || typeof viewport.addEventListener !== "function") return;
7984
+ if (on) {
7985
+ viewport.addEventListener("resize", repositionMenu);
7986
+ viewport.addEventListener("scroll", repositionMenu);
7987
+ } else {
7988
+ viewport.removeEventListener("resize", repositionMenu);
7989
+ viewport.removeEventListener("scroll", repositionMenu);
7990
+ }
7991
+ } catch { /* older browsers simply skip the follow-up */ }
7947
7992
  }
7948
7993
 
7949
7994
  function onDocumentPointerDown(event) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cowwo/dsh-lookout",
3
3
  "description": "瞭望哨:盯着你的每个会话——谁跑完了、谁出错了、谁卡住了、谁在等你审批,用浏览器图标角标、红点/黄点和邮件通知你;顺带显示当前会话 ID。· Lookout: watches every session — who finished, who errored, who is stuck, and who is waiting for your approval — and notifies you through the browser favicon badge, red/yellow dots, and email, while also showing the current session ID.",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",