@@ -10,7 +10,7 @@ import {
1010 isObservable ,
1111} from '@nx-js/observer-util' ;
1212
13- import { hasHooks } from './utils' ;
13+ import { globalObj , hasHooks } from './utils' ;
1414
1515export let isInsideFunctionComponent = false ;
1616export let isInsideClassComponentRender = false ;
@@ -32,10 +32,25 @@ function mapStateToStores(state) {
3232// is to prevent excessive rendering in situations where updates can occur
3333// outside of React's built-in batching. e.g. after resolving a promise,
3434// in a setTimeout callback, in an event handler.
35+ //
36+ // NOTE: This should be revisited after React improves batching for
37+ // Suspense / etc.
3538let batchesPending = { } ;
3639let taskPending = false ;
3740let viewIndexCounter = 0 ;
41+ let inEventLoop = false ;
42+
3843function 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+
3954 batchesPending [ viewIndex ] = fn ;
4055 if ( ! taskPending ) {
4156 taskPending = true ;
@@ -57,6 +72,66 @@ function clearBatch(viewIndex) {
5772 delete batchesPending [ viewIndex ] ;
5873}
5974
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+ try {
127+ fn . apply ( ctx , args ) ;
128+ } finally {
129+ inEventLoop = false ;
130+ }
131+ } ,
132+ ) ;
133+ }
134+
60135export function view ( Comp ) {
61136 const isStatelessComp = ! (
62137 Comp . prototype && Comp . prototype . isReactComponent
0 commit comments