UNPKG

reka-ui

Version:

Vue port for Radix UI Primitives.

174 lines (138 loc) 4.75 kB
import type { PanelConstraints, PanelData } from '../SplitterPanel.vue' export type SizeUnit = '%' | 'px' type ConvertConstraintsToPercentOptions = { panelDataArray: PanelData[] groupSizeInPixels: number | null } type ConvertValueOptions = { sizeUnit: SizeUnit groupSizeInPixels: number | null value: number | undefined } function convertValueToPercent({ sizeUnit, groupSizeInPixels, value }: ConvertValueOptions): number | undefined { if (value == null) return value if (sizeUnit === '%') return value if (groupSizeInPixels == null || groupSizeInPixels === 0) return undefined return (value / groupSizeInPixels) * 100 } export function convertPanelConstraintsToPercent({ panelDataArray, groupSizeInPixels, }: ConvertConstraintsToPercentOptions): PanelConstraints[] | null { const requiresMeasurement = panelDataArray.some( panelData => (panelData.constraints.sizeUnit ?? '%') === 'px', ) if (requiresMeasurement && (!groupSizeInPixels || Number.isNaN(groupSizeInPixels))) return null return panelDataArray.map((panelData) => { const constraints = panelData.constraints const sizeUnit = constraints.sizeUnit ?? '%' const collapsedSize = convertValueToPercent({ groupSizeInPixels, sizeUnit, value: constraints.collapsedSize, }) const defaultSize = convertValueToPercent({ groupSizeInPixels, sizeUnit, value: constraints.defaultSize, }) const maxSize = convertValueToPercent({ groupSizeInPixels, sizeUnit, value: constraints.maxSize, }) const minSize = convertValueToPercent({ groupSizeInPixels, sizeUnit, value: constraints.minSize, }) return { ...constraints, collapsedSize: collapsedSize ?? constraints.collapsedSize ?? 0, defaultSize, maxSize: maxSize ?? 100, minSize: minSize ?? 0, sizeUnit: '%', } }) } type RecalculateLayoutOptions = { layout: number[] panelDataArray: PanelData[] prevGroupSize: number | null nextGroupSize: number | null } export function hasPixelSizedPanel(panelDataArray: PanelData[]): boolean { return panelDataArray.some(panelData => (panelData.constraints.sizeUnit ?? '%') === 'px') } export function recalculateLayoutForPixelPanels({ layout, panelDataArray, prevGroupSize, nextGroupSize, }: RecalculateLayoutOptions): number[] | null { if (!hasPixelSizedPanel(panelDataArray)) { return null } if ( prevGroupSize == null || nextGroupSize == null || prevGroupSize === 0 || nextGroupSize === 0 || Number.isNaN(prevGroupSize) || Number.isNaN(nextGroupSize) ) { return null } const pixelPanelIndices = panelDataArray.reduce<number[]>((indices, panelData, index) => { if ((panelData.constraints.sizeUnit ?? '%') === 'px') indices.push(index) return indices }, []) if (pixelPanelIndices.length === 0) { return null } const pixelIndexSet = new Set(pixelPanelIndices) const nextLayout = layout.map(size => size ?? 0) const prevPercentForPixelPanels = pixelPanelIndices.reduce((sum, index) => sum + (layout[index] ?? 0), 0) const nextPixelPercents = pixelPanelIndices.map((index) => { const prevPercent = layout[index] ?? 0 const prevPixelSize = (prevPercent / 100) * prevGroupSize return (prevPixelSize / nextGroupSize) * 100 }) const totalNextPixelPercent = nextPixelPercents.reduce((sum, value) => sum + value, 0) const prevPercentForPercentPanels = layout.reduce((sum, size, index) => { if (pixelIndexSet.has(index)) return sum return sum + (size ?? 0) }, 0) const availableNextPercent = Math.max(0, 100 - totalNextPixelPercent) const scaleFactor = prevPercentForPercentPanels > 0 ? availableNextPercent / prevPercentForPercentPanels : 0 pixelPanelIndices.forEach((panelIndex, arrayIndex) => { nextLayout[panelIndex] = nextPixelPercents[arrayIndex] }) for (let index = 0; index < nextLayout.length; index++) { if (pixelIndexSet.has(index)) continue const prevSize = layout[index] ?? 0 nextLayout[index] = prevSize * scaleFactor } // Normalize to ensure percentages always sum to exactly 100. // Floating-point arithmetic in the px→% conversions above can cause drift // (e.g. 99.89% instead of 100%) that gets persisted to localStorage and // triggers validation warnings on every subsequent page load. const total = nextLayout.reduce((sum, size) => sum + size, 0) if (total > 0 && Math.abs(total - 100) > 1e-9) { const factor = 100 / total for (let index = 0; index < nextLayout.length; index++) { nextLayout[index] = (nextLayout[index] ?? 0) * factor } } return nextLayout }