UNPKG

warp-grid

Version:

Create a complex grid, warped in 2D space and access data about its lines and cells

197 lines (165 loc) 5.98 kB
import type { CurveLengths, Step, UnprocessedStep, StepDefinition, } from '../types' import { times } from './functional' import { isInt, isNumber, isPlainObj, isPixelNumberString, isNil } from './is' // ----------------------------------------------------------------------------- // Types // ----------------------------------------------------------------------------- interface SideData { start: number curveLength: number nonAbsoluteRatio: number } // ----------------------------------------------------------------------------- // Utils // ----------------------------------------------------------------------------- // If its 0px default it to 0, otherwise return unchanged. const getProcessedStepValue = (step: number | string): number | string => { if (step === `0px`) { return 0 } return step } const ensureArray = (unprocessedSteps: StepDefinition): UnprocessedStep[] => isInt(unprocessedSteps) ? times(() => 1, unprocessedSteps) : unprocessedSteps const ensureObjectsAndProcess = (steps: UnprocessedStep[]) => steps.map((step: UnprocessedStep): Step => { if (isPlainObj(step)) { return { ...step, value: getProcessedStepValue(step.value), } } else { return { value: getProcessedStepValue(step), } } }) const insertGutters = (steps: Step[], gutter: number | string): Step[] => { const lastStepIndex = steps.length - 1 const hasGutter = isGutterNonZero(gutter) return steps.reduce((acc: Step[], step: Step, idx: number): Step[] => { const isLastStep = idx === lastStepIndex const nextStep = steps[idx + 1] const hasNextStep = !isNil(nextStep) const nextStepIsGutter = hasNextStep && nextStep.isGutter const shouldAddGutter = hasGutter && !isLastStep && !step.isGutter && !nextStepIsGutter // Insert a gutter step if we have gutters and are not the last step return shouldAddGutter ? [...acc, step, { value: gutter, isGutter: true }] : [...acc, step] }, []) } const addStepRatioValues = (steps: Step[]) => steps.reduce((total: number, step: Step) => { const { value } = step // Only add steps with values that are not absolute return isNumber(value) ? total + value : total }, 0) const addStepAbsoluteValues = (steps: Step[]) => steps.reduce((acc, step) => { const { value } = step return isPixelNumberString(value) ? acc + getPixelStringNumericComponent(value as string) : acc }, 0) const getCounts = (columns: Step[], rows: Step[]) => ({ columnsTotalCount: columns.length, rowsTotalCount: rows.length, }) const getTotalRatioValues = (columns: Step[], rows: Step[]) => ({ columnsTotalRatioValue: addStepRatioValues(columns), rowsTotalRatioValue: addStepRatioValues(rows), }) const getAbsoluteRatio = (value: string, totalLength: number): number => { return getPixelStringNumericComponent(value) / totalLength } const getNonAbsoluteSpaceRatios = ( columns: Step[], rows: Step[], curveLengths: CurveLengths ) => { const totalAbsoluteSizeU = addStepAbsoluteValues(columns) const totalAbsoluteSizeV = addStepAbsoluteValues(rows) const totalAbsoluteRatioTop = totalAbsoluteSizeU / curveLengths.top const totalAbsoluteRatioBottom = totalAbsoluteSizeU / curveLengths.bottom const totalAbsoluteRatioLeft = totalAbsoluteSizeV / curveLengths.left const totalAbsoluteRatioRight = totalAbsoluteSizeV / curveLengths.right const nonAbsoluteSpaceTopRatio = Math.max(1 - totalAbsoluteRatioTop, 0) const nonAbsoluteSpaceBottomRatio = Math.max(1 - totalAbsoluteRatioBottom, 0) // Prevent this from becoming a negative number const nonAbsoluteSpaceLeftRatio = Math.max(1 - totalAbsoluteRatioLeft, 0) const nonAbsoluteSpaceRightRatio = Math.max(1 - totalAbsoluteRatioRight, 0) return { nonAbsoluteSpaceTopRatio, nonAbsoluteSpaceBottomRatio, nonAbsoluteSpaceLeftRatio, nonAbsoluteSpaceRightRatio, } } const getSize = ( step: Step, totalNonAbsoluteValue: number, sideData: SideData ): number => { // If step value is absolute, we use the ratio of the value to curve length if (isPixelNumberString(step.value)) { return getAbsoluteRatio(step.value as string, sideData.curveLength) } // Otherwise we use the ratio of the value to the non-absolute space const stepRatio = (step.value as number) / totalNonAbsoluteValue return stepRatio * sideData.nonAbsoluteRatio } // ----------------------------------------------------------------------------- // Exports // ----------------------------------------------------------------------------- export function getPixelStringNumericComponent(value: string): number { return Number(value.split(`px`)[0]) } export const isGutterNonZero = (gutter: number | string) => (isNumber(gutter) && gutter > 0) || isPixelNumberString(gutter) export const getEndValues = ( step: Step, totalRatioValue: number, sideData: SideData, sideDataOpposite: SideData ) => { const size = getSize(step, totalRatioValue, sideData) const uOppositeSize = getSize(step, totalRatioValue, sideDataOpposite) const end = sideData.start + size const oppositeEnd = sideDataOpposite.start + uOppositeSize return [end, oppositeEnd] } export const processSteps = ({ steps, gutter, }: { steps: StepDefinition gutter: number | string }): Step[] => { const stepsArray = ensureArray(steps) const stepsArrayOfObjs = ensureObjectsAndProcess(stepsArray) return insertGutters(stepsArrayOfObjs, gutter) } export const getStepData = ( columns: Step[], rows: Step[], curveLengths: CurveLengths ) => { const processedColumns = columns const processedRows = rows return { ...getCounts(columns, rows), ...getTotalRatioValues(columns, rows), ...getNonAbsoluteSpaceRatios(columns, rows, curveLengths), processedColumns, processedRows, } } export const getNonGutterSteps = (steps: Step[]) => steps.filter((step: Step) => !step.isGutter)