@@ -10,7 +10,7 @@ import {
10
10
isObservable ,
11
11
} from '@nx-js/observer-util' ;
12
12
13
- import { hasHooks } from './utils' ;
13
+ import { globalObj , hasHooks } from './utils' ;
14
14
15
15
export let isInsideFunctionComponent = false ;
16
16
export let isInsideClassComponentRender = false ;
@@ -32,10 +32,25 @@ function mapStateToStores(state) {
32
32
// is to prevent excessive rendering in situations where updates can occur
33
33
// outside of React's built-in batching. e.g. after resolving a promise,
34
34
// in a setTimeout callback, in an event handler.
35
+ //
36
+ // NOTE: This should be revisited after React improves batching for
37
+ // Suspense / etc.
35
38
let batchesPending = { } ;
36
39
let taskPending = false ;
37
40
let viewIndexCounter = 0 ;
41
+ let inEventLoop = false ;
42
+
38
43
function batchSetState ( viewIndex , fn ) {
44
+ if ( inEventLoop ) {
45
+ // If we are in the main event loop, React handles the batching
46
+ // automatically, so we run the change immediately. Deferring the
47
+ // update can cause unexpected cursor shifts in input elements,
48
+ // since the change can't be tied back to the action:
49
+ // https://github.com/facebook/react/issues/5386
50
+ fn ( ) ;
51
+ return ;
52
+ }
53
+
39
54
batchesPending [ viewIndex ] = fn ;
40
55
if ( ! taskPending ) {
41
56
taskPending = true ;
@@ -57,6 +72,63 @@ function clearBatch(viewIndex) {
57
72
delete batchesPending [ viewIndex ] ;
58
73
}
59
74
75
+ // this creates and returns a wrapped version of the passed function
76
+ // the cache is necessary to always map the same thing to the same function
77
+ // which makes sure that addEventListener/removeEventListener pairs don't break
78
+ const cache = new WeakMap ( ) ;
79
+ function wrapFn ( fn , wrapper ) {
80
+ if ( typeof fn !== 'function' ) {
81
+ return fn ;
82
+ }
83
+ let wrapped = cache . get ( fn ) ;
84
+ if ( ! wrapped ) {
85
+ wrapped = function ( ...args ) {
86
+ return wrapper ( fn , this , args ) ;
87
+ } ;
88
+ cache . set ( fn , wrapped ) ;
89
+ }
90
+ return wrapped ;
91
+ }
92
+
93
+ function wrapMethodCallbacks ( obj , method , wrapper ) {
94
+ const descriptor = Object . getOwnPropertyDescriptor ( obj , method ) ;
95
+ if (
96
+ descriptor &&
97
+ descriptor . writable &&
98
+ typeof descriptor . value === 'function'
99
+ ) {
100
+ obj [ method ] = new Proxy ( descriptor . value , {
101
+ apply ( target , ctx , args ) {
102
+ return Reflect . apply (
103
+ target ,
104
+ ctx ,
105
+ args . map ( f => wrapFn ( f , wrapper ) ) ,
106
+ ) ;
107
+ } ,
108
+ } ) ;
109
+ }
110
+ }
111
+
112
+ // wrapped obj.addEventListener(cb) like callbacks
113
+ function wrapMethodsCallbacks ( obj , methods , wrapper ) {
114
+ methods . forEach ( method =>
115
+ wrapMethodCallbacks ( obj , method , wrapper ) ,
116
+ ) ;
117
+ }
118
+
119
+ // batch addEventListener calls
120
+ if ( globalObj . EventTarget ) {
121
+ wrapMethodsCallbacks (
122
+ EventTarget . prototype ,
123
+ [ 'addEventListener' , 'removeEventListener' ] ,
124
+ ( fn , ctx , args ) => {
125
+ inEventLoop = true ;
126
+ fn . apply ( ctx , args ) ;
127
+ inEventLoop = false ;
128
+ } ,
129
+ ) ;
130
+ }
131
+
60
132
export function view ( Comp ) {
61
133
const isStatelessComp = ! (
62
134
Comp . prototype && Comp . prototype . isReactComponent
0 commit comments