cx 26.7.4 → 26.7.5
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 +8 -5
- package/build/ui/Cx.d.ts.map +1 -1
- package/build/ui/Cx.js +114 -97
- package/build/ui/batchUpdates.d.ts +2 -3
- package/build/ui/batchUpdates.d.ts.map +1 -1
- package/build/ui/batchUpdates.js +11 -7
- package/dist/manifest.js +707 -710
- package/dist/ui.js +125 -114
- package/package.json +1 -1
- package/src/ui/Cx.spec.tsx +27 -0
- package/src/ui/Cx.tsx +505 -492
- package/src/ui/DataProxy.ts +55 -55
- package/src/ui/Rescope.ts +50 -50
- package/src/ui/batchUpdates.ts +12 -9
- package/src/ui/exprHelpers.ts +96 -96
- package/src/widgets/Sandbox.ts +104 -104
- package/src/widgets/form/ColorField.scss +112 -112
- package/src/widgets/form/DateTimeField.scss +111 -111
- package/src/widgets/form/LookupField.maps.scss +26 -26
- package/src/widgets/form/MonthField.scss +113 -113
- package/src/widgets/form/Select.scss +104 -104
- package/src/widgets/form/TextField.scss +66 -66
- package/src/widgets/nav/MenuItem.tsx +525 -525
package/build/ui/Cx.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface CxProps {
|
|
|
27
27
|
export interface CxState {
|
|
28
28
|
deferToken: number;
|
|
29
29
|
data?: any;
|
|
30
|
+
error?: boolean;
|
|
30
31
|
}
|
|
31
32
|
export declare class Cx extends VDOM.Component<CxProps, CxState> {
|
|
32
33
|
widget: Widget;
|
|
@@ -40,12 +41,13 @@ export declare class Cx extends VDOM.Component<CxProps, CxState> {
|
|
|
40
41
|
};
|
|
41
42
|
renderCount: number;
|
|
42
43
|
unsubscribe?: () => void;
|
|
43
|
-
componentDidCatch?: (error: Error, info: any) => void;
|
|
44
44
|
forceUpdateCallback: () => void;
|
|
45
45
|
deferCounter: number;
|
|
46
46
|
pendingUpdateTimer?: NodeJS.Timeout;
|
|
47
47
|
unsubscribeIdleRequest?: () => void;
|
|
48
|
-
|
|
48
|
+
owedNotifications: Set<number>;
|
|
49
|
+
mounted: boolean;
|
|
50
|
+
burstRoundCounted: boolean;
|
|
49
51
|
constructor(props: CxProps);
|
|
50
52
|
UNSAFE_componentWillReceiveProps(props: CxProps): void;
|
|
51
53
|
getInstance(): Instance;
|
|
@@ -53,12 +55,13 @@ export declare class Cx extends VDOM.Component<CxProps, CxState> {
|
|
|
53
55
|
componentDidMount(): void;
|
|
54
56
|
componentDidUpdate(): void;
|
|
55
57
|
update(): void;
|
|
56
|
-
|
|
58
|
+
issueStateUpdate(seq: number): void;
|
|
59
|
+
completeNotification(seq: number): void;
|
|
57
60
|
scheduleStateUpdate(): void;
|
|
58
|
-
onStateUpdateCompleted(): void;
|
|
59
61
|
waitForIdle(): void;
|
|
60
62
|
componentWillUnmount(): void;
|
|
61
63
|
shouldComponentUpdate(props: CxProps, state: CxState): boolean;
|
|
62
|
-
|
|
64
|
+
static getDerivedStateFromError(): Partial<CxState>;
|
|
65
|
+
componentDidCatch(error: Error, info: any): void;
|
|
63
66
|
}
|
|
64
67
|
//# sourceMappingURL=Cx.d.ts.map
|
package/build/ui/Cx.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"Cx.d.ts","sourceRoot":"","sources":["../../src/ui/Cx.tsx"],"names":[],"mappings":"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"}
|
package/build/ui/Cx.js
CHANGED
|
@@ -5,37 +5,41 @@ 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
|
|
8
|
+
import { isBatchingUpdates, notifyBatchedUpdateStarting, notifyBatchedUpdateCompleted } 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.
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
13
|
+
// On by default. Once a synchronous update burst grows deep, Cx starts issuing updates from microtasks
|
|
14
|
+
// so React's global nested-update counter resets before continuing -- preventing "Maximum update depth
|
|
15
|
+
// exceeded" on large renders that write to the store while they render (e.g. a several-hundred-page
|
|
16
|
+
// report). For updates that settle in a single render -- virtually all of them -- this is equivalent to
|
|
17
|
+
// rendering synchronously; it only diverges under a deep re-entrant render burst. If you suspect it
|
|
18
|
+
// causes trouble, opt out at app startup with disableSyncUpdateCoalescing() -- and please report the
|
|
19
|
+
// issue so it can be fixed.
|
|
20
|
+
// Synchronous updates allowed within one burst before Cx switches to issuing updates from a microtask.
|
|
21
|
+
// Kept under React's ~50 nested-update limit (which is global to the root, not per component); the
|
|
22
|
+
// default trades a little initial-render time for a safety margin. Override via
|
|
23
|
+
// enableSyncUpdateCoalescing(limit) if needed.
|
|
24
|
+
const defaultSyncBurstLimit = 35;
|
|
25
|
+
let syncBurstLimit = defaultSyncBurstLimit;
|
|
26
|
+
// Microtask-issued updates allowed within one burst before Cx escalates to setTimeout. Deep enough that
|
|
27
|
+
// only a store that never converges reaches it; timeouts let the event loop turn, so the page stays
|
|
28
|
+
// responsive and batchUpdatesAndNotify fallback timers can fire instead of the tab hanging.
|
|
29
|
+
let microtaskBurstLimit = 1000;
|
|
25
30
|
export function enableSyncUpdateCoalescing(limit) {
|
|
26
|
-
|
|
27
|
-
if (limit != null)
|
|
28
|
-
syncBurstLimit = limit;
|
|
31
|
+
syncBurstLimit = limit ?? defaultSyncBurstLimit;
|
|
29
32
|
}
|
|
30
33
|
export function disableSyncUpdateCoalescing() {
|
|
31
|
-
|
|
34
|
+
syncBurstLimit = Infinity;
|
|
32
35
|
}
|
|
33
|
-
// Module-global because React's nested-update limit is global to the root, not per component.
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
let syncBurstRounds = 0; //
|
|
36
|
+
// Module-global because React's nested-update limit is global to the root, not per component. The burst
|
|
37
|
+
// counter resets only when all notifications settle (see completeNotification) -- tying the burst
|
|
38
|
+
// window to unsettled work makes it immune to how React schedules the flushes. Legacy React chains
|
|
39
|
+
// re-entrant updates synchronously within one task, but React 19 may run each round from its own
|
|
40
|
+
// microtask, so no task/microtask boundary is a reliable reset point.
|
|
41
|
+
let syncBurstRounds = 0; // updates issued in the current burst
|
|
42
|
+
let outstandingNotifications = 0; // reported notifications not yet rendered, across all Cx instances
|
|
39
43
|
export class Cx extends VDOM.Component {
|
|
40
44
|
widget;
|
|
41
45
|
store;
|
|
@@ -44,14 +48,18 @@ export class Cx extends VDOM.Component {
|
|
|
44
48
|
flags;
|
|
45
49
|
renderCount;
|
|
46
50
|
unsubscribe;
|
|
47
|
-
componentDidCatch;
|
|
48
51
|
forceUpdateCallback;
|
|
49
52
|
deferCounter;
|
|
50
53
|
pendingUpdateTimer;
|
|
51
54
|
unsubscribeIdleRequest;
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
// store notifications reported to batchUpdatesAndNotify subscribers but not yet rendered and completed
|
|
56
|
+
owedNotifications = new Set();
|
|
57
|
+
// setState is not allowed before the component mounts; pre-mount notifications set flags.dirty instead
|
|
58
|
+
// and componentDidMount picks them up (see update())
|
|
59
|
+
mounted = false;
|
|
60
|
+
// true once this instance has contributed to syncBurstRounds for the current render round; cleared on
|
|
61
|
+
// render, so bursts are counted per round rather than per notification (see update())
|
|
62
|
+
burstRoundCounted = false;
|
|
55
63
|
constructor(props) {
|
|
56
64
|
super(props);
|
|
57
65
|
if (props.instance) {
|
|
@@ -78,14 +86,11 @@ export class Cx extends VDOM.Component {
|
|
|
78
86
|
if (props.subscribe) {
|
|
79
87
|
this.unsubscribe = this.store.subscribe(this.update.bind(this));
|
|
80
88
|
}
|
|
81
|
-
this.onStateUpdateCompleted = this.onStateUpdateCompleted.bind(this);
|
|
82
89
|
this.flags = {};
|
|
83
90
|
this.renderCount = 0;
|
|
84
|
-
if (props.onError)
|
|
85
|
-
this.componentDidCatch = this.componentDidCatchHandler.bind(this);
|
|
86
91
|
this.forceUpdateCallback = this.forceUpdate.bind(this);
|
|
87
|
-
|
|
88
|
-
this.
|
|
92
|
+
// deferredUntilIdle content stays hidden until the idle callback scheduled on mount bumps the token
|
|
93
|
+
this.deferCounter = props.deferredUntilIdle ? 1 : 0;
|
|
89
94
|
}
|
|
90
95
|
UNSAFE_componentWillReceiveProps(props) {
|
|
91
96
|
let newStore = props.instance
|
|
@@ -121,12 +126,21 @@ export class Cx extends VDOM.Component {
|
|
|
121
126
|
throw new Error("Could not resolve a widget instance in the Cx component.");
|
|
122
127
|
}
|
|
123
128
|
render() {
|
|
129
|
+
this.burstRoundCounted = false;
|
|
130
|
+
// an error was captured and is being dispatched to the onError callback (see componentDidCatch);
|
|
131
|
+
// render nothing until the callback repairs the state
|
|
132
|
+
if (this.state.error)
|
|
133
|
+
return null;
|
|
124
134
|
if (this.props.deferredUntilIdle && this.state.deferToken < this.deferCounter)
|
|
125
135
|
return null;
|
|
126
136
|
let cultureInfo = this.props.cultureInfo ?? getCurrentCulture();
|
|
127
137
|
return (_jsx(CxContext, { instance: this.getInstance(), flags: this.flags, options: this.props.options, buster: ++this.renderCount, contentFactory: this.props.contentFactory, forceUpdate: this.forceUpdateCallback, cultureInfo: cultureInfo }));
|
|
128
138
|
}
|
|
129
139
|
componentDidMount() {
|
|
140
|
+
this.mounted = true;
|
|
141
|
+
// schedule the deferredUntilIdle reveal here rather than in the constructor -- the idle callback
|
|
142
|
+
// could otherwise fire before the component mounts and issue a setState React does not allow yet
|
|
143
|
+
this.waitForIdle();
|
|
130
144
|
this.componentDidUpdate();
|
|
131
145
|
if (this.props.options && this.props.options.onPipeUpdate)
|
|
132
146
|
this.props.options.onPipeUpdate(this.update.bind(this));
|
|
@@ -139,83 +153,76 @@ export class Cx extends VDOM.Component {
|
|
|
139
153
|
update() {
|
|
140
154
|
let data = this.store.getData();
|
|
141
155
|
debug(appDataFlag, data);
|
|
142
|
-
if (this.flags.preparing)
|
|
156
|
+
if (this.flags.preparing || !this.mounted)
|
|
143
157
|
this.flags.dirty = true;
|
|
144
158
|
// Synchronous path: while batching (incl. batchUpdatesAndNotify, which page-breaking relies on) or for
|
|
145
159
|
// `immediate` instances.
|
|
146
160
|
else if (this.props.immediate || isBatchingUpdates()) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
// Every notification is reported to batchUpdatesAndNotify subscribers up front and completed only
|
|
162
|
+
// once the setState that renders it commits, so notify callbacks never fire before the DOM reflects
|
|
163
|
+
// the change. Each notification always gets its own setState; only its timing escalates as the
|
|
164
|
+
// burst grows: synchronous at first, then from a microtask (lets React's commit finish so its
|
|
165
|
+
// global nested-update counter resets instead of tripping "Maximum update depth exceeded"), and
|
|
166
|
+
// finally from a timeout (lets the event loop turn, so a store that never converges degrades to a
|
|
167
|
+
// responsive page instead of a frozen tab).
|
|
168
|
+
let seq = notifyBatchedUpdateStarting();
|
|
169
|
+
this.owedNotifications.add(seq);
|
|
170
|
+
outstandingNotifications++;
|
|
171
|
+
// Count render rounds rather than notifications: only the first notification an instance
|
|
172
|
+
// receives per render round bumps the shared depth (the flag clears on render). This tracks
|
|
173
|
+
// React's nested-commit count -- the thing the escalation must stay under -- so a round that
|
|
174
|
+
// writes many values doesn't burn through the budget in one go.
|
|
175
|
+
if (!this.burstRoundCounted) {
|
|
176
|
+
this.burstRoundCounted = true;
|
|
177
|
+
++syncBurstRounds;
|
|
178
|
+
if (process.env.NODE_ENV !== "production" && syncBurstRounds === microtaskBurstLimit + 1)
|
|
179
|
+
console.error("Cx: store updates are not converging after " +
|
|
180
|
+
microtaskBurstLimit +
|
|
181
|
+
" render rounds -- possible update loop. Updates are now issued from timeouts to keep the page responsive. Look for code that writes a new value to the store on every render.");
|
|
153
182
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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();
|
|
183
|
+
if (syncBurstRounds <= syncBurstLimit)
|
|
184
|
+
this.issueStateUpdate(seq);
|
|
185
|
+
else if (syncBurstRounds <= microtaskBurstLimit)
|
|
186
|
+
queueMicrotask(() => this.issueStateUpdate(seq));
|
|
187
|
+
else
|
|
188
|
+
setTimeout(() => this.issueStateUpdate(seq), 0);
|
|
166
189
|
}
|
|
167
190
|
else {
|
|
168
191
|
// standard mode: coalesce sequential store commands into a single deferred update
|
|
169
192
|
this.scheduleStateUpdate();
|
|
170
193
|
}
|
|
171
194
|
}
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
195
|
+
// Render the latest store data and report the notification completed once the commit is done, so
|
|
196
|
+
// batchUpdatesAndNotify resolves only when the DOM reflects the store.
|
|
197
|
+
issueStateUpdate(seq) {
|
|
198
|
+
// skip notifications no longer owed -- unmount may have already released them
|
|
199
|
+
if (!this.owedNotifications.has(seq))
|
|
200
|
+
return;
|
|
201
|
+
this.setState({ data: this.store.getData() }, () => this.completeNotification(seq));
|
|
202
|
+
}
|
|
203
|
+
completeNotification(seq) {
|
|
204
|
+
// skip notifications no longer owed -- unmount may have already released them
|
|
205
|
+
if (!this.owedNotifications.delete(seq))
|
|
206
|
+
return;
|
|
207
|
+
outstandingNotifications--;
|
|
208
|
+
notifyBatchedUpdateCompleted(seq);
|
|
209
|
+
// everything settled -- the next burst starts fresh, and so does React's nested-update counter
|
|
210
|
+
// (the commit that settled the last notification ends without scheduling further synchronous work)
|
|
211
|
+
if (outstandingNotifications === 0)
|
|
212
|
+
syncBurstRounds = 0;
|
|
191
213
|
}
|
|
192
214
|
scheduleStateUpdate() {
|
|
193
215
|
if (!this.pendingUpdateTimer) {
|
|
194
|
-
notifyBatchedUpdateStarting();
|
|
216
|
+
let seq = notifyBatchedUpdateStarting();
|
|
217
|
+
this.owedNotifications.add(seq);
|
|
218
|
+
outstandingNotifications++;
|
|
195
219
|
this.pendingUpdateTimer = setTimeout(() => {
|
|
196
220
|
delete this.pendingUpdateTimer;
|
|
197
221
|
// read fresh data at fire time so the coalesced update renders the latest store state
|
|
198
|
-
this.setState({ data: this.store.getData() },
|
|
222
|
+
this.setState({ data: this.store.getData() }, () => this.completeNotification(seq));
|
|
199
223
|
}, 0);
|
|
200
224
|
}
|
|
201
225
|
}
|
|
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
|
-
}
|
|
219
226
|
waitForIdle() {
|
|
220
227
|
if (!this.props.deferredUntilIdle)
|
|
221
228
|
return;
|
|
@@ -229,13 +236,10 @@ export class Cx extends VDOM.Component {
|
|
|
229
236
|
});
|
|
230
237
|
}
|
|
231
238
|
componentWillUnmount() {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.
|
|
236
|
-
activeSyncUpdates--;
|
|
237
|
-
notifyBatchedUpdateCompleted();
|
|
238
|
-
}
|
|
239
|
+
// Release notifications that will never render so a waiting batchUpdatesAndNotify can settle instead
|
|
240
|
+
// of waiting out its fallback timeout.
|
|
241
|
+
for (let seq of this.owedNotifications)
|
|
242
|
+
this.completeNotification(seq);
|
|
239
243
|
if (this.pendingUpdateTimer)
|
|
240
244
|
clearTimeout(this.pendingUpdateTimer);
|
|
241
245
|
if (this.unsubscribeIdleRequest)
|
|
@@ -257,9 +261,22 @@ export class Cx extends VDOM.Component {
|
|
|
257
261
|
props.parentInstance !== this.props.parentInstance ||
|
|
258
262
|
props.cultureInfo !== this.props.cultureInfo);
|
|
259
263
|
}
|
|
260
|
-
|
|
264
|
+
// Render null for the failed subtree while the error is dispatched to componentDidCatch below --
|
|
265
|
+
// returning a state update here is what React expects of an error boundary (rendering the broken
|
|
266
|
+
// children again would just rethrow).
|
|
267
|
+
static getDerivedStateFromError() {
|
|
268
|
+
return { error: true };
|
|
269
|
+
}
|
|
270
|
+
componentDidCatch(error, info) {
|
|
261
271
|
this.flags.preparing = false;
|
|
272
|
+
// without an onError callback this instance is not an error boundary -- rethrow so the error
|
|
273
|
+
// reaches the nearest ancestor boundary, matching the behavior before getDerivedStateFromError
|
|
274
|
+
// was introduced
|
|
275
|
+
if (!this.props.onError)
|
|
276
|
+
throw error;
|
|
262
277
|
this.props.onError(error, this.getInstance(), info);
|
|
278
|
+
// the callback had a chance to repair the state (e.g. replace the failing content) -- resume rendering
|
|
279
|
+
this.setState({ error: false });
|
|
263
280
|
}
|
|
264
281
|
}
|
|
265
282
|
class CxContext extends VDOM.Component {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export declare function batchUpdates(callback: () => void): void;
|
|
2
2
|
export declare function isBatchingUpdates(): boolean;
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function notifyBatchedUpdateCompleted(): void;
|
|
3
|
+
export declare function notifyBatchedUpdateStarting(): number;
|
|
4
|
+
export declare function notifyBatchedUpdateCompleted(seq: number): void;
|
|
6
5
|
export declare function batchUpdatesAndNotify(callback: () => void, notifyCallback: (success: boolean) => void, timeout?: number): void;
|
|
7
6
|
//# sourceMappingURL=batchUpdates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batchUpdates.d.ts","sourceRoot":"","sources":["../../src/ui/batchUpdates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"batchUpdates.d.ts","sourceRoot":"","sources":["../../src/ui/batchUpdates.ts"],"names":[],"mappings":"AAcA,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAWvD;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAID,wBAAgB,2BAA2B,IAAI,MAAM,CAMpD;AAED,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAS9D;AAED,wBAAgB,qBAAqB,CAClC,QAAQ,EAAE,MAAM,IAAI,EACpB,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EAC1C,OAAO,GAAE,MAAa,GACtB,IAAI,CAyBN"}
|
package/build/ui/batchUpdates.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { VDOM } from "./VDOM";
|
|
2
2
|
import { SubscriberList } from "../util/SubscriberList";
|
|
3
3
|
let isBatching = 0;
|
|
4
|
+
let updateSequence = 0;
|
|
4
5
|
let promiseSubscribers = new SubscriberList();
|
|
5
6
|
export function batchUpdates(callback) {
|
|
6
7
|
if (VDOM.DOM.unstable_batchedUpdates)
|
|
@@ -19,20 +20,22 @@ export function batchUpdates(callback) {
|
|
|
19
20
|
export function isBatchingUpdates() {
|
|
20
21
|
return isBatching > 0;
|
|
21
22
|
}
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// notify callback still fires right after the change commits.
|
|
25
|
-
export function hasBatchedUpdateSubscribers() {
|
|
26
|
-
return !promiseSubscribers.isEmpty();
|
|
27
|
-
}
|
|
23
|
+
// Returns a sequence number identifying the update; pass it to notifyBatchedUpdateCompleted once the
|
|
24
|
+
// update is rendered.
|
|
28
25
|
export function notifyBatchedUpdateStarting() {
|
|
26
|
+
let seq = ++updateSequence;
|
|
29
27
|
promiseSubscribers.execute((x) => {
|
|
30
28
|
x.pending++;
|
|
31
29
|
});
|
|
30
|
+
return seq;
|
|
32
31
|
}
|
|
33
|
-
export function notifyBatchedUpdateCompleted() {
|
|
32
|
+
export function notifyBatchedUpdateCompleted(seq) {
|
|
34
33
|
promiseSubscribers.execute((x) => {
|
|
35
34
|
let cb = x;
|
|
35
|
+
// ignore updates that started before this subscriber attached -- counting them would let the notify
|
|
36
|
+
// callback fire before the updates the subscriber is actually waiting on are rendered
|
|
37
|
+
if (seq <= cb.watermark)
|
|
38
|
+
return;
|
|
36
39
|
cb.finished++;
|
|
37
40
|
if (cb.finished >= cb.pending)
|
|
38
41
|
cb.complete(true);
|
|
@@ -45,6 +48,7 @@ export function batchUpdatesAndNotify(callback, notifyCallback, timeout = 1000)
|
|
|
45
48
|
const update = {
|
|
46
49
|
pending: 0,
|
|
47
50
|
finished: 0,
|
|
51
|
+
watermark: updateSequence,
|
|
48
52
|
complete: (success) => {
|
|
49
53
|
if (!done) {
|
|
50
54
|
done = true;
|