@illlia/css-merge
Version:
css-merge is a tiny package to compose css classes on the 'last class wins' basis
99 lines (98 loc) • 4.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCssMerge = exports.SPECIAL_SPLIT_CHART = void 0;
const log_when_1 = require("./utils/log-when");
const map_values_1 = require("./utils/map-values");
exports.SPECIAL_SPLIT_CHART = ",";
const createCssMerge = (compressedConfig) => {
if (!compressedConfig)
throw "No config";
const config = (0, map_values_1.mapValues)(compressedConfig, (data) => {
const obj = {};
// split into 4s
const groups = [];
data.forEach((curr, i) => {
const index = i % 4;
// @ts-ignore
if (index === 0)
groups.push([]);
groups.at(-1).push(curr);
});
// populate config
groups.forEach(([props, value, order, important]) => {
props.split(exports.SPECIAL_SPLIT_CHART).forEach((p) => {
obj[p] = { v: value, o: order, ...(important && { i: 1 }) };
});
});
return obj;
});
return (...allClasses) => {
const log = (0, log_when_1.logWhen)(false);
const currentStyles = {};
/**
TODO: performance
test using split & join VS regex replaceAll for conflicting classes
Ex. (?:^| +)(?:hello|how) *
string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
@link https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
*/
return allClasses
.reverse()
.filter(isTruthy)
.map((classNames, i, arr) => {
// return first one unchanged to improve perf and do less work when merging is not required
// let TW linter handle duplicates and classes that update the same thing
// return classNames
if (i === 0)
return classNames;
// populate current styles only when needed
if (i === 1) {
getClasses(arr[0]).forEach((c) => Object.assign(currentStyles, config[c]));
}
const classes = getClasses(classNames);
const nonConflictingClasses = classes.reverse().filter((c) => {
const styles = config[c];
// propagate unknown styles, assume custom css classes or css modules
if (!styles)
return true;
const entries = Object.entries(styles);
// adds some styles without overriding existing
let addsStyle = false;
let addsImportant = false;
const noOverride = entries.every(([prop, map]) => {
const noExistingStyle = currentStyles[prop] === undefined;
addsStyle = addsStyle || noExistingStyle;
addsImportant = !currentStyles[prop]?.i && map.i;
return (noExistingStyle ||
currentStyles[prop].v === map.v ||
currentStyles[prop].o > map.o);
});
const shouldAdd = (addsStyle && noOverride) || addsImportant;
log({
shouldAdd,
addsStyle,
noOverride,
class: c,
allClasses,
currentStyles: { ...currentStyles },
styles,
});
return shouldAdd;
});
// TODO: add all styles as a batch instead of one by one ? treat each passed string as a checked one?
// No performance benefit, but might influence the styles output
// Ex. px-4 p-2 + pb-6 --> px-4 p-2 pb-6 OR p-2 pb-6 --> 1st result might be more expected (as batch)
nonConflictingClasses.forEach((c) => Object.assign(currentStyles, config[c]));
return nonConflictingClasses.reverse().join(" ");
})
.reverse()
.filter(isTruthy)
.join(" ");
};
};
exports.createCssMerge = createCssMerge;
const getClasses = (classNames) => classNames
.split(" ")
.map((x) => x.trim())
.filter(Boolean);
const isTruthy = (value) => !!value;