fix(files-overlay): post-deploy bug sweep + root-as-row UX
Three bugs surfaced in browser testing, plus one UX request:
1. The Uploads panel and the binary-mode editor sub-panels stayed
visible after `el.hidden = true` because their `display: flex/grid`
rules in components.css have the same specificity as the UA's
`[hidden]{display:none}` and come later in cascade. Add a targeted
`[hidden]!important` rule for the affected classes.
2. Clicking a folder toggle inside a `files` overlay did nothing.
`file-tree.js` looked for `.file-tree-children` via
`button.nextElementSibling`, but the files-overlay row template
inserts a per-row action span between the toggle and the children
div. Switch to `closest('.file-tree-row').querySelector(':scope >
.file-tree-children')` so both row variants resolve correctly.
3. Pressing Enter on the new-folder dialog did nothing — the keydown
handler was attached with `{once:true}` inside `openNewFolder`,
so the first letter the user typed consumed the listener and Enter
never fired. Move the listener to module init so it survives
subsequent keystrokes and dialog reopenings.
UX: render the overlay root as a row inside the tree (label
"(overlay root)") rather than as a separate toolbar. The root row
carries the same `+ new file · + new folder · ⬇ zip` hover-action
column as every other folder row, so drop-on-row, hover-reveal, and
data-target-path semantics are uniform across the tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
76cd7ddda0
commit
2bba1f31d0
4 changed files with 81 additions and 57 deletions
|
|
@ -517,21 +517,29 @@ button.danger-outline:hover {
|
|||
floating Uploads panel.
|
||||
============================================================ */
|
||||
|
||||
.files-manager {
|
||||
display: grid;
|
||||
gap: var(--space-m);
|
||||
/* The display: flex / grid declarations on the elements below have the
|
||||
same specificity as the UA's `[hidden]{display:none}` rule and come
|
||||
later in the cascade, so without this they'd win and elements would
|
||||
stay visible after JS sets `el.hidden = true`. Targeted rather than
|
||||
a global `[hidden]!important` so we don't fight unknown UA defaults. */
|
||||
.files-uploads[hidden],
|
||||
.files-editor-binary[hidden],
|
||||
.files-editor-text[hidden],
|
||||
.files-editor-replace-idle[hidden],
|
||||
.files-editor-replace-queued[hidden],
|
||||
.files-editor-rename-hint[hidden],
|
||||
.files-uploads-clear[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.files-manager-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-m);
|
||||
flex-wrap: wrap;
|
||||
.files-manager {
|
||||
display: grid;
|
||||
gap: var(--space-s);
|
||||
}
|
||||
|
||||
.files-manager-hint {
|
||||
flex: 1;
|
||||
font-size: 0.85em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.files-tree-root {
|
||||
|
|
@ -551,6 +559,16 @@ button.danger-outline:hover {
|
|||
background: color-mix(in srgb, var(--color-success) 10%, transparent);
|
||||
}
|
||||
|
||||
.files-row-root > .files-row-root-label {
|
||||
font-style: italic;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.files-root-children {
|
||||
flex-basis: 100%;
|
||||
margin-top: var(--space-xs);
|
||||
}
|
||||
|
||||
.files-empty {
|
||||
margin: var(--space-s) var(--space-xs);
|
||||
}
|
||||
|
|
@ -613,13 +631,6 @@ button.danger-outline:hover {
|
|||
border-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.files-root-actions {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.files-row.is-drag-source {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@
|
|||
// carries `data-files-url`. First expand fires a fetch and innerHTMLs the
|
||||
// returned partial into the next `.file-tree-children`; subsequent clicks
|
||||
// just toggle visibility — no re-fetch.
|
||||
//
|
||||
// Children-div lookup goes through the row's <li> rather than the button's
|
||||
// nextElementSibling so the files-overlay variant — where a per-row action
|
||||
// column sits between the toggle button and the children div — works too.
|
||||
(function () {
|
||||
document.addEventListener("click", function (event) {
|
||||
const button = event.target.closest(".file-tree-toggle");
|
||||
if (!button) return;
|
||||
|
||||
const children = button.nextElementSibling;
|
||||
if (!children || !children.classList.contains("file-tree-children")) return;
|
||||
const row = button.closest(".file-tree-row");
|
||||
const children = row ? row.querySelector(":scope > .file-tree-children") : null;
|
||||
if (!children) return;
|
||||
|
||||
const wasExpanded = button.getAttribute("aria-expanded") === "true";
|
||||
button.setAttribute("aria-expanded", wasExpanded ? "false" : "true");
|
||||
|
|
|
|||
|
|
@ -136,20 +136,22 @@
|
|||
}
|
||||
|
||||
if (!path) {
|
||||
// Overlay root — replace the tree-root container's children.
|
||||
const empty = treeRoot.querySelector(".files-empty");
|
||||
// Overlay root — swap into the synthetic root row's children div.
|
||||
const target = manager.querySelector(".files-root-children");
|
||||
if (!target) return;
|
||||
const empty = target.querySelector(".files-empty");
|
||||
if (empty) empty.remove();
|
||||
const existingUl = treeRoot.querySelector(":scope > ul.file-tree");
|
||||
const existingUl = target.querySelector(":scope > ul.file-tree");
|
||||
if (existingUl) existingUl.remove();
|
||||
treeRoot.insertAdjacentHTML("beforeend", html);
|
||||
target.insertAdjacentHTML("beforeend", html);
|
||||
// If the new content is also empty, restore the placeholder.
|
||||
const newUl = treeRoot.querySelector(":scope > ul.file-tree");
|
||||
const newUl = target.querySelector(":scope > ul.file-tree");
|
||||
if (newUl && newUl.children.length === 0) {
|
||||
newUl.remove();
|
||||
const p = document.createElement("p");
|
||||
p.className = "muted files-empty";
|
||||
p.textContent = 'Empty — drop files here, or click "+ new file" above.';
|
||||
treeRoot.appendChild(p);
|
||||
p.textContent = 'Empty — drop files here, or click "+ new file" on this row.';
|
||||
target.appendChild(p);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -602,21 +604,22 @@
|
|||
}
|
||||
});
|
||||
|
||||
input.addEventListener(
|
||||
"keydown",
|
||||
(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
fresh.click();
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
newFolderDialog.showModal();
|
||||
setTimeout(() => input.focus(), 0);
|
||||
}
|
||||
|
||||
// Enter on the new-folder input submits — bound once at module init so
|
||||
// it survives multiple openings of the dialog. (A previous version used
|
||||
// `{once: true}` inside openNewFolder, which was consumed by the first
|
||||
// letter the user typed and never saw Enter.)
|
||||
newFolderDialog
|
||||
.querySelector(".files-new-folder-name")
|
||||
.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
event.preventDefault();
|
||||
newFolderDialog.querySelector(".files-new-folder-create")?.click();
|
||||
});
|
||||
|
||||
// ---------- upload queue + progress panel -------------------------------
|
||||
|
||||
const uploadQueue = [];
|
||||
|
|
|
|||
|
|
@ -65,27 +65,32 @@
|
|||
<div class="files-manager"
|
||||
data-overlay-id="{{ overlay.id }}"
|
||||
data-base-url="/overlays/{{ overlay.id }}">
|
||||
<div class="files-manager-toolbar">
|
||||
<span class="muted files-manager-hint">Drop files / folders onto a folder row to upload, drag rows to move</span>
|
||||
<span class="files-row-actions files-root-actions" aria-label="Overlay root actions">
|
||||
<button type="button" class="files-row-action" data-action="new-file" data-target-path="">+ new file</button>
|
||||
<button type="button" class="files-row-action" data-action="new-folder" data-target-path="">+ new folder</button>
|
||||
<button type="button" class="files-row-action" data-action="zip" data-target-path="">⬇ zip</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="files-tree-root" data-row-kind="dir" data-target-path="">
|
||||
{% if not file_tree_root_entries %}
|
||||
<p class="muted files-empty">Empty — drop files here, or click "+ new file" above.</p>
|
||||
{% else %}
|
||||
{% set entries = file_tree_root_entries %}
|
||||
{% set truncated = file_tree_truncated %}
|
||||
{% set truncated_count = file_tree_truncated_count %}
|
||||
{% set files_base_url = "/overlays/" ~ overlay.id %}
|
||||
{% set download_supported = True %}
|
||||
{% set files_overlay = True %}
|
||||
{% include "_overlay_file_tree.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="muted files-manager-hint">Drop files or folders onto a folder row to upload. Drag rows inside the tree to move them.</p>
|
||||
<ul class="file-tree files-tree-root" data-files-overlay="1">
|
||||
<li class="file-tree-row file-tree-row-dir files-row files-row-root"
|
||||
data-target-path=""
|
||||
data-row-kind="dir">
|
||||
<span class="files-row-root-label">(overlay root)</span>
|
||||
<span class="files-row-actions" aria-label="Overlay root actions">
|
||||
<button type="button" class="files-row-action" data-action="new-file" data-target-path="">+ new file</button>
|
||||
<button type="button" class="files-row-action" data-action="new-folder" data-target-path="">+ new folder</button>
|
||||
<button type="button" class="files-row-action" data-action="zip" data-target-path="">⬇ zip</button>
|
||||
</span>
|
||||
<div class="file-tree-children files-root-children">
|
||||
{% if not file_tree_root_entries %}
|
||||
<p class="muted files-empty">Empty — drop files here, or click "+ new file" on this row.</p>
|
||||
{% else %}
|
||||
{% set entries = file_tree_root_entries %}
|
||||
{% set truncated = file_tree_truncated %}
|
||||
{% set truncated_count = file_tree_truncated_count %}
|
||||
{% set files_base_url = "/overlays/" ~ overlay.id %}
|
||||
{% set download_supported = True %}
|
||||
{% set files_overlay = True %}
|
||||
{% include "_overlay_file_tree.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if not file_tree_root_entries %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue