mirror of
https://github.com/simonw/datasette.git
synced 2026-09-19 23:14:06 +02:00
Render the column chooser without shadow DOM, refs #2790
This commit is contained in:
parent
e60d1bfe1c
commit
15d511e2da
3 changed files with 389 additions and 258 deletions
|
|
@ -1209,6 +1209,312 @@ navigation-search .visually-hidden {
|
|||
}
|
||||
|
||||
|
||||
/* column-chooser */
|
||||
column-chooser {
|
||||
display: contents;
|
||||
--ink: #0f0f0f;
|
||||
--paper: #eef6ff;
|
||||
--muted: #6b6b6b;
|
||||
--rule: #d8e6f5;
|
||||
--accent: #1a56db;
|
||||
--accent-light: #e8effd;
|
||||
--card: #ffffff;
|
||||
}
|
||||
|
||||
column-chooser * {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
column-chooser dialog.datasette-modal {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
max-height: min(640px, calc(100vh - 32px));
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
column-chooser dialog.datasette-modal[open] {
|
||||
height: min(640px, calc(100vh - 32px));
|
||||
}
|
||||
|
||||
column-chooser .modal-header {
|
||||
padding: 20px 24px 16px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
column-chooser .list-toolbar {
|
||||
padding: 6px 24px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
column-chooser .list-toolbar button {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent);
|
||||
cursor: pointer;
|
||||
padding: 3px 10px;
|
||||
transition:
|
||||
background 0.12s,
|
||||
color 0.12s;
|
||||
}
|
||||
|
||||
column-chooser .list-toolbar button:hover {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
column-chooser .list-wrap {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
column-chooser .list-wrap::before,
|
||||
column-chooser .list-wrap::after {
|
||||
content: "";
|
||||
position: sticky;
|
||||
display: block;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 20px;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
column-chooser .list-wrap::before {
|
||||
top: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(255, 255, 255, 0.9),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
column-chooser .list-wrap::after {
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, rgba(255, 255, 255, 0.9), transparent);
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
column-chooser .scroll-zone {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 72px;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
column-chooser .scroll-zone-top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
column-chooser .scroll-zone-bot {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
column-chooser .drag-list {
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
column-chooser .drag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
position: relative;
|
||||
transition: background 0.08s;
|
||||
}
|
||||
|
||||
column-chooser .drag-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
column-chooser .drag-handle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
cursor: grab;
|
||||
color: #c8c4bc;
|
||||
touch-action: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
column-chooser .drag-handle:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
column-chooser .drag-handle svg {
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
column-chooser .drag-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
column-chooser .drag-item-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
column-chooser .drag-item-check input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
column-chooser .drag-item-label {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
line-height: 48px;
|
||||
padding-right: 16px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
column-chooser .drag-item.is-dragging {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
column-chooser .drop-indicator {
|
||||
position: absolute;
|
||||
left: 48px;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
border-radius: 99px;
|
||||
pointer-events: none;
|
||||
z-index: 20;
|
||||
display: none;
|
||||
}
|
||||
|
||||
column-chooser .drop-indicator.top {
|
||||
top: -1px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
column-chooser .drop-indicator.bottom {
|
||||
bottom: -1px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
column-chooser .drag-ghost {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.18),
|
||||
0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1.5px solid var(--accent-light);
|
||||
opacity: 0.97;
|
||||
will-change: transform;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
column-chooser .scroll-pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
column-chooser .scroll-pulse.top {
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
column-chooser .scroll-pulse.bot {
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
column-chooser .scroll-pulse.active {
|
||||
opacity: 0.18;
|
||||
animation: column-chooser-pulse 0.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes column-chooser-pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(-50%) scale(1);
|
||||
opacity: 0.18;
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-50%) scale(1.5);
|
||||
opacity: 0.07;
|
||||
}
|
||||
}
|
||||
|
||||
column-chooser .modal-btn-primary {
|
||||
color: white;
|
||||
}
|
||||
|
||||
column-chooser .modal-btn-primary:hover {
|
||||
background: #1448c0;
|
||||
}
|
||||
|
||||
column-chooser .list-wrap::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
column-chooser .list-wrap::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
column-chooser .list-wrap::-webkit-scrollbar-thumb {
|
||||
background: var(--rule);
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
column-chooser input,
|
||||
column-chooser textarea {
|
||||
-webkit-user-select: auto;
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
dialog.mobile-column-actions-dialog {
|
||||
width: min(420px, calc(100vw - 32px));
|
||||
max-height: min(640px, calc(100vh - 32px));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
let columnChooserInstanceCounter = 0;
|
||||
|
||||
class ColumnChooser extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: "open" });
|
||||
this.titleId = `column-chooser-title-${++columnChooserInstanceCounter}`;
|
||||
|
||||
// State
|
||||
this._items = [];
|
||||
|
|
@ -26,273 +28,45 @@ class ColumnChooser extends HTMLElement {
|
|||
// Bound handlers
|
||||
this._onMove = this._onMove.bind(this);
|
||||
this._onUp = this._onUp.bind(this);
|
||||
}
|
||||
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
--ink: #0f0f0f;
|
||||
--paper: #eef6ff;
|
||||
--muted: #6b6b6b;
|
||||
--rule: #d8e6f5;
|
||||
--accent: #1a56db;
|
||||
--accent-light: #e8effd;
|
||||
--card: #ffffff;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
dialog.datasette-modal {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
max-height: min(640px, calc(100vh - 32px));
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
dialog.datasette-modal[open] {
|
||||
height: min(640px, calc(100vh - 32px));
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px 24px 16px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.list-toolbar {
|
||||
padding: 6px 24px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list-toolbar button {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent);
|
||||
cursor: pointer;
|
||||
padding: 3px 10px;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
}
|
||||
.list-toolbar button:hover { background: var(--accent); color: white; }
|
||||
|
||||
.list-wrap {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.list-wrap::before,
|
||||
.list-wrap::after {
|
||||
content: '';
|
||||
position: sticky;
|
||||
display: block;
|
||||
left: 0; right: 0;
|
||||
height: 20px;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.list-wrap::before {
|
||||
top: 0;
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,0.9), transparent);
|
||||
}
|
||||
.list-wrap::after {
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, rgba(255,255,255,0.9), transparent);
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.scroll-zone {
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
height: 72px;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
.scroll-zone-top { top: 0; }
|
||||
.scroll-zone-bot { bottom: 0; }
|
||||
|
||||
.drag-list {
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.drag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
position: relative;
|
||||
transition: background 0.08s;
|
||||
}
|
||||
|
||||
.drag-item:last-child { border-bottom: none; }
|
||||
|
||||
.drag-handle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
cursor: grab;
|
||||
color: #c8c4bc;
|
||||
touch-action: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.drag-handle:hover { color: var(--accent); }
|
||||
.drag-handle svg { pointer-events: none; display: block; }
|
||||
|
||||
.drag-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.drag-item-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drag-item-check input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.drag-item-label {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
line-height: 48px;
|
||||
padding-right: 16px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.drag-item.is-dragging {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.drop-indicator {
|
||||
position: absolute;
|
||||
left: 48px;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
border-radius: 99px;
|
||||
pointer-events: none;
|
||||
z-index: 20;
|
||||
display: none;
|
||||
}
|
||||
.drop-indicator.top { top: -1px; display: block; }
|
||||
.drop-indicator.bottom { bottom: -1px; display: block; }
|
||||
|
||||
.drag-ghost {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1.5px solid var(--accent-light);
|
||||
opacity: 0.97;
|
||||
will-change: transform;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.scroll-pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.scroll-pulse.top { top: 8px; }
|
||||
.scroll-pulse.bot { bottom: 8px; }
|
||||
.scroll-pulse.active {
|
||||
opacity: 0.18;
|
||||
animation: pulse 0.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: translateX(-50%) scale(1); opacity: 0.18; }
|
||||
50% { transform: translateX(-50%) scale(1.5); opacity: 0.07; }
|
||||
}
|
||||
|
||||
.modal-btn-primary {
|
||||
color: white;
|
||||
}
|
||||
.modal-btn-primary:hover { background: #1448c0; }
|
||||
|
||||
.list-wrap::-webkit-scrollbar { width: 5px; }
|
||||
.list-wrap::-webkit-scrollbar-track { background: transparent; }
|
||||
.list-wrap::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 99px; }
|
||||
|
||||
input, textarea { -webkit-user-select: auto; user-select: auto; }
|
||||
</style>
|
||||
|
||||
<datasette-modal><dialog aria-labelledby="modalTitle">
|
||||
connectedCallback() {
|
||||
if (this._modal) return;
|
||||
this.innerHTML = `
|
||||
<datasette-modal><dialog aria-labelledby="${this.titleId}">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title" id="modalTitle">Choose columns</span>
|
||||
<span class="modal-meta" id="selectedCount"></span>
|
||||
<span class="modal-title" id="${this.titleId}">Choose columns</span>
|
||||
<span class="modal-meta"></span>
|
||||
</div>
|
||||
<div class="list-toolbar">
|
||||
<button id="selectAllBtn">Select all</button>
|
||||
<button id="deselectAllBtn">Deselect all</button>
|
||||
<button class="select-all">Select all</button>
|
||||
<button class="deselect-all">Deselect all</button>
|
||||
</div>
|
||||
<div class="list-wrap" id="listWrap">
|
||||
<div class="scroll-pulse top" id="pulseTop"></div>
|
||||
<div class="scroll-pulse bot" id="pulseBot"></div>
|
||||
<ul class="drag-list" id="dragList"></ul>
|
||||
<div class="list-wrap">
|
||||
<div class="scroll-pulse top"></div>
|
||||
<div class="scroll-pulse bot"></div>
|
||||
<ul class="drag-list"></ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="footer-info" id="footerInfo"></span>
|
||||
<button class="modal-btn modal-btn-ghost" id="cancelBtn">Cancel</button>
|
||||
<button class="modal-btn modal-btn-primary" id="applyBtn">Apply</button>
|
||||
<span class="footer-info"></span>
|
||||
<button class="modal-btn modal-btn-ghost">Cancel</button>
|
||||
<button class="modal-btn modal-btn-primary">Apply</button>
|
||||
</div>
|
||||
</dialog></datasette-modal>
|
||||
`;
|
||||
|
||||
// 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);
|
||||
|
|
|
|||
|
|
@ -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("""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue