tailwind-compositor
Version:
Compositor is a system of constraints designed to produce aesthetically pleasing, typographic compositions, based on objective, constant dimensions of space.
700 lines (666 loc) • 24 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var fs = _interopDefault(require('fs'));
var fontkit = _interopDefault(require('fontkit'));
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
var bgBaselineRel = function (_a) {
var baseline = _a.baseline, root = _a.root, color = _a.color;
return ({
position: 'relative',
backgroundRepeat: 'repeat',
backgroundSize: "100% " + baseline / root + "rem",
backgroundImage: "linear-gradient(\n\t\t" + color + " 1px,\n\t\ttransparent 0\n\t)",
});
};
var bgBaseline = function (_a) {
var baseline = _a.baseline, color = _a.color;
return ({
position: 'relative',
backgroundRepeat: 'repeat',
backgroundImage: "linear-gradient(\n\t\t" + color + " 1px,\n\t\ttransparent 0\n\t)",
backgroundSize: "100% " + baseline + "px",
});
};
/**
*
*
*/
var styleText = function (_a) {
var _b;
var font = _a.font, baseline = _a.baseline, size = _a.size, _c = _a.leading, leading = _c === void 0 ? 0 : _c, _d = _a.snap, snap = _d === void 0 ? false : _d, _e = _a.root, root = _e === void 0 ? null : _e, _f = _a.useRem, useRem = _f === void 0 ? false : _f;
// ratios
var preventCollapse = 0.4;
var descentAbs = Math.abs(font.descent);
var capHeightRatio = font.capHeight / font.upm;
var xHeightRatio = font.xHeight / font.upm;
var ascentRatio = (font.ascent - font.capHeight) / font.upm;
var descentRatio = descentAbs / font.upm;
// bounding box
var boundingBox = font.ascent + descentAbs + font.lineGap;
var boundingBoxHeight = (boundingBox / font.upm) * size;
// type height
var capSize = capHeightRatio * size;
var baselineRows = capSize / baseline;
var typeRows = Math.round(baselineRows);
// const typeRows = capSize / baseline % 1 < 0.8 ? Math.floor(baselineRows) : Math.ceil(baselineRows);
var typeHeight = snap ? typeRows * baseline : capSize;
// leading
var leadingValue = snap ? Math.round(leading) : leading;
var minLeading = snap ? typeRows : typeHeight;
var typeLeading = leading < 0 ? Math.max(leadingValue, minLeading * -1) : leadingValue;
// line height
var typeLineGap = typeLeading * baseline;
var typeLineHeight = typeHeight + typeLineGap;
// leading trim
var lineGapHeight = (font.lineGap / font.upm) * size;
var lineHeightOffset = (boundingBoxHeight - typeLineHeight - lineGapHeight) / 2;
var trimTop = ascentRatio * size - lineHeightOffset;
var trimBottom = descentRatio * size - lineHeightOffset;
// align to baseline
var paddingTop = snap
? preventCollapse + ((trimBottom + trimTop) % baseline)
: preventCollapse;
var trimTopSize = trimTop * -1 - preventCollapse;
var trimBottomSize = trimBottom * -1 - preventCollapse;
var trimTopValue = useRem
? trimTopSize / size + "em"
: trimTopSize + "px";
var trimBottomValue = useRem
? trimBottomSize / size + "em"
: trimBottomSize + "px";
var fontSizeValue = useRem ? size / root + "rem" : size + "px";
var lineHeightValue = useRem
? "" + typeLineHeight / size
: typeLineHeight + "px";
var paddingTopValue = useRem
? paddingTop / size + "em"
: paddingTop + "px";
var paddingBottomValue = useRem
? preventCollapse / size + "em"
: preventCollapse + "px";
return _b = {
fontSize: fontSizeValue,
lineHeight: lineHeightValue,
paddingTop: paddingTopValue,
paddingBottom: paddingBottomValue
},
_b['&::before'] = {
marginTop: trimTopValue,
},
_b['&::after'] = {
marginBottom: trimBottomValue,
},
_b;
};
/**
*
*
*/
var styleFontFamily = function (params) {
var _a;
//
var font = params.font;
return _a = {
fontFamily: "\"" + font.familyName + "\", " + font.fallback,
fontWeight: font.weight,
fontStyle: font.italic ? 'italic' : 'normal',
display: 'block'
},
_a['&::before, &::after'] = {
content: "''",
display: 'block',
height: 0,
},
_a;
};
var matrixColumns = function (_a) {
var columns = _a.columns;
var result = {
display: 'grid',
gridTemplateColumns: "repeat(" + columns + ", minmax(auto,1fr))",
};
return result;
};
var matrixRows = function (_a) {
var size = _a.size;
var result = {
gridAutoRows: "minmax(" + size + ", auto)",
};
return result;
};
var matrixCellStartX = function (_a) {
var start = _a.start;
var result = {
gridColumnStart: start + " !important",
};
return result;
};
var matrixCellSpanX = function (_a) {
var span = _a.span;
var result = {
gridColumn: "auto / span " + span,
};
return result;
};
var matrixCellStartY = function (_a) {
var start = _a.start;
var result = {
gridRowStart: start + " !important",
};
return result;
};
var matrixCellSpanY = function (_a) {
var span = _a.span;
var result = {
gridRow: "auto / span " + span,
};
return result;
};
var matrixGap = function (_a) {
var rhythm = _a.rhythm;
return {
gridRowGap: rhythm,
gridColumnGap: rhythm,
};
};
var matrixGapX = function (_a) {
var rhythm = _a.rhythm;
return {
gridColumnGap: rhythm,
};
};
var matrixGapY = function (_a) {
var rhythm = _a.rhythm;
return {
gridRowGap: rhythm,
};
};
var measure = function (_a) {
var space = _a.space;
return {
maxWidth: "" + space,
};
};
var owlY = function (_a) {
var _b;
var space = _a.space;
return _b = {},
_b["& > * + * "] = {
marginTop: space,
},
_b;
};
var owlX = function (_a) {
var _b;
var space = _a.space;
return _b = {},
_b["& > * + * "] = {
marginLeft: space,
},
_b;
};
var flattenDeep = require('lodash.flattendeep');
/**
*
*
*/
var createTextStyles = function (_a) {
var theme = _a.theme, e = _a.e, addUtilities = _a.addUtilities;
var _b = theme('compositor'), root = _b.root, baseline = _b.baseline, leading = _b.leading, fonts = _b.fonts, type = _b.type, options = _b.options;
var familyStyles = fonts.map(function (font) {
var _a;
return _a = {},
_a[".font-" + font.key] = styleFontFamily({ font: font }),
_a;
});
var leadingScale = Array.from(new Array(leading + 1), function (v, i) { return i; });
var sizeStyles = flattenDeep(fonts.map(function (font) {
return type.map(function (size, sizeIdx) {
return leadingScale.map(function (lead) {
var _a, _b;
//
// create baseline styles
var outputTextStyle = styleText({
font: font,
root: root,
baseline: baseline,
size: size,
leading: lead,
snap: options.snap,
useRem: options.useRem,
});
// apply if variant type is enabled
var textStyles = (_a = {},
_a["&." + e("text-" + sizeIdx + "/" + lead)] = outputTextStyle,
_a);
return _b = {},
_b[".font-" + font.key] = __assign({}, textStyles),
_b;
});
});
}));
addUtilities(familyStyles, []);
addUtilities(sizeStyles, ['responsive']);
};
// based on https://github.com/developit/dlv
var get = function (obj, key, def, p, undef) {
if (p === void 0) { p = 0; }
key = key && key.split ? key.split('.') : [key];
for (p = 0; p < key.length; p++) {
obj = obj ? obj[key[p]] : undef;
}
return obj === undef ? def : obj;
};
var createBackgroundStyles = function (_a) {
var _b;
var theme = _a.theme, e = _a.e, addUtilities = _a.addUtilities;
var _c = theme('compositor'), options = _c.options, root = _c.root, baseline = _c.baseline, styles = _c.styles;
var rulerColor = get(styles, 'ruler.color', '#ff00cc');
if (!options.xray)
return;
var baselineBgStyles = (_b = {},
_b[".bg-baseline"] = options.useRem
? bgBaselineRel({ baseline: baseline, root: root, color: rulerColor })
: bgBaseline({ baseline: baseline, color: rulerColor }),
_b);
addUtilities(baselineBgStyles, []);
};
var createMeasureStyles = function (_a) {
var theme = _a.theme, e = _a.e, addUtilities = _a.addUtilities;
var _b = theme('compositor'), measureScale = _b.measure, options = _b.options;
if (!options.measure)
return;
var measureStyles = Object.keys(measureScale).map(function (key) {
var _a;
var space = measureScale[key];
return _a = {},
_a["." + e("measure-" + key)] = measure({ space: space }),
_a;
});
addUtilities(measureStyles, ['responsive']);
};
/**
*
*
*/
var createRhythmStyles = function (_a) {
var theme = _a.theme, e = _a.e, addUtilities = _a.addUtilities;
var options = theme('compositor').options;
if (!options.rhythm)
return;
var rhythmScale = theme('spacing');
var rhythmStyles = Object.keys(rhythmScale).map(function (key) {
var _a;
var space = rhythmScale[key];
return _a = {},
_a["." + e("rhythm-" + key)] = owlY({ space: space }),
_a["." + e("rhythm-y-" + key)] = owlY({ space: space }),
_a["." + e("rhythm-x-" + key)] = owlX({ space: space }),
_a;
});
addUtilities(rhythmStyles, ['responsive']);
};
/**
*
*
*/
var createMatrixStyles = function (_a) {
var theme = _a.theme, e = _a.e, addUtilities = _a.addUtilities;
var _b = theme('compositor'), matrix = _b.matrix, options = _b.options;
if (!options.matrix)
return;
var columnsScale = Array.from(new Array(matrix + 1), function (v, i) { return i; });
var rhythmScale = theme('spacing');
var matrixColumnsStyles = columnsScale.map(function (columnIndex) {
var _a;
var columns = columnIndex === 0 ? 1 : columnIndex;
return _a = {},
_a["." + e("matrix-" + columnIndex)] = matrixColumns({
columns: columns,
}),
_a;
});
var matrixCellStyles = columnsScale.map(function (columnIndex) {
var _a;
var column = columnIndex === 0 ? 1 : columnIndex;
return _a = {},
_a["." + e("cell-start-x-" + column)] = matrixCellStartX({
start: column,
}),
_a["." + e("cell-span-x-" + column)] = matrixCellSpanX({
span: column,
}),
_a["." + e("cell-start-y-" + column)] = matrixCellStartY({
start: column,
}),
_a["." + e("cell-span-y-" + column)] = matrixCellSpanY({
span: column,
}),
_a;
});
var matrixGapStyles = Object.keys(rhythmScale).map(function (key) {
var _a;
var rhythm = rhythmScale[key];
return _a = {},
_a["." + e("matrix-row-h-" + key)] = matrixRows({
size: rhythm,
}),
_a["." + e("matrix-gap-" + key)] = matrixGap({ rhythm: rhythm }),
_a["." + e("matrix-gap-x-" + key)] = matrixGapX({ rhythm: rhythm }),
_a["." + e("matrix-gap-y-" + key)] = matrixGapY({ rhythm: rhythm }),
_a;
});
addUtilities(matrixColumnsStyles, ['responsive']);
addUtilities(matrixGapStyles, ['responsive']);
addUtilities(matrixCellStyles, ['responsive']);
};
var tailwindPluginCompositor = function () {
return function (_a) {
var theme = _a.theme, e = _a.e, addUtilities = _a.addUtilities;
createTextStyles({ theme: theme, e: e, addUtilities: addUtilities });
createBackgroundStyles({ theme: theme, e: e, addUtilities: addUtilities });
createMeasureStyles({ theme: theme, e: e, addUtilities: addUtilities });
createRhythmStyles({ theme: theme, e: e, addUtilities: addUtilities });
createMatrixStyles({ theme: theme, e: e, addUtilities: addUtilities });
};
};
var getFontMetrics = function (file) {
var font = fontkit.openSync(file);
var weight = font['OS/2'].usWeightClass;
var italic = font['OS/2'].fsSelection.italic;
return {
familyName: font.familyName,
upm: font.unitsPerEm,
xHeight: font.xHeight,
capHeight: font.capHeight,
lineGap: font.lineGap,
ascent: font.ascent,
descent: font.descent,
weight: weight,
italic: italic,
};
};
var is = {
truthy: function (n) { return n !== undefined && n !== null && n !== false; },
exists: function (n) { return n !== undefined && n !== null; },
undefined: function (n) { return n === undefined; },
array: function (n) { return Array.isArray(n); },
num: function (n) { return typeof n === 'number' && !Number.isNaN(n); },
func: function (f) { return typeof f === 'function'; },
object: function (n) { return typeof n === 'object' && n !== null; },
emptyObject: function (obj) {
return Object.entries(obj).length === 0 && obj.constructor === Object;
},
string: function (str) { return Object.prototype.toString.call(str) === '[object String]'; },
cssunit: function (value) {
return [
'px',
'em',
'ex',
'ch',
'rem',
'vh',
'vw',
'vmin',
'vmax',
'inherit',
].some(function (u) { return value.includes(u); });
},
};
var merge = function (a, b) {
var _a;
var result = Object.assign({}, a, b);
for (var key in a) {
if (!a[key] || typeof b[key] !== 'object')
continue;
Object.assign(result, (_a = {},
_a[key] = Object.assign(a[key], b[key]),
_a));
}
return result;
};
var pxToRem = function (root) { return function (px) { return px / root; }; };
var baselineScaleToRem = function (baseline) { return function (root) { return function (scale) {
var result = Object.keys(scale).reduce(function (res, key) {
var _a;
var toRootEm = pxToRem(root);
var value = scale[key];
return __assign((_a = {}, _a[key] = is.num(value) ? toRootEm(value * baseline) + "rem" : value, _a), res);
}, {});
return result;
}; }; };
var baselineScaleToPx = function (baseline) { return function (scale) {
var result = Object.keys(scale).reduce(function (res, key) {
var _a;
var value = scale[key];
return __assign((_a = {}, _a[key] = is.num(value) ? value * baseline + "px" : value, _a), res);
}, {});
return result;
}; };
var pxScaleToRem = function (root) { return function (scale) {
//
var result = Object.keys(scale).reduce(function (res, key) {
var _a;
var toRootEm = pxToRem(root);
var value = scale[key];
//
return __assign((_a = {}, _a[key] = is.num(value) ? toRootEm(value) + "rem" : value, _a), res);
}, {});
return result;
}; };
var scaleAddSuffix = function (suffix) { return function (scale) {
//
var result = Object.keys(scale).reduce(function (res, key) {
var _a;
var value = scale[key];
//
return __assign((_a = {}, _a[key] = is.num(value) ? "" + value + suffix : value, _a), res);
}, {});
return result;
}; };
// baseline grid row height in px
var root = 16;
// baseline grid row height in px
var baseline = 8;
// type scale in px
var type = [
12,
14,
16,
18,
20,
24,
28,
32,
36,
42,
48,
54,
60,
68,
76,
84,
92,
];
// rhythm scale in baseline units
var rhythm = [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14];
// measure scale in characters unit
var measure$1 = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65];
// font config
var fonts = [
{
key: 'sans-400',
fallback: 'sans-serif',
familyName: 'Inter',
upm: 2816,
xHeight: 1536,
capHeight: 2048,
ascent: 2728,
descent: -680,
weight: 400,
italic: false,
},
{
key: 'sans-400i',
fallback: 'sans-serif',
familyName: 'Inter',
upm: 2816,
xHeight: 1536,
capHeight: 2048,
ascent: 2728,
descent: -680,
weight: 400,
italic: true,
},
{
key: 'sans-600',
fallback: 'sans-serif',
familyName: 'Inter',
upm: 2816,
xHeight: 1536,
capHeight: 2048,
ascent: 2728,
descent: -680,
weight: 600,
italic: false,
},
{
key: 'sans-600i',
fallback: 'sans-serif',
familyName: 'Inter',
upm: 2816,
xHeight: 1536,
capHeight: 2048,
ascent: 2728,
descent: -680,
weight: 600,
italic: true,
},
];
var options = {
snap: true,
useRem: true,
type: true,
rhythm: true,
measure: true,
matrix: true,
xray: true,
};
var cacheFileName = '.compositor';
var defaultStyles = {
ruler: {
color: 'rgba(255, 0, 255, 0.3)',
},
};
var compositor = function (compositorConfig) { return function (tailwindConfig) {
var fontsConfig = [];
// try {
// if (fs.existsSync(cacheFileName)) {
// const rawCache: any = fs.readFileSync(cacheFileName);
// fontsConfig = JSON.parse(rawCache);
// fontsCached = true;
// }
// } catch (err) {
// console.error(err);
// }
// first get some tailwind
// tailwind config values
var _a = tailwindConfig.theme, theme = _a === void 0 ? {} : _a, _b = tailwindConfig.plugins, plugins = _b === void 0 ? [] : _b, tailwindRest = __rest(tailwindConfig, ["theme", "plugins"]);
// extend is nested under theme
var _c = theme.extend, extend = _c === void 0 ? {} : _c;
// get necessary params from compositor
// we only need type and rhythm
// to transform to px or rem usings
// based on useRem, root and baseline params
var _d = compositorConfig.root, root$1 = _d === void 0 ? root : _d, _e = compositorConfig.baseline, baseline$1 = _e === void 0 ? baseline : _e, _f = compositorConfig.fonts, fonts$1 = _f === void 0 ? fonts : _f, _g = compositorConfig.type, type$1 = _g === void 0 ? type : _g, _h = compositorConfig.rhythm, rhythm$1 = _h === void 0 ? rhythm : _h, _j = compositorConfig.measure, measure = _j === void 0 ? measure$1 : _j, configOptions = compositorConfig.options;
//
var options$1 = merge(options, configOptions);
if (options$1.useRem && !is.exists(root$1)) {
throw 'With options.useRem:true, root is required';
}
{
fontsConfig = [];
fonts$1.forEach(function (_a) {
var file = _a.file, fontRest = __rest(_a, ["file"]);
var fontOT;
if (is.string(file) && is.exists(file)) {
fontOT = merge(getFontMetrics(file), __assign({}, fontRest));
}
else {
fontOT = __assign({}, fontRest);
}
fontsConfig.push(fontOT);
});
fs.writeFileSync(cacheFileName, JSON.stringify(fontsConfig));
}
// [16,22,30,42,56]
// type scale is described in px units
// so transform to rem or px
// depending on useRem
var typeScale = options$1.useRem ? pxScaleToRem(root$1)(type$1) : type$1;
// [1,2,3,4,5]
// rhythm scale is described in baseline units
// transform to tailwind format
// rem or px depending on useRem param
var spacingScale = options$1.useRem
? baselineScaleToRem(baseline$1)(root$1)(rhythm$1)
: baselineScaleToPx(baseline$1)(rhythm$1);
//
// measure scale is described in ch units
// transform to tailwind format
// string or ch
var measureScale = scaleAddSuffix('ch')(measure);
// deconstruct tailwind extend
// and get height, minHeight, maxHeight scales
// to merge with the spacingScale
// don't extend spacing,
var _k = extend.height, height = _k === void 0 ? {} : _k, _l = extend.minHeight, minHeight = _l === void 0 ? {} : _l, _m = extend.maxHeight, maxHeight = _m === void 0 ? {} : _m, _o = extend.fontSize, extendRest = __rest(extend, ["height", "minHeight", "maxHeight", "fontSize"]);
//
var out = __assign(__assign({}, tailwindRest), { theme: __assign(__assign({}, theme), { fontSize: typeScale, spacing: spacingScale,
// compositor params via theme
// rather than plugin below
compositor: __assign(__assign({}, compositorConfig), { styles: defaultStyles, measure: measureScale, fonts: fontsConfig }), extend: __assign(__assign({}, extendRest), { height: merge(height, spacingScale), minHeight: merge(minHeight, spacingScale), maxHeight: merge(maxHeight, spacingScale) }) }), plugins: __spreadArrays(plugins, [tailwindPluginCompositor()]) });
return out;
}; };
exports.compositor = compositor;
exports.tailwindPluginCompositor = tailwindPluginCompositor;