1 | import PropTypes from 'prop-types';
|
2 |
|
3 |
|
4 | export function getScrollbarWidth() {
|
5 | let scrollDiv = document.createElement('div');
|
6 |
|
7 | scrollDiv.style.position = 'absolute';
|
8 | scrollDiv.style.top = '-9999px';
|
9 | scrollDiv.style.width = '50px';
|
10 | scrollDiv.style.height = '50px';
|
11 | scrollDiv.style.overflow = 'scroll';
|
12 | document.body.appendChild(scrollDiv);
|
13 | const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
|
14 | document.body.removeChild(scrollDiv);
|
15 | return scrollbarWidth;
|
16 | }
|
17 |
|
18 | export function setScrollbarWidth(padding) {
|
19 | document.body.style.paddingRight = padding > 0 ? `${padding}px` : null;
|
20 | }
|
21 |
|
22 | export function isBodyOverflowing() {
|
23 | return document.body.clientWidth < window.innerWidth;
|
24 | }
|
25 |
|
26 | export function getOriginalBodyPadding() {
|
27 | const style = window.getComputedStyle(document.body, null);
|
28 |
|
29 | return parseInt((style && style.getPropertyValue('padding-right')) || 0, 10);
|
30 | }
|
31 |
|
32 | export function conditionallyUpdateScrollbar() {
|
33 | const scrollbarWidth = getScrollbarWidth();
|
34 |
|
35 | const fixedContent = document.querySelectorAll(
|
36 | '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',
|
37 | )[0];
|
38 | const bodyPadding = fixedContent
|
39 | ? parseInt(fixedContent.style.paddingRight || 0, 10)
|
40 | : 0;
|
41 |
|
42 | if (isBodyOverflowing()) {
|
43 | setScrollbarWidth(bodyPadding + scrollbarWidth);
|
44 | }
|
45 | }
|
46 |
|
47 | let globalCssModule;
|
48 |
|
49 | export function setGlobalCssModule(cssModule) {
|
50 | globalCssModule = cssModule;
|
51 | }
|
52 |
|
53 | export function mapToCssModules(className = '', cssModule = globalCssModule) {
|
54 | if (!cssModule) return className;
|
55 | return className
|
56 | .split(' ')
|
57 | .map((c) => cssModule[c] || c)
|
58 | .join(' ');
|
59 | }
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | export function omit(obj, omitKeys) {
|
65 | const result = {};
|
66 | Object.keys(obj).forEach((key) => {
|
67 | if (omitKeys.indexOf(key) === -1) {
|
68 | result[key] = obj[key];
|
69 | }
|
70 | });
|
71 | return result;
|
72 | }
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | export function pick(obj, keys) {
|
78 | const pickKeys = Array.isArray(keys) ? keys : [keys];
|
79 | let { length } = pickKeys;
|
80 | let key;
|
81 | const result = {};
|
82 |
|
83 | while (length > 0) {
|
84 | length -= 1;
|
85 | key = pickKeys[length];
|
86 | result[key] = obj[key];
|
87 | }
|
88 | return result;
|
89 | }
|
90 |
|
91 | let warned = {};
|
92 |
|
93 | export function warnOnce(message) {
|
94 | if (!warned[message]) {
|
95 |
|
96 | if (typeof console !== 'undefined') {
|
97 | console.error(message);
|
98 | }
|
99 | warned[message] = true;
|
100 | }
|
101 | }
|
102 |
|
103 | export function deprecated(propType, explanation) {
|
104 | return function validate(props, propName, componentName, ...rest) {
|
105 | if (props[propName] !== null && typeof props[propName] !== 'undefined') {
|
106 | warnOnce(
|
107 | `"${propName}" property of "${componentName}" has been deprecated.\n${explanation}`,
|
108 | );
|
109 | }
|
110 |
|
111 | return propType(props, propName, componentName, ...rest);
|
112 | };
|
113 | }
|
114 |
|
115 |
|
116 | const Element =
|
117 | (typeof window === 'object' && window.Element) || function () {};
|
118 |
|
119 | export function DOMElement(props, propName, componentName) {
|
120 | if (!(props[propName] instanceof Element)) {
|
121 | return new Error(
|
122 | 'Invalid prop `' +
|
123 | propName +
|
124 | '` supplied to `' +
|
125 | componentName +
|
126 | '`. Expected prop to be an instance of Element. Validation failed.',
|
127 | );
|
128 | }
|
129 | }
|
130 |
|
131 | export const targetPropType = PropTypes.oneOfType([
|
132 | PropTypes.string,
|
133 | PropTypes.func,
|
134 | DOMElement,
|
135 | PropTypes.shape({ current: PropTypes.any }),
|
136 | ]);
|
137 |
|
138 | export const tagPropType = PropTypes.oneOfType([
|
139 | PropTypes.func,
|
140 | PropTypes.string,
|
141 | PropTypes.shape({ $$typeof: PropTypes.symbol, render: PropTypes.func }),
|
142 | PropTypes.arrayOf(
|
143 | PropTypes.oneOfType([
|
144 | PropTypes.func,
|
145 | PropTypes.string,
|
146 | PropTypes.shape({ $$typeof: PropTypes.symbol, render: PropTypes.func }),
|
147 | ]),
|
148 | ),
|
149 | ]);
|
150 |
|
151 |
|
152 |
|
153 | export const TransitionTimeouts = {
|
154 | Fade: 150,
|
155 | Collapse: 350,
|
156 | Modal: 300,
|
157 | Carousel: 600,
|
158 | Offcanvas: 300,
|
159 | };
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | export const TransitionPropTypeKeys = [
|
165 | 'in',
|
166 | 'mountOnEnter',
|
167 | 'unmountOnExit',
|
168 | 'appear',
|
169 | 'enter',
|
170 | 'exit',
|
171 | 'timeout',
|
172 | 'onEnter',
|
173 | 'onEntering',
|
174 | 'onEntered',
|
175 | 'onExit',
|
176 | 'onExiting',
|
177 | 'onExited',
|
178 | ];
|
179 |
|
180 | export const TransitionStatuses = {
|
181 | ENTERING: 'entering',
|
182 | ENTERED: 'entered',
|
183 | EXITING: 'exiting',
|
184 | EXITED: 'exited',
|
185 | };
|
186 |
|
187 | export const keyCodes = {
|
188 | esc: 27,
|
189 | space: 32,
|
190 | enter: 13,
|
191 | tab: 9,
|
192 | up: 38,
|
193 | down: 40,
|
194 | home: 36,
|
195 | end: 35,
|
196 | n: 78,
|
197 | p: 80,
|
198 | };
|
199 |
|
200 | export const PopperPlacements = [
|
201 | 'auto-start',
|
202 | 'auto',
|
203 | 'auto-end',
|
204 | 'top-start',
|
205 | 'top',
|
206 | 'top-end',
|
207 | 'right-start',
|
208 | 'right',
|
209 | 'right-end',
|
210 | 'bottom-end',
|
211 | 'bottom',
|
212 | 'bottom-start',
|
213 | 'left-end',
|
214 | 'left',
|
215 | 'left-start',
|
216 | ];
|
217 |
|
218 | export const canUseDOM = !!(
|
219 | typeof window !== 'undefined' &&
|
220 | window.document &&
|
221 | window.document.createElement
|
222 | );
|
223 |
|
224 | export function isReactRefObj(target) {
|
225 | if (target && typeof target === 'object') {
|
226 | return 'current' in target;
|
227 | }
|
228 | return false;
|
229 | }
|
230 |
|
231 | function getTag(value) {
|
232 | if (value == null) {
|
233 | return value === undefined ? '[object Undefined]' : '[object Null]';
|
234 | }
|
235 | return Object.prototype.toString.call(value);
|
236 | }
|
237 |
|
238 | export function isObject(value) {
|
239 | const type = typeof value;
|
240 | return value != null && (type === 'object' || type === 'function');
|
241 | }
|
242 |
|
243 | export function toNumber(value) {
|
244 | const type = typeof value;
|
245 | const NAN = 0 / 0;
|
246 | if (type === 'number') {
|
247 | return value;
|
248 | }
|
249 | if (
|
250 | type === 'symbol' ||
|
251 | (type === 'object' && getTag(value) === '[object Symbol]')
|
252 | ) {
|
253 | return NAN;
|
254 | }
|
255 | if (isObject(value)) {
|
256 | const other = typeof value.valueOf === 'function' ? value.valueOf() : value;
|
257 | value = isObject(other) ? `${other}` : other;
|
258 | }
|
259 | if (type !== 'string') {
|
260 | return value === 0 ? value : +value;
|
261 | }
|
262 | value = value.replace(/^\s+|\s+$/g, '');
|
263 | const isBinary = /^0b[01]+$/i.test(value);
|
264 | return isBinary || /^0o[0-7]+$/i.test(value)
|
265 | ? parseInt(value.slice(2), isBinary ? 2 : 8)
|
266 | : /^[-+]0x[0-9a-f]+$/i.test(value)
|
267 | ? NAN
|
268 | : +value;
|
269 | }
|
270 |
|
271 | export function isFunction(value) {
|
272 | if (!isObject(value)) {
|
273 | return false;
|
274 | }
|
275 |
|
276 | const tag = getTag(value);
|
277 | return (
|
278 | tag === '[object Function]' ||
|
279 | tag === '[object AsyncFunction]' ||
|
280 | tag === '[object GeneratorFunction]' ||
|
281 | tag === '[object Proxy]'
|
282 | );
|
283 | }
|
284 |
|
285 | export function findDOMElements(target) {
|
286 | if (isReactRefObj(target)) {
|
287 | return target.current;
|
288 | }
|
289 | if (isFunction(target)) {
|
290 | return target();
|
291 | }
|
292 | if (typeof target === 'string' && canUseDOM) {
|
293 | let selection = document.querySelectorAll(target);
|
294 | if (!selection.length) {
|
295 | selection = document.querySelectorAll(`#${target}`);
|
296 | }
|
297 | if (!selection.length) {
|
298 | throw new Error(
|
299 | `The target '${target}' could not be identified in the dom, tip: check spelling`,
|
300 | );
|
301 | }
|
302 | return selection;
|
303 | }
|
304 | return target;
|
305 | }
|
306 |
|
307 | export function isArrayOrNodeList(els) {
|
308 | if (els === null) {
|
309 | return false;
|
310 | }
|
311 | return Array.isArray(els) || (canUseDOM && typeof els.length === 'number');
|
312 | }
|
313 |
|
314 | export function getTarget(target, allElements) {
|
315 | const els = findDOMElements(target);
|
316 | if (allElements) {
|
317 | if (isArrayOrNodeList(els)) {
|
318 | return els;
|
319 | }
|
320 | if (els === null) {
|
321 | return [];
|
322 | }
|
323 | return [els];
|
324 | }
|
325 | if (isArrayOrNodeList(els)) {
|
326 | return els[0];
|
327 | }
|
328 | return els;
|
329 | }
|
330 |
|
331 | export const defaultToggleEvents = ['touchstart', 'click'];
|
332 |
|
333 | export function addMultipleEventListeners(_els, handler, _events, useCapture) {
|
334 | let els = _els;
|
335 | if (!isArrayOrNodeList(els)) {
|
336 | els = [els];
|
337 | }
|
338 |
|
339 | let events = _events;
|
340 | if (typeof events === 'string') {
|
341 | events = events.split(/\s+/);
|
342 | }
|
343 |
|
344 | if (
|
345 | !isArrayOrNodeList(els) ||
|
346 | typeof handler !== 'function' ||
|
347 | !Array.isArray(events)
|
348 | ) {
|
349 | throw new Error(`
|
350 | The first argument of this function must be DOM node or an array on DOM nodes or NodeList.
|
351 | The second must be a function.
|
352 | The third is a string or an array of strings that represents DOM events
|
353 | `);
|
354 | }
|
355 |
|
356 | Array.prototype.forEach.call(events, (event) => {
|
357 | Array.prototype.forEach.call(els, (el) => {
|
358 | el.addEventListener(event, handler, useCapture);
|
359 | });
|
360 | });
|
361 | return function removeEvents() {
|
362 | Array.prototype.forEach.call(events, (event) => {
|
363 | Array.prototype.forEach.call(els, (el) => {
|
364 | el.removeEventListener(event, handler, useCapture);
|
365 | });
|
366 | });
|
367 | };
|
368 | }
|
369 |
|
370 | export const focusableElements = [
|
371 | 'a[href]',
|
372 | 'area[href]',
|
373 | 'input:not([disabled]):not([type=hidden])',
|
374 | 'select:not([disabled])',
|
375 | 'textarea:not([disabled])',
|
376 | 'button:not([disabled])',
|
377 | 'object',
|
378 | 'embed',
|
379 | '[tabindex]:not(.modal):not(.offcanvas)',
|
380 | 'audio[controls]',
|
381 | 'video[controls]',
|
382 | '[contenteditable]:not([contenteditable="false"])',
|
383 | ];
|
384 |
|
385 | export function addDefaultProps(defaultProps, props) {
|
386 | if (!defaultProps || !props) return props;
|
387 |
|
388 | let result = { ...props };
|
389 |
|
390 | Object.keys(defaultProps).forEach((key) => {
|
391 | if (result[key] === undefined) {
|
392 | result[key] = defaultProps[key];
|
393 | }
|
394 | if (
|
395 | Object.keys(defaultProps[key] || {}).length > 0 &&
|
396 | typeof defaultProps[key] === 'object'
|
397 | ) {
|
398 | addDefaultProps(defaultProps[key], result);
|
399 | }
|
400 | });
|
401 |
|
402 | return result;
|
403 | }
|