@takagaki/cortex-decisions-viewer 0.4.60 → 0.4.62

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/dist/render.js +51 -27
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -1708,7 +1708,10 @@ ${capabilityState.toString()}
1708
1708
  m.modal.appendChild(el("div", { class: "modal-actions" }, [
1709
1709
  el("button", { class: "ds-toggle-btn", type: "button", text: "閉じる" }),
1710
1710
  ]));
1711
- m.modal.querySelector(".modal-actions button").addEventListener("click", m.close);
1711
+ // **close をそのまま渡さない。** ハンドラに渡すと close(MouseEvent) になる。
1712
+ // いまは skipRefresh の判定が厳密比較なので効いているが、truthy 判定に
1713
+ // 緩めた瞬間「閉じる」ボタンだけが黙って取り込まなくなる
1714
+ m.modal.querySelector(".modal-actions button").addEventListener("click", function () { m.close(); });
1712
1715
  m.overlay.appendChild(m.modal);
1713
1716
  showModal(m.overlay, m.modal);
1714
1717
  }
@@ -1842,7 +1845,11 @@ ${capabilityState.toString()}
1842
1845
  col.appendChild(el("span", { class: "ds-inactive-note", text: "未使用(この案件では設定されていません)" }));
1843
1846
  }
1844
1847
  // id は接続マップのノードclickの飛び先(アンカー)
1845
- var card = el("div", { class: "ds-card" + (inactive ? " ds-inactive" : ""), id: "ds-" + (s.kind || "") }, [
1848
+ // **id は付けない。** #101 以前はこのカードが接続マップの飛び先だったが、
1849
+ // いまはタイルが飛び先。両方に付けると同じ id が2つある不正なHTMLになり、
1850
+ // モーダルの中身がタイルより前に来る変更が入った瞬間、getElementById が
1851
+ // 「開いていないモーダルの中」を指して黙って無反応になる
1852
+ var card = el("div", { class: "ds-card" + (inactive ? " ds-inactive" : "") }, [
1846
1853
  el("div", { class: "ds-card-main" }, [
1847
1854
  el("span", { class: "ds-icon", text: DS_KIND_ICON[s.kind] || "📁" }),
1848
1855
  el("span", { class: "ds-type", text: s.kind || "" }),
@@ -1873,15 +1880,10 @@ ${capabilityState.toString()}
1873
1880
  });
1874
1881
  var UNIT = { "slack": "チャンネル", "github-issues": "リポジトリ", "github-discussions": "リポジトリ" };
1875
1882
  // チャット未使用: Slackグループの位置(先頭)に「存在するが未設定」のグレー行を出す
1876
- if (chatOff) pushCap("チャット", inactiveSourceRow("💬", "チャット", "ds-チャット"));
1877
- var chatAnchored = chatOff;
1878
- var devAnchored = devOff;
1883
+ // **アンカーは渡さない**(タイルが飛び先。同 §id の重複を作らない)
1884
+ if (chatOff) pushCap("チャット", inactiveSourceRow("💬", "チャット", null));
1879
1885
  groups.forEach(function (g) {
1880
1886
  var meta = DS_TYPE[g.type] || { icon: "🔗", label: g.type || "その他" };
1881
- // 接続マップのノードclickの飛び先(アンカー)。チャット=Slack群の先頭・開発=GitHub群の先頭
1882
- var anchorId = null;
1883
- if (g.type === "slack" && !chatAnchored) { anchorId = "ds-チャット"; chatAnchored = true; }
1884
- if (g.type.indexOf("github") === 0 && !devAnchored) { anchorId = "ds-開発"; devAnchored = true; }
1885
1887
  var cards = el("div", { class: "ds-list" });
1886
1888
  g.items.forEach(function (s) { cards.appendChild(extSourceCard(s, meta)); });
1887
1889
  if (g.items.length >= 4) {
@@ -1906,10 +1908,8 @@ ${capabilityState.toString()}
1906
1908
  : "⚠️ " + bad + "件に問題" }),
1907
1909
  ]);
1908
1910
  var det = el("details", { class: "ds-group" }, [sum, cards]);
1909
- if (anchorId) det.id = anchorId;
1910
1911
  pushCap(capOf(g.type), det);
1911
1912
  } else {
1912
- if (anchorId && cards.firstChild) cards.firstChild.id = anchorId;
1913
1913
  // **appendChild と違い pushCap はノードを動かさない。** firstChild を条件にすると
1914
1914
  // 永久に真のままになる(実際に無限ループを作った)。先に配列へ取り出す。
1915
1915
  var kids = [];
@@ -1919,7 +1919,7 @@ ${capabilityState.toString()}
1919
1919
  });
1920
1920
  }
1921
1921
  // 開発未使用: グレー行も能力の受け皿へ
1922
- if (devOff) pushCap("開発", inactiveSourceRow("🐙", "GitHub", "ds-開発"));
1922
+ if (devOff) pushCap("開発", inactiveSourceRow("🐙", "GitHub", null));
1923
1923
 
1924
1924
  // **追加フォームはグループの外に置く。** グループは0件だと描かれず、4件以上だと
1925
1925
  // 畳まれる(既定は閉)。中に入れると**新規案件(0件)=一番追加したい場面**で消える。
@@ -3243,15 +3243,14 @@ ${capabilityState.toString()}
3243
3243
  // ノード**で、掴んでいる一覧も古い。作り直すと、下のタイルは「2リポジトリ」・
3244
3244
  // 上のモーダルは「1リポジトリ」という食い違いになる。
3245
3245
  // 取り込みは**最後の1枚を閉じたとき**に行えば足りる
3246
+ // **戻り先は畳む前に取る。** 既存の close() が前のタイルへフォーカスを戻すので、
3247
+ // 後で取ると押し替え後に閉じたとき「1枚目に開いたタイル」へ戻ってしまう
3248
+ var returnTo = document.activeElement;
3246
3249
  var existing = app.querySelector(".modal-overlay");
3247
3250
  if (existing) {
3248
3251
  if (existing.__close) existing.__close(true);
3249
3252
  else if (existing.parentNode) existing.parentNode.removeChild(existing);
3250
3253
  }
3251
-
3252
- // 開く前にフォーカスがどこにあったかを覚え、閉じたら戻す。
3253
- // 戻さないと、キーボードだけで操作している人は一覧の先頭へ飛ばされる
3254
- var returnTo = document.activeElement;
3255
3254
  var overlay = el("div", { class: "modal-overlay" });
3256
3255
  var modal = el("div", { class: wide ? "modal modal-wide" : "modal", role: "dialog", "aria-modal": "true" });
3257
3256
  // **dialog に名前を付ける。** 無いとスクリーンリーダは「ダイアログ」としか
@@ -3277,13 +3276,18 @@ ${capabilityState.toString()}
3277
3276
 
3278
3277
  function close(skipRefresh) {
3279
3278
  if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
3280
- var behind = app.querySelector(".datasources");
3281
- if (behind) { behind.removeAttribute("inert"); behind.removeAttribute("aria-hidden"); }
3279
+ setBackdropInert(overlay, false);
3282
3280
  document.removeEventListener("keydown", onKey);
3283
- if (returnTo && typeof returnTo.focus === "function" && returnTo.isConnected !== false) returnTo.focus();
3284
- // **閉じたら差し替えを試し直す。** 開いている間はライブ値の反映を見送るが、
3285
- // 見送ったままだと再取得の口が無く、タブを離れて戻るまで古い値のままになる。
3281
+ // **差し替えを先に、フォーカスを戻すのは後。**
3282
+ // isBeingEdited は「節の中にフォーカスがあるか」を見て早期returnする。
3283
+ // 先にタイルへフォーカスを戻すと、直後の refreshDataSources が自分で
3284
+ // 弾かれる——**実ブラウザではクリックでもフォーカスが当たるので必ず起きる**。
3285
+ // jsdom の .click() はフォーカスを当てないので、テストからは見えなかった。
3286
3286
  if (skipRefresh !== true && typeof refreshDataSources === "function") refreshDataSources();
3287
+ // 節ごと差し替わっていると元のノードは切り離されている。同じ id で引き直す
3288
+ var back = returnTo;
3289
+ if (back && back.id && !app.contains(back)) back = document.getElementById(back.id) || back;
3290
+ if (back && typeof back.focus === "function" && app.contains(back)) back.focus();
3287
3291
  }
3288
3292
  function onKey(e) { if (e.key === "Escape") close(); }
3289
3293
  document.addEventListener("keydown", onKey);
@@ -3301,13 +3305,33 @@ ${capabilityState.toString()}
3301
3305
  * もう一度 Enter を押しただけで、確認なしに設定ファイルが書き換わる。
3302
3306
  * 移す先は閉じるボタン——押しても何も壊れないものにする。
3303
3307
  */
3308
+ /**
3309
+ * モーダルの下地を触れなくする(解除は on=false)。
3310
+ *
3311
+ * **.datasources だけでは足りない。** タブ行・接続マップ・ヘッダのリンクが外に残り、
3312
+ * Tab で抜けられる。とくに「決定事項」タブに届くので、押すとモーダルを開いたまま
3313
+ * 画面が切り替わり、close() を通らないので keydown が1本残る。
3314
+ */
3315
+ function setBackdropInert(overlay, on) {
3316
+ var scopes = [app, document.body];
3317
+ for (var i = 0; i < scopes.length; i++) {
3318
+ var kids = scopes[i].children;
3319
+ for (var j = 0; j < kids.length; j++) {
3320
+ var n = kids[j];
3321
+ if (n === overlay || n.contains(overlay) || n.tagName === "SCRIPT" || n.tagName === "STYLE") continue;
3322
+ if (on) { n.setAttribute("inert", ""); n.setAttribute("aria-hidden", "true"); }
3323
+ else { n.removeAttribute("inert"); n.removeAttribute("aria-hidden"); }
3324
+ }
3325
+ }
3326
+ }
3327
+
3304
3328
  function showModal(overlay, modal) {
3305
- // **下地を触れなくする。** 無いと Tab がモーダルの外へ抜け、読み上げも
3306
- // 背後の一覧を読み続ける(モーダルを開いた意味が無くなる)
3307
- var behind = app.querySelector(".datasources");
3308
- if (behind) { behind.setAttribute("inert", ""); behind.setAttribute("aria-hidden", "true"); }
3309
3329
  app.appendChild(overlay);
3310
- var safe = modal.querySelector(".modal-actions button, .modal-cancel");
3330
+ setBackdropInert(overlay, true);
3331
+ // **✕ を先に探す。** 「閉じる」は一覧の最後尾にあり、focus() は対象まで
3332
+ // スクロールするので、開いた瞬間にモーダルが最下部まで飛ぶ(見出しも一覧の
3333
+ // 先頭も見えない)。✕ は見出し行=先頭で、押しても何も壊れない
3334
+ var safe = modal.querySelector(".modal-x, .modal-actions button, .modal-cancel");
3311
3335
  if (!safe) {
3312
3336
  // 閉じるボタンが無い形のモーダル用。見出しに載せて読み上げの起点にする
3313
3337
  modal.setAttribute("tabindex", "-1");
@@ -3393,7 +3417,7 @@ ${capabilityState.toString()}
3393
3417
  nameInput.addEventListener("input", update);
3394
3418
 
3395
3419
  // 閉じる経路(Escape・背景クリック)は makeModal 側に用意されている
3396
- cancelBtn.addEventListener("click", mk.close);
3420
+ cancelBtn.addEventListener("click", function () { mk.close(); });
3397
3421
  submitBtn.addEventListener("click", function () { submitIntake(kind, selectedFiles, nameInput.value, status, submitBtn); });
3398
3422
  }
3399
3423
  async function submitIntake(kind, files, submitter, status, submitBtn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.60",
3
+ "version": "0.4.62",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,