mirror of
https://github.com/simonw/datasette.git
synced 2026-07-14 03:24:42 +02:00
Add declarative data-modal-cancel attribute to datasette-modal
Clicking any element inside a <datasette-modal> that carries a
data-modal-cancel attribute now calls requestClose("cancel"), so
Cancel buttons no longer need JavaScript wiring. Like other
dismissals this respects the busy property and the closeGuard hook.
The set-column-type, row-delete and create-table dialogs now use the
attribute instead of their own click listeners, and the plugin
documentation example is simplified to match (the regenerated docs
screenshot is unchanged).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
This commit is contained in:
parent
14397ac4c1
commit
32be92fa24
6 changed files with 27 additions and 25 deletions
|
|
@ -11,6 +11,8 @@
|
|||
* - The standard Datasette modal frame: sizing, rounded corners,
|
||||
* backdrop, animations, and optional .modal-header scaffolding
|
||||
* - Close-on-backdrop-click and Escape key handling
|
||||
* - Declarative cancel buttons: clicking any element inside the modal
|
||||
* with a `data-modal-cancel` attribute calls requestClose("cancel")
|
||||
* - A `busy` property that blocks user-initiated dismissal while an
|
||||
* operation is in flight
|
||||
* - A `closeGuard` hook for "discard unsaved changes?" style prompts
|
||||
|
|
@ -526,6 +528,14 @@ datasette-modal .btn:disabled {
|
|||
dialog.addEventListener("click", (ev) => {
|
||||
if (ev.target === dialog) {
|
||||
this.requestClose("backdrop");
|
||||
return;
|
||||
}
|
||||
// Declarative cancel buttons: any element inside the modal
|
||||
// with a data-modal-cancel attribute requests a close
|
||||
var cancelTrigger =
|
||||
ev.target.closest && ev.target.closest("[data-modal-cancel]");
|
||||
if (cancelTrigger && dialog.contains(cancelTrigger)) {
|
||||
this.requestClose("cancel");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1559,7 +1559,7 @@ function ensureTableCreateDialog(manager) {
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ghost table-create-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-ghost table-create-cancel" data-modal-cancel>Cancel</button>
|
||||
<button type="submit" class="btn btn-primary table-create-save">Create table</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -1601,10 +1601,6 @@ function ensureTableCreateDialog(manager) {
|
|||
row.querySelector(".table-create-column-name").focus();
|
||||
});
|
||||
|
||||
tableCreateDialogState.cancelButton.addEventListener("click", function () {
|
||||
closeTableCreateDialogIfConfirmed(tableCreateDialogState);
|
||||
});
|
||||
|
||||
tableCreateDialogState.tableName.addEventListener("input", function () {
|
||||
clearTableCreateDialogError(tableCreateDialogState);
|
||||
});
|
||||
|
|
@ -3640,7 +3636,7 @@ function ensureRowDeleteDialog(manager) {
|
|||
<p class="row-delete-message" id="row-delete-message">Delete row <span class="row-delete-id"></span>?</p>
|
||||
<p class="row-delete-error" role="alert" hidden></p>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ghost row-delete-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-ghost row-delete-cancel" data-modal-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary row-delete-confirm">Delete row</button>
|
||||
</div>
|
||||
`,
|
||||
|
|
@ -3663,10 +3659,6 @@ function ensureRowDeleteDialog(manager) {
|
|||
isBusy: false,
|
||||
};
|
||||
|
||||
rowDeleteDialogState.cancelButton.addEventListener("click", function () {
|
||||
modal.requestClose("cancel");
|
||||
});
|
||||
|
||||
dialog.addEventListener("keydown", function (ev) {
|
||||
if (
|
||||
ev.key === "Enter" &&
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ function ensureSetColumnTypeDialog() {
|
|||
<div class="set-column-type-options"></div>
|
||||
<div class="modal-footer">
|
||||
<span class="footer-info"></span>
|
||||
<button type="button" class="btn btn-ghost set-column-type-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-ghost set-column-type-cancel" data-modal-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary set-column-type-save">Save</button>
|
||||
</div>
|
||||
`,
|
||||
|
|
@ -223,10 +223,6 @@ function ensureSetColumnTypeDialog() {
|
|||
isBusy: false,
|
||||
};
|
||||
|
||||
setColumnTypeDialogState.cancelButton.addEventListener("click", function () {
|
||||
modal.requestClose("cancel");
|
||||
});
|
||||
|
||||
modal.addEventListener("datasette-modal-close", function () {
|
||||
clearSetColumnTypeDialogError(setColumnTypeDialogState);
|
||||
setSetColumnTypeDialogBusy(setColumnTypeDialogState, false);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ document.addEventListener("datasette_init", function (event) {
|
|||
<p style="padding: 16px 24px">Hello from a plugin!</p>
|
||||
<div class="modal-footer">
|
||||
<span class="footer-info"></span>
|
||||
<button type="button" class="btn btn-ghost my-plugin-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-ghost" data-modal-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary my-plugin-save">Save</button>
|
||||
</div>
|
||||
`,
|
||||
|
|
@ -81,9 +81,6 @@ document.addEventListener("datasette_init", function (event) {
|
|||
if (!modal) {
|
||||
return; // Browser does not support <dialog>
|
||||
}
|
||||
modal.dialog
|
||||
.querySelector(".my-plugin-cancel")
|
||||
.addEventListener("click", () => modal.requestClose("cancel"));
|
||||
// Open it later, for example from a button click:
|
||||
// modal.showModal({trigger: button});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ The simplest way to create a modal from a plugin is ``datasetteManager.createMod
|
|||
<p style="padding: 16px 24px">Hello from a plugin!</p>
|
||||
<div class="modal-footer">
|
||||
<span class="footer-info"></span>
|
||||
<button type="button" class="btn btn-ghost my-plugin-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-ghost" data-modal-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary my-plugin-save">Save</button>
|
||||
</div>
|
||||
`,
|
||||
|
|
@ -98,13 +98,12 @@ The simplest way to create a modal from a plugin is ``datasetteManager.createMod
|
|||
if (!modal) {
|
||||
return; // Browser does not support <dialog>
|
||||
}
|
||||
modal.dialog
|
||||
.querySelector(".my-plugin-cancel")
|
||||
.addEventListener("click", () => modal.requestClose("cancel"));
|
||||
// Open it later, for example from a button click:
|
||||
// modal.showModal({trigger: button});
|
||||
});
|
||||
|
||||
The ``data-modal-cancel`` attribute on the Cancel button is a declarative shortcut: clicking any element inside the modal that carries this attribute calls ``modal.requestClose("cancel")``, so Cancel buttons need no JavaScript wiring. Like other dismissals it is blocked while the modal is ``busy`` and consults ``closeGuard``, both described below.
|
||||
|
||||
Calling ``modal.showModal()`` then displays the dialog:
|
||||
|
||||
.. Regenerate this screenshot with docs/generate-datasette-modal-example.sh
|
||||
|
|
@ -146,6 +145,7 @@ The element can also be used declaratively in a template - light DOM children be
|
|||
<datasette-modal dialog-id="my-dialog" modal-title="My dialog">
|
||||
<p>Dialog content</p>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ghost" data-modal-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary">OK</button>
|
||||
</div>
|
||||
</datasette-modal>
|
||||
|
|
@ -171,7 +171,7 @@ Properties, methods and events
|
|||
Closes the modal unconditionally, skipping ``busy`` and ``closeGuard``. Pass ``{restoreFocus: false}`` to leave focus where it is, for example when the page is about to navigate.
|
||||
|
||||
``modal.requestClose(reason)``
|
||||
Asks the modal to close on the user's behalf, respecting ``busy`` and ``closeGuard``. Returns true if the modal closed. Backdrop clicks and the ``Escape`` key call this internally with reasons ``"backdrop"`` and ``"escape"``. Wire Cancel buttons to this method.
|
||||
Asks the modal to close on the user's behalf, respecting ``busy`` and ``closeGuard``. Returns true if the modal closed. Backdrop clicks and the ``Escape`` key call this internally with reasons ``"backdrop"`` and ``"escape"``, and clicking an element with a ``data-modal-cancel`` attribute calls it with ``"cancel"``. Cancel buttons can either carry that attribute or call this method directly.
|
||||
|
||||
``modal.busy`` - boolean
|
||||
While true, ``Escape``, backdrop clicks and ``requestClose()`` will not close the modal. Set this while an operation is in flight. A ``busy`` attribute is reflected on the element for CSS.
|
||||
|
|
|
|||
|
|
@ -975,6 +975,13 @@ def test_set_column_type_dialog(page, datasette_server):
|
|||
option_names = dialog.locator(".set-column-type-option-name").all_inner_texts()
|
||||
assert "asset" in option_names
|
||||
|
||||
# Escape closes the dialog via the shared datasette-modal component
|
||||
# The declarative data-modal-cancel attribute closes the dialog
|
||||
dialog.locator("[data-modal-cancel]").click()
|
||||
dialog.wait_for(state="hidden")
|
||||
|
||||
# Escape also closes the dialog via the shared datasette-modal component
|
||||
page.locator('th[data-column="title"] svg.dropdown-menu-icon').click()
|
||||
page.get_by_role("link", name="Set custom type").click()
|
||||
dialog.wait_for(state="visible")
|
||||
page.keyboard.press("Escape")
|
||||
dialog.wait_for(state="hidden")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue