fix(files): review round 2, the page — the viewer's Escape, the dialog's drop, the capped list's honesty

- Escape closes the file viewer again: its layer sits above its dialog's, so it takes the Escape.
- The page's drop guard leaves a drag the open Upload dialog's zone accepted alone (it ran last and
  refused that drop).
- A capped list's server sort or search keeps the rows on screen until its answer is in (no skeleton
  under the pressed header), says "The first N items" when that answer is cut too, and an arrival is
  decided on the rows shown: one past the end of a cut list is said to be so, not gone.
- The search an arrival clears is taken at once.
- Tests: confirm names asserted per confirm, a fitting text pages under the arrows, a deleted file
  leaves the tray's finished list.
This commit is contained in:
2026-10-13 02:32:50 +02:00
parent 54a6bbf07f
commit 5bb101e493
6 changed files with 218 additions and 24 deletions
+7 -5
View File
@@ -77,11 +77,13 @@ export default function FilePreview({
if (next) onFileChange(next.id);
};
useKeyScope("fileViewer");
// The viewer's own place on the layer stack (above its dialog): ← / → are its only while nothing is
// on top of it — its "…" menu, a confirm, the Share or Details dialog opened from that menu. There an
// arrow moves through the layer's own buttons; paging the viewer under it would show one file while
// the dialog acts on another.
const layer = useLayer(true, undefined, { passive: true });
// The viewer's own place on the layer stack: ← / → are its only while nothing is on top of it — its
// "…" menu, a confirm, the Share or Details dialog opened from that menu. There an arrow moves
// through the layer's own buttons; paging the viewer under it would show one file while the dialog
// acts on another. It mounts with its dialog (both only while there is a file), and effects run
// child first, so it sits just ABOVE the dialog's layer: the dialog is never topmost, and Escape is
// this layer's to take — passive, so the dialog keeps its Tab trap.
const layer = useLayer(!!file, onClose, { passive: true });
const onKey = useEffectEvent((e: KeyboardEvent) => {
if (layer.blockingAbove()) return;
// The text, scrolled sideways by its arrows: a line wider than the stage is read that way.