// NOTICE: This file is generated by Rollup. To modify it, // please instead edit the ESM counterpart and rebuild with Rollup (npm run build). 'use strict'; const cssTokenizer = require('@csstools/css-tokenizer'); const mediaQueryListParser = require('@csstools/media-query-list-parser'); const cssParserAlgorithms = require('@csstools/css-parser-algorithms'); /** @typedef {Array} MediaQueryList */ /** @typedef {import('@csstools/css-tokenizer').TokenIdent} TokenIdent */ /** @typedef {{ stringify: () => string }} MediaQuerySerializer */ const rangeFeatureOperator = /[<>=]/; /** * Search a CSS string for Media Feature names. * For every found name, invoke the callback, passing the token * as an argument. * * Found tokens are mutable and modifications made to them will be reflected in the output. * * This function supports some non-standard syntaxes like SCSS variables and interpolation. * * @param {string} mediaQueryParams * @param {(mediaFeatureName: TokenIdent) => void} callback * * @returns {MediaQuerySerializer} */ function findMediaFeatureNames(mediaQueryParams, callback) { const tokens = cssTokenizer.tokenize({ css: mediaQueryParams }); const list = cssParserAlgorithms.parseCommaSeparatedListOfComponentValues(tokens); const mediaQueryConditions = list.flatMap((listItem) => { return listItem.flatMap((componentValue) => { if ( !cssParserAlgorithms.isSimpleBlockNode(componentValue) || componentValue.startToken[0] !== cssTokenizer.TokenType.OpenParen ) { return []; } const blockTokens = componentValue.tokens(); const mediaQueryList = mediaQueryListParser.parseFromTokens(blockTokens, { preserveInvalidMediaQueries: true, }); return mediaQueryList.filter((mediaQuery) => { return !mediaQueryListParser.isMediaQueryInvalid(mediaQuery); }); }); }); mediaQueryConditions.forEach((mediaQuery) => { mediaQuery.walk(({ node }) => { if (mediaQueryListParser.isMediaFeature(node)) { const token = node.getNameToken(); if (token[0] !== cssTokenizer.TokenType.Ident) return; callback(token); } if (mediaQueryListParser.isGeneralEnclosed(node)) { topLevelTokenNodes(node).forEach((token, i, topLevelTokens) => { if (token[0] !== cssTokenizer.TokenType.Ident) { return; } const nextToken = topLevelTokens[i + 1]; const prevToken = topLevelTokens[i - 1]; if ( // Media Feature (!prevToken && nextToken && nextToken[0] === cssTokenizer.TokenType.Colon) || // Range Feature (nextToken && nextToken[0] === cssTokenizer.TokenType.Delim && rangeFeatureOperator.test(nextToken[4].value)) || // Range Feature (prevToken && prevToken[0] === cssTokenizer.TokenType.Delim && rangeFeatureOperator.test(prevToken[4].value)) ) { callback(token); } }); } }); }); // Serializing takes time/resources and not all callers will use this. // By returning an object with a stringify method, we can avoid doing // this work when it's not needed. return { stringify() { return cssTokenizer.stringify(...tokens); }, }; } /** @param {import('@csstools/media-query-list-parser').GeneralEnclosed} node */ function topLevelTokenNodes(node) { const components = node.value.value; if (cssTokenizer.isToken(components) || components.length === 0 || cssTokenizer.isToken(components[0])) { return []; } /** @type {Array} */ const relevantTokens = []; // To consume the next token if it is a scss variable let lastWasDollarSign = false; components.forEach((component) => { // Only preserve top level tokens (idents, delims, ...) // Discard all blocks, functions, ... if (component && cssParserAlgorithms.isTokenNode(component)) { if (component.value[0] === cssTokenizer.TokenType.Delim && component.value[4].value === '$') { lastWasDollarSign = true; return; } if (lastWasDollarSign) { lastWasDollarSign = false; return; } relevantTokens.push(component.value); } }); return relevantTokens; } module.exports = findMediaFeatureNames;