Skip to content

Extract shared datasette-modal web component for all modal dialogs#2820

Open
simonw wants to merge 7 commits into
mainfrom
claude/modal-dialog-component-1o94hp
Open

Extract shared datasette-modal web component for all modal dialogs#2820
simonw wants to merge 7 commits into
mainfrom
claude/modal-dialog-component-1o94hp

Conversation

@simonw

@simonw simonw commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Inventory all of the places in the code that use a modal dialog

Refactor those to share as much code as possible via a new web component which is also considered a public stable API for plugins and documented as such

Fable 5 PR

All eight modal dialogs (create table, alter table, insert/edit row,
delete row, set column type, column chooser, mobile column actions and
the navigation jump menu) previously each implemented their own
creation, header/footer markup, backdrop-click and Escape handling,
busy-state guards, focus restoration and near-identical frame CSS.

This extracts all of that into a new web component
(datasette/static/datasette-modal.js) that wraps a native and
provides:

  • The standard modal frame, header (title + meta chip), footer and
    button styles, distributed via a stylesheet adopted into whichever
    document or shadow root the element is connected to - so it also
    works inside the shadow DOM of column-chooser and navigation-search
  • Close on backdrop click and Escape, a busy property that blocks
    dismissal during saves, and a closeGuard hook for discard-changes
    confirmation prompts
  • Focus restoration to the triggering element on close
  • datasette-modal-open and datasette-modal-close events
  • Per-dialog sizing via --datasette-modal-width /
    --datasette-modal-max-height custom properties

The component is exposed as window.DatasetteModal and via a new
datasetteManager.createModal() method, and is documented in
docs/javascript_plugins.rst as a stable public API for plugins.

This removes roughly 1,200 lines of duplicated frame markup, event
wiring and CSS across table.js, edit-tools.js, mobile-column-actions.js,
column-chooser.js, navigation-search.js and app.css, while keeping the
existing dialog ids, class names and inner structure intact.

Also adds Playwright coverage for the column chooser, mobile column
actions and set-column-type dialogs, which previously had none.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34

Documentation: https://datasette--2820.org.readthedocs.build/en/2820/javascript_plugins.html#modal-dialogs-the-datasette-modal-element

All eight modal dialogs (create table, alter table, insert/edit row,
delete row, set column type, column chooser, mobile column actions and
the navigation jump menu) previously each implemented their own <dialog>
creation, header/footer markup, backdrop-click and Escape handling,
busy-state guards, focus restoration and near-identical frame CSS.

This extracts all of that into a new <datasette-modal> web component
(datasette/static/datasette-modal.js) that wraps a native <dialog> and
provides:

- The standard modal frame, header (title + meta chip), footer and
  button styles, distributed via a stylesheet adopted into whichever
  document or shadow root the element is connected to - so it also
  works inside the shadow DOM of column-chooser and navigation-search
- Close on backdrop click and Escape, a busy property that blocks
  dismissal during saves, and a closeGuard hook for discard-changes
  confirmation prompts
- Focus restoration to the triggering element on close
- datasette-modal-open and datasette-modal-close events
- Per-dialog sizing via --datasette-modal-width /
  --datasette-modal-max-height custom properties

The component is exposed as window.DatasetteModal and via a new
datasetteManager.createModal() method, and is documented in
docs/javascript_plugins.rst as a stable public API for plugins.

This removes roughly 1,200 lines of duplicated frame markup, event
wiring and CSS across table.js, edit-tools.js, mobile-column-actions.js,
column-chooser.js, navigation-search.js and app.css, while keeping the
existing dialog ids, class names and inner structure intact.

Also adds Playwright coverage for the column chooser, mobile column
actions and set-column-type dialogs, which previously had none.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (34ab85e) to head (32be92f).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff          @@
##            main   #2820   +/-   ##
=====================================
  Coverage   0.00%   0.00%           
=====================================
  Files         73      73           
  Lines      12037   12043    +6     
=====================================
- Misses     12037   12043    +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@simonw

simonw commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

Add a nicely cropped screenshot to the docs, taken using "uvx shot-scraper --help" against a local dev instance running the example plugin code from the docs

Don't commit the screenshot until you have shown it to me so I can evaluate the quality and file size before it gets added to git

Taken with shot-scraper against a local Datasette instance running the
createModal() example plugin code from the Modal dialogs docs section,
then palette-quantized to keep the file size down (87KB at 1520x920).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
@simonw

simonw commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author
IMG_6282

claude added 5 commits July 2, 2026 16:19
docs/generate-datasette-modal-example.sh builds a temporary demo
database and plugins directory containing the createModal() example
from the Modal dialogs documentation, starts a Datasette server, takes
the screenshot with shot-scraper, quantizes it to an 8-bit palette PNG
and stops the server again. Output is byte-identical to the committed
docs/datasette-modal-example.png.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
The dialog's aria-expanded sync on the trigger button runs from the
native dialog close event, which fires in a queued task after the
dialog is already hidden. Use a retrying expect() assertion instead of
reading the attribute immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
Each test boots its own Datasette subprocess and the 10 second
wait_for_server timeout was too tight on loaded CI runners, causing
intermittent connection-refused errors at fixture setup (seen on the
webkit run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants