UNPKG

style-dictionary

Version:

Style once, use everywhere. A build system for creating cross-platform styles.

112 lines (106 loc) 4.62 kB
/* * Copyright 2017 Amazon.com, Inc. or its affiliates. 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. A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0 * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ import isPlainObject from 'is-plain-obj'; import { convertTokenData } from './utils/convertTokenData.js'; /** * @typedef {import('../types/DesignToken.d.ts').Dictionary} Dictionary * @typedef {import('../types/DesignToken.d.ts').TransformedTokens} Tokens * @typedef {import('../types/DesignToken.d.ts').TransformedToken} Token * @typedef {import('../types/Filter.d.ts').Filter} Filter * @typedef {import('../types/Config.d.ts').Config} Config */ /** * @param {Token[]} arr * @param {Filter['filter']} predicate * @param {Config} options */ async function asyncFilter(arr, predicate, options) { return Promise.all(arr.map((token) => predicate(token, options))).then((results) => arr.filter((_, index) => results[index]), ); } /** * Takes a nested object of tokens and filters them using the provided * function. * * @param {Tokens} tokens * @param {Filter['filter']} filter - A function that receives a property object and * returns `true` if the property should be included in the output or `false` * if the property should be excluded from the output. * @param {Config} options * @returns {Promise<Tokens>} tokens - A new object containing only the tokens * that matched the filter. */ async function filterTokenObject(tokens, filter, options) { // Use reduce to generate a new object with the unwanted tokens filtered // out const result = await Object.entries(tokens ?? []).reduce(async (_acc, [key, token]) => { const acc = await _acc; // If the token is not an object, we don't know what it is. We return it as-is. if (!isPlainObject(token)) { return acc; } else { const tokenValue = options.usesDtcg ? token.$value : token.value; if (typeof tokenValue === 'undefined') { // If we got here we have an object that is not a token. We'll assume // it's token group containing multiple tokens and recursively filter it // using the `filterTokenObject` function. const filtered = await filterTokenObject(token, filter, options); // If the filtered object is not empty then add it to the final `acc` // object. If it is empty then every token inside of it was filtered // out, then exclude it entirely from the final `acc` object. return Object.entries(filtered || {}).length < 1 ? acc : { ...acc, [key]: filtered }; } else { // If the token has a `value` member we know it's a token, pass it to // the filter function and either include it in the final `acc` object or // exclude it (by returning the `acc` object without it added). const filtered = await asyncFilter(/** @type {Token[]} */ ([token]), filter, options); return filtered.length === 0 ? acc : { ...acc, [key]: token }; } } }, {}); return result; } /** * Takes a dictionary and filters the `allTokens` array and the `tokens` * object using a function provided by the user. * * @param {Dictionary} dictionary * @param {Filter['filter']} [filter] - A function that receives a token object * and returns `true` if the token should be included in the output * or `false` if the token should be excluded from the output * @param {Config} [options] * @returns {Promise<Dictionary>} dictionary - A new dictionary containing only the * tokens that matched the filter (or the original dictionary if no filter * function was provided). */ export default async function filterTokens(dictionary, filter, options = {}) { if (!filter) { return dictionary; } else { if (typeof filter !== 'function') { throw new Error('filter is not a function'); } else { const allTokens = await asyncFilter(dictionary.allTokens ?? [], filter, options); const tokens = await filterTokenObject(dictionary.tokens, filter, options); const tokenMap = /** @type {Map<string,Token>} */ ( convertTokenData(allTokens, { output: 'map' }) ); return { allTokens, tokens, tokenMap, }; } } }