Skip to content

Commit 3706c6a

Browse files
authored
Centralize platform/browser checks in one place (#1514)
1 parent 309a8a2 commit 3706c6a

File tree

7 files changed

+55
-40
lines changed

7 files changed

+55
-40
lines changed

packages/@react-aria/combobox/src/useComboBox.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {announce} from '@react-aria/live-announcer';
1414
import {AriaButtonProps} from '@react-types/button';
1515
import {ariaHideOutside} from '@react-aria/overlays';
16-
import {chain, mergeProps, useLabels} from '@react-aria/utils';
16+
import {chain, isAppleDevice, mergeProps, useLabels} from '@react-aria/utils';
1717
import {ComboBoxProps} from '@react-types/combobox';
1818
import {ComboBoxState} from '@react-stately/combobox';
1919
import {FocusEvent, HTMLAttributes, InputHTMLAttributes, KeyboardEvent, RefObject, TouchEvent, useEffect, useMemo, useRef} from 'react';
@@ -42,12 +42,6 @@ interface ComboBoxAria {
4242
labelProps: HTMLAttributes<HTMLElement>
4343
}
4444

45-
function isAppleDevice() {
46-
return typeof window !== 'undefined' && window.navigator != null
47-
? /^(Mac|iPhone|iPad)/.test(window.navigator.platform)
48-
: false;
49-
}
50-
5145
export function useComboBox<T>(props: AriaComboBoxProps<T>, state: ComboBoxState<T>): ComboBoxAria {
5246
let {
5347
buttonRef,

packages/@react-aria/interactions/src/useFocusVisible.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// NOTICE file in the root directory of this source tree.
1616
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
1717

18+
import {isMac} from '@react-aria/utils';
1819
import {isVirtualClick} from './utils';
1920
import {useEffect, useState} from 'react';
2021

@@ -38,11 +39,6 @@ let changeHandlers = new Set<Handler>();
3839
let hasSetupGlobalListeners = false;
3940
let hasEventBeforeFocus = false;
4041

41-
const isMac =
42-
typeof window !== 'undefined' && window.navigator != null
43-
? /^Mac/.test(window.navigator.platform)
44-
: false;
45-
4642
// Only Tab or Esc keys will make focus visible on text input elements
4743
const FOCUS_VISIBLE_INPUT_KEYS = {
4844
Tab: true,
@@ -59,7 +55,7 @@ function triggerChangeHandlers(modality: Modality, e: HandlerEvent) {
5955
* Helper function to determine if a KeyboardEvent is unmodified and could make keyboard focus styles visible.
6056
*/
6157
function isValidKey(e: KeyboardEvent) {
62-
return !(e.metaKey || (!isMac && e.altKey) || e.ctrlKey);
58+
return !(e.metaKey || (!isMac() && e.altKey) || e.ctrlKey);
6359
}
6460

6561
function handleKeyboardEvent(e: KeyboardEvent) {

packages/@react-aria/listbox/src/useOption.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import {getItemCount} from '@react-stately/collections';
1414
import {getItemId} from './utils';
1515
import {HTMLAttributes, Key, RefObject} from 'react';
1616
import {isFocusVisible, useHover, usePress} from '@react-aria/interactions';
17+
import {isMac, isWebKit, mergeProps, useSlotId} from '@react-aria/utils';
1718
import {ListState} from '@react-stately/list';
18-
import {mergeProps, useSlotId} from '@react-aria/utils';
1919
import {useSelectableItem} from '@react-aria/selection';
2020

2121
interface OptionAria {
@@ -55,13 +55,6 @@ interface AriaOptionProps {
5555
shouldUseVirtualFocus?: boolean
5656
}
5757

58-
const isSafariMacOS =
59-
typeof window !== 'undefined' && window.navigator != null
60-
? /^Mac/.test(window.navigator.platform) &&
61-
/Safari/.test(window.navigator.userAgent) &&
62-
!/Chrome/.test(window.navigator.userAgent)
63-
: false;
64-
6558
/**
6659
* Provides the behavior and accessibility implementation for an option in a listbox.
6760
* See `useListBox` for more details about listboxes.
@@ -91,7 +84,7 @@ export function useOption<T>(props: AriaOptionProps, state: ListState<T>, ref: R
9184
// Safari with VoiceOver on macOS misreads options with aria-labelledby or aria-label as simply "text".
9285
// We should not map slots to the label and description on Safari and instead just have VoiceOver read the textContent.
9386
// https://bugs.webkit.org/show_bug.cgi?id=209279
94-
if (!isSafariMacOS) {
87+
if (!(isMac() && isWebKit())) {
9588
optionProps['aria-label'] = props['aria-label'];
9689
optionProps['aria-labelledby'] = labelId;
9790
optionProps['aria-describedby'] = descriptionId;

packages/@react-aria/overlays/src/usePreventScroll.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,13 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {chain, getScrollParent, useLayoutEffect} from '@react-aria/utils';
13+
import {chain, getScrollParent, isIOS, useLayoutEffect} from '@react-aria/utils';
1414

1515
interface PreventScrollOptions {
1616
/** Whether the scroll lock is disabled. */
1717
isDisabled?: boolean
1818
}
1919

20-
const isMobileSafari =
21-
typeof window !== 'undefined' && window.navigator != null
22-
? /AppleWebKit/.test(window.navigator.userAgent) && (
23-
/^(iPhone|iPad)$/.test(window.navigator.platform) ||
24-
// iPadOS 13 lies and says its a Mac, but we can distinguish by detecting touch support.
25-
(window.navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
26-
)
27-
: false;
28-
2920
// @ts-ignore
3021
const visualViewport = typeof window !== 'undefined' && window.visualViewport;
3122

@@ -55,7 +46,7 @@ export function usePreventScroll(options: PreventScrollOptions = {}) {
5546
return;
5647
}
5748

58-
if (isMobileSafari) {
49+
if (isIOS()) {
5950
return preventScrollMobileSafari();
6051
} else {
6152
return preventScrollStandard();

packages/@react-aria/selection/src/useSelectableCollection.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@
1313
import {FocusEvent, HTMLAttributes, KeyboardEvent, RefObject, useEffect} from 'react';
1414
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
1515
import {FocusStrategy, KeyboardDelegate} from '@react-types/shared';
16-
import {mergeProps} from '@react-aria/utils';
16+
import {isMac, mergeProps} from '@react-aria/utils';
1717
import {MultipleSelectionManager} from '@react-stately/selection';
1818
import {useTypeSelect} from './useTypeSelect';
1919

20-
const isMac =
21-
typeof window !== 'undefined' && window.navigator != null
22-
? /^Mac/.test(window.navigator.platform)
23-
: false;
24-
2520
function isCtrlKeyPressed(e: KeyboardEvent) {
26-
if (isMac) {
21+
if (isMac()) {
2722
return e.metaKey;
2823
}
2924

packages/@react-aria/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ export * from './useLayoutEffect';
2626
export * from './useResizeObserver';
2727
export * from './getScrollParent';
2828
export * from './useViewportSize';
29+
export * from './platform';
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
function testUserAgent(re: RegExp) {
14+
return typeof window !== 'undefined' && window.navigator != null
15+
? re.test(window.navigator.userAgent)
16+
: false;
17+
}
18+
19+
function testPlatform(re: RegExp) {
20+
return typeof window !== 'undefined' && window.navigator != null
21+
? re.test(window.navigator.platform)
22+
: false;
23+
}
24+
25+
export function isMac() {
26+
return testPlatform(/^Mac/);
27+
}
28+
29+
export function isIOS() {
30+
return testPlatform(/^(iPhone|iPad)/) ||
31+
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
32+
(isMac() && navigator.maxTouchPoints > 1);
33+
}
34+
35+
export function isAppleDevice() {
36+
return isMac() || isIOS();
37+
}
38+
39+
export function isWebKit() {
40+
return testUserAgent(/AppleWebKit/) && !isChrome();
41+
}
42+
43+
export function isChrome() {
44+
return testUserAgent(/Chrome/);
45+
}

0 commit comments

Comments
 (0)