@enact/ui
Version:
A collection of simplified unstyled cross-platform UI components for Enact
512 lines (489 loc) • 17.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.calculateFontSize = calculateFontSize;
exports.config = void 0;
exports.defineScreenTypes = defineScreenTypes;
exports.getAspectRatio = getAspectRatio;
exports.getAspectRatioName = getAspectRatioName;
exports.getResolutionClasses = getResolutionClasses;
exports.getScreenType = getScreenType;
exports.getScreenTypeObject = getScreenTypeObject;
exports.init = init;
exports.scale = scale;
exports.scaleToRem = void 0;
exports.selectSrc = selectSrc;
exports.unit = unit;
exports.unitToPixelFactors = void 0;
var baseScreen,
orientation,
riRatio,
screenType,
workspaceBounds = {
width: typeof window === 'object' ? window.innerWidth : 1920,
height: typeof window === 'object' ? window.innerHeight : 1080
},
screenTypes = [{
name: 'standard',
pxPerRem: 16,
width: workspaceBounds.width,
height: workspaceBounds.height,
aspectRatioName: 'standard',
base: true
}],
// Assign one sane type in case defineScreenTypes is never run.
screenTypeObject,
config;
/**
* Object that stores the pixel conversion factors to each keyed unit.
*
* @type {Object}
* @memberof ui/resolution
* @public
*/
var unitToPixelFactors = exports.unitToPixelFactors = {
'rem': 12,
'in': 96
};
var configDefaults = {
intermediateScreenHandling: 'normal',
matchSmallerScreenType: false,
orientationHandling: 'normal'
};
/**
* Update the common measured boundary object.
*
* This object is used as "what size screen are we looking at". Providing no arguments has no
* effect and updates nothing.
*
* @function
* @memberOf ui/resolution
* @param {Node} measurementNode A standard DOM node or the `window` node.
*
* @returns {undefined}
* @private
*/
var updateWorkspaceBounds = function updateWorkspaceBounds(measurementNode) {
if (measurementNode) {
if (measurementNode.clientHeight || measurementNode.clientWidth) {
workspaceBounds = {
height: measurementNode.clientHeight,
width: measurementNode.clientWidth
};
} else if (measurementNode.innerHeight || measurementNode.innerWidth) {
// A backup for if measurementNode is actually `window` and not a normal node
workspaceBounds = {
height: measurementNode.innerHeight,
width: measurementNode.innerWidth
};
}
}
};
/**
* Fetch the screenType object
*
* @function
* @memberof ui/resolution
* @param {String} type The key string for the screen type object. If falsy, the current
* screenType is used
*
* @returns {Object} screenTypeObject
* @private
*/
function getScreenTypeObject(type) {
type = type || screenType;
if (screenTypeObject && screenTypeObject.name === type) {
return screenTypeObject;
}
return screenTypes.filter(function (elem) {
return type === elem.name;
})[0];
}
/**
* Sets up screen resolution scaling capabilities by defining an array of all the screens
* being used.
*
* These should be listed in order from smallest to largest, according to
* width.
*
* The `name`, `pxPerRem`, `width`, and `aspectRatioName` properties are required for
* each screen type in the array. Setting `base: true` on a screen type marks it as the
* default resolution, upon which everything else will be based.
*
* Executing this method also initializes the rest of the resolution-independence code.
*
* Example:
* ```
* import ri from 'enact/ui/resolution';
*
* ri.defineScreenTypes([
* {name: 'vga', pxPerRem: 8, width: 640, height: 480, aspectRatioName: 'standard'},
* {name: 'xga', pxPerRem: 16, width: 1024, height: 768, aspectRatioName: 'standard'},
* {name: 'hd', pxPerRem: 16, width: 1280, height: 720, aspectRatioName: 'hdtv'},
* {name: 'uw-hd', pxPerRem: 16, width: 1920, height: 804, aspectRatioName: 'cinema'},
* {name: 'fhd', pxPerRem: 24, width: 1920, height: 1080, aspectRatioName: 'hdtv', base: true},
* {name: 'uw-uxga', pxPerRem: 24, width: 2560, height: 1080, aspectRatioName: 'cinema'},
* {name: 'qhd', pxPerRem: 32, width: 2560, height: 1440, aspectRatioName: 'hdtv'},
* {name: 'wqhd', pxPerRem: 32, width: 3440, height: 1440, aspectRatioName: 'cinema'},
* {name: 'uhd', pxPerRem: 48, width: 3840, height: 2160, aspectRatioName: 'hdtv'},
* {name: 'uhd2', pxPerRem: 96, width: 7680, height: 4320, aspectRatioName: 'hdtv'}
* ]);
* ```
*
* @function
* @memberof ui/resolution
* @param {Object[]} types An array of objects containing screen configuration data, as in the
* preceding example.
* @returns {undefined}
* @public
*/
function defineScreenTypes(types) {
if (types) screenTypes = types;
for (var i = 0; i < screenTypes.length; i++) {
if (screenTypes[i]['base']) baseScreen = screenTypes[i];
}
init();
}
/**
* Fetches the name of the screen type that best matches the current screen size.
*
* The best match is defined as the screen type that is the closest to the screen resolution without
* going over. ("The Price is Right" style.)
*
* @function
* @memberof ui/resolution
* @param {Object} rez Optional measurement scheme. Must include `height` and `width` properties.
* @returns {String} Screen type (e.g., `'fhd'`, `'uhd'`, etc.)
* @public
*/
function getScreenType(rez) {
rez = rez || workspaceBounds;
var types = screenTypes;
var bestMatch = config.matchSmallerScreenType ? types[0].name : types[types.length - 1].name; // Blindly set the first screen type, in case no matches are found later.
orientation = 'landscape';
if (rez.height > rez.width) {
orientation = 'portrait';
var swap = rez.width;
rez.width = rez.height;
rez.height = swap;
}
if (config.matchSmallerScreenType) {
// Loop through resolutions, first->last, smallest->largest
for (var i = 0; i <= types.length - 1; i++) {
// Does this screenType definition fit inside the current resolution? If so, save it as the current best match.
if (rez.height >= types[i].height && rez.width >= types[i].width) {
bestMatch = types[i].name;
}
}
} else {
// Loop through resolutions, last->first, largest->smallest
for (var _i = types.length - 1; _i >= 0; _i--) {
// Does the current resolution fit inside this screenType definition? If so, save it as the current best match.
if (rez.height <= types[_i].height && rez.width <= types[_i].width) {
bestMatch = types[_i].name;
}
}
}
// Return the name of the closest fitting set of dimensions.
return bestMatch;
}
/**
* Calculate the base rem font size.
*
* This is how the magic happens. This accepts an optional `screenType` name. If one isn't provided,
* the currently detected screen type is used. This uses the config option `orientationHandling`,
* which when set to "scale" and the screen is in portrait orientation, will dynamically calculate
* what the base font size should be, if the width were proportionally scaled down to fit in the portrait space.
*
* To use, put the following in your application code:
* ```
* import ri from '@enact/ui/resolution';
*
* ri.config.orientationHandling = 'scale';
* ri.init();
* ```
*
* This has no effect if the screen is in landscape, or if `orientationHandling` is unset.
*
* @function
* @memberof ui/resolution
* @param {String} type Screen type to base size the calculation on. If no
* screen type is provided, the current screen type will be used.
* @returns {String} The calculated pixel size (with unit suffix. Ex: "24px").
* @public
*/
function calculateFontSize(type) {
var scrObj = getScreenTypeObject(type);
var shouldScaleFontSize = config.intermediateScreenHandling === 'scale' && (config.matchSmallerScreenType ? workspaceBounds.width > scrObj.width && workspaceBounds.height > scrObj.height : workspaceBounds.width < scrObj.width && workspaceBounds.height < scrObj.height);
var size;
if (orientation === 'portrait' && config.orientationHandling === 'scale') {
size = scrObj.height / scrObj.width * scrObj.pxPerRem;
} else {
size = scrObj.pxPerRem;
if (orientation === 'landscape' && shouldScaleFontSize) {
size = parseInt(workspaceBounds.height * scrObj.pxPerRem / scrObj.height);
}
}
return size + 'px';
}
/**
* @function
* @memberof ui/resolution
* @param {String} size A valid CSS measurement to be applied as the base document font size.
* @returns {undefined}
* @private
*/
function updateBaseFontSize(size) {
if (typeof window === 'object') {
document.documentElement.style.fontSize = size;
}
}
/**
* Returns the CSS classes for the given `type`.
*
* @function
* @memberof ui/resolution
* @param {String} type Screen type
* @returns {String} CSS class names
* @public
*/
function getResolutionClasses() {
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : screenType;
var classes = [];
if (orientation) {
classes.push('enact-orientation-' + orientation);
}
if (type) {
classes.push('enact-res-' + type.toLowerCase());
var scrObj = getScreenTypeObject(type);
if (scrObj.aspectRatioName) {
classes.push('enact-aspect-ratio-' + scrObj.aspectRatioName.toLowerCase());
}
}
return classes.join(' ');
}
/**
* Returns the ratio of pixels per rem for the given `type` to the pixels per rem for the base type.
*
* @function
* @memberof ui/resolution
* @param {String} type Screen type
*
* @returns {Number} ratio
*/
function getRiRatio() {
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : screenType;
if (type && baseScreen) {
var ratio = getUnitToPixelFactors(type) / getUnitToPixelFactors(baseScreen.name);
if (type === screenType) {
// cache this if it's for our current screen type.
riRatio = ratio;
}
return ratio;
}
return 1;
}
/**
* Returns the pixels per rem for the given `type`.
*
* @memberof ui/resolution
* @param {String} type Screen type
*
* @returns {Number} pixels per rem
*/
function getUnitToPixelFactors() {
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : screenType;
if (type) {
return getScreenTypeObject(type).pxPerRem;
}
return 1;
}
/**
* Calculates the aspect ratio of the specified screen type.
*
* If no screen type is provided, the current screen type is used.
*
* @function
* @memberof ui/resolution
* @param {String} type Screen type whose aspect ratio will be calculated. If no screen
* type is provided, the current screen type is used.
* @returns {Number} The calculated screen ratio (e.g., `1.333`, `1.777`, `2.333`, etc.)
* @public
*/
function getAspectRatio(type) {
var scrObj = getScreenTypeObject(type);
if (scrObj.width && scrObj.height) {
return scrObj.width / scrObj.height;
}
return 1;
}
/**
* Returns the name of the aspect ratio for a specified screen type, or for the default
* screen type if none is provided.
*
* @function
* @memberof ui/resolution
* @param {String} type Screen type whose aspect ratio name will be returned. If no
* screen type is provided, the current screen type will be used.
* @returns {String} The name of the screen type's aspect ratio
* @public
*/
function getAspectRatioName(type) {
var scrObj = getScreenTypeObject(type);
return scrObj.aspectRatioName || 'standard';
}
/**
* Takes a provided pixel value and performs a scaling operation based on the current
* screen type.
*
* @function
* @memberof ui/resolution
* @param {Number} px The quantity of standard-resolution pixels to scale to the
* current screen resolution.
* @returns {Number} The scaled value based on the current screen scaling factor
* @public
*/
function scale(px) {
return (riRatio || getRiRatio()) * px;
}
/**
* Convert to various unit formats.
*
* Useful for converting pixels to a resolution-independent
* measurement method, like "rem". Other units are available if defined in the
* {@link ui/resolution.unitToPixelFactors} object.
*
* Example:
* ```
* import ri from '@enact/ui/resolution';
*
* // Do calculations and get back the desired CSS unit.
* var frameWidth = 250,
* frameWithMarginInches = ri.unit( 10 + frameWidth + 10, 'in' ), // '2.8125in' == frameWithMarginInches
* frameWithMarginRems = ri.unit( 10 + frameWidth + 10, 'rem' ); // '22.5rem' == frameWithMarginRems
* ```
*
* @function
* @memberof ui/resolution
* @param {String|Number} pixels The pixels or math to convert to the unit ("px" suffix in String
* format is permitted. ex: `'20px'`)
* @param {String} toUnit The name of the unit to convert to.
*
* @returns {String|undefined} Resulting conversion in CSS safe format, in case of malformed input, `undefined`
* @public
*/
function unit(pixels, toUnit) {
if (!toUnit || !unitToPixelFactors[toUnit]) return;
if (typeof pixels === 'string' && pixels.substr(-2) === 'px') pixels = parseInt(pixels.substr(0, pixels.length - 2));
if (typeof pixels !== 'number') return;
return pixels / unitToPixelFactors[toUnit] + '' + toUnit;
}
/**
* Shorthand for when you know you need to scale some pixel value and have it converted to "rem" for
* proper scaling.
*
* This runs {@link ui/resolution.scale} and {@link ui/resolution.unit} together.
*
* @function
* @memberof ui/resolution
* @param {Number} pixels The quantity of standard-resolution pixels to scale to rems
*
* @returns {String|undefined} Resulting conversion or, in case of malformed input, `undefined`
* @public
*/
var scaleToRem = exports.scaleToRem = function scaleToRem(pixels) {
return unit(scale(pixels), 'rem');
};
/**
* The default configurable options for {@link ui/resolution.selectSrc}. Additional resolutions
* may be added.
*
* @typedef {Object} selectSrcOptions
* @memberof ui/resolution
* @property {String} [hd] HD / 720p Resolution image asset source URI/URL
* @property {String} [fhd] FHD / 1080p Resolution image asset source URI/URL
* @property {String} [uhd] UHD / 4K Resolution image asset source URI/URL
*/
/**
* Selects the ideal image asset from a set of assets, based on various screen
* resolutions: HD (720p), FHD (1080p), UHD (4k).
*
* When a `src` argument is provided, `selectSrc()` will choose the best image with
* respect to the current screen resolution. `src` may be either the traditional
* string, which will pass straight through, or a hash/object of screen types and
* their asset sources (keys:screen and values:src). The image sources will be used
* when the screen resolution is less than or equal to the provided screen types.
*
* Example:
* ```
* // Take advantage of the multi-res mode
* import {Image} from '@enact/ui/Image';
*
* const src = {
* 'hd': 'http://lorempixel.com/64/64/city/1/',
* 'fhd': 'http://lorempixel.com/128/128/city/1/',
* 'uhd': 'http://lorempixel.com/256/256/city/1/'
* };
* ...
* <Image src={src} ... />
* ...
* ```
*
* @function
* @memberof ui/resolution
* @param {String|ui/resolution.selectSrcSrcOptions} src A string containing a single image
* source or a key/value hash/object
* containing keys representing screen
* types (`'hd'`, `'fhd'`, `'uhd'`,
* etc.) and values containing the asset
* source for that target screen
* resolution.
*
* @returns {String} The chosen source, given the string
* or hash provided
* @public
*/
function selectSrc(src) {
if (typeof src != 'string' && src) {
var newSrc = src.fhd || src.uhd || src.hd;
var types = screenTypes;
// loop through resolutions
for (var i = types.length - 1; i >= 0; i--) {
var t = types[i].name;
if (screenType === t && src[t]) newSrc = src[t];
}
src = newSrc;
}
return src;
}
/**
* This will need to be re-run any time the screen size changes, so all the values can be re-cached.
*
* @function
* @memberof ui/resolution
* @param {Object} args A hash of options. The key `measurementNode` is used to as the node,
* typically the root element, to measure and use as the dimensions for
* the `screenType`.
*
* @returns {undefined}
* @public
*/
function init() {
var args = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var measurementNode = args.measurementNode;
updateWorkspaceBounds(measurementNode);
screenType = getScreenType();
screenTypeObject = getScreenTypeObject();
unitToPixelFactors.rem = getUnitToPixelFactors();
riRatio = getRiRatio();
updateBaseFontSize(calculateFontSize());
}
/**
* The current configuration
*
* @type {Object}
* @memberof ui/resolution
* @private
*/
exports.config = config = Object.assign({}, configDefaults);
;