5105ac224c
* feat: add anchored range selection to multi-select reducer * feat: cmd+click and shift+click multi-select on kanban board * feat: add cmd/shift multi-select to sidebar task list * test: e2e coverage for cmd/shift multi-select * fix: harden multi-select selection lifecycle from review feedback * test: scope multi-select e2e helpers to active containers * fix: add sidebar multi-select hook tests and tighten bulk-action lifecycle * fix: prune hidden sidebar selections and order bulk moves by view * test: cover visible-order bulk move and anchor realignment * fix: harden sidebar bulk-move scope and keyboard selection parity * fix: order kanban bulk moves by board position * test: cover multi-select helpers and fix stale-id sort fallback * fix: order kanban card context-menu moves by board position * feat: sidebar multi-select shows only bulk-valid actions with bulk pin and delete * fix: harden multi-select bulk ordering, selection clearing, and a11y * fix: gate kanban card move-to-step for mixed-workflow selections * fix: unpin fully pinned sidebar selections * refactor: keep sidebar task components under limits * test: restore sidebar selection console spy safely
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
type CreatedTask = {
|
|
createdAt?: string;
|
|
};
|
|
|
|
function createdAtTime(task: CreatedTask): number {
|
|
if (!task.createdAt) return Number.NEGATIVE_INFINITY;
|
|
const time = Date.parse(task.createdAt);
|
|
return Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time;
|
|
}
|
|
|
|
export function compareTasksByCreatedDesc(a: CreatedTask, b: CreatedTask): number {
|
|
const aTime = createdAtTime(a);
|
|
const bTime = createdAtTime(b);
|
|
if (bTime > aTime) return 1;
|
|
if (bTime < aTime) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Sort `ids` into the board's visible created-desc order using `taskById` for
|
|
* lookups. Ids without a known task keep their relative order. Used before a
|
|
* kanban bulk move so a backward range selection doesn't land scrambled when
|
|
* sequential positions are assigned.
|
|
*/
|
|
export function sortIdsByCreatedDesc(ids: string[], taskById: Map<string, CreatedTask>): string[] {
|
|
// Missing ids fall back to `{}`, which `compareTasksByCreatedDesc` treats as
|
|
// the oldest (sorts last) — keeping the comparator transitive rather than
|
|
// returning 0 whenever either side is unknown.
|
|
return [...ids].sort((a, b) =>
|
|
compareTasksByCreatedDesc(taskById.get(a) ?? {}, taskById.get(b) ?? {}),
|
|
);
|
|
}
|