From 17b19b4d27087a1866b521b9008c29a6dd0cf4ce Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 17 Sep 2026 13:28:24 -0700 Subject: [PATCH] Refactor mobile column actions to use the shared modal, refs #2790 --- datasette/static/app.css | 88 ----------------------- datasette/static/mobile-column-actions.js | 40 +++-------- tests/test_playwright.py | 2 +- 3 files changed, 9 insertions(+), 121 deletions(-) diff --git a/datasette/static/app.css b/datasette/static/app.css index 70150f10..f8f033bb 100644 --- a/datasette/static/app.css +++ b/datasette/static/app.css @@ -982,61 +982,13 @@ p.zero-results { } dialog.mobile-column-actions-dialog { - --ink: #0f0f0f; - --paper: #eef6ff; - --muted: #6b6b6b; - --rule: #d8e6f5; - --accent: #1a56db; - --card: #ffffff; - border: none; - border-radius: var(--modal-border-radius, 0.75rem); - padding: 0; - margin: auto; width: min(420px, calc(100vw - 32px)); - max-width: 95vw; max-height: min(640px, calc(100vh - 32px)); - box-shadow: var(--modal-shadow, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)); - animation: datasette-modal-slide-in var(--modal-animation-duration, 0.2s) ease-out; - overflow: hidden; - font-family: system-ui, -apple-system, sans-serif; - background: var(--card); -} - -dialog.mobile-column-actions-dialog[open] { - display: flex; - flex-direction: column; -} - -dialog.mobile-column-actions-dialog::backdrop { - background: var(--modal-backdrop-bg, rgba(0, 0, 0, 0.5)); - backdrop-filter: var(--modal-backdrop-blur, blur(4px)); - -webkit-backdrop-filter: var(--modal-backdrop-blur, blur(4px)); - animation: datasette-modal-fade-in var(--modal-animation-duration, 0.2s) ease-out; } .mobile-column-actions-dialog .modal-header { padding: 20px 24px 16px; - border-bottom: 1px solid var(--rule); - display: flex; - align-items: center; justify-content: space-between; - gap: 12px; - flex-shrink: 0; -} - -.mobile-column-actions-dialog .modal-title { - font-size: 1rem; - font-weight: 600; - color: var(--ink); -} - -.mobile-column-actions-dialog .modal-meta { - font-family: ui-monospace, monospace; - font-size: 0.7rem; - color: var(--muted); - background: var(--paper); - padding: 3px 9px; - border-radius: 20px; } .mobile-column-actions-dialog .list-wrap { @@ -1169,46 +1121,6 @@ dialog.mobile-column-actions-dialog::backdrop { font-size: 0.85em; } -.mobile-column-actions-dialog .modal-footer { - padding: 14px 20px; - border-top: 1px solid var(--rule); - display: flex; - align-items: center; - gap: 10px; - flex-shrink: 0; - background: var(--paper); -} - -.mobile-column-actions-dialog .footer-info { - flex: 1; - font-family: ui-monospace, monospace; - font-size: 0.68rem; - color: var(--muted); -} - -.mobile-column-actions-dialog .btn { - border: none; - border-radius: 5px; - padding: 9px 20px; - font-size: 0.85rem; - font-weight: 500; - cursor: pointer; - touch-action: manipulation; - font-family: inherit; - transition: background 0.12s; -} - -.mobile-column-actions-dialog .btn-ghost { - background: transparent; - color: var(--muted); - border: 1px solid var(--rule); -} - -.mobile-column-actions-dialog .btn-ghost:hover { - background: var(--rule); - color: var(--ink); -} - dialog.set-column-type-dialog { --ink: #0f0f0f; --paper: #eef6ff; diff --git a/datasette/static/mobile-column-actions.js b/datasette/static/mobile-column-actions.js index a386b1fc..29082d4e 100644 --- a/datasette/static/mobile-column-actions.js +++ b/datasette/static/mobile-column-actions.js @@ -66,7 +66,8 @@ function initMobileColumnActions(manager) { return; } - var dialog = document.createElement("dialog"); + var modal = DatasetteModal.create(); + var dialog = modal.dialog; dialog.className = "mobile-column-actions-dialog"; dialog.id = MOBILE_COLUMN_DIALOG_ID; dialog.setAttribute("aria-labelledby", MOBILE_COLUMN_DIALOG_TITLE_ID); @@ -78,10 +79,10 @@ function initMobileColumnActions(manager) {
`; - document.body.appendChild(dialog); + document.body.appendChild(modal); triggerButton.setAttribute("aria-haspopup", "dialog"); triggerButton.setAttribute("aria-controls", MOBILE_COLUMN_DIALOG_ID); @@ -91,7 +92,6 @@ function initMobileColumnActions(manager) { var listWrap = dialog.querySelector(".mobile-column-list"); var doneButton = dialog.querySelector(".mobile-column-actions-done"); var expandedSectionId = null; - var shouldRestoreFocus = true; function updateExpandedSection() { Array.from(dialog.querySelectorAll(".col-header")).forEach((button) => { @@ -128,16 +128,7 @@ function initMobileColumnActions(manager) { } function closeDialog(options) { - options = options || {}; - shouldRestoreFocus = options.restoreFocus !== false; - if (dialog.open) { - dialog.close(); - } else { - triggerButton.setAttribute("aria-expanded", "false"); - if (shouldRestoreFocus) { - triggerButton.focus(); - } - } + modal.close(options); } function renderDialog() { @@ -166,7 +157,8 @@ function initMobileColumnActions(manager) { topActions.className = "mobile-column-top-actions"; var showAllColumns = document.createElement("a"); - showAllColumns.className = "btn btn-ghost mobile-column-top-action"; + showAllColumns.className = + "modal-btn modal-btn-ghost mobile-column-top-action"; showAllColumns.href = manager.columnActions.showAllColumnsUrl(); showAllColumns.textContent = "Show all columns"; @@ -265,9 +257,7 @@ function initMobileColumnActions(manager) { if (!renderDialog()) { return; } - if (!dialog.open) { - dialog.showModal(); - } + modal.show({ trigger: triggerButton }); triggerButton.setAttribute("aria-expanded", "true"); var focusTarget = dialog.querySelector(".mobile-column-top-action") || @@ -288,22 +278,8 @@ function initMobileColumnActions(manager) { closeDialog(); }); - dialog.addEventListener("click", function (ev) { - if (ev.target === dialog) { - closeDialog(); - } - }); - - dialog.addEventListener("cancel", function (ev) { - ev.preventDefault(); - closeDialog(); - }); - dialog.addEventListener("close", function () { triggerButton.setAttribute("aria-expanded", "false"); - if (shouldRestoreFocus) { - triggerButton.focus(); - } }); window.addEventListener("resize", function () { diff --git a/tests/test_playwright.py b/tests/test_playwright.py index a76bd8ec..bc582c8a 100644 --- a/tests/test_playwright.py +++ b/tests/test_playwright.py @@ -1763,7 +1763,7 @@ def test_modal_lifecycle(page, datasette_server, shadow): @pytest.mark.playwright -@pytest.mark.parametrize("name", ["jump", "columns"]) +@pytest.mark.parametrize("name", ["jump", "columns", "mobile"]) def test_modal_consumers_dismiss_and_restore_focus(page, datasette_server, name): from playwright.sync_api import expect