mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 12:58:38 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			177 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/*
 | 
						|
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
 | 
						|
Copyrights licensed under the New BSD License.
 | 
						|
See the accompanying LICENSE file for terms.
 | 
						|
*/
 | 
						|
 | 
						|
// https://github.com/ericf/css-mediaquery
 | 
						|
 | 
						|
import { Trace } from '../trace';
 | 
						|
import { Length } from '../ui/styling/style-properties';
 | 
						|
 | 
						|
// -----------------------------------------------------------------------------
 | 
						|
 | 
						|
const RE_MEDIA_QUERY = /^(?:(only|not)?\s*([_a-z][_a-z0-9-]*)|(\([^\)]+\)))(?:\s*and\s*(.*))?$/i,
 | 
						|
	RE_MQ_EXPRESSION = /^\(\s*([_a-z-][_a-z0-9-]*)\s*(?:\:\s*([^\)]+))?\s*\)$/,
 | 
						|
	RE_MQ_FEATURE = /^(?:(min|max)-)?(.+)/,
 | 
						|
	RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?\s*$/,
 | 
						|
	RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/;
 | 
						|
 | 
						|
export enum MediaQueryType {
 | 
						|
	all = 'all',
 | 
						|
	print = 'print',
 | 
						|
	screen = 'screen',
 | 
						|
}
 | 
						|
 | 
						|
export type MediaQueryProperties = 'width' | 'height' | 'device-width' | 'device-height' | 'orientation' | 'prefers-color-scheme';
 | 
						|
 | 
						|
export interface MediaQueryEnvironmentParams {
 | 
						|
	type?: MediaQueryType;
 | 
						|
	width?: number;
 | 
						|
	height?: number;
 | 
						|
	'device-width'?: number;
 | 
						|
	'device-height'?: number;
 | 
						|
	orientation?: string;
 | 
						|
	'prefers-color-scheme'?: string;
 | 
						|
}
 | 
						|
 | 
						|
export interface MediaQueryExpression {
 | 
						|
	inverse: boolean;
 | 
						|
	type: MediaQueryType;
 | 
						|
	features: MediaQueryFeature[];
 | 
						|
}
 | 
						|
 | 
						|
export interface MediaQueryFeature {
 | 
						|
	modifier: string;
 | 
						|
	property: MediaQueryProperties | string;
 | 
						|
	value: string;
 | 
						|
}
 | 
						|
 | 
						|
export function matchQuery(mediaQuery: string, values: MediaQueryEnvironmentParams): boolean {
 | 
						|
	const expressions = parseQuery(mediaQuery);
 | 
						|
 | 
						|
	return expressions.some((query) => {
 | 
						|
		const { type, inverse, features } = query;
 | 
						|
 | 
						|
		// Either the parsed or specified `type` is "all", or the types must be
 | 
						|
		// equal for a match.
 | 
						|
		const typeMatch = query.type === 'all' || values.type === query.type;
 | 
						|
 | 
						|
		// Quit early when `type` doesn't match, but take "not" into account
 | 
						|
		if ((typeMatch && inverse) || !(typeMatch || inverse)) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		const expressionsMatch = features.every((feature) => {
 | 
						|
			const value: any = values[feature.property];
 | 
						|
 | 
						|
			// Missing or falsy values don't match
 | 
						|
			if (!value && value !== 0) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			switch (feature.property) {
 | 
						|
				case 'orientation':
 | 
						|
				case 'prefers-color-scheme':
 | 
						|
					if (typeof value !== 'string') {
 | 
						|
						return false;
 | 
						|
					}
 | 
						|
 | 
						|
					return value.toLowerCase() === feature.value.toLowerCase();
 | 
						|
				default: {
 | 
						|
					// Numeric properties
 | 
						|
					let numVal: number;
 | 
						|
 | 
						|
					if (typeof value !== 'number') {
 | 
						|
						Trace.write(`Unknown CSS media query feature property: '${feature.property}' on '${query}'`, Trace.categories.MediaQuery, Trace.messageType.warn);
 | 
						|
						return false;
 | 
						|
					}
 | 
						|
 | 
						|
					switch (feature.property) {
 | 
						|
						case 'width':
 | 
						|
						case 'height':
 | 
						|
						case 'device-width':
 | 
						|
						case 'device-height': {
 | 
						|
							numVal = Length.toDevicePixels(Length.parse(feature.value), 0);
 | 
						|
							break;
 | 
						|
						}
 | 
						|
						default:
 | 
						|
							Trace.write(`Unknown CSS media query feature property: '${feature.property}' on '${query}'`, Trace.categories.MediaQuery, Trace.messageType.warn);
 | 
						|
							break;
 | 
						|
					}
 | 
						|
 | 
						|
					switch (feature.modifier) {
 | 
						|
						case 'min':
 | 
						|
							return value >= numVal;
 | 
						|
						case 'max':
 | 
						|
							return value <= numVal;
 | 
						|
						default:
 | 
						|
							return value === numVal;
 | 
						|
					}
 | 
						|
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		});
 | 
						|
 | 
						|
		return (expressionsMatch && !inverse) || (!expressionsMatch && inverse);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
export function parseQuery(mediaQuery: string): MediaQueryExpression[] {
 | 
						|
	const mediaQueryStrings = mediaQuery.split(',');
 | 
						|
 | 
						|
	return mediaQueryStrings.map((query) => {
 | 
						|
		query = query.trim();
 | 
						|
 | 
						|
		const captures = query.match(RE_MEDIA_QUERY);
 | 
						|
 | 
						|
		// Media query must be valid
 | 
						|
		if (!captures) {
 | 
						|
			throw new SyntaxError(`Invalid CSS media query: '${query}'`);
 | 
						|
		}
 | 
						|
 | 
						|
		const modifier = captures[1];
 | 
						|
		const type = captures[2];
 | 
						|
		const featureString = ((captures[3] || '') + (captures[4] || '')).trim();
 | 
						|
 | 
						|
		const expression: MediaQueryExpression = {
 | 
						|
			inverse: !!modifier && modifier.toLowerCase() === 'not',
 | 
						|
			type: MediaQueryType[type ? type.toLowerCase() : 'all'] ?? 'all',
 | 
						|
			features: [],
 | 
						|
		};
 | 
						|
 | 
						|
		// Check for media query features
 | 
						|
		if (!featureString) {
 | 
						|
			return expression;
 | 
						|
		}
 | 
						|
 | 
						|
		// Split features string into a list
 | 
						|
		const features = featureString.match(/\([^\)]+\)/g);
 | 
						|
 | 
						|
		// Media query must be valid
 | 
						|
		if (!features) {
 | 
						|
			throw new SyntaxError(`Invalid CSS media query features: '${featureString}' on '${query}'`);
 | 
						|
		}
 | 
						|
 | 
						|
		for (const feature of features) {
 | 
						|
			const captures = feature.match(RE_MQ_EXPRESSION);
 | 
						|
 | 
						|
			// Media query must be valid
 | 
						|
			if (!captures) {
 | 
						|
				throw new SyntaxError(`Invalid CSS media query feature: '${feature}' on '${query}'`);
 | 
						|
			}
 | 
						|
 | 
						|
			const featureData = captures[1].toLowerCase().match(RE_MQ_FEATURE);
 | 
						|
 | 
						|
			expression.features.push({
 | 
						|
				modifier: featureData[1],
 | 
						|
				property: featureData[2],
 | 
						|
				value: captures[2],
 | 
						|
			});
 | 
						|
		}
 | 
						|
 | 
						|
		return expression;
 | 
						|
	});
 | 
						|
}
 |