@financial-times/o-grid
Version:
A 12 column responsive, flexbox-based grid system for laying out documents, templates and components.
142 lines (130 loc) • 4.34 kB
JavaScript
/* eslint-disable no-console */
const missingDataMessage = 'Could not find layout information. ' +
'You may need to include o-grid css. See the README (https://registry.origami.ft.com/components/o-grid/readme) ' +
'for more information.';
/**
* Grab grid properties
*
* @returns {object} layout names and gutter widths
*/
function getGridProperties() {
const properties = getGridFromDoc('after');
if (Object.keys(properties).length === 0) {
console.warn(missingDataMessage);
}
return properties;
}
/**
* Get all layout sizes.
* CSS must be included so JavaScript can retrieve layout information.
* See the README for more information.
*
* @returns {object} layout names and sizes
*/
function getGridBreakpoints() {
const breakpoints = getGridFromDoc('before');
if (Object.keys(breakpoints).length === 0) {
console.warn(missingDataMessage);
}
return breakpoints;
}
/**
* Grab grid properties surfaced in html:after and html:before's content
*
* @param {string} position Whether to get all properties in :before, or current properties in :after
* @returns {object} layout names and gutter widths
*/
function getGridFromDoc(position) {
// Contained in a try/catch as it should not error if o-grid styles are not (deliberately or accidentally) loaded
// e.g. o-tracking will always try to read this property, but the page is not obliged to use o-grid for layout
try {
let gridProperties = window.getComputedStyle(document.documentElement, ':' + position).getPropertyValue('content');
// Firefox computes: "{\"foo\": \"bar\"}"
// We want readable JSON: {"foo": "bar"}
gridProperties = gridProperties.replace(/'/g, '').replace(/\\/g, '').replace(/^"/, '').replace(/"$/, '');
return JSON.parse(gridProperties);
} catch (e) {
return {};
}
}
/**
* Grab the current layout.
* CSS must be included so JavaScript can retrieve layout information.
* See the README for more information.
*
* @returns {string} Layout name
*/
function getCurrentLayout() {
return getGridProperties().layout;
}
/**
* Grab the current space between columns.
* CSS must be included so JavaScript can retrieve layout information.
* See the README for more information.
*
* @returns {string} Gutter width in pixels
*/
function getCurrentGutter() {
return getGridProperties().gutter;
}
/**
* This sets MediaQueryListeners on all the o-grid breakpoints
* and fires a `o-grid.layoutChange` event on layout change.
* CSS must be included so JavaScript can retrieve layout information.
* See the README for more information.
*
* @returns {void}
*/
function enableLayoutChangeEvents() {
// Create a map containing all breakpoints exposed via html:before
const gridLayouts = getGridBreakpoints();
// eslint-disable-next-line no-prototype-builtins
if (gridLayouts.hasOwnProperty('layouts')) {
const layouts = gridLayouts.layouts;
const breakpoints = [
...Object.entries(layouts),
['default', '240px']
].sort((a, b) => parseFloat(a[1]) - parseFloat(b[1]));
const setupQuery = (query, size) => {
// matchMedia listener handler: Dispatch `o-grid.layoutChange` event if a match
const handleMQChange = mql => {
if (mql.matches) {
window.dispatchEvent(new CustomEvent('o-grid.layoutChange', {
detail: {
layout: size,
}
}));
}
};
const mql = window.matchMedia(query);
mql.addListener(handleMQChange);
handleMQChange(mql);
};
// Generate media queries for each
const decr1 = val => `${Number(val.replace('px', '') - 1)}px`;
for (let index = 0; index < breakpoints.length; index++) {
const [layoutName, layoutWidth] = breakpoints[index];
const isLast = index === breakpoints.length - 1;
if (isLast) {
setupQuery(`(min-width: ${layoutWidth})`, layoutName);
continue;
}
const [,nextLayoutWidth] = breakpoints[index + 1];
setupQuery(`(min-width: ${layoutWidth}) and (max-width: ${decr1(nextLayoutWidth)})`, layoutName);
}
} else {
console.error('Could not enable grid layout change events. Include o-grid css. See the README (https://registry.origami.ft.com/components/o-grid/readme) for more details.');
}
}
export {
getCurrentLayout,
getCurrentGutter,
getGridBreakpoints,
enableLayoutChangeEvents
};
export default {
getCurrentLayout,
getCurrentGutter,
getGridBreakpoints,
enableLayoutChangeEvents
};