UNPKG

@isotope/prototope-server

Version:

Server-Side Rendering (SSR) implementation for Prototope

157 lines (134 loc) 3.95 kB
import { Breakpoints, Config, CurrentData } from "@isotope/prototope/src/declarations"; import { Properties } from "csstype"; import { PrototopeRegistry } from "@isotope/prototope"; interface Listing { [identifier: string]: Rule; } interface Rule { selector: string; properties: Properties<string>; } /** * Class representing Prototope CSS classes registry through strings. */ class PrototopeStringRegistry extends PrototopeRegistry { protected breakpoints: Partial<Breakpoints<Rule[]>> = {}; protected breakpointsCount = 0; protected config: Config; protected listing: Listing = {}; protected styles: Rule[] = []; /** * Creates new PrototopeRegistry instance. * * @param config - Prototope config object. */ public constructor(config: Config) { super(); this.config = config; } /** * Adds new media breakpoint rule. * * @param breakpoint - Breakpoint to add media rule for. */ public addBreakpoint(breakpoint: keyof Breakpoints<any>): void { if (!this.breakpoints[breakpoint]) { this.breakpoints[breakpoint] = []; this.breakpointsCount += 1; } } /** * Adds or configures Prototope's CSS rule. * * @param properties - CSS properties to be set for the rule. * @param config - CSS rule configuration. * @returns - Rule-related CSS class name. */ public addRule(properties: object, config: CurrentData = {}): string { const className = config.className || this.randomID(); const fullSelector = `${className}${ config.subSelector ? `:${config.subSelector}` : "" }`; const listingID = `${ config.breakpoint ? `${config.breakpoint}-` : "" }${fullSelector}`; let { styles } = this; if (config.breakpoint) { if (!this.breakpoints[config.breakpoint]) { this.addBreakpoint(config.breakpoint); } styles = this.breakpoints[config.breakpoint]!; } if (!this.listing[listingID]) { const position = styles.length - this.breakpointsCount; const rule = { properties: {}, selector: fullSelector }; styles.splice(position > 0 ? position : 0, 0, rule); this.listing[listingID] = rule; } Object.assign(this.listing[listingID].properties, properties); return className; } /** * Retrieves rule's CSS properties. * * @param data - Data used to retrieve the rule. * @returns - Retrieved CSS properties. */ public getRule(data: CurrentData): Properties<string> | null { const fullSelector = `${data.className}${ data.subSelector ? `:${data.subSelector}` : "" }`; const listingID = `${data.breakpoint ? `${data.breakpoint}-` : ""}${fullSelector}`; const rule = this.listing[listingID]; return rule ? rule.properties : null; } /** * Retrieves Prototope's CSS stylesheet string. * * @returns Prototope's CSS stylesheet string. */ public getCSS(): string { const basicStyles = this.parseStyleRules(this.styles); const breakpointStyles = Object.entries(this.breakpoints) .map(([breakpoint, styles]) => { return `@media(min-width:${ this.config.breakpoints[breakpoint as keyof Breakpoints<any>] }px){${this.parseStyleRules(styles || [])}}`; }) .join(""); return `${basicStyles}${breakpointStyles}`; } /** * Parses style properties to CSS-like string. * * @param properties - Properties to be parsed. * @returns CSS-like string. */ private parseStyleProperties(properties: Properties<string>): string { return Object.entries(properties) .map(([property, value]) => { return `${property.replace( /[A-Z]/g, (match) => `-${match.toLowerCase()}` )}:${value};`; }) .join(""); } /** * Parses style rules to CSS-like string. * * @param rules - Rules to be parsed. * @returns - CSS-like string. */ private parseStyleRules(rules: Rule[]): string { return rules .map((rule) => { return `.${rule.selector}{${this.parseStyleProperties(rule.properties)}}`; }) .join(""); } } export { PrototopeStringRegistry };