| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- import { TokenType, isToken, stringify, tokenize } from '@csstools/css-tokenizer';
- import {
- isGeneralEnclosed,
- isMediaFeature,
- isMediaQueryInvalid,
- parseFromTokens,
- } from '@csstools/media-query-list-parser';
- import {
- isSimpleBlockNode,
- isTokenNode,
- parseCommaSeparatedListOfComponentValues,
- } from '@csstools/css-parser-algorithms';
- /** @typedef {Array<import('@csstools/media-query-list-parser').MediaQuery>} 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}
- */
- export default function findMediaFeatureNames(mediaQueryParams, callback) {
- const tokens = tokenize({ css: mediaQueryParams });
- const list = parseCommaSeparatedListOfComponentValues(tokens);
- const mediaQueryConditions = list.flatMap((listItem) => {
- return listItem.flatMap((componentValue) => {
- if (
- !isSimpleBlockNode(componentValue) ||
- componentValue.startToken[0] !== TokenType.OpenParen
- ) {
- return [];
- }
- const blockTokens = componentValue.tokens();
- const mediaQueryList = parseFromTokens(blockTokens, {
- preserveInvalidMediaQueries: true,
- });
- return mediaQueryList.filter((mediaQuery) => {
- return !isMediaQueryInvalid(mediaQuery);
- });
- });
- });
- mediaQueryConditions.forEach((mediaQuery) => {
- mediaQuery.walk(({ node }) => {
- if (isMediaFeature(node)) {
- const token = node.getNameToken();
- if (token[0] !== TokenType.Ident) return;
- callback(token);
- }
- if (isGeneralEnclosed(node)) {
- topLevelTokenNodes(node).forEach((token, i, topLevelTokens) => {
- if (token[0] !== TokenType.Ident) {
- return;
- }
- const nextToken = topLevelTokens[i + 1];
- const prevToken = topLevelTokens[i - 1];
- if (
- // Media Feature
- (!prevToken && nextToken && nextToken[0] === TokenType.Colon) ||
- // Range Feature
- (nextToken &&
- nextToken[0] === TokenType.Delim &&
- rangeFeatureOperator.test(nextToken[4].value)) ||
- // Range Feature
- (prevToken &&
- prevToken[0] === 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 stringify(...tokens);
- },
- };
- }
- /** @param {import('@csstools/media-query-list-parser').GeneralEnclosed} node */
- function topLevelTokenNodes(node) {
- const components = node.value.value;
- if (isToken(components) || components.length === 0 || isToken(components[0])) {
- return [];
- }
- /** @type {Array<import('@csstools/css-tokenizer').CSSToken>} */
- 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 && isTokenNode(component)) {
- if (component.value[0] === TokenType.Delim && component.value[4].value === '$') {
- lastWasDollarSign = true;
- return;
- }
- if (lastWasDollarSign) {
- lastWasDollarSign = false;
- return;
- }
- relevantTokens.push(component.value);
- }
- });
- return relevantTokens;
- }
|