@patternfly/react-core
Version:
This library provides a set of common React components for use with the PatternFly reference implementation.
1,336 lines (1,193 loc) • 1.22 MB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.PatternFlyReact = {}, global.React, global.ReactDOM));
})(this, (function (exports, React, ReactDOM) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespace(React);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __rest$1(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter$1(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
/** Joins args into a className string
*
* @param {any} args list of objects, string, or arrays to reduce
*/
function css(...args) {
// Adapted from https://github.com/JedWatson/classnames/blob/master/index.js
const classes = [];
const hasOwn = {}.hasOwnProperty;
args.filter(Boolean).forEach((arg) => {
const argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
}
else if (Array.isArray(arg) && arg.length) {
const inner = css(...arg);
if (inner) {
classes.push(inner);
}
}
else if (argType === 'object') {
for (const key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
});
return classes.join(' ');
}
Promise.resolve().then(function () { return backdrop; });
var styles$1i = {
"backdrop": "pf-c-backdrop",
"backdropOpen": "pf-c-backdrop__open"
};
const KEY_CODES = { ARROW_UP: 38, ARROW_DOWN: 40, ESCAPE_KEY: 27, TAB: 9, ENTER: 13, SPACE: 32 };
const SIDE = { RIGHT: 'right', LEFT: 'left', BOTH: 'both', NONE: 'none' };
const KEYHANDLER_DIRECTION = { UP: 'up', DOWN: 'down', RIGHT: 'right', LEFT: 'left' };
exports.ValidatedOptions = void 0;
(function (ValidatedOptions) {
ValidatedOptions["success"] = "success";
ValidatedOptions["error"] = "error";
ValidatedOptions["warning"] = "warning";
ValidatedOptions["default"] = "default";
})(exports.ValidatedOptions || (exports.ValidatedOptions = {}));
const KeyTypes = {
Tab: 'Tab',
Space: ' ',
Escape: 'Escape',
Enter: 'Enter',
ArrowUp: 'ArrowUp',
ArrowDown: 'ArrowDown',
ArrowLeft: 'ArrowLeft',
ArrowRight: 'ArrowRight'
};
/*!
* tabbable 5.1.4
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
*/
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details'];
var candidateSelector = /* #__PURE__ */candidateSelectors.join(',');
var matches = typeof Element === 'undefined' ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
var getCandidates = function getCandidates(el, includeContainer, filter) {
var candidates = Array.prototype.slice.apply(el.querySelectorAll(candidateSelector));
if (includeContainer && matches.call(el, candidateSelector)) {
candidates.unshift(el);
}
candidates = candidates.filter(filter);
return candidates;
};
var isContentEditable = function isContentEditable(node) {
return node.contentEditable === 'true';
};
var getTabindex = function getTabindex(node) {
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
if (!isNaN(tabindexAttr)) {
return tabindexAttr;
} // Browsers do not return `tabIndex` correctly for contentEditable nodes;
// so if they don't have a tabindex attribute specifically set, assume it's 0.
if (isContentEditable(node)) {
return 0;
} // in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default
// `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM,
// yet they are still part of the regular tab order; in FF, they get a default
// `tabIndex` of 0; since Chrome still puts those elements in the regular tab
// order, consider their tab index to be 0.
if ((node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) {
return 0;
}
return node.tabIndex;
};
var sortOrderedTabbables = function sortOrderedTabbables(a, b) {
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
};
var isInput$1 = function isInput(node) {
return node.tagName === 'INPUT';
};
var isHiddenInput = function isHiddenInput(node) {
return isInput$1(node) && node.type === 'hidden';
};
var isDetailsWithSummary = function isDetailsWithSummary(node) {
var r = node.tagName === 'DETAILS' && Array.prototype.slice.apply(node.children).some(function (child) {
return child.tagName === 'SUMMARY';
});
return r;
};
var getCheckedRadio = function getCheckedRadio(nodes, form) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked && nodes[i].form === form) {
return nodes[i];
}
}
};
var isTabbableRadio = function isTabbableRadio(node) {
if (!node.name) {
return true;
}
var radioScope = node.form || node.ownerDocument;
var radioSet = radioScope.querySelectorAll('input[type="radio"][name="' + node.name + '"]');
var checked = getCheckedRadio(radioSet, node.form);
return !checked || checked === node;
};
var isRadio = function isRadio(node) {
return isInput$1(node) && node.type === 'radio';
};
var isNonTabbableRadio = function isNonTabbableRadio(node) {
return isRadio(node) && !isTabbableRadio(node);
};
var isHidden = function isHidden(node) {
if (getComputedStyle(node).visibility === 'hidden') {
return true;
}
var isDirectSummary = matches.call(node, 'details>summary:first-of-type');
var nodeUnderDetails = isDirectSummary ? node.parentElement : node;
if (matches.call(nodeUnderDetails, 'details:not([open]) *')) {
return true;
}
while (node) {
if (getComputedStyle(node).display === 'none') {
return true;
}
node = node.parentElement;
}
return false;
};
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(node) {
if (node.disabled || isHiddenInput(node) || isHidden(node) ||
/* For a details element with a summary, the summary element gets the focused */
isDetailsWithSummary(node)) {
return false;
}
return true;
};
var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable(node) {
if (!isNodeMatchingSelectorFocusable(node) || isNonTabbableRadio(node) || getTabindex(node) < 0) {
return false;
}
return true;
};
var tabbable = function tabbable(el, options) {
options = options || {};
var regularTabbables = [];
var orderedTabbables = [];
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable);
candidates.forEach(function (candidate, i) {
var candidateTabindex = getTabindex(candidate);
if (candidateTabindex === 0) {
regularTabbables.push(candidate);
} else {
orderedTabbables.push({
documentOrder: i,
tabIndex: candidateTabindex,
node: candidate
});
}
});
var tabbableNodes = orderedTabbables.sort(sortOrderedTabbables).map(function (a) {
return a.node;
}).concat(regularTabbables);
return tabbableNodes;
};
var focusableCandidateSelector = /* #__PURE__ */candidateSelectors.concat('iframe').join(',');
var isFocusable = function isFocusable(node) {
if (!node) {
throw new Error('No node provided');
}
if (matches.call(node, focusableCandidateSelector) === false) {
return false;
}
return isNodeMatchingSelectorFocusable(node);
};
/*!
* focus-trap 6.2.2
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
*/
function _defineProperty$1(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty$1(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
var activeFocusDelay;
var activeFocusTraps = function () {
var trapQueue = [];
return {
activateTrap: function activateTrap(trap) {
if (trapQueue.length > 0) {
var activeTrap = trapQueue[trapQueue.length - 1];
if (activeTrap !== trap) {
activeTrap.pause();
}
}
var trapIndex = trapQueue.indexOf(trap);
if (trapIndex === -1) {
trapQueue.push(trap);
} else {
// move this existing trap to the front of the queue
trapQueue.splice(trapIndex, 1);
trapQueue.push(trap);
}
},
deactivateTrap: function deactivateTrap(trap) {
var trapIndex = trapQueue.indexOf(trap);
if (trapIndex !== -1) {
trapQueue.splice(trapIndex, 1);
}
if (trapQueue.length > 0) {
trapQueue[trapQueue.length - 1].unpause();
}
}
};
}();
var isSelectableInput = function isSelectableInput(node) {
return node.tagName && node.tagName.toLowerCase() === 'input' && typeof node.select === 'function';
};
var isEscapeEvent = function isEscapeEvent(e) {
return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
};
var isTabEvent = function isTabEvent(e) {
return e.key === 'Tab' || e.keyCode === 9;
};
var delay = function delay(fn) {
return setTimeout(fn, 0);
};
var createFocusTrap = function createFocusTrap(elements, userOptions) {
var doc = document;
var config = _objectSpread2({
returnFocusOnDeactivate: true,
escapeDeactivates: true,
delayInitialFocus: true
}, userOptions);
var state = {
// @type {Array<HTMLElement>}
containers: [],
// list of objects identifying the first and last tabbable nodes in all containers/groups in
// the trap
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap
// is active, but the trap should never get to a state where there isn't at least one group
// with at least one tabbable node in it (that would lead to an error condition that would
// result in an error being thrown)
// @type {Array<{ firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>}
tabbableGroups: [],
nodeFocusedBeforeActivation: null,
mostRecentlyFocusedNode: null,
active: false,
paused: false
};
var trap; // eslint-disable-line prefer-const -- some private functions reference it, and its methods reference private functions, so we must declare here and define later
var containersContain = function containersContain(element) {
return state.containers.some(function (container) {
return container.contains(element);
});
};
var getNodeForOption = function getNodeForOption(optionName) {
var optionValue = config[optionName];
if (!optionValue) {
return null;
}
var node = optionValue;
if (typeof optionValue === 'string') {
node = doc.querySelector(optionValue);
if (!node) {
throw new Error("`".concat(optionName, "` refers to no known node"));
}
}
if (typeof optionValue === 'function') {
node = optionValue();
if (!node) {
throw new Error("`".concat(optionName, "` did not return a node"));
}
}
return node;
};
var getInitialFocusNode = function getInitialFocusNode() {
var node;
if (getNodeForOption('initialFocus') !== null) {
node = getNodeForOption('initialFocus');
} else if (containersContain(doc.activeElement)) {
node = doc.activeElement;
} else {
var firstTabbableGroup = state.tabbableGroups[0];
var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
node = firstTabbableNode || getNodeForOption('fallbackFocus');
}
if (!node) {
throw new Error('Your focus-trap needs to have at least one focusable element');
}
return node;
};
var updateTabbableNodes = function updateTabbableNodes() {
state.tabbableGroups = state.containers.map(function (container) {
var tabbableNodes = tabbable(container);
if (tabbableNodes.length > 0) {
return {
firstTabbableNode: tabbableNodes[0],
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1]
};
}
return undefined;
}).filter(function (group) {
return !!group;
}); // remove groups with no tabbable nodes
// throw if no groups have tabbable nodes and we don't have a fallback focus node either
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus')) {
throw new Error('Your focus-trap must have at least one container with at least one tabbable node in it at all times');
}
};
var tryFocus = function tryFocus(node) {
if (node === doc.activeElement) {
return;
}
if (!node || !node.focus) {
tryFocus(getInitialFocusNode());
return;
}
node.focus({
preventScroll: !!config.preventScroll
});
state.mostRecentlyFocusedNode = node;
if (isSelectableInput(node)) {
node.select();
}
};
var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) {
var node = getNodeForOption('setReturnFocus');
return node ? node : previousActiveElement;
}; // This needs to be done on mousedown and touchstart instead of click
// so that it precedes the focus event.
var checkPointerDown = function checkPointerDown(e) {
if (containersContain(e.target)) {
// allow the click since it ocurred inside the trap
return;
}
if (config.clickOutsideDeactivates) {
// immediately deactivate the trap
trap.deactivate({
// if, on deactivation, we should return focus to the node originally-focused
// when the trap was activated (or the configured `setReturnFocus` node),
// then assume it's also OK to return focus to the outside node that was
// just clicked, causing deactivation, as long as that node is focusable;
// if it isn't focusable, then return focus to the original node focused
// on activation (or the configured `setReturnFocus` node)
// NOTE: by setting `returnFocus: false`, deactivate() will do nothing,
// which will result in the outside click setting focus to the node
// that was clicked, whether it's focusable or not; by setting
// `returnFocus: true`, we'll attempt to re-focus the node originally-focused
// on activation (or the configured `setReturnFocus` node)
returnFocus: config.returnFocusOnDeactivate && !isFocusable(e.target)
});
return;
} // This is needed for mobile devices.
// (If we'll only let `click` events through,
// then on mobile they will be blocked anyways if `touchstart` is blocked.)
if (config.allowOutsideClick && (typeof config.allowOutsideClick === 'boolean' ? config.allowOutsideClick : config.allowOutsideClick(e))) {
// allow the click outside the trap to take place
return;
} // otherwise, prevent the click
e.preventDefault();
}; // In case focus escapes the trap for some strange reason, pull it back in.
var checkFocusIn = function checkFocusIn(e) {
var targetContained = containersContain(e.target); // In Firefox when you Tab out of an iframe the Document is briefly focused.
if (targetContained || e.target instanceof Document) {
if (targetContained) {
state.mostRecentlyFocusedNode = e.target;
}
} else {
// escaped! pull it back in to where it just left
e.stopImmediatePropagation();
tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
}
}; // Hijack Tab events on the first and last focusable nodes of the trap,
// in order to prevent focus from escaping. If it escapes for even a
// moment it can end up scrolling the page and causing confusion so we
// kind of need to capture the action at the keydown phase.
var checkTab = function checkTab(e) {
updateTabbableNodes();
var destinationNode = null;
if (state.tabbableGroups.length > 0) {
if (e.shiftKey) {
var startOfGroupIndex = state.tabbableGroups.findIndex(function (_ref) {
var firstTabbableNode = _ref.firstTabbableNode;
return e.target === firstTabbableNode;
});
if (startOfGroupIndex >= 0) {
var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
var destinationGroup = state.tabbableGroups[destinationGroupIndex];
destinationNode = destinationGroup.lastTabbableNode;
}
} else {
var lastOfGroupIndex = state.tabbableGroups.findIndex(function (_ref2) {
var lastTabbableNode = _ref2.lastTabbableNode;
return e.target === lastTabbableNode;
});
if (lastOfGroupIndex >= 0) {
var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
destinationNode = _destinationGroup.firstTabbableNode;
}
}
} else {
destinationNode = getNodeForOption('fallbackFocus');
}
if (destinationNode) {
e.preventDefault();
tryFocus(destinationNode);
}
};
var checkKey = function checkKey(e) {
if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
e.preventDefault();
trap.deactivate();
return;
}
if (isTabEvent(e)) {
checkTab(e);
return;
}
};
var checkClick = function checkClick(e) {
if (config.clickOutsideDeactivates) {
return;
}
if (containersContain(e.target)) {
return;
}
if (config.allowOutsideClick && (typeof config.allowOutsideClick === 'boolean' ? config.allowOutsideClick : config.allowOutsideClick(e))) {
return;
}
e.preventDefault();
e.stopImmediatePropagation();
}; //
// EVENT LISTENERS
//
var addListeners = function addListeners() {
if (!state.active) {
return;
} // There can be only one listening focus trap at a time
activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
// that caused the focus trap activation.
activeFocusDelay = config.delayInitialFocus ? delay(function () {
tryFocus(getInitialFocusNode());
}) : tryFocus(getInitialFocusNode());
doc.addEventListener('focusin', checkFocusIn, true);
doc.addEventListener('mousedown', checkPointerDown, {
capture: true,
passive: false
});
doc.addEventListener('touchstart', checkPointerDown, {
capture: true,
passive: false
});
doc.addEventListener('click', checkClick, {
capture: true,
passive: false
});
doc.addEventListener('keydown', checkKey, {
capture: true,
passive: false
});
return trap;
};
var removeListeners = function removeListeners() {
if (!state.active) {
return;
}
doc.removeEventListener('focusin', checkFocusIn, true);
doc.removeEventListener('mousedown', checkPointerDown, true);
doc.removeEventListener('touchstart', checkPointerDown, true);
doc.removeEventListener('click', checkClick, true);
doc.removeEventListener('keydown', checkKey, true);
return trap;
}; //
// TRAP DEFINITION
//
trap = {
activate: function activate(activateOptions) {
if (state.active) {
return this;
}
updateTabbableNodes();
state.active = true;
state.paused = false;
state.nodeFocusedBeforeActivation = doc.activeElement;
var onActivate = activateOptions && activateOptions.onActivate ? activateOptions.onActivate : config.onActivate;
if (onActivate) {
onActivate();
}
addListeners();
return this;
},
deactivate: function deactivate(deactivateOptions) {
if (!state.active) {
return this;
}
clearTimeout(activeFocusDelay);
removeListeners();
state.active = false;
state.paused = false;
activeFocusTraps.deactivateTrap(trap);
var onDeactivate = deactivateOptions && deactivateOptions.onDeactivate !== undefined ? deactivateOptions.onDeactivate : config.onDeactivate;
if (onDeactivate) {
onDeactivate();
}
var returnFocus = deactivateOptions && deactivateOptions.returnFocus !== undefined ? deactivateOptions.returnFocus : config.returnFocusOnDeactivate;
if (returnFocus) {
delay(function () {
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
});
}
return this;
},
pause: function pause() {
if (state.paused || !state.active) {
return this;
}
state.paused = true;
removeListeners();
return this;
},
unpause: function unpause() {
if (!state.paused || !state.active) {
return this;
}
state.paused = false;
updateTabbableNodes();
addListeners();
return this;
},
updateContainerElements: function updateContainerElements(containerElements) {
var elementsAsArray = [].concat(containerElements).filter(Boolean);
state.containers = elementsAsArray.map(function (element) {
return typeof element === 'string' ? doc.querySelector(element) : element;
});
if (state.active) {
updateTabbableNodes();
}
return this;
}
}; // initialize container elements
trap.updateContainerElements(elements);
return trap;
};
class FocusTrap extends React__namespace.Component {
constructor(props) {
super(props);
this.divRef = React__namespace.createRef();
if (typeof document !== 'undefined') {
this.previouslyFocusedElement = document.activeElement;
}
}
componentDidMount() {
// We need to hijack the returnFocusOnDeactivate option,
// because React can move focus into the element before we arrived at
// this lifecycle hook (e.g. with autoFocus inputs). So the component
// captures the previouslyFocusedElement in componentWillMount,
// then (optionally) returns focus to it in componentWillUnmount.
this.focusTrap = createFocusTrap(this.divRef.current, Object.assign(Object.assign({}, this.props.focusTrapOptions), { returnFocusOnDeactivate: false }));
if (this.props.active) {
this.focusTrap.activate();
}
if (this.props.paused) {
this.focusTrap.pause();
}
}
componentDidUpdate(prevProps) {
if (prevProps.active && !this.props.active) {
this.focusTrap.deactivate();
}
else if (!prevProps.active && this.props.active) {
this.focusTrap.activate();
}
if (prevProps.paused && !this.props.paused) {
this.focusTrap.unpause();
}
else if (!prevProps.paused && this.props.paused) {
this.focusTrap.pause();
}
}
componentWillUnmount() {
this.focusTrap.deactivate();
if (this.props.focusTrapOptions.returnFocusOnDeactivate !== false &&
this.previouslyFocusedElement &&
this.previouslyFocusedElement.focus) {
this.previouslyFocusedElement.focus({ preventScroll: this.props.preventScrollOnDeactivate });
}
}
render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _a = this.props, { children, className, focusTrapOptions, active, paused, preventScrollOnDeactivate } = _a, rest = __rest$1(_a, ["children", "className", "focusTrapOptions", "active", "paused", "preventScrollOnDeactivate"]);
return (React__namespace.createElement("div", Object.assign({ ref: this.divRef, className: className }, rest), children));
}
}
FocusTrap.displayName = 'FocusTrap';
FocusTrap.defaultProps = {
active: true,
paused: false,
focusTrapOptions: {},
preventScrollOnDeactivate: false
};
/** This Component can be used to wrap a functional component in order to generate a random ID
* Example of how to use this component
*
* const Component = ({id}: {id: string}) => (
* <GenerateId>{randomId => (
* <div id={id || randomId}>
* div with random ID
* </div>
* )}
* </GenerateId>
* );
*/
let currentId$3 = 0;
class GenerateId extends React__namespace.Component {
constructor() {
super(...arguments);
this.id = `${this.props.prefix}${currentId$3++}`;
}
render() {
return this.props.children(this.id);
}
}
GenerateId.displayName = 'GenerateId';
GenerateId.defaultProps = {
prefix: 'pf-random-id-'
};
const ASTERISK = '*';
let uid = 0;
const ouiaPrefix = 'OUIA-Generated-';
const ouiaIdByRoute = {};
/** Get props to conform to OUIA spec
*
* For functional components, use the useOUIAProps function instead
*
* In class based components, create a state variable ouiaStateId to create a static generated ID:
* state = {
* ouiaStateId: getDefaultOUIAId(Chip.displayName)
* }
* This generated ID should remain alive as long as the component is not unmounted.
*
* Then add the attributes to the component
* {...getOUIAProps('OverflowChip', this.props.ouiaId !== undefined ? this.props.ouiaId : this.state.ouiaStateId)}
*
* @param {string} componentType OUIA component type
* @param {number|string} id OUIA component id
* @param {boolean} ouiaSafe false if in animation
*/
function getOUIAProps(componentType, id, ouiaSafe = true) {
return {
'data-ouia-component-type': `PF4/${componentType}`,
'data-ouia-safe': ouiaSafe,
'data-ouia-component-id': id
};
}
/**
* Hooks version of the getOUIAProps function that also memoizes the generated ID
* Can only be used in functional components
*
* @param {string} componentType OUIA component type
* @param {number|string} id OUIA component id
* @param {boolean} ouiaSafe false if in animation
* @param {string} variant Optional variant to add to the generated ID
*/
const useOUIAProps = (componentType, id, ouiaSafe = true, variant) => ({
'data-ouia-component-type': `PF4/${componentType}`,
'data-ouia-safe': ouiaSafe,
'data-ouia-component-id': useOUIAId(componentType, id, variant)
});
/**
* Returns the ID or the memoized generated ID
*
* @param {string} componentType OUIA component type
* @param {number|string} id OUIA component id
* @param {string} variant Optional variant to add to the generated ID
*/
const useOUIAId = (componentType, id, variant) => {
if (id !== undefined) {
return id;
}
return React.useMemo(() => getDefaultOUIAId(componentType, variant), [componentType, variant]);
};
/**
* Returns a generated id based on the URL location
*
* @param {string} componentType OUIA component type
* @param {string} variant Optional variant to add to the generated ID
*/
function getDefaultOUIAId(componentType, variant) {
/*
ouiaIdByRoute = {
[route+componentType]: [number]
}
*/
try {
let key;
if (typeof window !== 'undefined') {
// browser environments
key = `${window.location.href}-${componentType}-${variant || ''}`;
}
else {
// node/SSR environments
key = `${componentType}-${variant || ''}`;
}
if (!ouiaIdByRoute[key]) {
ouiaIdByRoute[key] = 0;
}
return `${ouiaPrefix}${componentType}-${variant ? `${variant}-` : ''}${++ouiaIdByRoute[key]}`;
}
catch (exception) {
return `${ouiaPrefix}${componentType}-${variant ? `${variant}-` : ''}${++uid}`;
}
}
/**
* @param {string} input - String to capitalize first letter
*/
function capitalize(input) {
return input[0].toUpperCase() + input.substring(1);
}
/**
* @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}`;
}
/**
* @param { any } this - "This" reference
* @param { Function } func - Function to debounce
* @param { number } wait - Debounce amount
*/
function debounce$1(func, wait) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
/** 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;
}
/** 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 = SIDE.NONE;
if (isOffRight && isOffLeft) {
side = SIDE.BOTH;
}
else if (isOffRight) {
side = SIDE.RIGHT;
}
else if (isOffLeft) {
side = SIDE.LEFT;
}
// Return outcome
return side;
}
/** 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] || '');
}
/**
* 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__namespace.findDOMNode(refsCollection[nextIndex]);
element.focus();
}
else if (position !== 'tab') {
if (isMultiDimensional) {
refsCollection[nextIndex][nextInnerIndex].focus();
}
else {
refsCollection[nextIndex].focus();
}
}
}
/** 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;
}
/** 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;
}
}
/** 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}`;
}
/**
* 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 }), {});
/**
* 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) => {
if (!mods) {
return '';
}
if (breakpoint) {
if (breakpoint in mods) {
return styles.modifiers[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[toCamel(`${stylePrefix}${mods[breakpointsOrder[i]]}`)];
}
}
return '';
}
return Object.entries(mods || {})
.map(([breakpoint, mod]) => `${stylePrefix}${mod}${breakpoint !== 'default' ? `-on-${breakpoint}` : ''}`)
.map(toCamel)
.map(mod => mod.replace(/-?(\dxl)/gi, (_res, group) => `_${group}`))
.map(modifierKey => styles.modifiers[modifierKey])
.filter(Boolean)
.join(' ');
};
/**
* 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 >= 1450) {
return '2xl';
}
if (width >= 1200) {
return 'xl';
}
if (width >= 992) {
return 'lg';
}
if (width >= 768) {
return 'md';
}
if (width >= 576) {
return 'sm';
}
return 'default';
};
const camelize = (s) => s
.toUpperCase()
.replace('-', '')
.replace('_', '');
/**
*
* @param {string} s string to make camelCased
*/
const toCamel = (s) => s.replace(/([-_][a-z])/gi, camelize);
/**
* Copied from exenv
*/
const 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)