• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JedWatson / react-select / 9de42ca4-8c55-4031-8310-e7e0a2b0128b

26 Oct 2024 11:27AM CUT coverage: 75.844%. Remained the same
9de42ca4-8c55-4031-8310-e7e0a2b0128b

Pull #5880

circleci

lukebennett88
add box-sizing to border-box for RequiredInput

adding `required` would otherwise cause an extra (unstylable) component to be added which has some implicit padding from the user agent style sheet (inputs have padding) which could cause horizontal scrolling when the whole scroll field is 100% wide.
Pull Request #5880: add box-sizing to border-box for RequiredInput

658 of 1052 branches covered (62.55%)

1033 of 1362 relevant lines covered (75.84%)

1934.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.0
/packages/react-select/src/utils.ts
1
import type { StylesProps } from './styles';
2
import type {
3
  ClassNamesState,
4
  CommonPropsAndClassName,
5
  GroupBase,
6
  InputActionMeta,
7
  MultiValue,
8
  OnChangeValue,
9
  Options,
10
  PropsValue,
11
  SingleValue,
12
} from './types';
13

14
// ==============================
15
// NO OP
16
// ==============================
17

18
export const noop = () => {};
5✔
19
export const emptyString = () => '';
5✔
20

21
// ==============================
22
// Class Name Prefixer
23
// ==============================
24

25
/**
26
 String representation of component state for styling with class names.
27

28
 Expects an array of strings OR a string/object pair:
29
 - className(['comp', 'comp-arg', 'comp-arg-2'])
30
   @returns 'react-select__comp react-select__comp-arg react-select__comp-arg-2'
31
 - className('comp', { some: true, state: false })
32
   @returns 'react-select__comp react-select__comp--some'
33
*/
34
function applyPrefixToName(prefix: string, name: string) {
35
  if (!name) {
20,016!
36
    return prefix;
×
37
  } else if (name[0] === '-') {
20,016✔
38
    return prefix + name;
3✔
39
  } else {
40
    return prefix + '__' + name;
20,013✔
41
  }
42
}
43

44
export function classNames(
45
  prefix?: string | null,
46
  state?: ClassNamesState,
47
  ...classNameList: string[]
48
) {
49
  const arr = [...classNameList];
17,844✔
50
  if (state && prefix) {
17,844✔
51
    for (let key in state) {
17,731✔
52
      if (state.hasOwnProperty(key) && state[key]) {
50,076✔
53
        arr.push(`${applyPrefixToName(prefix, key)}`);
20,016✔
54
      }
55
    }
56
  }
57

58
  return arr
17,844✔
59
    .filter((i) => i)
54,893✔
60
    .map((i) => String(i).trim())
20,818✔
61
    .join(' ');
62
}
63
// ==============================
64
// Clean Value
65
// ==============================
66

67
export const cleanValue = <Option>(
5✔
68
  value: PropsValue<Option>
69
): Options<Option> => {
70
  if (isArray(value)) return value.filter(Boolean);
1,135✔
71
  if (typeof value === 'object' && value !== null) return [value];
1,055✔
72
  return [];
901✔
73
};
74

75
// ==============================
76
// Clean Common Props
77
// ==============================
78

79
export const cleanCommonProps = <
5✔
80
  Option,
81
  IsMulti extends boolean,
82
  Group extends GroupBase<Option>,
83
  AdditionalProps
84
>(
85
  props: Partial<CommonPropsAndClassName<Option, IsMulti, Group>> &
86
    AdditionalProps
87
): Omit<
88
  AdditionalProps,
89
  keyof CommonPropsAndClassName<Option, IsMulti, Group>
90
> => {
91
  //className
92
  const {
93
    className, // not listed in commonProps documentation, needs to be removed to allow Emotion to generate classNames
94
    clearValue,
95
    cx,
96
    getStyles,
97
    getClassNames,
98
    getValue,
99
    hasValue,
100
    isMulti,
101
    isRtl,
102
    options, // not listed in commonProps documentation
103
    selectOption,
104
    selectProps,
105
    setValue,
106
    theme, // not listed in commonProps documentation
107
    ...innerProps
108
  } = props;
841✔
109
  return { ...innerProps };
841✔
110
};
111

112
// ==============================
113
// Get Style Props
114
// ==============================
115

116
export const getStyleProps = <
5✔
117
  Option,
118
  IsMulti extends boolean,
119
  Group extends GroupBase<Option>,
120
  Key extends keyof StylesProps<Option, IsMulti, Group>
121
>(
122
  props: Pick<
123
    CommonPropsAndClassName<Option, IsMulti, Group>,
124
    'cx' | 'getStyles' | 'getClassNames' | 'className'
125
  > &
126
    StylesProps<Option, IsMulti, Group>[Key],
127
  name: Key,
128
  classNamesState?: ClassNamesState
129
) => {
130
  const { cx, getStyles, getClassNames, className } = props;
17,033✔
131
  return {
17,033✔
132
    css: getStyles(name, props),
133
    className: cx(classNamesState ?? {}, getClassNames(name, props), className),
17,033!
134
  };
135
};
136

137
// ==============================
138
// Handle Input Change
139
// ==============================
140

141
export function handleInputChange(
142
  inputValue: string,
143
  actionMeta: InputActionMeta,
144
  onInputChange?: (
145
    newValue: string,
146
    actionMeta: InputActionMeta
147
  ) => string | void
148
) {
149
  if (onInputChange) {
11!
150
    const newValue = onInputChange(inputValue, actionMeta);
×
151
    if (typeof newValue === 'string') return newValue;
×
152
  }
153
  return inputValue;
11✔
154
}
155

156
// ==============================
157
// Scroll Helpers
158
// ==============================
159

160
export function isDocumentElement(
161
  el: HTMLElement | typeof window
162
): el is typeof window {
163
  return [document.documentElement, document.body, window].indexOf(el) > -1;
×
164
}
165

166
// Normalized Scroll Top
167
// ------------------------------
168

169
export function normalizedHeight(el: HTMLElement | typeof window): number {
170
  if (isDocumentElement(el)) {
×
171
    return window.innerHeight;
×
172
  }
173

174
  return el.clientHeight;
×
175
}
176

177
// Normalized scrollTo & scrollTop
178
// ------------------------------
179

180
export function getScrollTop(el: HTMLElement | typeof window): number {
181
  if (isDocumentElement(el)) {
×
182
    return window.pageYOffset;
×
183
  }
184
  return el.scrollTop;
×
185
}
186

187
export function scrollTo(el: HTMLElement | typeof window, top: number): void {
188
  // with a scroll distance, we perform scroll on the element
189
  if (isDocumentElement(el)) {
×
190
    window.scrollTo(0, top);
×
191
    return;
×
192
  }
193

194
  el.scrollTop = top;
×
195
}
196

197
// Get Scroll Parent
198
// ------------------------------
199

200
export function getScrollParent(element: HTMLElement) {
201
  let style = getComputedStyle(element);
169✔
202
  const excludeStaticParent = style.position === 'absolute';
169✔
203
  const overflowRx = /(auto|scroll)/;
169✔
204

205
  if (style.position === 'fixed') return document.documentElement;
169!
206

207
  for (
169✔
208
    let parent: HTMLElement | null = element;
169✔
209
    (parent = parent.parentElement);
210

211
  ) {
212
    style = getComputedStyle(parent);
678✔
213
    if (excludeStaticParent && style.position === 'static') {
678!
214
      continue;
×
215
    }
216
    if (overflowRx.test(style.overflow + style.overflowY + style.overflowX)) {
678!
217
      return parent;
×
218
    }
219
  }
220

221
  return document.documentElement;
169✔
222
}
223

224
// Animated Scroll To
225
// ------------------------------
226

227
/**
228
  @param t: time (elapsed)
229
  @param b: initial value
230
  @param c: amount of change
231
  @param d: duration
232
*/
233
function easeOutCubic(t: number, b: number, c: number, d: number): number {
234
  return c * ((t = t / d - 1) * t * t + 1) + b;
×
235
}
236

237
export function animatedScrollTo(
238
  element: HTMLElement | typeof window,
239
  to: number,
240
  duration = 200,
×
241
  callback: (element: HTMLElement | typeof window) => void = noop
×
242
) {
243
  const start = getScrollTop(element);
×
244
  const change = to - start;
×
245
  const increment = 10;
×
246
  let currentTime = 0;
×
247

248
  function animateScroll() {
249
    currentTime += increment;
×
250
    const val = easeOutCubic(currentTime, start, change, duration);
×
251
    scrollTo(element, val);
×
252
    if (currentTime < duration) {
×
253
      window.requestAnimationFrame(animateScroll);
×
254
    } else {
255
      callback(element);
×
256
    }
257
  }
258
  animateScroll();
×
259
}
260

261
// Scroll Into View
262
// ------------------------------
263

264
export function scrollIntoView(
265
  menuEl: HTMLElement,
266
  focusedEl: HTMLElement
267
): void {
268
  const menuRect = menuEl.getBoundingClientRect();
338✔
269
  const focusedRect = focusedEl.getBoundingClientRect();
338✔
270
  const overScroll = focusedEl.offsetHeight / 3;
338✔
271

272
  if (focusedRect.bottom + overScroll > menuRect.bottom) {
338!
273
    scrollTo(
×
274
      menuEl,
275
      Math.min(
276
        focusedEl.offsetTop +
277
          focusedEl.clientHeight -
278
          menuEl.offsetHeight +
279
          overScroll,
280
        menuEl.scrollHeight
281
      )
282
    );
283
  } else if (focusedRect.top - overScroll < menuRect.top) {
338!
284
    scrollTo(menuEl, Math.max(focusedEl.offsetTop - overScroll, 0));
×
285
  }
286
}
287

288
// ==============================
289
// Get bounding client object
290
// ==============================
291

292
// cannot get keys using array notation with DOMRect
293
export function getBoundingClientObj(element: HTMLElement) {
294
  const rect = element.getBoundingClientRect();
×
295
  return {
×
296
    bottom: rect.bottom,
297
    height: rect.height,
298
    left: rect.left,
299
    right: rect.right,
300
    top: rect.top,
301
    width: rect.width,
302
  };
303
}
304
export interface RectType {
305
  left: number;
306
  right: number;
307
  bottom: number;
308
  height: number;
309
  width: number;
310
}
311

312
// ==============================
313
// String to Key (kebabify)
314
// ==============================
315

316
export function toKey(str: string) {
317
  return str.replace(/\W/g, '-');
×
318
}
319

320
// ==============================
321
// Touch Capability Detector
322
// ==============================
323

324
export function isTouchCapable() {
325
  try {
10✔
326
    document.createEvent('TouchEvent');
10✔
327
    return true;
10✔
328
  } catch (e) {
329
    return false;
×
330
  }
331
}
332

333
// ==============================
334
// Mobile Device Detector
335
// ==============================
336

337
export function isMobileDevice() {
338
  try {
5✔
339
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
5✔
340
      navigator.userAgent
341
    );
342
  } catch (e) {
343
    return false;
×
344
  }
345
}
346

347
// ==============================
348
// Passive Event Detector
349
// ==============================
350

351
// https://github.com/rafgraph/detect-it/blob/main/src/index.ts#L19-L36
352
let passiveOptionAccessed = false;
5✔
353
const options = {
5✔
354
  get passive() {
355
    return (passiveOptionAccessed = true);
5✔
356
  },
357
};
358
// check for SSR
359
const w:
360
  | typeof window
361
  | { addEventListener?: never; removeEventListener?: never } =
362
  typeof window !== 'undefined' ? window : {};
5!
363
if (w.addEventListener && w.removeEventListener) {
5!
364
  w.addEventListener('p', noop, options);
5✔
365
  w.removeEventListener('p', noop, false);
5✔
366
}
367

368
export const supportsPassiveEvents: boolean = passiveOptionAccessed;
5✔
369

370
export function notNullish<T>(item: T | null | undefined): item is T {
371
  return item != null;
36,394✔
372
}
373

374
export function isArray<T>(arg: unknown): arg is readonly T[] {
375
  return Array.isArray(arg);
1,135✔
376
}
377

378
export function valueTernary<Option, IsMulti extends boolean>(
379
  isMulti: IsMulti | undefined,
380
  multiValue: MultiValue<Option>,
381
  singleValue: SingleValue<Option>
382
): OnChangeValue<Option, IsMulti> {
383
  return (isMulti ? multiValue : singleValue) as OnChangeValue<Option, IsMulti>;
67✔
384
}
385

386
export function singleValueAsValue<Option, IsMulti extends boolean>(
387
  singleValue: SingleValue<Option>
388
): OnChangeValue<Option, IsMulti> {
389
  return singleValue as OnChangeValue<Option, IsMulti>;
26✔
390
}
391

392
export function multiValueAsValue<Option, IsMulti extends boolean>(
393
  multiValue: MultiValue<Option>
394
): OnChangeValue<Option, IsMulti> {
395
  return multiValue as OnChangeValue<Option, IsMulti>;
27✔
396
}
397

398
export const removeProps = <Props extends object, K extends string[]>(
5✔
399
  propsObj: Props,
400
  ...properties: K
401
): Omit<Props, K[number]> => {
402
  let propsMap = Object.entries(propsObj).filter(
9✔
403
    ([key]) => !properties.includes(key)
193✔
404
  );
405

406
  return propsMap.reduce((newProps: { [key: string]: any }, [key, val]) => {
9✔
407
    newProps[key] = val;
193✔
408
    return newProps;
193✔
409
  }, {}) as Omit<Props, K[number]>;
410
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc