UNPKG

node-sass-json-functions

Version:
314 lines (293 loc) 8.8 kB
import * as sass from 'sass'; import rgbHex from 'rgb-hex'; import shortHexColor from 'shorten-css-hex'; import isPlainObject from 'is-plain-obj'; import parseColor from 'parse-color'; import parseUnit from 'parse-css-dimension'; import { OrderedMap } from 'immutable'; import { parse } from 'postcss-values-parser'; /** * @typedef {import('../index').JsonValue} JsonValue * @typedef {import('../index').JsonObject} JsonObject * @typedef {import('../index').JsonArray} JsonArray */ /** * @param {sass.Value|undefined} value */ function getJsonValueFromSassValue(value) { var _resolvedValue; let resolvedValue; if (value instanceof sass.SassList) { resolvedValue = listToArray(value); } else if (value instanceof sass.SassMap) { resolvedValue = mapToObject(value); } else if (value instanceof sass.SassColor) { /** @type {[number, number, number]} */ const rgbValue = [value.red, value.green, value.blue]; const alphaValue = value.alpha; if (alphaValue === 1) { resolvedValue = shortHexColor(`#${rgbHex.apply(null, rgbValue)}`); } else { resolvedValue = `rgba(${rgbValue.join(',')},${alphaValue})`; } } else if (value instanceof sass.SassNumber) { if (value.hasUnits) { resolvedValue = String(value.value) + value.numeratorUnits.last(); } else { resolvedValue = Number(value.value); } } else if (value instanceof sass.SassString) { resolvedValue = String(value.text); } else if (value instanceof sass.SassBoolean) { resolvedValue = Boolean(value.value); } else if (typeof value === 'undefined') { resolvedValue = null; } else if (value.isTruthy) { resolvedValue = value.toString(); } else if (value.realNull) { resolvedValue = null; } return (_resolvedValue = resolvedValue) !== null && _resolvedValue !== void 0 ? _resolvedValue : null; } /** * @param {sass.SassList} list */ function listToArray(list) { const length = list.asList.size; /** @type {JsonArray} */ const data = []; for (const index of Array.from({ length }).keys()) { const value = getJsonValueFromSassValue(list.get(index)); data.push(value); } return data; } /** * @param {sass.SassMap} map */ function mapToObject(map) { const length = map.contents.size; /** @type {JsonObject} */ const data = {}; for (const index of Array.from({ length }).keys()) { const resolvedValue = map.get(index); if (typeof resolvedValue !== 'undefined') { const key = String(getJsonValueFromSassValue(resolvedValue.get(0))); const value = getJsonValueFromSassValue(resolvedValue.get(1)); data[key] = value; } } return data; } /** * @typedef {import('../index').JsonValue} JsonValue * @typedef {import('../index').JsonObject} JsonObject * @typedef {import('../index').JsonArray} JsonArray */ const unitTypes = ['length', 'angle', 'resolution', 'frequency', 'time']; /** * @param {string} value */ function isColor(value) { return typeof parseColor(value).rgba !== 'undefined'; } /** * @param {string} value */ function isCalculation(value) { if (!value.includes('calc')) { return false; } try { const root = parse(value); const node = root.first; return (node === null || node === void 0 ? void 0 : node.type) === 'func' && node.name === 'calc'; } catch { return false; } } /** * @param {string} value * @param {{forceNumber: boolean}=} options */ function parseValueToStringOrNumber(value, options) { const { forceNumber = false } = options !== null && options !== void 0 ? options : {}; let resolvedValue; try { const { value: parsedValue, unit, type } = parseUnit(value); if (unitTypes.includes(type)) { resolvedValue = new sass.SassNumber(parsedValue, unit); } else if (type === 'percentage') { resolvedValue = new sass.SassNumber(parsedValue, '%'); } else if (forceNumber && !Number.isNaN(Number(value))) { resolvedValue = new sass.SassNumber(Number(value)); } else { resolvedValue = new sass.SassString(value); } } catch (error) { resolvedValue = new sass.SassString(value); } return resolvedValue; } /** * @param {string} value */ function parseValueToColor(value) { const [red, green, blue, alpha] = parseColor(value).rgba; return new sass.SassColor({ red, green, blue, alpha }); } /** * @typedef {object} CalculationContainer * @property {?sass.CalculationOperator} operator * @property {?sass.CalculationValue} left * @property {?sass.CalculationValue} right */ /** * @param {string} value */ function parseValueToCalculation(value) { const root = parse(value); const node = root.first; let calc = /** @type {CalculationContainer} */{ operator: null, left: null, right: null }; if ((node === null || node === void 0 ? void 0 : node.type) === 'func' && node.name === 'calc') { if (node.nodes.length > 3) { return parseValueToStringOrNumber(value); } node.nodes.forEach((node, index) => { if (node.type === 'operator') { calc.operator = /** @type {sass.CalculationOperator} */ node.value; } else if (index === 0) { calc.left = parseValueToStringOrNumber(node.toString()); } else { calc.right = parseValueToStringOrNumber(node.toString(), { forceNumber: true }); } }); } try { return sass.SassCalculation.calc(new sass.CalculationOperation( // @ts-ignore calc.operator, calc.left, calc.right)); } catch { return parseValueToStringOrNumber(value); } } /** * @param {JsonValue} value */ function setJsonValueToSassValue(value) { let resolvedValue; if (Array.isArray(value)) { resolvedValue = arrayToList(value); } else if (isPlainObject(value)) { resolvedValue = objectToMap(value); } else if (isColor(String(value))) { resolvedValue = parseValueToColor(String(value)); } else if (isCalculation(String(value))) { resolvedValue = parseValueToCalculation(String(value)); } else if (typeof value === 'string') { resolvedValue = parseValueToStringOrNumber(value); } else if (typeof value === 'number') { resolvedValue = new sass.SassNumber(value); } else if (typeof value === 'boolean') { resolvedValue = value ? sass.sassTrue : sass.sassFalse; } else { resolvedValue = sass.sassNull; } return resolvedValue; } /** * @param {JsonArray} array */ function arrayToList(array) { /** @type {sass.Value[]} */ const data = []; for (const item of array) { data.push(setJsonValueToSassValue(item)); } return new sass.SassList(data); } /** * @param {JsonObject} object */ function objectToMap(object) { /** @type {[sass.Value, sass.Value][]} */ const data = []; for (const [property, value = null] of Object.entries(object)) { data.push([setJsonValueToSassValue(property), setJsonValueToSassValue(value)]); } // eslint-disable-next-line new-cap return new sass.SassMap(OrderedMap(data)); } /** * @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue * @typedef {JsonValue[]} JsonArray * @typedef {string | number | boolean | null} JsonPrimitive * @typedef {{[Key in string]?: JsonValue}} JsonObject */ /** * Encodes (`JSON.stringify`) data and returns Sass string. By default, string is quoted with single quotes so that it can be easily used in standard CSS values. * * First argument: `sass.Value` - Data to encode (stringify). * * Second argument: `sass.SassBoolean` - Should output string be quoted with single quotes. * * @param {sass.Value[]} encodeArguments */ function encode(encodeArguments) { const [data, quotes_] = encodeArguments; const quotes = quotes_.assertBoolean('quotes'); const shouldQuote = quotes.value; let resolvedValue = JSON.stringify(getJsonValueFromSassValue(data)); if (shouldQuote) { resolvedValue = `'${resolvedValue}'`; } return new sass.SassString(resolvedValue); } /** * Decodes (`JSON.parse`) string and returns one of available Sass types. * * First argument: `sass.SassString` - String to decode (parse). * * @param {sass.Value[]} decodeArguments */ function decode(decodeArguments) { const [string_] = decodeArguments; const string = string_.assertString('string'); /** @type {JsonValue?} */ let resolvedValue = {}; try { resolvedValue = JSON.parse(string.text); } catch (error) { resolvedValue = null; } return setJsonValueToSassValue(resolvedValue); } /** @type {{ 'json-encode($data, $quotes: true)': typeof encode, 'json-decode($string)': typeof decode }} */ const api = { 'json-encode($data, $quotes: true)': encode, 'json-decode($string)': decode }; export { api as default }; //# sourceMappingURL=index.js.map