UNPKG

@selenite/commons

Version:

This typescript package provides a set of frequently used utilities, types and svelte actions for building projects with Typescript and Svelte.

190 lines (189 loc) 6.93 kB
/** * Utilities for layouting elements. * @module */ import { Vector2D } from '../datastructure'; /** * Returns the offsets computed by the function `f` for each rect in `rects`. * @param rects - Bounding rectangles of elements to layout. * @param f - Function that computes the offset for each element. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ function getOffsets(rects, f, options = {}) { if (rects.length === 0) return []; if (options.refPos && !(options.refPos in rects)) { throw new Error(`Invalid refPos: ${options.refPos}`); } const ref = rects[options.refPos ?? 0]; const res = []; for (const o of rects) { res.push(f(ref, o)); } return res; } /** * Returns offsets to align the tops of elements to the top of a reference element. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getAlignTopOffsets(rects, options) { return getOffsets(rects, (r, o) => ({ x: 0, y: r.y - o.y }), options); } /** * Returns offsets to align the bottoms of elements to the bottom of a reference element. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getAlignBottomOffsets(rects, options) { return getOffsets(rects, (r, o) => ({ x: 0, y: r.y + r.h - o.y - o.h }), options); } /** * Returns offsets to align the middles of elements to the middle of a reference element. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getAlignMiddleOffsets(rects, options) { return getOffsets(rects, (r, o) => ({ x: 0, y: r.y + r.h / 2 - o.y - o.h / 2 }), options); } /** * Returns offsets to align the lefts of elements to the left of a reference element. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getJustifyLeftOffsets(rects, options) { return getOffsets(rects, (r, o) => ({ x: r.x - o.x, y: 0 }), options); } /** * Returns offsets to align the rights of elements to the right of a reference element. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getJustifyRightOffsets(rects, options) { return getOffsets(rects, (r, o) => ({ x: r.x + r.w - o.x - o.w, y: 0 }), options); } /** * Returns offsets to align the centers of elements to the center of a reference element. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getJustifyCenterOffsets(rects, options) { return getOffsets(rects, (r, o) => ({ x: r.x + r.w / 2 - o.x - o.w / 2, y: 0 }), options); } /** * Returns offsets to space elements evenly on the x axis with a minimum gap. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getJustifyBetweenOffsets(rects, { minGap = 10, centerBased = false } = {}) { if (rects.length === 0) return []; // Left based if (!centerBased) { const sortedRects = rects.map((r, i) => ({ i, r })).toSorted((a, b) => a.r.x - b.r.x); const min = sortedRects[0].r.x; const res = []; let offset = 0; let whitespace = 0; for (let i = 0; i < sortedRects.length - 1; i++) { const a = sortedRects[i].r; const b = sortedRects[i + 1].r; whitespace += Math.max(minGap, Math.abs(b.x - a.x - a.w)); } const gap = whitespace / (sortedRects.length - 1); for (const [, { i, r }] of sortedRects.entries()) { res[i] = { x: min + offset - r.x, y: 0 }; offset += r.w + gap; } return res; } else { const centers = []; let min = Infinity; let totalWidth = 0; for (const [i, r] of rects.entries()) { const c = r.x + r.w / 2; min = Math.min(min, c); totalWidth += r.w; centers.push([i, c]); } totalWidth += (rects.length - 1) * minGap; const max = min + totalWidth; const step = (max - min) / (rects.length - 1); const sortedCenters = centers.sort((a, b) => a[1] - b[1]); const res = []; for (const [k, [i, c]] of sortedCenters.entries()) { res[i] = { x: min + k * step - c, y: 0 }; } return res; } } /** * Returns offsets to space elements evenly on the y axis with a minimum gap. * @param rects - Bounding rectangles of elements to align. * @param options - Additional options for the layout. * @returns Array of offsets for each element. */ export function getAlignBetweenOffsets(rects, { minGap = 10, centerBased = false } = {}) { if (rects.length === 0) return []; // Top based if (!centerBased) { const sortedRects = rects.map((r, i) => ({ i, r })).toSorted((a, b) => a.r.y - b.r.y); const min = sortedRects[0].r.y; const res = []; let offset = 0; let whitespace = 0; for (let i = 0; i < sortedRects.length - 1; i++) { const a = sortedRects[i].r; const b = sortedRects[i + 1].r; whitespace += Math.max(minGap, Math.abs(b.y - a.y - a.h)); } const gap = whitespace / (sortedRects.length - 1); for (const [, { i, r }] of sortedRects.entries()) { res[i] = { x: 0, y: min + offset - r.y }; offset += r.h + gap; } return res; } else { const centers = []; let min = Infinity; let totalWidth = 0; for (const [i, r] of rects.entries()) { const c = r.y + r.h / 2; min = Math.min(min, c); totalWidth += r.w; centers.push([i, c]); } totalWidth += (rects.length - 1) * minGap; const max = min + totalWidth; const step = (max - min) / (rects.length - 1); const sortedCenters = centers.sort((a, b) => a[1] - b[1]); const res = []; for (const [k, [i, c]] of sortedCenters.entries()) { res[i] = { x: 0, y: min + k * step - c }; } return res; } }