-
-
-
+
`;
// DOM refs
- this._modal = this.shadowRoot.querySelector("datasette-modal");
- this._listWrap = this.shadowRoot.getElementById("listWrap");
- this._dragList = this.shadowRoot.getElementById("dragList");
- this._pulseTop = this.shadowRoot.getElementById("pulseTop");
- this._pulseBot = this.shadowRoot.getElementById("pulseBot");
- this._selectAllBtn = this.shadowRoot.getElementById("selectAllBtn");
- this._deselectAllBtn = this.shadowRoot.getElementById("deselectAllBtn");
- this._cancelBtn = this.shadowRoot.getElementById("cancelBtn");
- this._applyBtn = this.shadowRoot.getElementById("applyBtn");
- this._countEl = this.shadowRoot.getElementById("selectedCount");
- this._footerEl = this.shadowRoot.getElementById("footerInfo");
+ this._modal = this.querySelector("datasette-modal");
+ this._listWrap = this.querySelector(".list-wrap");
+ this._dragList = this.querySelector(".drag-list");
+ this._pulseTop = this.querySelector(".scroll-pulse.top");
+ this._pulseBot = this.querySelector(".scroll-pulse.bot");
+ this._selectAllBtn = this.querySelector(".select-all");
+ this._deselectAllBtn = this.querySelector(".deselect-all");
+ this._cancelBtn = this.querySelector(".modal-btn-ghost");
+ this._applyBtn = this.querySelector(".modal-btn-primary");
+ this._countEl = this.querySelector(".modal-meta");
+ this._footerEl = this.querySelector(".footer-info");
// Event listeners
this._selectAllBtn.addEventListener("click", () => this._selectAll());
@@ -416,7 +190,7 @@ class ColumnChooser extends HTMLElement {
this._ghostOffX = e.clientX - rect.left;
this._ghostOffY = e.clientY - rect.top;
- // Build ghost inside shadow DOM
+ // Keep the drag preview inside the dialog so it stays above the backdrop.
this._ghost = document.createElement("div");
this._ghost.className = "drag-ghost";
this._ghost.style.width = rect.width + "px";
@@ -425,7 +199,7 @@ class ColumnChooser extends HTMLElement {
this._ghost.querySelector(".drop-indicator")?.remove();
const h = this._ghost.querySelector(".drag-handle");
if (h) h.style.color = "var(--accent)";
- this.shadowRoot.appendChild(this._ghost);
+ this._modal.dialog.appendChild(this._ghost);
srcEl.classList.add("is-dragging");
this._positionGhost(e.clientX, e.clientY);
diff --git a/tests/test_playwright.py b/tests/test_playwright.py
index 890ba076..5404d94f 100644
--- a/tests/test_playwright.py
+++ b/tests/test_playwright.py
@@ -1118,6 +1118,57 @@ def test_navigation_search_created_from_javascript(page, datasette_server):
page.wait_for_url("**/data/projects")
+@pytest.mark.playwright
+def test_column_chooser_selection_and_drag_in_document(page, datasette_server):
+ from playwright.sync_api import expect
+
+ page.goto(datasette_server + "data/projects")
+ page.emulate_media(reduced_motion="reduce")
+ page.evaluate("""() => {
+ const chooser = document.createElement('column-chooser');
+ chooser.id = 'additional-chooser';
+ document.body.append(chooser);
+ window.appliedColumns = null;
+ chooser.open({
+ columns: ['title', 'notes', 'score'],
+ selected: ['title', 'notes'],
+ onApply: columns => { window.appliedColumns = columns; }
+ });
+ }""")
+ chooser = page.locator("#additional-chooser")
+ dialog = chooser.get_by_role("dialog", name="Choose columns")
+ expect(dialog).to_be_visible()
+ assert dialog.evaluate("""node => {
+ const id = node.getAttribute('aria-labelledby');
+ return document.querySelectorAll(`#${id}`).length === 1 &&
+ node.contains(document.getElementById(id));
+ }""")
+ expect(dialog.locator(".modal-meta")).to_have_text("2 of 3 selected")
+ dialog.get_by_role("button", name="Deselect all", exact=True).click()
+ expect(dialog.locator(".modal-meta")).to_have_text("0 of 3 selected")
+ dialog.get_by_role("button", name="Select all", exact=True).click()
+ expect(dialog.locator(".modal-meta")).to_have_text("3 of 3 selected")
+ # Move title after score using the same pointer events as mouse/touch dragging.
+ handle = dialog.locator(".drag-handle").first.bounding_box()
+ target = dialog.locator(".drag-item").last.bounding_box()
+ page.mouse.move(handle["x"] + handle["width"] / 2, handle["y"] + 24)
+ page.mouse.down()
+ page.mouse.move(target["x"] + 24, target["y"] + target["height"] - 4, steps=5)
+ expect(dialog.locator(".drag-ghost")).to_be_visible()
+ page.mouse.up()
+ expect(dialog.locator(".drag-item-label")).to_have_text(["notes", "score", "title"])
+ dialog.get_by_role("button", name="Apply", exact=True).click()
+ expect(dialog).not_to_be_visible()
+ assert page.evaluate("appliedColumns") == ["notes", "score", "title"]
+ chooser.evaluate(
+ "node => node.open({columns: ['title', 'notes'], selected: ['title']})"
+ )
+ dialog.get_by_role("button", name="Deselect all", exact=True).click()
+ dialog.get_by_role("button", name="Cancel", exact=True).click()
+ expect(dialog).not_to_be_visible()
+ assert page.evaluate("appliedColumns") == ["notes", "score", "title"]
+
+
@pytest.mark.playwright
def test_insert_row_flow_uses_custom_column_field(page, datasette_server):
page.add_init_script("""