Column
@@ -3986,18 +3928,19 @@ function ensureTableAlterDialog(manager) {
-
+
`;
- document.body.appendChild(dialog);
+ document.body.appendChild(modal);
tableAlterDialogState = {
+ modal: modal,
dialog: dialog,
form: dialog.querySelector(".table-alter-form"),
title: dialog.querySelector(".modal-title"),
@@ -4012,8 +3955,6 @@ function ensureTableAlterDialog(manager) {
dropButton: dialog.querySelector(".table-alter-drop"),
cancelButton: dialog.querySelector(".table-alter-cancel"),
saveButton: dialog.querySelector(".table-alter-save"),
- currentButton: null,
- shouldRestoreFocus: true,
isSaving: false,
initialSignature: "",
originalTableName: "",
@@ -4055,7 +3996,7 @@ function ensureTableAlterDialog(manager) {
});
tableAlterDialogState.cancelButton.addEventListener("click", function () {
- closeTableAlterDialog(tableAlterDialogState);
+ modal.requestClose("cancel");
});
tableAlterDialogState.dropButton.addEventListener("click", function () {
@@ -4076,36 +4017,17 @@ function ensureTableAlterDialog(manager) {
}
});
- dialog.addEventListener("click", function (ev) {
- if (ev.target === dialog) {
- closeTableAlterDialogIfConfirmed(tableAlterDialogState);
- }
- });
-
- dialog.addEventListener("keydown", function (ev) {
- if (ev.key !== "Escape") {
- return;
- }
- ev.preventDefault();
- closeTableAlterDialogIfConfirmed(tableAlterDialogState);
- });
-
- dialog.addEventListener("cancel", function (ev) {
- ev.preventDefault();
- closeTableAlterDialogIfConfirmed(tableAlterDialogState);
- });
+ modal.beforeClose = function (source) {
+ return (
+ source === "cancel" ||
+ confirmDiscardTableAlterChanges(tableAlterDialogState)
+ );
+ };
dialog.addEventListener("close", function () {
var state = tableAlterDialogState;
clearTableAlterDialogError(state);
setTableAlterDialogSaving(state, false);
- if (
- state.shouldRestoreFocus &&
- state.currentButton &&
- document.contains(state.currentButton)
- ) {
- state.currentButton.focus();
- }
});
return tableAlterDialogState;
@@ -4126,8 +4048,7 @@ function openTableAlterDialog(button, manager) {
menu.open = false;
}
state.manager = manager;
- state.currentButton = button;
- state.shouldRestoreFocus = true;
+
state.title.textContent = "Alter table " + data.tableName;
clearTableAlterDialogError(state);
resetTableAlterDialog(state, data);
@@ -4137,9 +4058,7 @@ function openTableAlterDialog(button, manager) {
tableAlterForeignKeyTargetsUrl(),
{ filterByType: false },
);
- if (!state.dialog.open) {
- state.dialog.showModal();
- }
+ state.modal.show({ returnFocusTo: button });
var firstName = state.columnList.querySelector(".table-alter-column-name");
if (firstName) {
firstName.focus();
@@ -4442,7 +4361,8 @@ function ensureRowDeleteDialog(manager) {
return null;
}
- var dialog = document.createElement("dialog");
+ var modal = DatasetteModal.create();
+ var dialog = modal.dialog;
dialog.id = ROW_DELETE_DIALOG_ID;
dialog.className = "row-delete-dialog";
dialog.setAttribute("aria-labelledby", "row-delete-title");
@@ -4454,13 +4374,14 @@ function ensureRowDeleteDialog(manager) {
Delete row ?
`;
- document.body.appendChild(dialog);
+ document.body.appendChild(modal);
rowDeleteDialogState = {
+ modal: modal,
dialog: dialog,
title: dialog.querySelector(".modal-title"),
message: dialog.querySelector(".row-delete-message"),
@@ -4473,21 +4394,10 @@ function ensureRowDeleteDialog(manager) {
currentPkPath: null,
manager: manager,
isBusy: false,
- shouldRestoreFocus: true,
};
rowDeleteDialogState.cancelButton.addEventListener("click", function () {
- if (!rowDeleteDialogState.isBusy) {
- rowDeleteDialogState.shouldRestoreFocus = true;
- dialog.close();
- }
- });
-
- dialog.addEventListener("click", function (ev) {
- if (ev.target === dialog && !rowDeleteDialogState.isBusy) {
- rowDeleteDialogState.shouldRestoreFocus = true;
- dialog.close();
- }
+ modal.requestClose("cancel");
});
dialog.addEventListener("keydown", function (ev) {
@@ -4499,25 +4409,6 @@ function ensureRowDeleteDialog(manager) {
if (!rowDeleteDialogState.isBusy) {
rowDeleteDialogState.confirmButton.click();
}
- return;
- }
- if (ev.key !== "Escape") {
- return;
- }
- if (rowDeleteDialogState.isBusy) {
- ev.preventDefault();
- return;
- }
- ev.preventDefault();
- rowDeleteDialogState.shouldRestoreFocus = true;
- dialog.close();
- });
-
- dialog.addEventListener("cancel", function (ev) {
- if (rowDeleteDialogState.isBusy) {
- ev.preventDefault();
- } else {
- rowDeleteDialogState.shouldRestoreFocus = true;
}
});
@@ -4525,13 +4416,6 @@ function ensureRowDeleteDialog(manager) {
var state = rowDeleteDialogState;
clearRowDeleteDialogError(state);
setRowDeleteDialogBusy(state, false);
- if (
- state.shouldRestoreFocus &&
- state.currentButton &&
- document.contains(state.currentButton)
- ) {
- state.currentButton.focus();
- }
});
rowDeleteDialogState.confirmButton.addEventListener(
@@ -4558,8 +4442,7 @@ function ensureRowDeleteDialog(manager) {
throw rowMutationRequestError(response, data);
}
if (data && data.redirect) {
- state.shouldRestoreFocus = false;
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
location.href = data.redirect;
return;
}
@@ -4571,8 +4454,7 @@ function ensureRowDeleteDialog(manager) {
var statusMessage = state.currentPkPath
? "Deleted row " + state.currentPkPath + "."
: "Deleted row.";
- state.shouldRestoreFocus = false;
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
state.currentRow.remove();
showRowMutationStatus(state.manager, statusMessage, false);
if (focusTarget && document.contains(focusTarget)) {
@@ -4601,11 +4483,9 @@ function openRowDeleteDialog(button, manager) {
}
state.manager = manager;
- state.currentButton = button;
state.currentRow = row;
state.currentDeleteUrl = rowDeleteUrl(row);
state.currentPkPath = rowDisplayLabel(row);
- state.shouldRestoreFocus = true;
clearRowDeleteDialogError(state);
setRowDeleteDialogBusy(state, false);
@@ -4617,9 +4497,7 @@ function openRowDeleteDialog(button, manager) {
);
state.rowId.textContent = state.currentPkPath || "this row";
- if (!state.dialog.open) {
- state.dialog.showModal();
- }
+ state.modal.show({ returnFocusTo: button });
state.confirmButton.focus();
}
@@ -5694,6 +5572,7 @@ function setRowEditDialogLoading(state, isLoading) {
function setRowEditDialogSaving(state, isSaving) {
state.isSaving = isSaving;
+ state.modal.busy = isSaving;
updateRowEditDialogButtons(state);
}
@@ -5911,18 +5790,6 @@ function confirmDiscardRowEditChanges(state) {
return window.confirm(message);
}
-function closeRowEditDialogIfConfirmed(state) {
- if (!state || state.isSaving) {
- return false;
- }
- if (!confirmDiscardRowEditChanges(state)) {
- return false;
- }
- state.shouldRestoreFocus = true;
- state.dialog.close();
- return true;
-}
-
function setRowInsertDialogTitle(state) {
var insertData = tableInsertData() || {};
var title = rowEditIsMultipleInsert(state)
@@ -6748,38 +6615,6 @@ async function insertBulkPreviewRows(state) {
}
}
-function scheduleCloseRowEditDialogIfConfirmed(state) {
- // Fix for an issue in Safari where hitting Esc would show
- // the confirm() prompt asking if state should be discarded
- // but the Esc key press would then cancel that dialog too.
- // Wait for keyup, then move the confirm() to a fresh timer tick.
- if (!state || state.isSaving || state.isClosePending) {
- return false;
- }
- if (!rowEditDialogHasChanges(state)) {
- state.shouldRestoreFocus = true;
- state.dialog.close();
- return true;
- }
- state.isClosePending = true;
- var closeAfterKeyup = function () {
- if (!state.isClosePending) {
- return;
- }
- state.isClosePending = false;
- closeRowEditDialogIfConfirmed(state);
- };
- var onKeyup = function (ev) {
- if (ev.key !== "Escape") {
- return;
- }
- document.removeEventListener("keyup", onKeyup, true);
- setTimeout(closeAfterKeyup, 0);
- };
- document.addEventListener("keyup", onKeyup, true);
- return true;
-}
-
function findDataRowElement(root, rowId) {
var elements = root.querySelectorAll("[data-row]");
for (var i = 0; i < elements.length; i += 1) {
@@ -6869,9 +6704,8 @@ async function saveRowEditDialog(state) {
}
var formValues = collectRowFormValues(state);
if (state.mode === "edit" && !Object.keys(formValues).length) {
- state.shouldRestoreFocus = true;
hideRowMutationStatus();
- state.dialog.close();
+ state.modal.close();
return;
}
var payload =
@@ -6904,9 +6738,8 @@ async function saveRowEditDialog(state) {
insertedRowData,
insertData.primaryKeys || [],
);
- state.shouldRestoreFocus = false;
if (!insertedRowId) {
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
var missingIdStatus = showRowMutationStatus(
state.manager,
"Inserted row. Refresh the page to see it.",
@@ -6922,7 +6755,7 @@ async function saveRowEditDialog(state) {
try {
insertedRow = await fetchUpdatedRowElement(state);
} catch (_error) {
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
var refreshFailedStatus = showRowMutationStatus(
state.manager,
"Inserted row, but could not refresh the table row. Refresh the page to see it.",
@@ -6937,7 +6770,7 @@ async function saveRowEditDialog(state) {
rowTitleLabel(insertedRow),
);
var addedRow = addInsertedRowToPage(insertedRow);
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
showRowMutationStatus(state.manager, insertedStatusMessage, false);
if (addedRow) {
var insertedFocusTarget =
@@ -6946,7 +6779,7 @@ async function saveRowEditDialog(state) {
insertedFocusTarget.focus();
}
} else {
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
var filteredStatus = showRowMutationStatus(
state.manager,
"Inserted row. It does not match the current filters.",
@@ -6958,8 +6791,7 @@ async function saveRowEditDialog(state) {
}
if (isRowPage()) {
- state.shouldRestoreFocus = false;
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
location.reload();
return;
}
@@ -6995,8 +6827,7 @@ async function saveRowEditDialog(state) {
);
}
- state.shouldRestoreFocus = false;
- state.dialog.close();
+ state.modal.close({ restoreFocus: false });
if (focusTarget && document.contains(focusTarget)) {
focusTarget.focus();
}
@@ -7140,7 +6971,8 @@ function ensureRowEditDialog(manager) {
return null;
}
- var dialog = document.createElement("dialog");
+ var modal = DatasetteModal.create();
+ var dialog = modal.dialog;
dialog.id = ROW_EDIT_DIALOG_ID;
dialog.className = "row-edit-dialog";
dialog.setAttribute("aria-labelledby", "row-edit-title");
@@ -7152,8 +6984,8 @@ function ensureRowEditDialog(manager) {
Loading row...
-
-
+
+
-
+
You can paste the template into Google Sheets or Excel.Paste into Google Sheets or Excel
@@ -7183,14 +7015,15 @@ function ensureRowEditDialog(manager) {
`;
- document.body.appendChild(dialog);
+ document.body.appendChild(modal);
rowEditDialogState = {
+ modal: modal,
dialog: dialog,
form: dialog.querySelector(".row-edit-form"),
title: dialog.querySelector(".modal-title"),
@@ -7221,7 +7054,6 @@ function ensureRowEditDialog(manager) {
singleInsertLink: dialog.querySelector(".row-edit-single-insert"),
cancelButton: dialog.querySelector(".row-edit-cancel"),
saveButton: dialog.querySelector(".row-edit-save"),
- currentButton: null,
currentRow: null,
currentRowId: null,
currentPkPath: null,
@@ -7249,9 +7081,7 @@ function ensureRowEditDialog(manager) {
manager: manager,
isLoading: false,
isSaving: false,
- isClosePending: false,
hasLoaded: false,
- shouldRestoreFocus: true,
};
rowEditDialogState.form.addEventListener("submit", function (ev) {
@@ -7271,10 +7101,7 @@ function ensureRowEditDialog(manager) {
rowEditDialogState.bulkInsertTextarea.focus();
return;
}
- if (!rowEditDialogState.isSaving) {
- rowEditDialogState.shouldRestoreFocus = true;
- dialog.close();
- }
+ modal.requestClose("cancel");
});
rowEditDialogState.bulkInsertLink.addEventListener("click", function (ev) {
@@ -7393,31 +7220,17 @@ function ensureRowEditDialog(manager) {
},
);
- dialog.addEventListener("click", function (ev) {
- if (ev.target === dialog) {
- closeRowEditDialogIfConfirmed(rowEditDialogState);
- }
- });
-
- dialog.addEventListener("keydown", function (ev) {
- if (ev.key !== "Escape") {
- return;
- }
- ev.preventDefault();
- scheduleCloseRowEditDialogIfConfirmed(rowEditDialogState);
- });
-
- dialog.addEventListener("cancel", function (ev) {
- ev.preventDefault();
- scheduleCloseRowEditDialogIfConfirmed(rowEditDialogState);
- });
+ modal.beforeClose = function (source) {
+ return (
+ source === "cancel" || confirmDiscardRowEditChanges(rowEditDialogState)
+ );
+ };
dialog.addEventListener("close", function () {
var state = rowEditDialogState;
var shouldReloadOnClose = state.shouldReloadOnClose;
var redirectOnCloseUrl = state.redirectOnCloseUrl;
state.loadId += 1;
- state.isClosePending = false;
state.bulkInsertLiveValidationError = null;
state.shouldReloadOnClose = false;
state.redirectOnCloseUrl = null;
@@ -7430,13 +7243,6 @@ function ensureRowEditDialog(manager) {
destroyRowEditFields(state);
setRowEditDialogLoading(state, false);
setRowEditDialogSaving(state, false);
- if (
- state.shouldRestoreFocus &&
- state.currentButton &&
- document.contains(state.currentButton)
- ) {
- state.currentButton.focus();
- }
if (shouldReloadOnClose) {
if (redirectOnCloseUrl) {
location.href = redirectOnCloseUrl;
@@ -7461,7 +7267,6 @@ async function openRowEditDialog(button, manager) {
state.manager = manager;
state.mode = "edit";
- state.currentButton = button;
state.currentRow = row;
state.currentRowId = row.getAttribute("data-row") || "";
state.currentPkPath = rowDisplayLabel(row);
@@ -7478,7 +7283,7 @@ async function openRowEditDialog(button, manager) {
} else {
state.form.removeAttribute("action");
}
- state.shouldRestoreFocus = true;
+
state.hasLoaded = false;
state.loadId += 1;
var loadId = state.loadId;
@@ -7497,9 +7302,7 @@ async function openRowEditDialog(button, manager) {
state.summary.textContent = "";
syncRowEditInsertModeUi(state);
- if (!state.dialog.open) {
- state.dialog.showModal();
- }
+ state.modal.show({ returnFocusTo: button });
state.cancelButton.focus();
try {
@@ -7539,7 +7342,6 @@ function openRowInsertDialog(button, manager) {
state.manager = manager;
state.mode = "insert";
- state.currentButton = button;
state.currentRow = null;
state.currentRowId = null;
state.currentPkPath = null;
@@ -7554,7 +7356,7 @@ function openRowInsertDialog(button, manager) {
state.shouldReloadOnClose = false;
state.redirectOnCloseUrl = null;
resetBulkInsertPreview(state);
- state.shouldRestoreFocus = true;
+
state.hasLoaded = false;
state.loadId += 1;
@@ -7576,9 +7378,7 @@ function openRowInsertDialog(button, manager) {
state.summary.textContent = "";
syncRowEditInsertModeUi(state);
- if (!state.dialog.open) {
- state.dialog.showModal();
- }
+ state.modal.show({ returnFocusTo: button });
renderRowInsertFields(state, insertData);
}
diff --git a/datasette/static/mobile-column-actions.js b/datasette/static/mobile-column-actions.js
index a386b1fc..e94ee865 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);
@@ -75,13 +76,13 @@ function initMobileColumnActions(manager) {
Column actions
`;
- 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({ returnFocusTo: 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/datasette/static/modal.js b/datasette/static/modal.js
new file mode 100644
index 00000000..ca9ab353
--- /dev/null
+++ b/datasette/static/modal.js
@@ -0,0 +1,167 @@
+// Shared lifecycle for native modal dialogs.
+(() => {
+ class DatasetteModal extends HTMLElement {
+ constructor() {
+ super();
+ this.beforeClose = null;
+ this._busy = false;
+ this._restoreFocus = true;
+ this._returnFocusTo = null;
+ this._escapeCleanup = null;
+ this._escapeTimer = null;
+ }
+
+ static create() {
+ const modal = document.createElement("datasette-modal");
+ modal.appendChild(document.createElement("dialog"));
+ return modal;
+ }
+
+ get dialog() {
+ return this.querySelector(":scope > dialog");
+ }
+
+ get busy() {
+ return this._busy;
+ }
+
+ set busy(value) {
+ this._busy = !!value;
+ if (this.dialog) {
+ this.dialog.setAttribute("aria-busy", String(this._busy));
+ }
+ }
+
+ connectedCallback() {
+ const dialog = this.dialog;
+ if (!dialog) return;
+ dialog.classList.add("datasette-modal");
+ this._listeners?.abort();
+ this._listeners = new AbortController();
+ const options = { signal: this._listeners.signal };
+ let backdropPointerDown = false;
+ const outside = (event) => {
+ const rect = dialog.getBoundingClientRect();
+ return (
+ event.target === dialog &&
+ (event.clientX < rect.left ||
+ event.clientX > rect.right ||
+ event.clientY < rect.top ||
+ event.clientY > rect.bottom)
+ );
+ };
+ dialog.addEventListener(
+ "pointerdown",
+ (event) => {
+ backdropPointerDown = outside(event);
+ },
+ options,
+ );
+ dialog.addEventListener(
+ "click",
+ (event) => {
+ if (backdropPointerDown && outside(event))
+ this.requestClose("backdrop");
+ backdropPointerDown = false;
+ },
+ options,
+ );
+ dialog.addEventListener(
+ "keydown",
+ (event) => {
+ if (event.key !== "Escape" || event.defaultPrevented) return;
+ // A nested native dialog or plugin picker gets first refusal.
+ if (event.target.closest("dialog") !== dialog) return;
+ event.preventDefault();
+ if (this.busy || this._escapeCleanup || this._escapeTimer !== null)
+ return;
+ // Safari can otherwise use this Escape press to cancel confirm() too.
+ // Only keyboard dismissals wait for keyup; native cancel events needn't.
+ const onKeyup = (up) => {
+ if (up.key !== "Escape") return;
+ this._escapeCleanup();
+ this._escapeCleanup = null;
+ this._escapeTimer = setTimeout(() => {
+ this._escapeTimer = null;
+ this.requestClose("escape");
+ }, 0);
+ };
+ this.ownerDocument.addEventListener("keyup", onKeyup, true);
+ this._escapeCleanup = () =>
+ this.ownerDocument.removeEventListener("keyup", onKeyup, true);
+ },
+ options,
+ );
+ dialog.addEventListener(
+ "cancel",
+ (event) => {
+ if (event.target !== dialog) return;
+ event.preventDefault();
+ if (!this._escapeCleanup && this._escapeTimer === null)
+ this.requestClose("escape");
+ },
+ options,
+ );
+ dialog.addEventListener(
+ "close",
+ (event) => {
+ if (event.target !== dialog || dialog.open) return;
+ this._clearPendingClose();
+ this.busy = false;
+ if (this._restoreFocus && this._returnFocusTo?.isConnected) {
+ // Menu actions may have become hidden while the dialog was open.
+ const details = this._returnFocusTo.closest("details:not([open])");
+ const target =
+ details?.querySelector("summary") || this._returnFocusTo;
+ target.focus({ preventScroll: true });
+ }
+ this._returnFocusTo = null;
+ },
+ options,
+ );
+ }
+
+ disconnectedCallback() {
+ this._listeners?.abort();
+ this._clearPendingClose();
+ this._returnFocusTo = null;
+ if (this.dialog?.open) this.dialog.close();
+ this.busy = false;
+ }
+
+ _clearPendingClose() {
+ this._escapeCleanup?.();
+ this._escapeCleanup = null;
+ clearTimeout(this._escapeTimer);
+ this._escapeTimer = null;
+ }
+
+ show({ returnFocusTo, initialFocus } = {}) {
+ const dialog = this.dialog;
+ if (!dialog.open) {
+ this._clearPendingClose();
+ this._returnFocusTo = returnFocusTo || this.ownerDocument.activeElement;
+ this._restoreFocus = true;
+ dialog.showModal();
+ }
+ if (typeof initialFocus === "function") initialFocus();
+ else initialFocus?.focus();
+ }
+
+ requestClose(source = "cancel") {
+ if (!this.dialog.open || this.busy) return false;
+ if (this.beforeClose && this.beforeClose(source) === false) return false;
+ this.close();
+ return true;
+ }
+
+ close({ restoreFocus = true } = {}) {
+ this._clearPendingClose();
+ this._restoreFocus = restoreFocus;
+ this.dialog.close();
+ }
+ }
+
+ customElements.define("datasette-modal", DatasetteModal);
+ window.DatasetteModal = DatasetteModal;
+})();
diff --git a/datasette/static/navigation-search.js b/datasette/static/navigation-search.js
index ec2d23d8..df2516f9 100644
--- a/datasette/static/navigation-search.js
+++ b/datasette/static/navigation-search.js
@@ -10,277 +10,22 @@ class NavigationSearch extends HTMLElement {
this.recentHeadingId = `navigation-search-recent-${this.instanceId}`;
this.statusId = `navigation-search-status-${this.instanceId}`;
this.titleId = `navigation-search-title-${this.instanceId}`;
- this.attachShadow({ mode: "open" });
this.selectedIndex = -1;
this.matches = [];
this.renderedMatches = [];
this.debounceTimer = null;
- this.restoreFocusTarget = null;
- this.shouldRestoreFocus = true;
+ }
+ connectedCallback() {
+ if (this._initialized) return;
+ this._initialized = true;
this.render();
this.setupEventListeners();
}
render() {
- this.shadowRoot.innerHTML = `
-
-
-