lib/dd.breakpoints.es6.js (222 lines of code) (raw):
/**
* Breakpoints for JavaScript (ES6). Works with the Deloitte Digital SCSS @bp mixin and Less .bp mixin
*
* @namespace DDBreakpoints
* @version 2.0.4
* @copyright 2012-2021 Deloitte Digital Australia - http://www.deloittedigital.com/au
* @author Deloitte Digital Australia deloittedigital@deloitte.com.au
* @license BSD 3-Clause (http://opensource.org/licenses/BSD-3-Clause)
*
* @example
* import DDBreakpoints from '@deloitte-digital-au/ddbreakpoints/lib/dd.breakpoints.es6';
*/
let _minBreakpoints;
let _maxBreakpoints;
let _options = {
isResponsive: true,
baseFontSize: 16,
breakpoints: [
{
name: 'xxs',
px: 359
},
{
name: 'xs',
px: 480
},
{
name: 's',
px: 640
},
{
name: 'm',
px: 768
},
{
name: 'l',
px: 1024
},
{
name: 'xl',
px: 1244
},
{
name: 'xxl',
px: 1410
},
{
name: 'xxxl',
px: 1570
},
{
name: 'fhd',
px: 1900
}
],
staticRange: {
min: 0,
max: 'xl'
}
};
/**
* Sorts the breakpoints and assigns them to an associative array for more efficient lookup.
* Immediately invoked on initialisation
*
* @memberof DDBreakpoints
* @private
*/
const _initBreakpoints = function() {
//sort the breakpoints into order of smallest to largest
const sortedBreakpoints = _options.breakpoints.sort(function(a, b) {
// only sort if the correct objects are present
if (a.px < b.px) {
return -1;
}
if (a.px > b.px) {
return 1;
}
return 0;
});
// reset the breakpoints
_minBreakpoints = {};
_maxBreakpoints = {};
// loop through sorted breakpoints to generate a quick lookup object using the name as a key
for (let i = 0, len = sortedBreakpoints.length, last = len - 1; i < len; i += 1) {
_minBreakpoints[sortedBreakpoints[i].name] = parseInt(sortedBreakpoints[i].px, 10);
// skip the last item in the list as we assume there is no maximum for the last
if (i < last) {
// the max breakpoint of the current size is the next breakpoints
// width minus 1px so there is no overlap between breakpoints
_maxBreakpoints[sortedBreakpoints[i].name] = parseInt(sortedBreakpoints[i + 1].px - 1, 10);
}
}
};
_initBreakpoints();
/**
* Splits string syntax 'xs,m' into separate values 'xs' and 'm'
* Converts string '5' to numeric 5
*
* @memberof DDBreakpoints
* @private
* @param {String|Number} min Number in pixels or string notation
* @param {String|Number} max Number in pixels or string notation
* @return {Object} Object containing the min and max values parsed as a number
*/
const _parseMinMaxInputs = (min, max) => {
const parseValue = (val) => {
if (typeof (val) === 'string') {
// Strip whitespace
val = val.replace(/\s/g, '');
// If val only contains digits, convert it to a number
if (/^\d+$/.test(val)) {
val = parseInt(val, 10);
}
}
return val;
};
let bpArray;
let resultMin = min;
let resultMax = max || 0;
// check if it's using the string syntax, if so - split it
if (typeof (min) === 'string' && min.indexOf(',') !== -1 && resultMax === 0) {
bpArray = min.split(',');
if (bpArray.length === 2) {
resultMin = bpArray[0];
resultMax = bpArray[1];
}
}
return {
min: parseValue(resultMin),
max: parseValue(resultMax)
};
};
/**
* Converts a number of pixels into em
*
* @memberof DDBreakpoints
* @private
* @param {Number} px Number in pixels
* @return {String} The converted number in em as a string
*/
const _pxToEms = (px) => {
return px / _options.baseFontSize;
};
/**
* Converts a breakpoint name/value (e.g. l) to the px variable then to ems
*
* @memberof DDBreakpoints
* @private
* @param {String|Number} breakpoint Breakpoint name as a string, or as a number in pixels
* @param {Boolean} [isMax=false] Flag to determine if the min or max of the breakpoint needs to be used
* @return {String} The converted number in em as a string
*/
const _bpToEms = (breakpoint, isMax = false) => {
if (typeof (breakpoint) === 'number') {
return _pxToEms(breakpoint);
}
const list = (isMax === true) ? _maxBreakpoints : _minBreakpoints;
let ems = '0';
for (var key in list) {
if (list.hasOwnProperty(key)) {
if (breakpoint === key.toLowerCase()) {
ems = _pxToEms(list[key]);
}
}
}
if (ems === '0') {
console.warn('DD.bp: Breakpoint \'' + breakpoint + '\' doesn\'t exist - replacing with 0');
}
return ems;
};
/**
* Checks if the breakpoint provided falls inside the valid static min/max region
*
* @memberof DDBreakpoints
* @private
* @param {String|Number} min Breakpoint name as a string, as a number in pixels, or as string notation containing both breakpoints
* @param {String|Number} max Breakpoint name as a string, or as a number in pixels
* @param {Boolean} [property='width'] which property to check for (e.g. width or height)
* @return {Boolean} If the breakpoint fits inside the static range or not
*/
const _bpIsValidForStatic = (min, max, property = 'width') => {
if (property !== 'width') {
return false;
}
const bpValidMin = _bpToEms(_options.staticRange.min);
const bpValidMax = _bpToEms(_options.staticRange.max, true);
const bps = _parseMinMaxInputs(min, max);
const bpMin = _bpToEms(bps.min);
const bpMax = _bpToEms(bps.max);
// if max is 0 we have a min-and-above situation
if (bps.max === 0) {
// need to check that the min is greater than the valid min,
// AND also that the min is less than the valid maximum
if (bpMin >= bpValidMin && bpMin < bpValidMax) {
return true;
}
return false;
}
// if min is 0 we have a max-and-below situation
if (bps.min === 0) {
if (bpMax >= bpValidMax) {
return true;
}
return false;
}
// if the min is above the valid max, or the max is below the valid min
if (bpMin > bpValidMax || bpMax < bpValidMin) {
return false;
}
// if the breakpoint is a bp-between (assumed because $max and $min aren't 0)
// don't show if the max isn't above the valid max
if (bpMax < bpValidMax) {
return false;
}
return true;
};
/**
* Returns a min-width media query based on bp name or px
*
* @memberof DDBreakpoints
* @private
* @param {String|Number} min Breakpoint name as a string, or as a number in pixels
* @param {String} [property='width'] Property to check using a media query. e.g. width or height
* @return {String} Media query string
*/
const _bpMin = function(min, property = 'width') {
var bpMin = _bpToEms(min);
return '(min-' + property + ': ' + bpMin + 'em)';
};
/**
* Returns a max-width media query based on bp name or px
*
* @memberof DDBreakpoints
* @private
* @param {String|Number} max Breakpoint name as a string, or as a number in pixels
* @param {String} [property='width'] Property to check using a media query. e.g. width or height
* @return {String} Media query string
*/
const _bpMax = (max, property = 'width') => {
return '(max-' + property + ': ' + _bpToEms(max, true) + 'em)';
};
/**
* Returns a min-width and max-width media query based on bp name (can be the same bp name) or px
*
* @memberof DDBreakpoints
* @private
* @param {String|Number} min Breakpoint name as a string, or as a number in pixels
* @param {String|Number} max Breakpoint name as a string, or as a number in pixels
* @param {String} [property='width'] Property to check using a media query. e.g. width or height
* @return {String} Media query string
*/
const _bpBetween = (min, max, property = 'width') => {
return '(min-' + property + ': ' + _bpToEms(min) + 'em) and (max-' + property + ': ' + _bpToEms(max, true) + 'em)';
};
/**
* Breakpoint function that can take the input of a min and max
* breakpoint by name or number (in px) along with a property
* (like width or height) and returns the media query as a string
*
* @memberof DDBreakpoints
* @example
* // large and above
* DDBreakpoints.get('l');
*
* @example
* // 300px and above
* DDBreakpoints.get(300);
*
* @example
* // large and below
* DDBreakpoints.get(0, 'l');
*
* @example
* // 300px and below
* DDBreakpoints.get(0, 300);
*
* @example
* // Between small and large
* DDBreakpoints.get('s', 'l');
*
* @example
* // Between 100px and 300px
* DDBreakpoints.get(100, 300);
*
* @example
* // High resolution displays (can use 'hdpi' as well)
* DDBreakpoints.get('retina');
*
* @example
* // Can mix and match names and numbers - between 200px and xlarge
* DDBreakpoints.get(200, 'xl');
*
* @example
* // Between small and 960px
* DDBreakpoints.get('s', 960);
*
* @example
* // Can use a single string (no spaces) - useful for passing through from HTML to JS
* DDBreakpoints.get('m,l');
*
* @example
* // Can also mix names and numbers
* DDBreakpoints.get('xs,1000');
*
* @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation
* @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels
* @param {String} [property='width'] Property to check using a media query. e.g. width or height
* @return {String} Media query string
*/
export const get = (min, max = 0, property = 'width') => {
const bps = _parseMinMaxInputs(min, max);
//check what type of bp it is
if (bps.min === 'retina' || bps.min === 'hdpi') {
return '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5)';
} else if (bps.max === 0) {
return _bpMin(bps.min, property);
} else if (bps.min === 0) {
return _bpMax(bps.max, property);
} else {
return _bpBetween(bps.min, bps.max, property);
}
};
/**
* Shortcut for the get() function that returns a height
* based media query and returns the media query as a string
*
* @memberof DDBreakpoints
* @example
* // Height of 300px and above
* DDBreakpoints.getHeight(300);
*
* @example
* // Height of 300px and below
* DDBreakpoints.getHeight(0, 300);
*
* @example
* // Between 100px and 300px high
* DDBreakpoints.getHeight(100, 300);
*
* @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation
* @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels
* @return {String} Media query string
*/
export const getHeight = (min, max = 0) => {
return get(min, max, 'height');
};
/**
* Breakpoint function that takes the same inputs as get() but
* instead of returning the media query as a string returns
* if the current page matches that query as a boolean using
* window.matchMedia(mq).matches
*
* @memberof DDBreakpoints
* @example
* // returns true if the page is between xs and s
* DDBreakpoints.is('xs,s');
* DDBreakpoints.is('xs','s');
*
* @example
* // returns true if the page is between 0 and 300px wide
* DDBreakpoints.is('0,300');
* DDBreakpoints.is(0, 300);
*
* @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation
* @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels
* @param {String} [property='width'] Property to check using a media query. e.g. width or height
* @return {Boolean}
*/
export const is = (min, max = 0, property = 'width') => {
if (_options.isResponsive === false) {
return _bpIsValidForStatic(min, max, property);
}
if (window.matchMedia) {
return window.matchMedia(get(min, max, property)).matches;
}
console.warn('DD.bp: Match Media not supported by this browser. Consider adding a polyfill.');
return false;
};
/**
* Shortcut for the is() function that returns a height
* based media query and returns the media query as a boolean
*
* @memberof DDBreakpoints
* @example
* // returns true if the page is between 0 and 300px high
* DDBreakpoints.isHeight('0,300');
* DDBreakpoints.isHeight(0, 300);
*
* @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation
* @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels
* @return {Boolean}
*/
export const isHeight = (min, max = 0) => {
return is(min, max, 'height');
};
/**
* Valid options for the Breakpoints array
*
* @typedef {Object} DDBreakpointsOptionsArray
* @property {String} name Name of the breakpoint e.g. 's', 'm', 'l'
* @property {Number} px Number in px for the size of the breakpoint
*/
/**
* Valid options for the Breakpoints library
*
* @typedef {Object} DDBreakpointsOptions
* @property {Number} [baseFontSize] Number in px to be used as a base font size in order to calculate em values
* @property {DDBreakpointsOptionsArray[]} [breakpoints]
*/
/**
* User updatable options
*
* @memberof DDBreakpoints
* @example
* // update the base font size only
* DDBreakpoints.options({
* baseFontSize: 14
* });
*
* @example
* // update the breakpoints
* DDBreakpoints.options({
* breakpoints: [
* { name: 'small', px: 400 },
* { name: 'medium', px: 800 },
* { name: 'large', px: 1200 }
* ]
* });
*
* @param {DDBreakpointsOptions} opts Options inside the library to be updated
* @return {Boolean}
*/
export const options = (opts) => {
if (typeof (opts.isResponsive) === 'boolean') {
_options.isResponsive = opts.isResponsive;
}
if (typeof (opts.baseFontSize) === 'number') {
_options.baseFontSize = opts.baseFontSize;
}
if (typeof (opts.breakpoints) === 'object' && opts.breakpoints.length > 0) {
var isValid = true,
bps = opts.breakpoints;
// loop through the breakpoints to check validity
for (var i = 0, len = bps.length; i < len; i += 1) {
if ((bps[i].hasOwnProperty('name') && bps[i].hasOwnProperty('px')) === false) {
isValid = false;
}
}
if (isValid) {
_options.breakpoints = opts.breakpoints;
_initBreakpoints();
} else {
console.warn('DD.bp: Invalid breakpoints array entered. Please use the format {name: \'string\', px: number}');
return false;
}
}
return true;
};
export default {
get,
getHeight,
is,
isHeight,
options,
};