package.dist.js.helpers.util.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of react-core Show documentation
Show all versions of react-core Show documentation
This library provides a set of common React components for use with the PatternFly reference implementation.
The newest version!
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLanguageDirection = exports.clearTimeouts = exports.preventedEvents = exports.trimLeft = exports.innerDimensions = exports.getTextWidth = exports.canUseDOM = exports.toCamel = exports.getBreakpoint = exports.getVerticalBreakpoint = exports.formatBreakpointMods = exports.setBreakpointCssVars = exports.pluralize = exports.getNextIndex = exports.findTabbableElements = exports.keyHandler = exports.fillTemplate = exports.sideElementIsOutOfView = exports.isElementInView = exports.debounce = exports.getUniqueId = exports.capitalize = void 0;
const tslib_1 = require("tslib");
const ReactDOM = tslib_1.__importStar(require("react-dom"));
const constants_1 = require("./constants");
/**
* @param {string} input - String to capitalize first letter
*/
function capitalize(input) {
return input[0].toUpperCase() + input.substring(1);
}
exports.capitalize = capitalize;
/**
* @param {string} prefix - String to prefix ID with
*/
function getUniqueId(prefix = 'pf') {
const uid = new Date().getTime() + Math.random().toString(36).slice(2);
return `${prefix}-${uid}`;
}
exports.getUniqueId = getUniqueId;
/**
* @param { any } this - "This" reference
* @param { Function } func - Function to debounce
* @param { number } wait - Debounce amount
*/
function debounce(func, wait) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
exports.debounce = debounce;
/** This function returns whether or not an element is within the viewable area of a container. If partial is true,
* then this function will return true even if only part of the element is in view.
*
* @param {HTMLElement} container The container to check if the element is in view of.
* @param {HTMLElement} element The element to check if it is view
* @param {boolean} partial true if partial view is allowed
* @param {boolean} strict true if strict mode is set, never consider the container width and element width
*
* @returns { boolean } True if the component is in View.
*/
function isElementInView(container, element, partial, strict = false) {
if (!container || !element) {
return false;
}
const containerBounds = container.getBoundingClientRect();
const elementBounds = element.getBoundingClientRect();
const containerBoundsLeft = Math.ceil(containerBounds.left);
const containerBoundsRight = Math.floor(containerBounds.right);
const elementBoundsLeft = Math.ceil(elementBounds.left);
const elementBoundsRight = Math.floor(elementBounds.right);
// Check if in view
const isTotallyInView = elementBoundsLeft >= containerBoundsLeft && elementBoundsRight <= containerBoundsRight;
const isPartiallyInView = (partial || (!strict && containerBounds.width < elementBounds.width)) &&
((elementBoundsLeft < containerBoundsLeft && elementBoundsRight > containerBoundsLeft) ||
(elementBoundsRight > containerBoundsRight && elementBoundsLeft < containerBoundsRight));
// Return outcome
return isTotallyInView || isPartiallyInView;
}
exports.isElementInView = isElementInView;
/** This function returns the side the element is out of view on (right, left or both)
*
* @param {HTMLElement} container The container to check if the element is in view of.
* @param {HTMLElement} element The element to check if it is view
*
* @returns {string} right if the element is of the right, left if element is off the left or both if it is off on both sides.
*/
function sideElementIsOutOfView(container, element) {
const containerBounds = container.getBoundingClientRect();
const elementBounds = element.getBoundingClientRect();
const containerBoundsLeft = Math.floor(containerBounds.left);
const containerBoundsRight = Math.floor(containerBounds.right);
const elementBoundsLeft = Math.floor(elementBounds.left);
const elementBoundsRight = Math.floor(elementBounds.right);
// Check if in view
const isOffLeft = elementBoundsLeft < containerBoundsLeft;
const isOffRight = elementBoundsRight > containerBoundsRight;
let side = constants_1.SIDE.NONE;
if (isOffRight && isOffLeft) {
side = constants_1.SIDE.BOTH;
}
else if (isOffRight) {
side = constants_1.SIDE.RIGHT;
}
else if (isOffLeft) {
side = constants_1.SIDE.LEFT;
}
// Return outcome
return side;
}
exports.sideElementIsOutOfView = sideElementIsOutOfView;
/** Interpolates a parameterized templateString using values from a templateVars object.
* The templateVars object should have keys and values which match the templateString's parameters.
* Example:
* const templateString: 'My name is ${firstName} ${lastName}';
* const templateVars: {
* firstName: 'Jon'
* lastName: 'Dough'
* };
* const result = fillTemplate(templateString, templateVars);
* // "My name is Jon Dough"
*
* @param {string} templateString The string passed by the consumer
* @param {object} templateVars The variables passed to the string
*
* @returns {string} The template string literal result
*/
function fillTemplate(templateString, templateVars) {
return templateString.replace(/\${(.*?)}/g, (_, match) => templateVars[match] || '');
}
exports.fillTemplate = fillTemplate;
/**
* This function allows for keyboard navigation through dropdowns. The custom argument is optional.
*
* @param {number} index The index of the element you're on
* @param {number} innerIndex Inner index number
* @param {string} position The orientation of the dropdown
* @param {string[]} refsCollection Array of refs to the items in the dropdown
* @param {object[]} kids Array of items in the dropdown
* @param {boolean} [custom] Allows for handling of flexible content
*/
function keyHandler(index, innerIndex, position, refsCollection, kids, custom = false) {
if (!Array.isArray(kids)) {
return;
}
const isMultiDimensional = refsCollection.filter((ref) => ref)[0].constructor === Array;
let nextIndex = index;
let nextInnerIndex = innerIndex;
if (position === 'up') {
if (index === 0) {
// loop back to end
nextIndex = kids.length - 1;
}
else {
nextIndex = index - 1;
}
}
else if (position === 'down') {
if (index === kids.length - 1) {
// loop back to beginning
nextIndex = 0;
}
else {
nextIndex = index + 1;
}
}
else if (position === 'left') {
if (innerIndex === 0) {
nextInnerIndex = refsCollection[index].length - 1;
}
else {
nextInnerIndex = innerIndex - 1;
}
}
else if (position === 'right') {
if (innerIndex === refsCollection[index].length - 1) {
nextInnerIndex = 0;
}
else {
nextInnerIndex = innerIndex + 1;
}
}
if (refsCollection[nextIndex] === null ||
refsCollection[nextIndex] === undefined ||
(isMultiDimensional &&
(refsCollection[nextIndex][nextInnerIndex] === null || refsCollection[nextIndex][nextInnerIndex] === undefined))) {
keyHandler(nextIndex, nextInnerIndex, position, refsCollection, kids, custom);
}
else if (custom) {
if (refsCollection[nextIndex].focus) {
refsCollection[nextIndex].focus();
}
// eslint-disable-next-line react/no-find-dom-node
const element = ReactDOM.findDOMNode(refsCollection[nextIndex]);
element.focus();
}
else if (position !== 'tab') {
if (isMultiDimensional) {
refsCollection[nextIndex][nextInnerIndex].focus();
}
else {
refsCollection[nextIndex].focus();
}
}
}
exports.keyHandler = keyHandler;
/** This function returns a list of tabbable items in a container
*
* @param {any} containerRef to the container
* @param {string} tababbleSelectors CSS selector string of tabbable items
*/
function findTabbableElements(containerRef, tababbleSelectors) {
const tabbable = containerRef.current.querySelectorAll(tababbleSelectors);
const list = Array.prototype.filter.call(tabbable, function (item) {
return item.tabIndex >= '0';
});
return list;
}
exports.findTabbableElements = findTabbableElements;
/** This function is a helper for keyboard navigation through dropdowns.
*
* @param {number} index The index of the element you're on
* @param {string} position The orientation of the dropdown
* @param {string[]} collection Array of refs to the items in the dropdown
*/
function getNextIndex(index, position, collection) {
let nextIndex;
if (position === 'up') {
if (index === 0) {
// loop back to end
nextIndex = collection.length - 1;
}
else {
nextIndex = index - 1;
}
}
else if (index === collection.length - 1) {
// loop back to beginning
nextIndex = 0;
}
else {
nextIndex = index + 1;
}
if (collection[nextIndex] === undefined || collection[nextIndex][0] === null) {
return getNextIndex(nextIndex, position, collection);
}
else {
return nextIndex;
}
}
exports.getNextIndex = getNextIndex;
/** This function is a helper for pluralizing strings.
*
* @param {number} i The quantity of the string you want to pluralize
* @param {string} singular The singular version of the string
* @param {string} plural The change to the string that should occur if the quantity is not equal to 1.
* Defaults to adding an 's'.
*/
function pluralize(i, singular, plural) {
if (!plural) {
plural = `${singular}s`;
}
return `${i || 0} ${i === 1 ? singular : plural}`;
}
exports.pluralize = pluralize;
/**
* This function is a helper for turning arrays of breakpointMod objects for flex and grid into style object
*
* @param {object} mods The modifiers object
* @param {string} css-variable The appropriate css variable for the component
*/
const setBreakpointCssVars = (mods, cssVar) => Object.entries(mods || {}).reduce((acc, [breakpoint, value]) => breakpoint === 'default' ? Object.assign(Object.assign({}, acc), { [cssVar]: value }) : Object.assign(Object.assign({}, acc), { [`${cssVar}-on-${breakpoint}`]: value }), {});
exports.setBreakpointCssVars = setBreakpointCssVars;
/**
* This function is a helper for turning arrays of breakpointMod objects for data toolbar and flex into classes
*
* @param {object} mods The modifiers object
* @param {any} styles The appropriate styles object for the component
*/
const formatBreakpointMods = (mods, styles, stylePrefix = '', breakpoint, vertical) => {
if (!mods) {
return '';
}
if (breakpoint && !vertical) {
if (breakpoint in mods) {
return styles.modifiers[(0, exports.toCamel)(`${stylePrefix}${mods[breakpoint]}`)];
}
// the current breakpoint is not specified in mods, so we try to find the next nearest
const breakpointsOrder = ['2xl', 'xl', 'lg', 'md', 'sm', 'default'];
const breakpointsIndex = breakpointsOrder.indexOf(breakpoint);
for (let i = breakpointsIndex; i < breakpointsOrder.length; i++) {
if (breakpointsOrder[i] in mods) {
return styles.modifiers[(0, exports.toCamel)(`${stylePrefix}${mods[breakpointsOrder[i]]}`)];
}
}
return '';
}
return Object.entries(mods || {})
.map(([breakpoint, mod]) => `${stylePrefix}${mod}${breakpoint !== 'default' ? `-on-${breakpoint}` : ''}${vertical && breakpoint !== 'default' ? '-height' : ''}`)
.map(exports.toCamel)
.map((mod) => mod.replace(/-?(\dxl)/gi, (_res, group) => `_${group}`))
.map((modifierKey) => styles.modifiers[modifierKey])
.filter(Boolean)
.join(' ');
};
exports.formatBreakpointMods = formatBreakpointMods;
/**
* Return the breakpoint for the given height
*
* @param {number | null} height The height to check
* @returns {'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'} The breakpoint
*/
const getVerticalBreakpoint = (height) => {
if (height === null) {
return null;
}
if (height >= constants_1.globalHeightBreakpoints['2xl']) {
return '2xl';
}
if (height >= constants_1.globalHeightBreakpoints.xl) {
return 'xl';
}
if (height >= constants_1.globalHeightBreakpoints.lg) {
return 'lg';
}
if (height >= constants_1.globalHeightBreakpoints.md) {
return 'md';
}
if (height >= constants_1.globalHeightBreakpoints.sm) {
return 'sm';
}
return 'default';
};
exports.getVerticalBreakpoint = getVerticalBreakpoint;
/**
* Return the breakpoint for the given width
*
* @param {number | null} width The width to check
* @returns {'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'} The breakpoint
*/
const getBreakpoint = (width) => {
if (width === null) {
return null;
}
if (width >= constants_1.globalWidthBreakpoints['2xl']) {
return '2xl';
}
if (width >= constants_1.globalWidthBreakpoints.xl) {
return 'xl';
}
if (width >= constants_1.globalWidthBreakpoints.lg) {
return 'lg';
}
if (width >= constants_1.globalWidthBreakpoints.md) {
return 'md';
}
if (width >= constants_1.globalWidthBreakpoints.sm) {
return 'sm';
}
return 'default';
};
exports.getBreakpoint = getBreakpoint;
const camelize = (s) => s.toUpperCase().replace('-', '').replace('_', '');
/**
*
* @param {string} s string to make camelCased
*/
const toCamel = (s) => s.replace(/([-_][a-z])/gi, camelize);
exports.toCamel = toCamel;
/**
* Copied from exenv
*/
exports.canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
/**
* Calculate the width of the text
* Example:
* getTextWidth('my text', node)
*
* @param {string} text The text to calculate the width for
* @param {HTMLElement} node The HTML element
*/
const getTextWidth = (text, node) => {
const computedStyle = getComputedStyle(node);
// Firefox returns the empty string for .font, so this function creates the .font property manually
const getFontFromComputedStyle = () => {
let computedFont = '';
// Firefox uses percentages for font-stretch, but Canvas does not accept percentages
// so convert to keywords, as listed at:
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch
const fontStretchLookupTable = {
'50%': 'ultra-condensed',
'62.5%': 'extra-condensed',
'75%': 'condensed',
'87.5%': 'semi-condensed',
'100%': 'normal',
'112.5%': 'semi-expanded',
'125%': 'expanded',
'150%': 'extra-expanded',
'200%': 'ultra-expanded'
};
// If the retrieved font-stretch percentage isn't found in the lookup table, use
// 'normal' as a last resort.
let fontStretch;
if (computedStyle.fontStretch in fontStretchLookupTable) {
fontStretch = fontStretchLookupTable[computedStyle.fontStretch];
}
else {
fontStretch = 'normal';
}
computedFont =
computedStyle.fontStyle +
' ' +
computedStyle.fontVariant +
' ' +
computedStyle.fontWeight +
' ' +
fontStretch +
' ' +
computedStyle.fontSize +
'/' +
computedStyle.lineHeight +
' ' +
computedStyle.fontFamily;
return computedFont;
};
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = computedStyle.font || getFontFromComputedStyle();
return context.measureText(text).width;
};
exports.getTextWidth = getTextWidth;
/**
* Get the inner dimensions of an element
*
* @param {HTMLElement} node HTML element to calculate the inner dimensions for
*/
const innerDimensions = (node) => {
const computedStyle = getComputedStyle(node);
let width = node.clientWidth; // width with padding
let height = node.clientHeight; // height with padding
height -= parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom);
width -= parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight);
return { height, width };
};
exports.innerDimensions = innerDimensions;
/**
* This function is a helper for truncating text content on the left, leaving the right side of the content in view
*
* @param {HTMLElement} node HTML element
* @param {string} value The original text value
*/
const trimLeft = (node, value) => {
const availableWidth = (0, exports.innerDimensions)(node).width;
let newValue = value;
if ((0, exports.getTextWidth)(value, node) > availableWidth) {
// we have text overflow, trim the text to the left and add ... in the front until it fits
while ((0, exports.getTextWidth)(`...${newValue}`, node) > availableWidth) {
newValue = newValue.substring(1);
}
// replace text with our truncated text
if (node.value) {
node.value = `...${newValue}`;
}
else {
node.innerText = `...${newValue}`;
}
}
else {
if (node.value) {
node.value = value;
}
else {
node.innerText = value;
}
}
};
exports.trimLeft = trimLeft;
/**
* @param {string[]} events - Operations to prevent when disabled
*/
const preventedEvents = (events) => events.reduce((handlers, eventToPrevent) => (Object.assign(Object.assign({}, handlers), { [eventToPrevent]: (event) => {
event.preventDefault();
} })), {});
exports.preventedEvents = preventedEvents;
/**
* @param {React.RefObject[]} timeoutRefs - Timeout refs to clear
*/
const clearTimeouts = (timeoutRefs) => {
timeoutRefs.forEach((ref) => {
if (ref.current) {
clearTimeout(ref.current);
}
});
};
exports.clearTimeouts = clearTimeouts;
/**
* Helper function to get the language direction of a given element, useful for figuring out if left-to-right
* or right-to-left specific logic should be applied.
*
* @param {HTMLElement} targetElement - Element the helper will get the language direction of
* @param {'ltr' | 'rtl'} defaultDirection - Language direction to assume if one can't be determined, defaults to 'ltr'
* @returns {'ltr' | 'rtl'} - The language direction of the target element
*/
const getLanguageDirection = (targetElement, defaultDirection = 'ltr') => {
if (!targetElement) {
return defaultDirection;
}
const computedDirection = getComputedStyle(targetElement).getPropertyValue('direction');
if (['ltr', 'rtl'].includes(computedDirection)) {
return computedDirection;
}
return defaultDirection;
};
exports.getLanguageDirection = getLanguageDirection;
//# sourceMappingURL=util.js.map