mirror of
https://github.com/simonw/datasette.git
synced 2026-09-18 22:44:07 +02:00
Rename modal trigger option to returnFocusTo, refs #2790
This commit is contained in:
parent
ad1d0bf6f9
commit
3e9356dde0
6 changed files with 30 additions and 29 deletions
|
|
@ -2384,7 +2384,7 @@ function openTableCreateDialog(button, manager) {
|
|||
clearTableCreateDialogError(state);
|
||||
resetTableCreateDialog(state);
|
||||
loadTableCreateForeignKeyTargets(state);
|
||||
state.modal.show({ trigger: button });
|
||||
state.modal.show({ returnFocusTo: button });
|
||||
state.tableName.focus();
|
||||
}
|
||||
|
||||
|
|
@ -4058,7 +4058,7 @@ function openTableAlterDialog(button, manager) {
|
|||
tableAlterForeignKeyTargetsUrl(),
|
||||
{ filterByType: false },
|
||||
);
|
||||
state.modal.show({ trigger: button });
|
||||
state.modal.show({ returnFocusTo: button });
|
||||
var firstName = state.columnList.querySelector(".table-alter-column-name");
|
||||
if (firstName) {
|
||||
firstName.focus();
|
||||
|
|
@ -4497,7 +4497,7 @@ function openRowDeleteDialog(button, manager) {
|
|||
);
|
||||
state.rowId.textContent = state.currentPkPath || "this row";
|
||||
|
||||
state.modal.show({ trigger: button });
|
||||
state.modal.show({ returnFocusTo: button });
|
||||
state.confirmButton.focus();
|
||||
}
|
||||
|
||||
|
|
@ -7302,7 +7302,7 @@ async function openRowEditDialog(button, manager) {
|
|||
state.summary.textContent = "";
|
||||
syncRowEditInsertModeUi(state);
|
||||
|
||||
state.modal.show({ trigger: button });
|
||||
state.modal.show({ returnFocusTo: button });
|
||||
state.cancelButton.focus();
|
||||
|
||||
try {
|
||||
|
|
@ -7378,7 +7378,7 @@ function openRowInsertDialog(button, manager) {
|
|||
state.summary.textContent = "";
|
||||
syncRowEditInsertModeUi(state);
|
||||
|
||||
state.modal.show({ trigger: button });
|
||||
state.modal.show({ returnFocusTo: button });
|
||||
renderRowInsertFields(state, insertData);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ function initMobileColumnActions(manager) {
|
|||
if (!renderDialog()) {
|
||||
return;
|
||||
}
|
||||
modal.show({ trigger: triggerButton });
|
||||
modal.show({ returnFocusTo: triggerButton });
|
||||
triggerButton.setAttribute("aria-expanded", "true");
|
||||
var focusTarget =
|
||||
dialog.querySelector(".mobile-column-top-action") ||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
this.beforeClose = null;
|
||||
this._busy = false;
|
||||
this._restoreFocus = true;
|
||||
this._trigger = null;
|
||||
this._returnFocusTo = null;
|
||||
this._escapeCleanup = null;
|
||||
this._escapeTimer = null;
|
||||
}
|
||||
|
|
@ -108,13 +108,14 @@
|
|||
if (event.target !== dialog || dialog.open) return;
|
||||
this._clearPendingClose();
|
||||
this.busy = false;
|
||||
if (this._restoreFocus && this._trigger?.isConnected) {
|
||||
if (this._restoreFocus && this._returnFocusTo?.isConnected) {
|
||||
// Menu actions may have become hidden while the dialog was open.
|
||||
const details = this._trigger.closest("details:not([open])");
|
||||
const target = details?.querySelector("summary") || this._trigger;
|
||||
const details = this._returnFocusTo.closest("details:not([open])");
|
||||
const target =
|
||||
details?.querySelector("summary") || this._returnFocusTo;
|
||||
target.focus({ preventScroll: true });
|
||||
}
|
||||
this._trigger = null;
|
||||
this._returnFocusTo = null;
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
|
@ -123,7 +124,7 @@
|
|||
disconnectedCallback() {
|
||||
this._listeners?.abort();
|
||||
this._clearPendingClose();
|
||||
this._trigger = null;
|
||||
this._returnFocusTo = null;
|
||||
if (this.dialog?.open) this.dialog.close();
|
||||
this.busy = false;
|
||||
}
|
||||
|
|
@ -135,11 +136,11 @@
|
|||
this._escapeTimer = null;
|
||||
}
|
||||
|
||||
show({ trigger, initialFocus } = {}) {
|
||||
show({ returnFocusTo, initialFocus } = {}) {
|
||||
const dialog = this.dialog;
|
||||
if (!dialog.open) {
|
||||
this._clearPendingClose();
|
||||
this._trigger = trigger || this.ownerDocument.activeElement;
|
||||
this._returnFocusTo = returnFocusTo || this.ownerDocument.activeElement;
|
||||
this._restoreFocus = true;
|
||||
dialog.showModal();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -570,11 +570,11 @@ class NavigationSearch extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
openMenu(trigger) {
|
||||
openMenu(returnFocusTo) {
|
||||
const input = this.querySelector(".search-input");
|
||||
|
||||
this.querySelector("datasette-modal").show({
|
||||
trigger,
|
||||
returnFocusTo,
|
||||
initialFocus: input,
|
||||
});
|
||||
this.setNavigationTriggersExpanded(true);
|
||||
|
|
|
|||
|
|
@ -493,13 +493,13 @@ This example adds a button that opens a reusable dialog:
|
|||
.. code-block:: javascript
|
||||
|
||||
document.addEventListener("datasette_init", () => {
|
||||
const trigger = document.createElement("button");
|
||||
trigger.type = "button";
|
||||
trigger.textContent = "Open example dialog";
|
||||
const openButton = document.createElement("button");
|
||||
openButton.type = "button";
|
||||
openButton.textContent = "Open example dialog";
|
||||
// Indicate that this button opens a dialog:
|
||||
trigger.setAttribute("aria-haspopup", "dialog");
|
||||
openButton.setAttribute("aria-haspopup", "dialog");
|
||||
// Identify which dialog it controls:
|
||||
trigger.setAttribute("aria-controls", "my-plugin-dialog");
|
||||
openButton.setAttribute("aria-controls", "my-plugin-dialog");
|
||||
|
||||
const modal = DatasetteModal.create();
|
||||
const dialog = modal.dialog;
|
||||
|
|
@ -522,12 +522,12 @@ This example adds a button that opens a reusable dialog:
|
|||
closeButton.addEventListener("click", () => {
|
||||
modal.requestClose("cancel");
|
||||
});
|
||||
trigger.addEventListener("click", () => {
|
||||
modal.show({ trigger, initialFocus: closeButton });
|
||||
openButton.addEventListener("click", () => {
|
||||
modal.show({ returnFocusTo: openButton, initialFocus: closeButton });
|
||||
});
|
||||
|
||||
document.body.append(modal);
|
||||
document.querySelector("section.content").append(trigger);
|
||||
document.querySelector("section.content").append(openButton);
|
||||
});
|
||||
|
||||
The example uses ``innerHTML`` for a static template. Use ``textContent`` when inserting database values or other user-supplied text. Give each dialog and its title unique IDs, and use ``aria-labelledby`` or ``aria-label`` to provide an accessible name.
|
||||
|
|
@ -535,8 +535,8 @@ The example uses ``innerHTML`` for a static template. Use ``textContent`` when i
|
|||
Opening and closing
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``modal.show({trigger, initialFocus})``
|
||||
Opens the native dialog using ``showModal()``. Both options are optional. ``trigger`` is the element to return focus to when the dialog closes; it defaults to the currently focused element. ``initialFocus`` can be an element to focus or a function that focuses a custom control. Without it, the browser chooses initial focus. Calling ``show()`` again while the dialog is open does not change where focus returns when it closes. For example, if an Edit button opened the dialog, focus will still return to that button.
|
||||
``modal.show({returnFocusTo, initialFocus})``
|
||||
Opens the native dialog using ``showModal()``. Both options are optional. ``returnFocusTo`` is the element to return focus to when the dialog closes; it defaults to the element with keyboard focus immediately before the dialog opens, which is not necessarily the element clicked to open it. ``initialFocus`` can be an element to focus or a function that focuses a custom control. Without it, the browser chooses initial focus. Calling ``show()`` again while the dialog is open does not change where focus returns when it closes. For example, if the first call sets ``returnFocusTo`` to an Edit button, focus will still return to that button even if a later call specifies a different element.
|
||||
|
||||
``modal.requestClose(source)``
|
||||
Requests dismissal through the busy-state and ``beforeClose`` guards described below. Returns ``true`` if it closes the dialog, or ``false`` if the dialog is already closed or a guard prevents dismissal. Close and Cancel buttons should use this method.
|
||||
|
|
@ -546,7 +546,7 @@ Opening and closing
|
|||
``modal.close({restoreFocus = true})``
|
||||
Closes the dialog directly, bypassing the guards. Use this after successfully completing an operation. Pass ``restoreFocus: false`` when your code will navigate away or move focus to another element, such as a newly inserted row.
|
||||
|
||||
On normal dismissal, the component restores focus if the trigger is still connected to the document. If the trigger is inside a menu implemented with a closed ``<details>`` element, focus returns to that menu's ``<summary>`` instead.
|
||||
On normal dismissal, the component restores focus if the saved return-focus element is still connected to the document. If that element is inside a menu implemented with a closed ``<details>`` element, focus returns to that menu's ``<summary>`` instead.
|
||||
|
||||
Closing a dialog leaves it in the page so it can be reopened. Listen for the native dialog's ``close`` event to clean up resources such as pending requests or custom fields:
|
||||
|
||||
|
|
|
|||
|
|
@ -1768,7 +1768,7 @@ def test_modal_lifecycle(page, datasette_server):
|
|||
};
|
||||
window.allowClose = false;
|
||||
trigger.onclick = () => testModal.show({
|
||||
trigger, initialFocus: dialog.querySelector('input')
|
||||
returnFocusTo: trigger, initialFocus: dialog.querySelector('input')
|
||||
});
|
||||
dialog.querySelector('button').onclick = () => testModal.requestClose('cancel');
|
||||
}""",
|
||||
|
|
@ -1819,7 +1819,7 @@ def test_modal_lifecycle(page, datasette_server):
|
|||
expect(trigger).to_be_focused()
|
||||
assert page.evaluate("closeSources") == ["escape", "backdrop", "cancel"]
|
||||
|
||||
# Reopening, including an extra show() call, preserves the original trigger.
|
||||
# Reopening, including an extra show() call, preserves the original return-focus target.
|
||||
trigger.click()
|
||||
page.evaluate("testModal.show()")
|
||||
page.keyboard.press("Escape")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue