cx 26.7.2 → 26.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/ui/Cx.d.ts CHANGED
@@ -4,6 +4,8 @@ import { Instance } from "./Instance";
4
4
  import { ResolvedCultureInfo } from "./Culture";
5
5
  import { View } from "../data/View";
6
6
  import { Config } from "./Prop";
7
+ export declare function enableSyncUpdateCoalescing(limit?: number): void;
8
+ export declare function disableSyncUpdateCoalescing(): void;
7
9
  export interface CxProps {
8
10
  widget?: Config;
9
11
  items?: Config;
@@ -43,7 +45,7 @@ export declare class Cx extends VDOM.Component<CxProps, CxState> {
43
45
  deferCounter: number;
44
46
  pendingUpdateTimer?: NodeJS.Timeout;
45
47
  unsubscribeIdleRequest?: () => void;
46
- stateUpdateDepth: number;
48
+ stateUpdateInFlight: boolean;
47
49
  constructor(props: CxProps);
48
50
  UNSAFE_componentWillReceiveProps(props: CxProps): void;
49
51
  getInstance(): Instance;
@@ -51,6 +53,8 @@ export declare class Cx extends VDOM.Component<CxProps, CxState> {
51
53
  componentDidMount(): void;
52
54
  componentDidUpdate(): void;
53
55
  update(): void;
56
+ issueSyncSetState(): void;
57
+ scheduleStateUpdate(): void;
54
58
  onStateUpdateCompleted(): void;
55
59
  waitForIdle(): void;
56
60
  componentWillUnmount(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"Cx.d.ts","sourceRoot":"","sources":["../../src/ui/Cx.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAE7B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAc,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQtC,OAAO,EAA2D,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACzG,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,WAAW,OAAO;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChE,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,GAAG,CAAA;KAAE,KAAK,GAAG,CAAC;IACnD,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;CACb;AAED,qBAAa,EAAG,SAAQ,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,CAAC;IACZ,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACtD,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpC,gBAAgB,EAAE,MAAM,CAAK;gBAEjB,KAAK,EAAE,OAAO;IA0C1B,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAsBtD,WAAW,IAAI,QAAQ;IAcvB,MAAM;IAkBN,iBAAiB,IAAI,IAAI;IAOzB,kBAAkB,IAAI,IAAI;IAM1B,MAAM,IAAI,IAAI;IAkCd,sBAAsB;IAWtB,WAAW,IAAI,IAAI;IAgBnB,oBAAoB,IAAI,IAAI;IAO5B,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;IAe9D,wBAAwB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI;CAIzD"}
1
+ {"version":3,"file":"Cx.d.ts","sourceRoot":"","sources":["../../src/ui/Cx.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAE7B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAc,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAatC,OAAO,EAA2D,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACzG,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAgBhC,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAG/D;AACD,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AASD,MAAM,WAAW,OAAO;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChE,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,GAAG,CAAA;KAAE,KAAK,GAAG,CAAC;IACnD,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;CACb;AAED,qBAAa,EAAG,SAAQ,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,CAAC;IACZ,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACtD,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGpC,mBAAmB,EAAE,OAAO,CAAS;gBAEzB,KAAK,EAAE,OAAO;IA0C1B,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAsBtD,WAAW,IAAI,QAAQ;IAcvB,MAAM;IAkBN,iBAAiB,IAAI,IAAI;IAOzB,kBAAkB,IAAI,IAAI;IAM1B,MAAM,IAAI,IAAI;IAmCd,iBAAiB,IAAI,IAAI;IAezB,mBAAmB;IAgBnB,sBAAsB;IAatB,WAAW,IAAI,IAAI;IAgBnB,oBAAoB,IAAI,IAAI;IAc5B,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;IAe9D,wBAAwB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI;CAIzD"}
package/build/ui/Cx.js CHANGED
@@ -5,11 +5,37 @@ import { Instance } from "./Instance";
5
5
  import { RenderingContext } from "./RenderingContext";
6
6
  import { debug, appDataFlag } from "../util/Debug";
7
7
  import { Timing, now, appLoopFlag, vdomRenderFlag } from "../util/Timing";
8
- import { isBatchingUpdates, notifyBatchedUpdateStarting, notifyBatchedUpdateCompleted } from "./batchUpdates";
8
+ import { isBatchingUpdates, notifyBatchedUpdateStarting, notifyBatchedUpdateCompleted, hasBatchedUpdateSubscribers, } from "./batchUpdates";
9
9
  import { shallowEquals } from "../util/shallowEquals";
10
10
  import { PureContainer } from "./PureContainer";
11
11
  import { onIdleCallback } from "../util/onIdleCallback";
12
12
  import { getCurrentCulture, pushCulture, popCulture } from "./Culture";
13
+ // On by default. Cx coalesces re-entrant synchronous updates and, once a burst grows deep, yields to a
14
+ // microtask so React's global per-root nested-update counter resets before continuing -- preventing
15
+ // "Maximum update depth exceeded" on large renders that write to the store while they render (e.g. a
16
+ // several-hundred-page report). For updates that settle in a single render -- virtually all of them --
17
+ // this is equivalent to the previous behavior; it only diverges under a deep re-entrant render burst.
18
+ // If you suspect it causes trouble, opt out at app startup with disableSyncUpdateCoalescing() -- and
19
+ // please report the issue so it can be fixed.
20
+ let coalesceSyncUpdates = true;
21
+ // Consecutive commit-phase re-render rounds allowed before Cx yields to a microtask. Kept under React's
22
+ // ~50 nested-update limit (which is global to the root, not per component); the default trades a little
23
+ // initial-render time for a safety margin. Override via enableSyncUpdateCoalescing(limit) if needed.
24
+ let syncBurstLimit = 35;
25
+ export function enableSyncUpdateCoalescing(limit) {
26
+ coalesceSyncUpdates = true;
27
+ if (limit != null)
28
+ syncBurstLimit = limit;
29
+ }
30
+ export function disableSyncUpdateCoalescing() {
31
+ coalesceSyncUpdates = false;
32
+ }
33
+ // Module-global because React's nested-update limit is global to the root, not per component. A large
34
+ // initial render can re-render the root Cx and every detached page Restate hundreds of times in one
35
+ // synchronous burst; once the burst grows past syncBurstLimit we yield so React's commit finishes
36
+ // without a synchronously-scheduled follow-up (which resets its counter) before we render again.
37
+ let activeSyncUpdates = 0; // Cx instances with a synchronous setState in flight
38
+ let syncBurstRounds = 0; // commit-phase re-render rounds issued since the last yield / burst start
13
39
  export class Cx extends VDOM.Component {
14
40
  widget;
15
41
  store;
@@ -23,8 +49,9 @@ export class Cx extends VDOM.Component {
23
49
  deferCounter;
24
50
  pendingUpdateTimer;
25
51
  unsubscribeIdleRequest;
26
- // 0 when no synchronous setState is in flight; >0 while one is pending (re-entrancy guard for update())
27
- stateUpdateDepth = 0;
52
+ // true while a coalesced synchronous setState is in flight for this Cx (re-entrancy guard for update());
53
+ // only used when coalesceSyncUpdates is enabled
54
+ stateUpdateInFlight = false;
28
55
  constructor(props) {
29
56
  super(props);
30
57
  if (props.instance) {
@@ -46,10 +73,10 @@ export class Cx extends VDOM.Component {
46
73
  }
47
74
  this.state = {
48
75
  deferToken: 0,
76
+ data: props.subscribe ? this.store.getData() : null,
49
77
  };
50
78
  if (props.subscribe) {
51
79
  this.unsubscribe = this.store.subscribe(this.update.bind(this));
52
- this.state.data = this.store.getData();
53
80
  }
54
81
  this.onStateUpdateCompleted = this.onStateUpdateCompleted.bind(this);
55
82
  this.flags = {};
@@ -114,47 +141,81 @@ export class Cx extends VDOM.Component {
114
141
  debug(appDataFlag, data);
115
142
  if (this.flags.preparing)
116
143
  this.flags.dirty = true;
117
- // Synchronous path (while batching, or for `immediate` instances). At most one setState may be in flight
118
- // per Cx: if one is already pending (stateUpdateDepth != 0) skip issuing another. Re-entrant setStates
119
- // nested deep -- e.g. a large initial render that writes to the store while it renders -- are what trip
120
- // React's "Maximum update depth exceeded"; coalescing them into a single in-flight update avoids that.
121
- // The pending update's completion callback re-reads the store and renders again if it changed
122
- // (see onStateUpdateCompleted), so no change is dropped.
144
+ // Synchronous path: while batching (incl. batchUpdatesAndNotify, which page-breaking relies on) or for
145
+ // `immediate` instances.
123
146
  else if (this.props.immediate || isBatchingUpdates()) {
124
- if (this.stateUpdateDepth == 0) {
125
- this.stateUpdateDepth = 1;
147
+ if (!coalesceSyncUpdates) {
148
+ // Opt-out path (disableSyncUpdateCoalescing()): the original behavior -- render synchronously
149
+ // for every update, no coalescing.
126
150
  notifyBatchedUpdateStarting();
127
- this.setState({ data: data }, this.onStateUpdateCompleted);
151
+ this.setState({ data: data }, notifyBatchedUpdateCompleted);
152
+ return;
128
153
  }
154
+ // Coalescing enabled: at most one setState may be in flight per Cx. Re-entrant update() calls (the
155
+ // render itself writing to the store, as happens throughout a large initial render) are skipped --
156
+ // the in-flight round re-reads the store on completion (see onStateUpdateCompleted), so nothing is
157
+ // dropped. Nesting these setStates deep is what trips React's "Maximum update depth exceeded".
158
+ if (this.stateUpdateInFlight)
159
+ return;
160
+ if (activeSyncUpdates === 0)
161
+ syncBurstRounds = 0; // fresh burst -> reset the shared round counter
162
+ activeSyncUpdates++;
163
+ this.stateUpdateInFlight = true;
164
+ notifyBatchedUpdateStarting();
165
+ this.issueSyncSetState();
129
166
  }
130
167
  else {
131
168
  // standard mode: coalesce sequential store commands into a single deferred update
132
- if (!this.pendingUpdateTimer) {
133
- notifyBatchedUpdateStarting();
134
- this.pendingUpdateTimer = setTimeout(() => {
135
- delete this.pendingUpdateTimer;
136
- // read fresh data at fire time so the coalesced update renders the latest store state
137
- this.setState({ data: this.store.getData() }, notifyBatchedUpdateCompleted);
138
- }, 0);
139
- }
169
+ this.scheduleStateUpdate();
140
170
  }
141
171
  }
142
- // Completion callback for the synchronous setState above. If the store changed while that update was
143
- // rendering -- i.e. the render itself wrote to the store, as happens throughout a large initial render --
144
- // render once more with the latest data, keeping this callback armed; otherwise the burst has settled, so
145
- // clear the in-flight flag and report completion. Re-arming coalesces until the store reaches a fixpoint;
146
- // a genuinely non-converging update loop is still caught by React's own max-update-depth guard.
147
- onStateUpdateCompleted() {
148
- let latestData = this.store.getData();
149
- if (this.state.data === latestData) {
150
- this.stateUpdateDepth = 0;
151
- notifyBatchedUpdateCompleted();
172
+ // Issue the next synchronous render round (coalescing path). Once a burst grows past SYNC_BURST_LIMIT we
173
+ // render from a microtask instead, so React's commit finishes without a synchronously-scheduled follow-up
174
+ // and its global nested-update counter resets before we continue. The yield is suppressed while a
175
+ // batchUpdatesAndNotify is in flight: page-breaking convergence is shallow, so staying fully synchronous
176
+ // keeps its notify callback firing right after the change commits (and the counter never gets near 50).
177
+ issueSyncSetState() {
178
+ if (hasBatchedUpdateSubscribers() || ++syncBurstRounds <= syncBurstLimit) {
179
+ this.setState({ data: this.store.getData() }, this.onStateUpdateCompleted);
152
180
  }
153
181
  else {
154
- this.stateUpdateDepth++;
155
- this.setState({ data: latestData }, this.onStateUpdateCompleted);
182
+ queueMicrotask(() => {
183
+ // The event loop has turned, so React's global nested-update counter has reset; realign ours.
184
+ // Resetting here (not before scheduling) keeps the counter high through the rest of the current
185
+ // commit, so any other Cx re-arming in the same commit also yields instead of extending the chain.
186
+ syncBurstRounds = 0;
187
+ if (this.stateUpdateInFlight)
188
+ this.setState({ data: this.store.getData() }, this.onStateUpdateCompleted);
189
+ });
190
+ }
191
+ }
192
+ scheduleStateUpdate() {
193
+ if (!this.pendingUpdateTimer) {
194
+ notifyBatchedUpdateStarting();
195
+ this.pendingUpdateTimer = setTimeout(() => {
196
+ delete this.pendingUpdateTimer;
197
+ // read fresh data at fire time so the coalesced update renders the latest store state
198
+ this.setState({ data: this.store.getData() }, notifyBatchedUpdateCompleted);
199
+ }, 0);
156
200
  }
157
201
  }
202
+ // Completion callback for the coalescing path's setState. React runs it after the commit, so the DOM
203
+ // already reflects this render. If the render wrote to the store, run another round -- keeping the
204
+ // batched-update accounting balanced (open the next round before closing this one) so `finished` never
205
+ // catches `pending` mid-convergence and batchUpdatesAndNotify resolves only at the store fixpoint.
206
+ // Otherwise the burst has settled: clear the in-flight flag and report completion.
207
+ onStateUpdateCompleted() {
208
+ if (this.state.data === this.store.getData()) {
209
+ // Converged: the store didn't change while this round rendered, so the DOM is up to date.
210
+ this.stateUpdateInFlight = false;
211
+ activeSyncUpdates--;
212
+ notifyBatchedUpdateCompleted();
213
+ return;
214
+ }
215
+ notifyBatchedUpdateStarting(); // open the next round
216
+ notifyBatchedUpdateCompleted(); // close this one
217
+ this.issueSyncSetState();
218
+ }
158
219
  waitForIdle() {
159
220
  if (!this.props.deferredUntilIdle)
160
221
  return;
@@ -162,12 +223,19 @@ export class Cx extends VDOM.Component {
162
223
  this.unsubscribeIdleRequest();
163
224
  let token = ++this.deferCounter;
164
225
  this.unsubscribeIdleRequest = onIdleCallback(() => {
165
- this.setState({ deferToken: token }, this.onStateUpdateCompleted);
226
+ this.setState({ deferToken: token });
166
227
  }, {
167
228
  timeout: this.props.idleTimeout || 30000,
168
229
  });
169
230
  }
170
231
  componentWillUnmount() {
232
+ if (this.stateUpdateInFlight) {
233
+ // Release the open pending round so a waiting batchUpdatesAndNotify can settle instead of waiting
234
+ // out its fallback timeout, and keep the shared in-flight refcount balanced.
235
+ this.stateUpdateInFlight = false;
236
+ activeSyncUpdates--;
237
+ notifyBatchedUpdateCompleted();
238
+ }
171
239
  if (this.pendingUpdateTimer)
172
240
  clearTimeout(this.pendingUpdateTimer);
173
241
  if (this.unsubscribeIdleRequest)
@@ -1,5 +1,6 @@
1
1
  export declare function batchUpdates(callback: () => void): void;
2
2
  export declare function isBatchingUpdates(): boolean;
3
+ export declare function hasBatchedUpdateSubscribers(): boolean;
3
4
  export declare function notifyBatchedUpdateStarting(): void;
4
5
  export declare function notifyBatchedUpdateCompleted(): void;
5
6
  export declare function batchUpdatesAndNotify(callback: () => void, notifyCallback: (success: boolean) => void, timeout?: number): void;
@@ -1 +1 @@
1
- {"version":3,"file":"batchUpdates.d.ts","sourceRoot":"","sources":["../../src/ui/batchUpdates.ts"],"names":[],"mappings":"AAYA,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAWvD;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAIlD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAMnD;AAED,wBAAgB,qBAAqB,CAClC,QAAQ,EAAE,MAAM,IAAI,EACpB,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EAC1C,OAAO,GAAE,MAAa,GACtB,IAAI,CAwBN"}
1
+ {"version":3,"file":"batchUpdates.d.ts","sourceRoot":"","sources":["../../src/ui/batchUpdates.ts"],"names":[],"mappings":"AAYA,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAWvD;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAKD,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAIlD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAMnD;AAED,wBAAgB,qBAAqB,CAClC,QAAQ,EAAE,MAAM,IAAI,EACpB,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EAC1C,OAAO,GAAE,MAAa,GACtB,IAAI,CAwBN"}
@@ -19,6 +19,12 @@ export function batchUpdates(callback) {
19
19
  export function isBatchingUpdates() {
20
20
  return isBatching > 0;
21
21
  }
22
+ // True while a batchUpdatesAndNotify accumulator is subscribed, i.e. some caller is waiting for the
23
+ // current batch's renders to finish. Cx uses this to stay fully synchronous during page-breaking so the
24
+ // notify callback still fires right after the change commits.
25
+ export function hasBatchedUpdateSubscribers() {
26
+ return !promiseSubscribers.isEmpty();
27
+ }
22
28
  export function notifyBatchedUpdateStarting() {
23
29
  promiseSubscribers.execute((x) => {
24
30
  x.pending++;