UNPKG

zeplin-extension-style-kit

Version:

Models and utilities to generate CSS-like style code in Zeplin extensions.

200 lines (174 loc) 6.53 kB
import { Length } from "../../values/length.js"; import { Margin, Padding } from "../../declarations/index.js"; import { Layer as ExtensionLayer } from "@zeplin/extension-model"; interface BoundsOptions { useForMeasurements: boolean; } type BaseLayer = { rect: { x: number; y: number; width: number; height: number; }; layers: ExtensionLayer[]; parent?: ExtensionLayer; }; const useRemUnitForMeasurement = ({ useForMeasurements }: BoundsOptions): boolean => useForMeasurements; function getAbsoluteX(layer: BaseLayer | ExtensionLayer): number { if (!layer.parent) { return layer.rect.x; } return getAbsoluteX(layer.parent) + layer.rect.x; } function getAbsoluteY(layer: BaseLayer | ExtensionLayer): number { if (!layer.parent) { return layer.rect.y; } return getAbsoluteY(layer.parent) + layer.rect.y; } export class Bound { xMin: number; xMax: number; yMin: number; yMax: number; children: Bound[]; parent: Bound | null; constructor(layer: BaseLayer | ExtensionLayer) { this.xMin = getAbsoluteX(layer); this.xMax = this.xMin + layer.rect.width; this.yMin = getAbsoluteY(layer); this.yMax = this.yMin + layer.rect.height; this.children = []; this.parent = null; } static layersToBounds(layers: ExtensionLayer[]): Bound[] { return layers.reduce( (bounds, layer) => { if (layer.exportable) { bounds.push(new Bound(layer)); } else if (layer.type !== "group" || layer.componentName || layer.fills) { bounds.push(new Bound(layer), ...Bound.layersToBounds(layer.layers || [])); } else { bounds.push(...Bound.layersToBounds(layer.layers || [])); } return bounds; }, [] as Bound[] ); } static layerToBound(layer: ExtensionLayer): Bound { const layers = layer.version?.layers; const width = layer.version?.image?.width || 0; const height = layer.version?.image?.width || 0; const duplicatedBounds = Bound.layersToBounds(layers || []); const boundMap = new Map(duplicatedBounds.map(val => [val.key, val])); const root = new Bound({ rect: { x: 0, y: 0, width, height }, layers: [] }); const bounds = Array.from(boundMap, ([, bound]) => bound); if (!boundMap.get(root.key)) { boundMap.set(root.key, root); } boundMap.forEach(bound => { const parent = bound.findParentFromBounds(bounds); if (parent) { bound.setParent(parent); } }); return boundMap.get(new Bound(layer).key) || root; } findParentFromBounds(bounds: Bound[]): Bound | null { const parentCandidates = bounds.filter(bound => bound.contains(this) && this !== bound); const [smallestParent, secondSmallestParent] = parentCandidates.sort((a, b) => a.area - b.area); // If there is only one smallest parent if (smallestParent && smallestParent.area !== (secondSmallestParent && secondSmallestParent.area)) { return smallestParent; } return null; } setParent(parent: Bound): void { this.parent = parent; parent.children.push(this); } contains(other: Bound): boolean { return ( this.xMin <= other.xMin && this.xMax >= other.xMax && this.yMin <= other.yMin && this.yMax >= other.yMax ); } get area(): number { return (this.xMax - this.xMin) * (this.yMax - this.yMin); } get key(): string { return `${this.xMin}-${this.xMax}-${this.yMin}-${this.yMax}`; } get padding(): Padding | null { if (this.children.length === 0) { return null; } return new Padding({ left: new Length( Math.min(...this.children.map(({ xMin }) => xMin)) - this.xMin, { useRemUnit: useRemUnitForMeasurement } ), right: new Length( this.xMax - Math.max(...this.children.map(({ xMax }) => xMax)), { useRemUnit: useRemUnitForMeasurement } ), top: new Length( Math.min(...this.children.map(({ yMin }) => yMin)) - this.yMin, { useRemUnit: useRemUnitForMeasurement } ), bottom: new Length( this.yMax - Math.max(...this.children.map(({ yMax }) => yMax)), { useRemUnit: useRemUnitForMeasurement } ) }); } get margin(): Margin | null { if (!this.parent) { return null; } const parentPadding = this.parent.padding; return new Margin( { left: new Length( this.xMin - Math.max( ...this.parent.children.map(({ xMax }) => xMax).filter(value => value <= this.xMin), this.parent.xMin + (parentPadding?.left.value || 0) ), { useRemUnit: useRemUnitForMeasurement } ), right: new Length( Math.min( ...this.parent.children.map(({ xMin }) => xMin).filter(value => value >= this.xMax), this.parent.xMax - (parentPadding?.right.value || 0) ) - this.xMax, { useRemUnit: useRemUnitForMeasurement } ), top: new Length( this.yMin - Math.max( ...this.parent.children.map(({ yMax }) => yMax).filter(value => value < this.yMin), this.parent.yMin + (parentPadding?.top.value || 0) ), { useRemUnit: useRemUnitForMeasurement } ), bottom: new Length( Math.min( ...this.parent.children.map(({ yMin }) => yMin).filter(value => value >= this.yMax), this.parent.yMax - (parentPadding?.bottom.value || 0) ) - this.yMax, { useRemUnit: useRemUnitForMeasurement } ) }); } }