7
7
* @flow strict-local
8
8
*/
9
9
10
+ import type { CallbackRef } from '../../types/react' ;
11
+
10
12
import * as React from 'react' ;
11
13
12
14
import { errorMsg } from '../../shared/logUtils' ;
15
+ import { useElementCallback } from '../../shared/useElementCallback' ;
13
16
import { useViewportScale } from './ContextViewportScale' ;
14
17
15
18
type Options = {
@@ -25,36 +28,36 @@ function errorUnimplemented(name: string) {
25
28
}
26
29
}
27
30
28
- function proxy ( nativeRef : React . RefObject < Node > , name : string ) {
31
+ function proxy ( node : Node , name : string ) {
29
32
return ( ...args : Array < mixed > ) => {
30
- const node = nativeRef . current ;
31
33
if ( node ?. [ name ] ) {
32
34
return node [ name ] ( ...args ) ;
33
35
}
34
36
errorUnimplemented ( name ) ;
35
37
} ;
36
38
}
37
39
38
- export function useStrictDOMElement < T > (
39
- ref : React . RefSetter < Node > ,
40
- { tagName } : Options
41
- ) : React . RefObject < T | null > {
42
- const nativeRef = React . useRef < T | null > ( null ) ;
43
- const { scale : viewportScale } = useViewportScale ( ) ;
40
+ const memoizedStrictRefs : WeakMap < Node , mixed > = new WeakMap ( ) ;
44
41
45
- React . useImperativeHandle ( ref , ( ) => {
46
- return {
42
+ function getOrCreateStrictRef (
43
+ node : Node ,
44
+ tagName : string ,
45
+ viewportScale : number
46
+ ) {
47
+ const ref = memoizedStrictRefs . get ( node ) ;
48
+ if ( ref != null ) {
49
+ return ref ;
50
+ } else {
51
+ const strictRef = {
47
52
get __nativeTag ( ) {
48
- const node = nativeRef . current as Node ;
49
53
return node ?. __nativeTag ;
50
54
} ,
51
- addEventListener : proxy ( nativeRef , 'addEventListener' ) ,
52
- animate : proxy ( nativeRef , 'animate' ) ,
53
- blur : proxy ( nativeRef , 'blur' ) ,
54
- click : proxy ( nativeRef , 'click' ) ,
55
+ addEventListener : proxy ( node , 'addEventListener' ) ,
56
+ animate : proxy ( node , 'animate' ) ,
57
+ blur : proxy ( node , 'blur' ) ,
58
+ click : proxy ( node , 'click' ) ,
55
59
get complete ( ) {
56
60
if ( tagName === 'img' ) {
57
- const node = nativeRef . current as Node ;
58
61
if ( node ?. complete == null ) {
59
62
// Assume images are never pre-loaded in React Native
60
63
return false ;
@@ -63,12 +66,11 @@ export function useStrictDOMElement<T>(
63
66
}
64
67
}
65
68
} ,
66
- contains : proxy ( nativeRef , 'contains' ) ,
67
- dispatchEvent : proxy ( nativeRef , 'dispatchEvent' ) ,
68
- focus : proxy ( nativeRef , 'focus' ) ,
69
- getAttribute : proxy ( nativeRef , 'getAttribute' ) ,
69
+ contains : proxy ( node , 'contains' ) ,
70
+ dispatchEvent : proxy ( node , 'dispatchEvent' ) ,
71
+ focus : proxy ( node , 'focus' ) ,
72
+ getAttribute : proxy ( node , 'getAttribute' ) ,
70
73
getBoundingClientRect ( ) {
71
- const node = nativeRef . current as Node ;
72
74
const getBoundingClientRect =
73
75
node ?. getBoundingClientRect ?? node ?. unstable_getBoundingClientRect ;
74
76
if ( getBoundingClientRect ) {
@@ -85,21 +87,59 @@ export function useStrictDOMElement<T>(
85
87
}
86
88
return errorUnimplemented ( 'getBoundingClientRect' ) ;
87
89
} ,
88
- getRootNode : proxy ( nativeRef , 'getRootNode' ) ,
89
- hasPointerCapture : proxy ( nativeRef , 'hasPointerCapture' ) ,
90
+ getRootNode : proxy ( node , 'getRootNode' ) ,
91
+ hasPointerCapture : proxy ( node , 'hasPointerCapture' ) ,
90
92
nodeName : tagName . toUpperCase ( ) ,
91
- releasePointerCapture : proxy ( nativeRef , 'releasePointerCapture' ) ,
92
- removeEventListener : proxy ( nativeRef , 'removeEventListener' ) ,
93
- scroll : proxy ( nativeRef , 'scroll' ) ,
94
- scrollBy : proxy ( nativeRef , 'scrollBy' ) ,
95
- scrollIntoView : proxy ( nativeRef , 'scrollIntoView' ) ,
96
- scrollTo : proxy ( nativeRef , 'scrollTo' ) ,
97
- select : proxy ( nativeRef , 'select' ) ,
98
- setSelectionRange : proxy ( nativeRef , 'setSelectionRange' ) ,
99
- setPointerCapture : proxy ( nativeRef , 'setPointerCapture' ) ,
100
- showPicker : proxy ( nativeRef , 'showPicker' )
93
+ releasePointerCapture : proxy ( node , 'releasePointerCapture' ) ,
94
+ removeEventListener : proxy ( node , 'removeEventListener' ) ,
95
+ scroll : proxy ( node , 'scroll' ) ,
96
+ scrollBy : proxy ( node , 'scrollBy' ) ,
97
+ scrollIntoView : proxy ( node , 'scrollIntoView' ) ,
98
+ scrollTo : proxy ( node , 'scrollTo' ) ,
99
+ select : proxy ( node , 'select' ) ,
100
+ setSelectionRange : proxy ( node , 'setSelectionRange' ) ,
101
+ setPointerCapture : proxy ( node , 'setPointerCapture' ) ,
102
+ showPicker : proxy ( node , 'showPicker' )
101
103
} ;
102
- } , [ tagName , viewportScale ] ) ;
103
104
104
- return nativeRef ;
105
+ memoizedStrictRefs . set ( node , strictRef ) ;
106
+ return strictRef ;
107
+ }
108
+ }
109
+
110
+ export function useStrictDOMElement < T > (
111
+ ref : React . RefSetter < Node > ,
112
+ { tagName } : Options
113
+ ) : CallbackRef < T > {
114
+ const { scale : viewportScale } = useViewportScale ( ) ;
115
+
116
+ const elementCallback = useElementCallback (
117
+ React . useCallback (
118
+ // $FlowFixMe[unclear-type]
119
+ ( node : any ) => {
120
+ if ( ref == null ) {
121
+ return undefined ;
122
+ } else {
123
+ const strictRef = getOrCreateStrictRef ( node , tagName , viewportScale ) ;
124
+ if ( typeof ref === 'function' ) {
125
+ // $FlowFixMe[incompatible-type] - Flow does not understand ref cleanup.
126
+ const cleanup : void | ( ( ) => void ) = ref ( strictRef ) ;
127
+ return typeof cleanup === 'function'
128
+ ? cleanup
129
+ : ( ) => {
130
+ ref ( null ) ;
131
+ } ;
132
+ } else {
133
+ ref . current = strictRef ;
134
+ return ( ) => {
135
+ ref . current = null ;
136
+ } ;
137
+ }
138
+ }
139
+ } ,
140
+ [ ref , tagName , viewportScale ]
141
+ )
142
+ ) ;
143
+
144
+ return elementCallback ;
105
145
}
0 commit comments