Files
siftlode/frontend/src/lib/moveInList.ts
T
peter 4d0abc61d5 feat(design): reorder without a drag everywhere a grip drags (WCAG 2.5.7, E28 S3e4a)
One rule, two forms: useOrderItems (Top / Up / Down / Bottom rows of a row
menu) and OrderButtons (four buttons with the place, or a compact Up / Down
pair). A move that would go nowhere is shown off.

- YouTube playlist rows: the four moves now come from useOrderItems.
- Plex playlist rows: a block among the blocks, an episode within its show,
  off while a write is in flight; drag and menu share moveBlock/moveEpisode.
- Saved views editor: the selected view's four buttons (owner: A), and the
  grip's keyboard pick-up (its instructions promised one it did not have).
- Side panel groups in edit mode: an Up / Down pair (owner: A); a group the
  stored order does not hold yet moves too; the name wraps there.
- The grip's touch-action: none is now test-held.
2026-09-26 18:14:28 +02:00

15 lines
827 B
TypeScript

// The non-drag way to reorder a list (WCAG 2.5.7, docs/DESIGN.md "Reordering"): anything a grip can
// drag also moves by Top / Up / Down / Bottom — a menu's rows or a row of buttons, one rule for both.
export type MoveTo = "top" | "up" | "down" | "bottom";
export const MOVES: readonly MoveTo[] = ["top", "up", "down", "bottom"];
/** Where `to` sends the item at `from` in a list of `count`, or null when it would not move (the
* first item going up, the last going down, a stale index) — the caller shows that move as off. */
export function moveTarget(from: number, count: number, to: MoveTo): number | null {
if (from < 0 || from >= count) return null;
const at = to === "top" ? 0 : to === "bottom" ? count - 1 : to === "up" ? from - 1 : from + 1;
return at === from || at < 0 || at >= count ? null : at;
}