UNPKG

17 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.preventedEvents = exports.trimLeft = exports.innerDimensions = exports.getTextWidth = exports.canUseDOM = exports.toCamel = exports.getBreakpoint = 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;
4const tslib_1 = require("tslib");
5const ReactDOM = tslib_1.__importStar(require("react-dom"));
6const constants_1 = require("./constants");
7/**
8 * @param {string} input - String to capitalize first letter
9 */
10function capitalize(input) {
11 return input[0].toUpperCase() + input.substring(1);
12}
13exports.capitalize = capitalize;
14/**
15 * @param {string} prefix - String to prefix ID with
16 */
17function getUniqueId(prefix = 'pf') {
18 const uid = new Date().getTime() +
19 Math.random()
20 .toString(36)
21 .slice(2);
22 return `${prefix}-${uid}`;
23}
24exports.getUniqueId = getUniqueId;
25/**
26 * @param { any } this - "This" reference
27 * @param { Function } func - Function to debounce
28 * @param { number } wait - Debounce amount
29 */
30function debounce(func, wait) {
31 let timeout;
32 return (...args) => {
33 clearTimeout(timeout);
34 timeout = setTimeout(() => func.apply(this, args), wait);
35 };
36}
37exports.debounce = debounce;
38/** This function returns whether or not an element is within the viewable area of a container. If partial is true,
39 * then this function will return true even if only part of the element is in view.
40 *
41 * @param {HTMLElement} container The container to check if the element is in view of.
42 * @param {HTMLElement} element The element to check if it is view
43 * @param {boolean} partial true if partial view is allowed
44 * @param {boolean} strict true if strict mode is set, never consider the container width and element width
45 *
46 * @returns { boolean } True if the component is in View.
47 */
48function isElementInView(container, element, partial, strict = false) {
49 if (!container || !element) {
50 return false;
51 }
52 const containerBounds = container.getBoundingClientRect();
53 const elementBounds = element.getBoundingClientRect();
54 const containerBoundsLeft = Math.ceil(containerBounds.left);
55 const containerBoundsRight = Math.floor(containerBounds.right);
56 const elementBoundsLeft = Math.ceil(elementBounds.left);
57 const elementBoundsRight = Math.floor(elementBounds.right);
58 // Check if in view
59 const isTotallyInView = elementBoundsLeft >= containerBoundsLeft && elementBoundsRight <= containerBoundsRight;
60 const isPartiallyInView = (partial || (!strict && containerBounds.width < elementBounds.width)) &&
61 ((elementBoundsLeft < containerBoundsLeft && elementBoundsRight > containerBoundsLeft) ||
62 (elementBoundsRight > containerBoundsRight && elementBoundsLeft < containerBoundsRight));
63 // Return outcome
64 return isTotallyInView || isPartiallyInView;
65}
66exports.isElementInView = isElementInView;
67/** This function returns the side the element is out of view on (right, left or both)
68 *
69 * @param {HTMLElement} container The container to check if the element is in view of.
70 * @param {HTMLElement} element The element to check if it is view
71 *
72 * @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.
73 */
74function sideElementIsOutOfView(container, element) {
75 const containerBounds = container.getBoundingClientRect();
76 const elementBounds = element.getBoundingClientRect();
77 const containerBoundsLeft = Math.floor(containerBounds.left);
78 const containerBoundsRight = Math.floor(containerBounds.right);
79 const elementBoundsLeft = Math.floor(elementBounds.left);
80 const elementBoundsRight = Math.floor(elementBounds.right);
81 // Check if in view
82 const isOffLeft = elementBoundsLeft < containerBoundsLeft;
83 const isOffRight = elementBoundsRight > containerBoundsRight;
84 let side = constants_1.SIDE.NONE;
85 if (isOffRight && isOffLeft) {
86 side = constants_1.SIDE.BOTH;
87 }
88 else if (isOffRight) {
89 side = constants_1.SIDE.RIGHT;
90 }
91 else if (isOffLeft) {
92 side = constants_1.SIDE.LEFT;
93 }
94 // Return outcome
95 return side;
96}
97exports.sideElementIsOutOfView = sideElementIsOutOfView;
98/** Interpolates a parameterized templateString using values from a templateVars object.
99 * The templateVars object should have keys and values which match the templateString's parameters.
100 * Example:
101 * const templateString: 'My name is ${firstName} ${lastName}';
102 * const templateVars: {
103 * firstName: 'Jon'
104 * lastName: 'Dough'
105 * };
106 * const result = fillTemplate(templateString, templateVars);
107 * // "My name is Jon Dough"
108 *
109 * @param {string} templateString The string passed by the consumer
110 * @param {object} templateVars The variables passed to the string
111 *
112 * @returns {string} The template string literal result
113 */
114function fillTemplate(templateString, templateVars) {
115 return templateString.replace(/\${(.*?)}/g, (_, match) => templateVars[match] || '');
116}
117exports.fillTemplate = fillTemplate;
118/**
119 * This function allows for keyboard navigation through dropdowns. The custom argument is optional.
120 *
121 * @param {number} index The index of the element you're on
122 * @param {number} innerIndex Inner index number
123 * @param {string} position The orientation of the dropdown
124 * @param {string[]} refsCollection Array of refs to the items in the dropdown
125 * @param {object[]} kids Array of items in the dropdown
126 * @param {boolean} [custom] Allows for handling of flexible content
127 */
128function keyHandler(index, innerIndex, position, refsCollection, kids, custom = false) {
129 if (!Array.isArray(kids)) {
130 return;
131 }
132 const isMultiDimensional = refsCollection.filter(ref => ref)[0].constructor === Array;
133 let nextIndex = index;
134 let nextInnerIndex = innerIndex;
135 if (position === 'up') {
136 if (index === 0) {
137 // loop back to end
138 nextIndex = kids.length - 1;
139 }
140 else {
141 nextIndex = index - 1;
142 }
143 }
144 else if (position === 'down') {
145 if (index === kids.length - 1) {
146 // loop back to beginning
147 nextIndex = 0;
148 }
149 else {
150 nextIndex = index + 1;
151 }
152 }
153 else if (position === 'left') {
154 if (innerIndex === 0) {
155 nextInnerIndex = refsCollection[index].length - 1;
156 }
157 else {
158 nextInnerIndex = innerIndex - 1;
159 }
160 }
161 else if (position === 'right') {
162 if (innerIndex === refsCollection[index].length - 1) {
163 nextInnerIndex = 0;
164 }
165 else {
166 nextInnerIndex = innerIndex + 1;
167 }
168 }
169 if (refsCollection[nextIndex] === null ||
170 refsCollection[nextIndex] === undefined ||
171 (isMultiDimensional &&
172 (refsCollection[nextIndex][nextInnerIndex] === null || refsCollection[nextIndex][nextInnerIndex] === undefined))) {
173 keyHandler(nextIndex, nextInnerIndex, position, refsCollection, kids, custom);
174 }
175 else if (custom) {
176 if (refsCollection[nextIndex].focus) {
177 refsCollection[nextIndex].focus();
178 }
179 // eslint-disable-next-line react/no-find-dom-node
180 const element = ReactDOM.findDOMNode(refsCollection[nextIndex]);
181 element.focus();
182 }
183 else if (position !== 'tab') {
184 if (isMultiDimensional) {
185 refsCollection[nextIndex][nextInnerIndex].focus();
186 }
187 else {
188 refsCollection[nextIndex].focus();
189 }
190 }
191}
192exports.keyHandler = keyHandler;
193/** This function returns a list of tabbable items in a container
194 *
195 * @param {any} containerRef to the container
196 * @param {string} tababbleSelectors CSS selector string of tabbable items
197 */
198function findTabbableElements(containerRef, tababbleSelectors) {
199 const tabbable = containerRef.current.querySelectorAll(tababbleSelectors);
200 const list = Array.prototype.filter.call(tabbable, function (item) {
201 return item.tabIndex >= '0';
202 });
203 return list;
204}
205exports.findTabbableElements = findTabbableElements;
206/** This function is a helper for keyboard navigation through dropdowns.
207 *
208 * @param {number} index The index of the element you're on
209 * @param {string} position The orientation of the dropdown
210 * @param {string[]} collection Array of refs to the items in the dropdown
211 */
212function getNextIndex(index, position, collection) {
213 let nextIndex;
214 if (position === 'up') {
215 if (index === 0) {
216 // loop back to end
217 nextIndex = collection.length - 1;
218 }
219 else {
220 nextIndex = index - 1;
221 }
222 }
223 else if (index === collection.length - 1) {
224 // loop back to beginning
225 nextIndex = 0;
226 }
227 else {
228 nextIndex = index + 1;
229 }
230 if (collection[nextIndex] === undefined || collection[nextIndex][0] === null) {
231 return getNextIndex(nextIndex, position, collection);
232 }
233 else {
234 return nextIndex;
235 }
236}
237exports.getNextIndex = getNextIndex;
238/** This function is a helper for pluralizing strings.
239 *
240 * @param {number} i The quantity of the string you want to pluralize
241 * @param {string} singular The singular version of the string
242 * @param {string} plural The change to the string that should occur if the quantity is not equal to 1.
243 * Defaults to adding an 's'.
244 */
245function pluralize(i, singular, plural) {
246 if (!plural) {
247 plural = `${singular}s`;
248 }
249 return `${i || 0} ${i === 1 ? singular : plural}`;
250}
251exports.pluralize = pluralize;
252/**
253 * This function is a helper for turning arrays of breakpointMod objects for flex and grid into style object
254 *
255 * @param {object} mods The modifiers object
256 * @param {string} css-variable The appropriate css variable for the component
257 */
258const 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 }), {});
259exports.setBreakpointCssVars = setBreakpointCssVars;
260/**
261 * This function is a helper for turning arrays of breakpointMod objects for data toolbar and flex into classes
262 *
263 * @param {object} mods The modifiers object
264 * @param {any} styles The appropriate styles object for the component
265 */
266const formatBreakpointMods = (mods, styles, stylePrefix = '', breakpoint) => {
267 if (!mods) {
268 return '';
269 }
270 if (breakpoint) {
271 if (breakpoint in mods) {
272 return styles.modifiers[exports.toCamel(`${stylePrefix}${mods[breakpoint]}`)];
273 }
274 // the current breakpoint is not specified in mods, so we try to find the next nearest
275 const breakpointsOrder = ['2xl', 'xl', 'lg', 'md', 'sm', 'default'];
276 const breakpointsIndex = breakpointsOrder.indexOf(breakpoint);
277 for (let i = breakpointsIndex; i < breakpointsOrder.length; i++) {
278 if (breakpointsOrder[i] in mods) {
279 return styles.modifiers[exports.toCamel(`${stylePrefix}${mods[breakpointsOrder[i]]}`)];
280 }
281 }
282 return '';
283 }
284 return Object.entries(mods || {})
285 .map(([breakpoint, mod]) => `${stylePrefix}${mod}${breakpoint !== 'default' ? `-on-${breakpoint}` : ''}`)
286 .map(exports.toCamel)
287 .map(mod => mod.replace(/-?(\dxl)/gi, (_res, group) => `_${group}`))
288 .map(modifierKey => styles.modifiers[modifierKey])
289 .filter(Boolean)
290 .join(' ');
291};
292exports.formatBreakpointMods = formatBreakpointMods;
293/**
294 * Return the breakpoint for the given width
295 *
296 * @param {number | null} width The width to check
297 * @returns {'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'} The breakpoint
298 */
299const getBreakpoint = (width) => {
300 if (width === null) {
301 return null;
302 }
303 if (width >= 1450) {
304 return '2xl';
305 }
306 if (width >= 1200) {
307 return 'xl';
308 }
309 if (width >= 992) {
310 return 'lg';
311 }
312 if (width >= 768) {
313 return 'md';
314 }
315 if (width >= 576) {
316 return 'sm';
317 }
318 return 'default';
319};
320exports.getBreakpoint = getBreakpoint;
321const camelize = (s) => s
322 .toUpperCase()
323 .replace('-', '')
324 .replace('_', '');
325/**
326 *
327 * @param {string} s string to make camelCased
328 */
329const toCamel = (s) => s.replace(/([-_][a-z])/gi, camelize);
330exports.toCamel = toCamel;
331/**
332 * Copied from exenv
333 */
334exports.canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
335/**
336 * Calculate the width of the text
337 * Example:
338 * getTextWidth('my text', node)
339 *
340 * @param {string} text The text to calculate the width for
341 * @param {HTMLElement} node The HTML element
342 */
343const getTextWidth = (text, node) => {
344 const computedStyle = getComputedStyle(node);
345 // Firefox returns the empty string for .font, so this function creates the .font property manually
346 const getFontFromComputedStyle = () => {
347 let computedFont = '';
348 // Firefox uses percentages for font-stretch, but Canvas does not accept percentages
349 // so convert to keywords, as listed at:
350 // https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch
351 const fontStretchLookupTable = {
352 '50%': 'ultra-condensed',
353 '62.5%': 'extra-condensed',
354 '75%': 'condensed',
355 '87.5%': 'semi-condensed',
356 '100%': 'normal',
357 '112.5%': 'semi-expanded',
358 '125%': 'expanded',
359 '150%': 'extra-expanded',
360 '200%': 'ultra-expanded'
361 };
362 // If the retrieved font-stretch percentage isn't found in the lookup table, use
363 // 'normal' as a last resort.
364 let fontStretch;
365 if (computedStyle.fontStretch in fontStretchLookupTable) {
366 fontStretch = fontStretchLookupTable[computedStyle.fontStretch];
367 }
368 else {
369 fontStretch = 'normal';
370 }
371 computedFont =
372 computedStyle.fontStyle +
373 ' ' +
374 computedStyle.fontVariant +
375 ' ' +
376 computedStyle.fontWeight +
377 ' ' +
378 fontStretch +
379 ' ' +
380 computedStyle.fontSize +
381 '/' +
382 computedStyle.lineHeight +
383 ' ' +
384 computedStyle.fontFamily;
385 return computedFont;
386 };
387 const canvas = document.createElement('canvas');
388 const context = canvas.getContext('2d');
389 context.font = computedStyle.font || getFontFromComputedStyle();
390 return context.measureText(text).width;
391};
392exports.getTextWidth = getTextWidth;
393/**
394 * Get the inner dimensions of an element
395 *
396 * @param {HTMLElement} node HTML element to calculate the inner dimensions for
397 */
398const innerDimensions = (node) => {
399 const computedStyle = getComputedStyle(node);
400 let width = node.clientWidth; // width with padding
401 let height = node.clientHeight; // height with padding
402 height -= parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom);
403 width -= parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight);
404 return { height, width };
405};
406exports.innerDimensions = innerDimensions;
407/**
408 * This function is a helper for truncating text content on the left, leaving the right side of the content in view
409 *
410 * @param {HTMLElement} node HTML element
411 * @param {string} value The original text value
412 */
413const trimLeft = (node, value) => {
414 const availableWidth = exports.innerDimensions(node).width;
415 let newValue = value;
416 if (exports.getTextWidth(value, node) > availableWidth) {
417 // we have text overflow, trim the text to the left and add ... in the front until it fits
418 while (exports.getTextWidth(`...${newValue}`, node) > availableWidth) {
419 newValue = newValue.substring(1);
420 }
421 // replace text with our truncated text
422 if (node.value) {
423 node.value = `...${newValue}`;
424 }
425 else {
426 node.innerText = `...${newValue}`;
427 }
428 }
429 else {
430 if (node.value) {
431 node.value = value;
432 }
433 else {
434 node.innerText = value;
435 }
436 }
437};
438exports.trimLeft = trimLeft;
439/**
440 * @param {string[]} events - Operations to prevent when disabled
441 */
442const preventedEvents = (events) => events.reduce((handlers, eventToPrevent) => (Object.assign(Object.assign({}, handlers), { [eventToPrevent]: (event) => {
443 event.preventDefault();
444 } })), {});
445exports.preventedEvents = preventedEvents;
446//# sourceMappingURL=util.js.map
\No newline at end of file