UNPKG

tailwind-compositor

Version:

Compositor is a system of constraints designed to produce aesthetically pleasing, typographic compositions, based on objective, constant dimensions of space.

693 lines (662 loc) 23.7 kB
import fs from 'fs'; import fontkit from '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; }; }; export { compositor, tailwindPluginCompositor };