61 lines
No EOL
2 KiB
TypeScript
61 lines
No EOL
2 KiB
TypeScript
/**
|
|
* Fractional index arithmetic for card ordering.
|
|
*
|
|
* A card's `position` is a float. Inserting between two neighbours takes
|
|
* their midpoint, so a drag writes a single row instead of renumbering the
|
|
* whole column. The tradeoff is finite precision: each midpoint insertion
|
|
* into the same slot halves the gap, and around the 50th the gap reaches
|
|
* the limit of a float64 and two cards silently collide. `needsRebalance`
|
|
* detects that approach; `rebalance` renumbers the column before it happens.
|
|
*/
|
|
|
|
/** Distance between consecutive cards in a freshly numbered column. */
|
|
export const SPACING = 1000;
|
|
|
|
/**
|
|
* Smallest adjacent gap tolerated before a column is renumbered. Well above
|
|
* float64 epsilon at these magnitudes, so rebalancing happens with room to
|
|
* spare rather than at the moment of failure.
|
|
*/
|
|
export const MIN_GAP = 1e-6;
|
|
|
|
/**
|
|
* The position for a card dropped between `before` and `after`.
|
|
* Pass null for either side to mean "no neighbour there".
|
|
*/
|
|
export function positionBetween(
|
|
before: number | null,
|
|
after: number | null
|
|
): number {
|
|
if (before === null && after === null) return SPACING;
|
|
if (before === null) return after! - SPACING;
|
|
if (after === null) return before + SPACING;
|
|
if (before >= after) {
|
|
throw new Error(
|
|
`positionBetween: neighbours out of order (before=${before}, after=${after})`
|
|
);
|
|
}
|
|
return (before + after) / 2;
|
|
}
|
|
|
|
/** True when any adjacent pair has drifted too close to stay distinguishable. */
|
|
export function needsRebalance(positions: number[]): boolean {
|
|
const sorted = [...positions].sort((a, b) => a - b);
|
|
for (let i = 1; i < sorted.length; i++) {
|
|
if (sorted[i] - sorted[i - 1] < MIN_GAP) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Evenly spaced positions for a column, given its card ids in display order.
|
|
* The caller writes these back; order is preserved exactly.
|
|
*/
|
|
export function rebalance(
|
|
orderedIds: string[]
|
|
): Array<{ id: string; position: number }> {
|
|
return orderedIds.map((id, index) => ({
|
|
id,
|
|
position: (index + 1) * SPACING,
|
|
}));
|
|
} |