apphouse
Version:
Component library for React that uses observable state management and theme-able components.
286 lines (261 loc) • 7.83 kB
text/typescript
import { toCSS, toJSON } from 'cssjson';
import { camelcaseify } from '../themes/utils/string.utils';
import { CSSProperties } from 'glamor';
import { camelcaseifyCssProperty } from '../themes/utils/styles.utils';
// eslint-disable-next-line @typescript-eslint/no-var-requires
// const beautify_css = require("js-beautify").css;
import { css } from 'js-beautify';
const beautify_css = css;
/**
* Helper function to determine if a property key is a selector
* @param styleKey
* @returns
*/
export const isSelector = (property: string): boolean => {
const selectorPrefixes = [':', '&:', '>', '.', '::', '@'];
const found = selectorPrefixes.find((prefix) => property.startsWith(prefix));
return found !== undefined;
};
export default class CSS {
static parseCss = (css: string): object => {
const cssObj: any = toJSON(css);
const allStyles: { [selector: string]: CSSProperties } = {};
const parseChildren = (children: any) => {
Object.keys(children).forEach((cssSelectorString) => {
const styles = children[cssSelectorString].attributes;
if (!styles) {
parseChildren(children.children);
} else {
allStyles[cssSelectorString] = styles;
}
});
};
parseChildren(cssObj.children);
return allStyles;
};
static toJSONAttributes(cssString: string): object {
return toJSON(cssString);
}
static getCssObjectWithCamelcaseProperties(values: any): object {
const _values: any = {};
Object.keys(values).forEach((k) => {
const _nKey = camelcaseify(k);
_values[_nKey] = values[k];
});
return _values;
}
static getCssObjectWithDashedProperties(values: {
[id: string]: any;
}): object {
const _values: any = {};
Object.keys(values).forEach((k) => {
const _nKey = CSS.camelcaseToDashed(k);
_values[_nKey] = values[k];
});
return _values;
}
static isMediaQuery(value: string): boolean {
return value.startsWith('@media');
}
static getSelectorValues(cssString: string): object {
const json: any = CSS.parseCss(cssString);
const _values: any = {};
Object.keys(json).forEach((key) => {
const values = json[key];
const appentToRoot = () => {
if (_values['root']) {
_values['root'] = {
..._values['root'],
...CSS.getCssObjectWithCamelcaseProperties(values)
};
} else {
_values['root'] = CSS.getCssObjectWithCamelcaseProperties(values);
}
};
if (isSelector(key)) {
let k = key;
if (!CSS.isMediaQuery(key)) {
const marker = '********';
const selectorKey = key.replace(':', marker);
k = selectorKey.split(marker)[1];
}
if (k !== undefined && !CSS.isMediaQuery(key)) {
_values[`:${k}`] = CSS.getCssObjectWithCamelcaseProperties(values);
} else if (CSS.isMediaQuery(key)) {
_values[k] = CSS.getCssObjectWithCamelcaseProperties(values);
} else {
if (_values['root']) {
_values['root'] = {
..._values['root'],
...CSS.getCssObjectWithCamelcaseProperties(values)
};
} else {
appentToRoot();
}
}
} else {
appentToRoot();
}
});
return _values;
}
static toCssInJs(cssString: string): object {
const json: any = CSS.getSelectorValues(cssString);
let cssInJs = {};
if (json.root) {
cssInJs = json.root;
}
Object.keys(json).forEach((key) => {
if (key !== 'root') {
//@ts-ignore
cssInJs[key] = json[key];
}
});
return cssInJs;
}
static toCssInJsPrettified(value: string): string {
const css = CSS.toCssInJs(value);
const result2 = CSS.prettify(JSON.stringify(css));
return result2;
}
static toCssInJs2Prettified(value: string): string {
const css = CSS.toCssInJs(value);
const result = CSS.prettify(toCSS(css));
return result;
}
static fromCssInJsToJSONAttributes(value: {
[id: string]: object | string;
}): object {
const tempSelector = '.container';
const jsonInAttributes: any = {
children: {},
attributes: {}
};
let json: any = {
children: {},
attributes: {}
};
Object.keys(value).forEach((key) => {
let id = tempSelector;
if (isSelector(key) && !CSS.isMediaQuery(key)) {
id = `${tempSelector}${key}`;
} else if (CSS.isMediaQuery(key)) {
id = key;
}
const styles = value[key];
if (typeof styles === 'object') {
// ONLY ACCEPTS ONE LEVEL
if (CSS.isMediaQuery(key)) {
jsonInAttributes.children[id] = {
attributes: {},
children: {
[tempSelector]: {
//@ts-ignore
attributes: CSS.getCssObjectWithDashedProperties(value[key]),
children: {}
}
}
};
} else {
jsonInAttributes.children[id] = {
children: {},
//@ts-ignore
attributes: CSS.getCssObjectWithDashedProperties(value[key])
};
}
} else {
const id = CSS.camelcaseToDashed(key);
if (!isSelector(id)) {
json = {
//@ts-ignore
attributes: {
...json.attributes,
[id]: value[key]
}
};
}
}
});
const jsonA = {
children: {
...{ [tempSelector]: { attributes: json.attributes, children: {} } },
...jsonInAttributes.children
},
attributes: {}
};
return jsonA;
}
static fromflatJSONToJSONAttributes(value: { [id: string]: object }): object {
const tempSelector = '.container';
const jsonInAttributes: any = {
children: {},
attributes: {}
};
Object.keys(value).forEach((key) => {
let id = tempSelector;
if (isSelector(key) && !CSS.isMediaQuery(key)) {
id = `${tempSelector}${key}`;
} else if (CSS.isMediaQuery(key)) {
id = key;
}
// ONLY ACCEPTS ONE LEVEL
if (CSS.isMediaQuery(key)) {
jsonInAttributes.children[id] = {
attributes: {},
children: {
[tempSelector]: {
attributes: CSS.getCssObjectWithDashedProperties(value[key]),
children: {}
}
}
};
} else {
jsonInAttributes.children[id] = {
children: {},
attributes: CSS.getCssObjectWithDashedProperties(value[key])
};
}
});
return jsonInAttributes;
}
/**
* Convert JSON to CSS
* @param value JSON in string or object format to be converted to CSS
* @returns
*/
static fromCssInJstoCssString(
value: { [id: string]: object | string },
prettify?: boolean
): string {
try {
const jsonAttributes = CSS.fromCssInJsToJSONAttributes(value);
const css = toCSS(jsonAttributes);
if (prettify) {
return CSS.prettify(css);
}
return css;
} catch (e) {
// return `UNABLE TO CONVERT JSON TO CSS ${e}`;
return `${value}`;
}
}
static prettify(value: string): string {
return beautify_css(value);
}
/**
* Finds all uppercase letters, converts them to lowercase and the sufix - before them
* @param str string in camelcase format
*/
static camelcaseToDashed(str: string): string {
return str.replace(/[A-Z]/g, (match) => {
return '-' + match.toLowerCase();
});
}
static toCamelcase(css: CSSProperties): CSSProperties {
const camel: CSSProperties = {};
Object.keys(css).forEach((key) => {
camel[camelcaseifyCssProperty(key)] = css[key];
});
return camel;
}
}