77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
|
|||
|
|
|
||
|
|
export interface Ordered {
|
||
|
|
readonly id: string;
|
||
|
|
readonly order: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function compareOrdered(a: Ordered, b: Ordered): number {
|
||
|
|
if (a.order !== b.order) {
|
||
|
|
return a.order < b.order ? -1 : 1;
|
||
|
|
}
|
||
|
|
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Where `item` sorts into `sorted`: its own index when present, else its insertion index. */
|
||
|
|
export function rankOf(sorted: readonly Ordered[], item: Ordered): number {
|
||
|
|
let low = 0;
|
||
|
|
let high = sorted.length;
|
||
|
|
while (low < high) {
|
||
|
|
const middle = (low + high) >>> 1;
|
||
|
|
if (compareOrdered(sorted[middle]!, item) < 0) {
|
||
|
|
low = middle + 1;
|
||
|
|
} else {
|
||
|
|
high = middle;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return low;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** A key after every item in `sorted`. */
|
||
|
|
export function appendKey(sorted: readonly Ordered[]): string {
|
||
|
|
return (
|
||
|
|
between(sorted[sorted.length - 1]?.order ?? null, null) ??
|
||
|
|
generateKeyBetween(null, null)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Keys that put `sorted[from]` at `index` among the other items; `repairs` re-key those only when no key fits between its new neighbours. */
|
||
|
|
export function placeAt(
|
||
|
|
sorted: readonly Ordered[],
|
||
|
|
from: number,
|
||
|
|
index: number,
|
||
|
|
): { key: string; repairs: Array<{ id: string; order: string }> } {
|
||
|
|
const other = (at: number) => sorted[at < from ? at : at + 1];
|
||
|
|
const key = between(
|
||
|
|
other(index - 1)?.order ?? null,
|
||
|
|
other(index)?.order ?? null,
|
||
|
|
);
|
||
|
|
if (key !== undefined) {
|
||
|
|
return { key, repairs: [] };
|
||
|
|
}
|
||
|
|
const keys = generateNKeysBetween(null, null, sorted.length);
|
||
|
|
return {
|
||
|
|
key: keys[index]!,
|
||
|
|
repairs: sorted
|
||
|
|
.filter((_, at) => at !== from)
|
||
|
|
.map((item, at) => ({
|
||
|
|
id: item.id,
|
||
|
|
order: keys[at < index ? at : at + 1]!,
|
||
|
|
})),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/** The index of `item` in `sorted`, or -1. */
|
||
|
|
export function indexOf(sorted: readonly Ordered[], item: Ordered): number {
|
||
|
|
const rank = rankOf(sorted, item);
|
||
|
|
return sorted[rank] === item ? rank : -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
function between(a: string | null, b: string | null): string | undefined {
|
||
|
|
try {
|
||
|
|
return generateKeyBetween(a, b);
|
||
|
|
} catch {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
}
|