Refactor mobile column actions to use the shared modal, refs #2790

This commit is contained in:
Simon Willison 2026-09-17 13:28:24 -07:00
commit 17b19b4d27
3 changed files with 9 additions and 121 deletions

View file

@ -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;

View file

@ -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) {
<div class="list-wrap mobile-column-list"></div>
<div class="modal-footer">
<span class="footer-info">Tap a column to reveal actions.</span>
<button type="button" class="btn btn-ghost mobile-column-actions-done">Done</button>
<button type="button" class="modal-btn modal-btn-ghost mobile-column-actions-done">Done</button>
</div>
`;
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 () {

View file

@ -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