UNPKG

10 kBJavaScriptView Raw
1import PropTypes from 'prop-types';
2
3// https://github.com/twbs/bootstrap/blob/v4.0.0-alpha.4/js/src/modal.js#L436-L443
4export function getScrollbarWidth() {
5 let scrollDiv = document.createElement('div');
6 // .modal-scrollbar-measure styles // https://github.com/twbs/bootstrap/blob/v4.0.0-alpha.4/scss/_modal.scss#L106-L113
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
18export function setScrollbarWidth(padding) {
19 document.body.style.paddingRight = padding > 0 ? `${padding}px` : null;
20}
21
22export function isBodyOverflowing() {
23 return document.body.clientWidth < window.innerWidth;
24}
25
26export function getOriginalBodyPadding() {
27 const style = window.getComputedStyle(document.body, null);
28
29 return parseInt((style && style.getPropertyValue('padding-right')) || 0, 10);
30}
31
32export function conditionallyUpdateScrollbar() {
33 const scrollbarWidth = getScrollbarWidth();
34 // https://github.com/twbs/bootstrap/blob/v4.0.0-alpha.6/js/src/modal.js#L433
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
47let globalCssModule;
48
49export function setGlobalCssModule(cssModule) {
50 globalCssModule = cssModule;
51}
52
53export 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 * Returns a new object with the key/value pairs from `obj` that are not in the array `omitKeys`.
63 */
64export 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 * Returns a filtered copy of an object with only the specified keys.
76 */
77export 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
91let warned = {};
92
93export function warnOnce(message) {
94 if (!warned[message]) {
95 /* istanbul ignore else */
96 if (typeof console !== 'undefined') {
97 console.error(message); // eslint-disable-line no-console
98 }
99 warned[message] = true;
100 }
101}
102
103export 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// Shim Element if needed (e.g. in Node environment)
116const Element =
117 (typeof window === 'object' && window.Element) || function () {};
118
119export 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
131export const targetPropType = PropTypes.oneOfType([
132 PropTypes.string,
133 PropTypes.func,
134 DOMElement,
135 PropTypes.shape({ current: PropTypes.any }),
136]);
137
138export 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// These are all setup to match what is in the bootstrap _variables.scss
152// https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss
153export const TransitionTimeouts = {
154 Fade: 150, // $transition-fade
155 Collapse: 350, // $transition-collapse
156 Modal: 300, // $modal-transition
157 Carousel: 600, // $carousel-transition
158 Offcanvas: 300, // $offcanvas-transition
159};
160
161// Duplicated Transition.propType keys to ensure that Reactstrap builds
162// for distribution properly exclude these keys for nested child HTML attributes
163// since `react-transition-group` removes propTypes in production builds.
164export 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
180export const TransitionStatuses = {
181 ENTERING: 'entering',
182 ENTERED: 'entered',
183 EXITING: 'exiting',
184 EXITED: 'exited',
185};
186
187export 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
200export 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
218export const canUseDOM = !!(
219 typeof window !== 'undefined' &&
220 window.document &&
221 window.document.createElement
222);
223
224export function isReactRefObj(target) {
225 if (target && typeof target === 'object') {
226 return 'current' in target;
227 }
228 return false;
229}
230
231function getTag(value) {
232 if (value == null) {
233 return value === undefined ? '[object Undefined]' : '[object Null]';
234 }
235 return Object.prototype.toString.call(value);
236}
237
238export function isObject(value) {
239 const type = typeof value;
240 return value != null && (type === 'object' || type === 'function');
241}
242
243export 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
271export 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
285export 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
307export function isArrayOrNodeList(els) {
308 if (els === null) {
309 return false;
310 }
311 return Array.isArray(els) || (canUseDOM && typeof els.length === 'number');
312}
313
314export 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
331export const defaultToggleEvents = ['touchstart', 'click'];
332
333export 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
370export 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
385export 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}