aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/components/SanpiTable.astro
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/SanpiTable.astro')
-rw-r--r--src/components/SanpiTable.astro96
1 files changed, 96 insertions, 0 deletions
diff --git a/src/components/SanpiTable.astro b/src/components/SanpiTable.astro
new file mode 100644
index 0000000..31c8678
--- /dev/null
+++ b/src/components/SanpiTable.astro
@@ -0,0 +1,96 @@
+---
+// 賛否一覧(/sanpi/)の表をスマートフォンでも読みやすくする。
+// 表そのものは Markdown で書かれているため、ここでは目印の要素と、
+// 実行時に表を加工するスクリプトだけを提供する(見た目は custom.css)。
+---
+
+<div class="sanpi-marker" hidden aria-hidden="true"></div>
+
+<script>
+ // マウスがある環境でのみホバー表示を有効にする(タップ端末では
+ // タップがホバーとして扱われ、ツールチップが残るのを避けるため)。
+ const CAN_HOVER = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
+
+ function enhanceSanpiTables() {
+ const marker = document.querySelector(".sanpi-marker");
+ if (!marker) return;
+ const content = marker.closest(".sl-markdown-content");
+ if (!content) return;
+
+ let tip = null;
+ const hideTip = () => {
+ if (tip) tip.hidden = true;
+ };
+ const showTip = (el) => {
+ if (!tip) {
+ tip = document.createElement("div");
+ tip.className = "sanpi-tip";
+ tip.setAttribute("role", "tooltip");
+ document.body.appendChild(tip);
+ }
+ tip.textContent = el.dataset.full || el.textContent.trim();
+ tip.hidden = false;
+ tip.style.left = "0px";
+ tip.style.top = "0px";
+ const r = el.getBoundingClientRect();
+ const tr = tip.getBoundingClientRect();
+ const left = Math.min(
+ Math.max(8, r.left),
+ Math.max(8, window.innerWidth - tr.width - 8),
+ );
+ let top = r.top - tr.height - 8;
+ if (top < 8) top = r.bottom + 8;
+ tip.style.left = `${left}px`;
+ tip.style.top = `${top}px`;
+ };
+
+ content.querySelectorAll("table").forEach((table) => {
+ // 各行の先頭セル(議案名)を、省略表示できるようにくるむ。
+ table.querySelectorAll("tbody tr").forEach((tr) => {
+ const cell = tr.querySelector("td");
+ if (!cell || cell.querySelector(".sanpi-title")) return;
+
+ const span = document.createElement("span");
+ span.className = "sanpi-title";
+ while (cell.firstChild) span.appendChild(cell.firstChild);
+ cell.appendChild(span);
+
+ const full = span.textContent.trim();
+ span.dataset.full = full;
+ span.tabIndex = 0;
+ span.setAttribute("role", "button");
+ span.setAttribute("aria-expanded", "false");
+ span.setAttribute("aria-label", `${full}(タップで全文を表示)`);
+
+ const toggle = () => {
+ hideTip();
+ const open = cell.classList.toggle("sanpi-open");
+ span.setAttribute("aria-expanded", String(open));
+ };
+
+ if (CAN_HOVER) {
+ span.addEventListener("mouseenter", () => showTip(span));
+ span.addEventListener("mouseleave", hideTip);
+ span.addEventListener("focus", () => showTip(span));
+ span.addEventListener("blur", hideTip);
+ }
+ span.addEventListener("click", toggle);
+ span.addEventListener("keydown", (event) => {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ toggle();
+ }
+ });
+ });
+ });
+
+ window.addEventListener("scroll", hideTip, { passive: true });
+ window.addEventListener("resize", hideTip);
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", enhanceSanpiTables);
+ } else {
+ enhanceSanpiTables();
+ }
+</script>