1
- import { useState , useMemo , useCallback , useRef , useLayoutEffect , } from 'react' ;
1
+ import { useState , useMemo , useCallback , useRef , useLayoutEffect } from 'react' ;
2
2
3
3
function getVpWidth ( ) {
4
4
return ( typeof window != 'undefined' ) ? Math . max (
@@ -44,6 +44,57 @@ const resolverMap = new Map();
44
44
let vpWidth = getVpWidth ( ) ;
45
45
let vpHeight = getVpHeight ( ) ;
46
46
47
+ /**
48
+ * called to update resize dimensions with the handlers
49
+ * passed themselves; separated from the actual
50
+ * pub-sub caller (onResize) so we can individually
51
+ * dispatch subscription events and avoid updating all
52
+ * components at once
53
+ *
54
+ * @param {* } listener
55
+ * @param {* } vpWidth
56
+ * @param {* } vpHeight
57
+ */
58
+ function triggerResizeListener ( listener , vpWidth , vpHeight ) {
59
+ const params = { vpW : vpWidth , vpH : vpHeight } ;
60
+
61
+ let shouldRun = false ;
62
+ let hash ;
63
+
64
+ const { options, prevHash= undefined } = resolverMap ?. get ( listener ) || { } ;
65
+ const { hasher } = options ;
66
+
67
+ if ( ! options ?. hasher ) {
68
+ const dimensionsUpdated = new Set ( ) ;
69
+
70
+ switch ( options ?. dimension ) {
71
+ case 'w' :
72
+ hash = `${ vpWidth } ` ;
73
+ break ;
74
+ case 'h' :
75
+ hash = `${ vpHeight } ` ;
76
+ break ;
77
+ default :
78
+ case 'both' :
79
+ hash = `${ vpWidth } _${ vpHeight } ` ;
80
+ break ;
81
+ }
82
+ }
83
+ else {
84
+ hash = hasher ( params ) ;
85
+ }
86
+
87
+ if ( hash != prevHash ) { shouldRun = true }
88
+
89
+ if ( shouldRun ) {
90
+ const state = { ...params , options, hash } ;
91
+ resolverMap . set ( listener , {
92
+ options, prevHash : hash , prevState : state
93
+ } ) ;
94
+ listener ( state ) ;
95
+ }
96
+ }
97
+
47
98
/**
48
99
* called to update resize dimensions;
49
100
* only called once throughout hooks so if
@@ -55,81 +106,103 @@ function onResize() {
55
106
vpWidth = getVpWidth ( ) ;
56
107
vpHeight = getVpHeight ( ) ;
57
108
58
- listeners . forEach ( listener => {
59
- const params = { vpW : vpWidth , vpH : vpHeight } ;
60
-
61
- let shouldRun = false ;
62
- let hash ;
63
-
64
- const { options, prevHash= undefined } = resolverMap ?. get ( listener ) || { } ;
65
- const { hasher } = options ;
66
-
67
- if ( ! options ?. hasher ) {
68
- shouldRun = true ;
69
- }
70
- else {
71
- hash = hasher ( params ) ;
72
- if ( hash != prevHash ) { shouldRun = true }
73
- }
74
-
75
- if ( shouldRun ) {
76
- resolverMap . set ( listener , { options, prevHash : hash } ) ;
77
- listener ( { ...params , options, hash } ) ;
78
- }
79
- } ) ;
109
+ listeners . forEach ( listener =>
110
+ triggerResizeListener ( listener , vpWidth , vpHeight )
111
+ ) ;
80
112
}
81
113
82
114
// =============== //
83
115
// the Hook //
84
116
// =============== //
85
117
86
- /**
87
- * observes the viewport. If input not specified,
88
- * returns the [width, height] when the window changes.
89
- * If input is specified as a number, it returns the [width, height].
90
- *
91
- * If the input is specified as a function, it accepts { vpW, vpH }
92
- * and will only return a new value and update when the value
93
- * computed changes.
94
- *
95
- * @input {Function|Number} input
96
- */
118
+ function getInitialState ( options , vpW , vpH ) {
119
+ let returnValue = { } ;
120
+ if ( options . hasher ) {
121
+ returnValue = options . hasher ( { vpW, vpH } ) ;
122
+ } else {
123
+ returnValue = { vpW, vpH } ;
124
+ }
125
+
126
+ return ( ! options . hasher ?
127
+ { vpW, vpH } :
128
+ hasher && hasher ( { vpW : vpWidth , vpH : vpHeight } )
129
+ )
130
+ }
131
+
97
132
function useViewportSizes ( input ) {
98
133
const hasher = ( ( typeof input == 'function' ) ?
99
134
input :
100
135
input ?. hasher
101
136
) || undefined ;
102
137
103
- const debounceTimeout = ( ( typeof input == 'number' ) ?
138
+ const debounceTimeout = ( ( typeof input ?. debounceTimeout == 'number' ) ?
139
+ input ?. debounceTimeout : 0
140
+ ) ;
141
+
142
+ const throttleTimeout = ( ( typeof input == 'number' ) ?
104
143
input :
105
- input ?. debounceTimeout
144
+ input ?. throttleTimeout
106
145
) || 0 ;
107
146
108
147
const dimension = input ?. dimension || 'both' ;
109
148
110
149
const options = {
111
- ...( typeof input == 'function ' ? { } : input ) ,
150
+ ...( typeof input == 'object ' ? input : { } ) ,
112
151
dimension,
113
152
hasher
114
153
} ;
115
154
116
- const [ state , setState ] = useState ( ( ) => ( ! hasher ?
117
- { vpW : vpWidth , vpH : vpHeight } :
118
- hasher && hasher ( { vpW : vpWidth , vpH : vpHeight } )
119
- ) ) ;
155
+ const [ state , setState ] = useState ( ( ) => getInitialState ( options ) ) ;
120
156
const debounceTimeoutRef = useRef ( undefined ) ;
121
- const listener = useCallback ( ( ! debounceTimeout ?
122
- state => setState ( state ) :
123
- state => {
157
+ const throttleTimeoutRef = useRef ( undefined ) ;
158
+ const lastThrottledRef = useRef ( undefined ) ;
159
+
160
+ const listener = useCallback ( nextState => {
161
+ if ( ! debounceTimeout && ! throttleTimeout ) {
162
+ setState ( nextState ) ;
163
+ return ;
164
+ }
165
+
166
+ if ( debounceTimeout ) {
124
167
if ( debounceTimeoutRef . current ) {
125
168
clearTimeout ( debounceTimeoutRef . current ) ;
126
169
}
127
170
128
- debounceTimeoutRef . current = setTimeout ( ( ) => {
129
- setState ( state ) ;
130
- } , debounceTimeout ) ;
171
+ debounceTimeoutRef . current = setTimeout ( ( ) => (
172
+ setState ( nextState )
173
+ ) , debounceTimeout ) ;
174
+ }
175
+
176
+ if ( throttleTimeout ) {
177
+ const lastTick = lastThrottledRef . current ;
178
+ const timeSinceLast = ( ! lastTick ? throttleTimeout : Date . now ( ) - lastTick ) ;
179
+ console . log ( 'should process in ->' , throttleTimeout - timeSinceLast ) ;
180
+
181
+ throttleTimeoutRef . current = setTimeout ( ( ) => {
182
+ lastThrottledRef . current = new Date ( ) . getTime ( ) ;
183
+ const vpWidth = getVpWidth ( ) ;
184
+ const vpHeight = getVpHeight ( ) ;
185
+
186
+ const dimensionsUpdated = new Set ( ) ;
187
+
188
+ if ( vpHeight != state . vpH ) {
189
+ dimensionsUpdated . add ( 'h' ) ;
190
+ }
191
+
192
+ if ( vpWidth != state . vpW ) {
193
+ dimensionsUpdated . add ( 'w' ) ;
194
+ }
195
+
196
+ if ( dimensionsUpdated . has ( 'w' ) || dimensionsUpdated . has ( 'h' ) ) {
197
+ dimensionsUpdated . add ( 'both' ) ;
198
+ }
199
+
200
+ if ( dimensionsUpdated . has ( dimension ) ) {
201
+ setState ( { vpW : vpWidth , vpH : vpHeight } ) ;
202
+ }
203
+ } , Math . max ( 0 , throttleTimeout - timeSinceLast ) ) ;
131
204
}
132
- ) , [ debounceTimeoutRef , hasher , dimension ] ) ;
205
+ } , [ debounceTimeoutRef , hasher , dimension , state ] ) ;
133
206
134
207
useLayoutEffect ( ( ) => {
135
208
resolverMap . set ( listener , {
0 commit comments