@@ -3,40 +3,25 @@ import * as React from 'react';
33
44export type ScreenType = 'normal' | 'touch' ;
55
6- let userHasTouchedScreen = false ;
7- let userHasMovedMouse = false ;
6+ // Module-level state shared across all hook instances.
7+ // A single pointerdown listener detects the current input type and only
8+ // notifies subscribers when the type actually changes (touch ↔ mouse/pen).
9+ let _screenType : ScreenType = 'normal' ;
10+ const _listeners : Set < ( type : ScreenType ) => void > = new Set ( ) ;
811
912if ( typeof window !== 'undefined' ) {
10- window . addEventListener (
11- 'touchstart' ,
12- function onFirstTouch ( ) {
13- console . info ( 'Touch detected, considering the screen as touch enabled.' ) ;
14- userHasTouchedScreen = true ;
15- window . removeEventListener ( 'touchstart' , onFirstTouch , false ) ;
16- } ,
17- false
18- ) ;
19-
20- // An event listener is added (and then removed at the first event triggering) and
21- // will determine if the user is on a device that uses a mouse.
22- // If the first pointermove event is not triggered by a mouse move, the device
23- // will never be considered as mouse-enabled.
24- // Note: mousemove cannot be used since browsers emulate the mouse movement when
25- // the screen is touched.
26- window . addEventListener (
27- 'pointermove' ,
28- function onPointerMove ( event : PointerEvent ) {
29- console . info ( 'Pointer move detected.' ) ;
30- if ( event . pointerType === 'mouse' ) {
31- console . info (
32- 'Pointer type is mouse, considering the device is a desktop/laptop computer.'
33- ) ;
34- userHasMovedMouse = true ;
35- }
36- window . removeEventListener ( 'pointermove' , onPointerMove , false ) ;
37- } ,
38- false
39- ) ;
13+ window . addEventListener ( 'pointerdown' , ( event : PointerEvent ) => {
14+ const newType : ScreenType =
15+ event . pointerType === 'touch' ? 'touch' : 'normal' ;
16+ if ( newType === _screenType ) return ;
17+ console . info (
18+ `Screen type changed from "${ _screenType } " to "${ newType } " (pointerType: "${
19+ event . pointerType
20+ } ").`
21+ ) ;
22+ _screenType = newType ;
23+ _listeners . forEach ( fn => fn ( newType ) ) ;
24+ } ) ;
4025}
4126
4227type Props = { |
@@ -50,21 +35,28 @@ export const ScreenTypeMeasurer = ({ children }: Props): React.Node =>
5035 children ( useScreenType ( ) ) ;
5136
5237/**
53- * Return if the screen is a touchscreen or not.
38+ * Returns whether the screen is currently being used as a touchscreen or not.
39+ * Dynamically switches when the user alternates between touch and mouse/pen,
40+ * so hybrid devices (e.g. Windows touchscreen laptops) are handled correctly.
5441 */
5542export const useScreenType = ( ) : ScreenType => {
56- // Note: this is not a React hook but is named as one to encourage
57- // components to use it as such, so that it could be reworked
58- // at some point to use a context (verify in this case all usages).
59- if ( typeof window === 'undefined' ) return 'normal' ;
43+ const [ screenType , setScreenType ] = React . useState < ScreenType > ( _screenType ) ;
6044
61- return userHasTouchedScreen ? 'touch' : 'normal' ;
62- } ;
45+ React . useEffect ( ( ) => {
46+ // setScreenType is stable across renders, safe to store in the Set.
47+ _listeners . add ( setScreenType ) ;
48+ return ( ) => {
49+ _listeners . delete ( setScreenType ) ;
50+ } ;
51+ } , [ ] ) ;
6352
64- export const useShouldAutofocusInput = ( ) : boolean => {
65- const isTouchscreen = useScreenType ( ) === 'touch' ;
66- // Whatever size the screen is, if a touch event has been detected, no autofocus should
67- // be triggered (that would annoyingly open the keyboard) unless a mouse move has been
68- // detected (in that case, the device should be a touch-enabled desktop/laptop computer).
69- return ! ( isTouchscreen && ! userHasMovedMouse ) ;
53+ return screenType ;
7054} ;
55+
56+ /**
57+ * Returns true if inputs should be auto-focused.
58+ * No autofocus when the last interaction was touch, to avoid opening the
59+ * on-screen keyboard unexpectedly.
60+ */
61+ export const useShouldAutofocusInput = ( ) : boolean =>
62+ useScreenType ( ) !== 'touch' ;
0 commit comments