@gitlab/ui
Version:
GitLab UI Components
263 lines (248 loc) • 9.59 kB
JavaScript
import { isVisible } from '../vendor/bootstrap-vue/src/utils/dom';
export { isVisible } from '../vendor/bootstrap-vue/src/utils/dom';
import { COMMA, labelColorOptions, CONTRAST_LEVELS, focusableTags } from './constants';
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function debounceByAnimationFrame(fn) {
let requestId;
return function debounced() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (requestId) {
window.cancelAnimationFrame(requestId);
}
requestId = window.requestAnimationFrame(() => fn.apply(this, args));
};
}
function throttle(fn) {
let frameId = null;
return function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
if (frameId) {
return;
}
frameId = window.requestAnimationFrame(() => {
fn(...args);
frameId = null;
});
};
}
function rgbFromHex(hex) {
const cleanHex = hex.replace('#', '');
const rgb = cleanHex.length === 3 ? cleanHex.split('').map(val => val + val) : cleanHex.match(/[\da-f]{2}/gi);
const _rgb$map = rgb.map(val => parseInt(val, 16)),
_rgb$map2 = _slicedToArray(_rgb$map, 3),
r = _rgb$map2[0],
g = _rgb$map2[1],
b = _rgb$map2[2];
return [r, g, b];
}
function rgbFromString(color, sub) {
const rgb = color.substring(sub, color.length - 1).split(COMMA);
const _rgb$map3 = rgb.map(i => parseInt(i, 10)),
_rgb$map4 = _slicedToArray(_rgb$map3, 3),
r = _rgb$map4[0],
g = _rgb$map4[1],
b = _rgb$map4[2];
return [r, g, b];
}
function hexToRgba(hex) {
let opacity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
const _rgbFromHex = rgbFromHex(hex),
_rgbFromHex2 = _slicedToArray(_rgbFromHex, 3),
r = _rgbFromHex2[0],
g = _rgbFromHex2[1],
b = _rgbFromHex2[2];
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
function toSrgb(value) {
const normalized = value / 255;
return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
}
function relativeLuminance(rgb) {
// WCAG 2.1 formula: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
// -
// WCAG 3.0 will use APAC
// Using APAC would be the ultimate goal, but was dismissed by engineering as of now
// See https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3418#note_1370107090
return 0.2126 * toSrgb(rgb[0]) + 0.7152 * toSrgb(rgb[1]) + 0.0722 * toSrgb(rgb[2]);
}
function colorFromBackground(backgroundColor) {
let contrastRatio = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2.4;
let color;
const lightColor = rgbFromHex('#FFFFFF');
const darkColor = rgbFromHex('#18171d');
if (backgroundColor.startsWith('#')) {
color = rgbFromHex(backgroundColor);
} else if (backgroundColor.startsWith('rgba(')) {
color = rgbFromString(backgroundColor, 5);
} else if (backgroundColor.startsWith('rgb(')) {
color = rgbFromString(backgroundColor, 4);
}
const luminance = relativeLuminance(color);
const lightLuminance = relativeLuminance(lightColor);
const darkLuminance = relativeLuminance(darkColor);
const contrastLight = (lightLuminance + 0.05) / (luminance + 0.05);
const contrastDark = (luminance + 0.05) / (darkLuminance + 0.05);
// Using a default threshold contrast of 2.4 instead of 3
// as this will solve weird color combinations in the mid tones
return contrastLight >= contrastRatio || contrastLight > contrastDark ? labelColorOptions.light : labelColorOptions.dark;
}
function getColorContrast(foreground, background) {
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
const backgroundLuminance = relativeLuminance(rgbFromHex(background)) + 0.05;
const foregroundLuminance = relativeLuminance(rgbFromHex(foreground)) + 0.05;
let score = backgroundLuminance / foregroundLuminance;
if (foregroundLuminance > backgroundLuminance) {
score = 1 / score;
}
const level = CONTRAST_LEVELS.find(_ref => {
let min = _ref.min,
max = _ref.max;
return score >= min && score < max;
});
return {
score: (Math.round(score * 10) / 10).toFixed(1),
level
};
}
function uid() {
return Math.random().toString(36).substring(2);
}
/**
* Receives an element and validates that it can be focused
* @param { HTMLElement } The element we want to validate
* @return { boolean } Is the element focusable
*/
function isElementFocusable(elt) {
if (!elt) return false;
const tagName = elt.tagName;
const isValidTag = focusableTags.includes(tagName);
const hasValidType = elt.getAttribute('type') !== 'hidden';
const isDisabled = elt.getAttribute('disabled') === '' || elt.getAttribute('disabled');
const hasValidZIndex = elt.getAttribute('z-index') !== '-1';
const isInvalidAnchorTag = tagName === 'A' && !elt.getAttribute('href');
return isValidTag && hasValidType && !isDisabled && hasValidZIndex && !isInvalidAnchorTag;
}
/**
* Receives an element and validates that it is reachable via sequential keyboard navigation
* @param { HTMLElement } The element to validate
* @return { boolean } Is the element focusable in a sequential tab order
*/
function isElementTabbable(el) {
if (!el) return false;
const tabindex = parseInt(el.getAttribute('tabindex'), 10);
return tabindex > -1;
}
/**
* Receives an array of HTML elements and focus the first one possible
* @param { Array.<HTMLElement> } An array of element to potentially focus
* @return { undefined }
*/
function focusFirstFocusableElement(elts) {
const focusableElt = elts.find(el => isElementFocusable(el));
if (focusableElt) focusableElt.focus();
}
/**
* Returns true if the current environment is considered a development environment (it's not
* production or test).
*
* @returns {boolean}
*/
function isDev() {
return !['test', 'production'].includes(process.env.NODE_ENV);
}
/**
* Prints a warning message to the console in non-test and non-production environments.
* @param {string} message Message to print to the console.
* @param {Object} [context] Optional object with additional context.
* @param {string} [context.name] The name of the context of the message. Usually the component's name.
* @param {HTMLElement} [context.element] The element relevant to the message.
*/
function logWarning(message) {
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (isDev()) {
const name = context.name,
element = context.element;
const formattedMessage = name ? `[${name}] ${message}` : message;
const args = element ? [formattedMessage, element] : [formattedMessage];
console.warn(...args); // eslint-disable-line no-console
}
}
/**
* Stop default event handling and propagation
*/
function stopEvent(event) {
let _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref2$preventDefault = _ref2.preventDefault,
preventDefault = _ref2$preventDefault === void 0 ? true : _ref2$preventDefault,
_ref2$stopPropagation = _ref2.stopPropagation,
stopPropagation = _ref2$stopPropagation === void 0 ? true : _ref2$stopPropagation,
_ref2$stopImmediatePr = _ref2.stopImmediatePropagation,
stopImmediatePropagation = _ref2$stopImmediatePr === void 0 ? false : _ref2$stopImmediatePr;
if (preventDefault) {
event.preventDefault();
}
if (stopPropagation) {
event.stopPropagation();
}
if (stopImmediatePropagation) {
event.stopImmediatePropagation();
}
}
/**
* Return an Array of visible items
*/
function filterVisible(els) {
return (els || []).filter(el => isVisible(el));
}
export { colorFromBackground, debounceByAnimationFrame, filterVisible, focusFirstFocusableElement, getColorContrast, hexToRgba, isDev, isElementFocusable, isElementTabbable, logWarning, relativeLuminance, rgbFromHex, rgbFromString, stopEvent, throttle, toSrgb, uid };