cx 26.7.0 → 26.7.2

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/src/ui/Cx.tsx CHANGED
@@ -1,386 +1,416 @@
1
- /** @jsxImportSource react */
2
-
3
- import { Widget, VDOM, getContent } from "./Widget";
4
- import { Instance } from "./Instance";
5
- import { RenderingContext } from "./RenderingContext";
6
- import { debug, appDataFlag } from "../util/Debug";
7
- import { Timing, now, appLoopFlag, vdomRenderFlag } from "../util/Timing";
8
- import { isBatchingUpdates, notifyBatchedUpdateStarting, notifyBatchedUpdateCompleted } from "./batchUpdates";
9
- import { shallowEquals } from "../util/shallowEquals";
10
- import { PureContainer } from "./PureContainer";
11
- import { onIdleCallback } from "../util/onIdleCallback";
12
- import { getCurrentCulture, pushCulture, popCulture, CultureInfo, ResolvedCultureInfo } from "./Culture";
13
- import { View } from "../data/View";
14
- import { Config } from "./Prop";
15
-
16
- export interface CxProps {
17
- widget?: Config;
18
- items?: Config;
19
- store?: View;
20
- instance?: Instance;
21
- parentInstance?: Instance;
22
- subscribe?: boolean;
23
- immediate?: boolean;
24
- deferredUntilIdle?: boolean;
25
- idleTimeout?: number;
26
- options?: any;
27
- onError?: (error: Error, instance: Instance, info: any) => void;
28
- params?: any;
29
- contentFactory?: (props: { children: any }) => any;
30
- cultureInfo?: ResolvedCultureInfo;
31
- }
32
-
33
- export interface CxState {
34
- deferToken: number;
35
- data?: any;
36
- }
37
-
38
- export class Cx extends VDOM.Component<CxProps, CxState> {
39
- widget: Widget;
40
- store: View;
41
- parentInstance?: Instance;
42
- instance?: Instance;
43
- flags: { preparing?: boolean; dirty?: boolean; rendering?: boolean };
44
- renderCount: number;
45
- unsubscribe?: () => void;
46
- componentDidCatch?: (error: Error, info: any) => void;
47
- forceUpdateCallback: () => void;
48
- deferCounter: number;
49
- pendingUpdateTimer?: NodeJS.Timeout;
50
- unsubscribeIdleRequest?: () => void;
51
-
52
- constructor(props: CxProps) {
53
- super(props);
54
-
55
- if (props.instance) {
56
- this.widget = (props.instance as any).widget;
57
- this.store = (props.instance as any).store;
58
- } else {
59
- this.widget = PureContainer.create({ items: props.widget || props.items });
60
-
61
- if (props.parentInstance) {
62
- this.parentInstance = props.parentInstance;
63
- this.store = props.store || (this.parentInstance as any).store;
64
- } else {
65
- this.parentInstance = new Instance(this.widget, "0", undefined, props.store);
66
- this.store = props.store!;
67
- }
68
-
69
- if (!this.store) throw new Error("Cx component requires a store.");
70
- }
71
-
72
- this.state = {
73
- deferToken: 0,
74
- };
75
-
76
- if (props.subscribe) {
77
- this.unsubscribe = this.store.subscribe(this.update.bind(this));
78
- (this.state as any).data = this.store.getData();
79
- }
80
-
81
- this.flags = {};
82
- this.renderCount = 0;
83
-
84
- if (props.onError) this.componentDidCatch = this.componentDidCatchHandler.bind(this);
85
-
86
- this.forceUpdateCallback = this.forceUpdate.bind(this);
87
-
88
- this.deferCounter = 0;
89
- this.waitForIdle();
90
- }
91
-
92
- UNSAFE_componentWillReceiveProps(props: CxProps): void {
93
- let newStore = props.instance
94
- ? (props.instance as any).store
95
- : props.store
96
- ? props.store
97
- : (props.parentInstance as any).store;
98
-
99
- if (newStore != this.store) {
100
- this.store = newStore;
101
- if (this.unsubscribe) this.unsubscribe();
102
- if (props.subscribe) this.unsubscribe = this.store.subscribe(this.update.bind(this));
103
- }
104
-
105
- if (props.subscribe) {
106
- let data = this.store.getData();
107
- if (data !== this.state.data) {
108
- this.waitForIdle();
109
- this.setState({ data });
110
- }
111
- }
112
- }
113
-
114
- getInstance(): Instance {
115
- if (this.props.instance) return this.props.instance;
116
-
117
- if (this.instance && (this.instance as any).widget === this.widget) {
118
- if ((this.instance as any).parentStore != this.store) (this.instance as any).setParentStore(this.store);
119
- return this.instance;
120
- }
121
-
122
- if (this.widget && this.parentInstance)
123
- return (this.instance = (this.parentInstance as any).getDetachedChild(this.widget, 0, this.store));
124
-
125
- throw new Error("Could not resolve a widget instance in the Cx component.");
126
- }
127
-
128
- render() {
129
- if (this.props.deferredUntilIdle && this.state.deferToken < this.deferCounter) return null;
130
-
131
- let cultureInfo = this.props.cultureInfo ?? getCurrentCulture();
132
-
133
- return (
134
- <CxContext
135
- instance={this.getInstance()}
136
- flags={this.flags}
137
- options={this.props.options}
138
- buster={++this.renderCount}
139
- contentFactory={this.props.contentFactory}
140
- forceUpdate={this.forceUpdateCallback}
141
- cultureInfo={cultureInfo}
142
- />
143
- );
144
- }
145
-
146
- componentDidMount(): void {
147
- this.componentDidUpdate();
148
-
149
- if (this.props.options && this.props.options.onPipeUpdate)
150
- this.props.options.onPipeUpdate(this.update.bind(this));
151
- }
152
-
153
- componentDidUpdate(): void {
154
- if (this.flags.dirty) {
155
- this.update();
156
- }
157
- }
158
-
159
- update(): void {
160
- let data = this.store.getData();
161
- debug(appDataFlag, data);
162
- if (this.flags.preparing) this.flags.dirty = true;
163
- else if (isBatchingUpdates() || this.props.immediate) {
164
- notifyBatchedUpdateStarting();
165
- this.setState({ data: data }, notifyBatchedUpdateCompleted);
166
- } else {
167
- //in standard mode sequential store commands are batched
168
- if (!this.pendingUpdateTimer) {
169
- notifyBatchedUpdateStarting();
170
- this.pendingUpdateTimer = setTimeout(() => {
171
- delete this.pendingUpdateTimer;
172
- this.setState({ data: data }, notifyBatchedUpdateCompleted);
173
- }, 0);
174
- }
175
- }
176
- }
177
-
178
- waitForIdle(): void {
179
- if (!this.props.deferredUntilIdle) return;
180
-
181
- if (this.unsubscribeIdleRequest) this.unsubscribeIdleRequest();
182
-
183
- let token = ++this.deferCounter;
184
- this.unsubscribeIdleRequest = onIdleCallback(
185
- () => {
186
- this.setState({ deferToken: token });
187
- },
188
- {
189
- timeout: this.props.idleTimeout || 30000,
190
- },
191
- );
192
- }
193
-
194
- componentWillUnmount(): void {
195
- if (this.pendingUpdateTimer) clearTimeout(this.pendingUpdateTimer);
196
- if (this.unsubscribeIdleRequest) this.unsubscribeIdleRequest();
197
- if (this.unsubscribe) this.unsubscribe();
198
- if (this.props.options && this.props.options.onPipeUpdate) this.props.options.onPipeUpdate(null);
199
- }
200
-
201
- shouldComponentUpdate(props: CxProps, state: CxState): boolean {
202
- if (props.deferredUntilIdle && state.deferToken != this.deferCounter) return false;
203
-
204
- return (
205
- state !== this.state ||
206
- !props.params ||
207
- !shallowEquals(props.params, this.props.params) ||
208
- props.instance !== this.props.instance ||
209
- props.widget !== this.props.widget ||
210
- props.store !== this.props.store ||
211
- props.parentInstance !== this.props.parentInstance ||
212
- props.cultureInfo !== this.props.cultureInfo
213
- );
214
- }
215
-
216
- componentDidCatchHandler(error: Error, info: any): void {
217
- this.flags.preparing = false;
218
- this.props.onError!(error, this.getInstance(), info);
219
- }
220
- }
221
-
222
- interface CxContextProps {
223
- instance: Instance;
224
- flags: { preparing?: boolean; dirty?: boolean; rendering?: boolean };
225
- options?: any;
226
- buster: number;
227
- contentFactory?: (props: { children: any }) => any;
228
- forceUpdate: () => void;
229
- cultureInfo?: ResolvedCultureInfo;
230
- }
231
-
232
- class CxContext extends VDOM.Component<CxContextProps, {}> {
233
- renderCount: number;
234
- timings: any;
235
- content: any;
236
- renderingContext?: RenderingContext;
237
-
238
- constructor(props: CxContextProps) {
239
- super(props);
240
- this.renderCount = 0;
241
- this.UNSAFE_componentWillReceiveProps(props);
242
- }
243
-
244
- UNSAFE_componentWillReceiveProps(props: CxContextProps): void {
245
- this.timings = {
246
- start: now(),
247
- };
248
-
249
- let { instance, options, contentFactory } = props;
250
- let count = 0,
251
- visible,
252
- context,
253
- forceContinue;
254
-
255
- //should not be tracked by parents for destroy
256
- if (!(instance as any).detached)
257
- throw new Error("The instance passed to a Cx component should be detached from its parent.");
258
-
259
- if (this.props.instance !== instance && (this.props.instance as any).destroyTracked)
260
- (this.props.instance as any).destroy();
261
-
262
- this.props.flags.preparing = true;
263
-
264
- if (this.props.cultureInfo) pushCulture(this.props.cultureInfo);
265
-
266
- try {
267
- do {
268
- count++;
269
- forceContinue = false;
270
- context = new RenderingContext(options);
271
- (context as any).forceUpdate = this.props.forceUpdate;
272
- this.props.flags.dirty = false;
273
- (instance as any).assignedRenderList = (context as any).getRootRenderList();
274
- visible = (instance as any).scheduleExploreIfVisible(context);
275
- if (visible) {
276
- while (!(context as any).exploreStack.empty()) {
277
- let inst = (context as any).exploreStack.pop();
278
- //console.log("EXPLORE", inst.widget.constructor.name, inst.widget.tag, inst.widget.widgetId);
279
- (inst as any).explore(context);
280
- }
281
- } else if ((instance as any).destroyTracked) {
282
- (instance as any).destroy();
283
- }
284
-
285
- if (this.props.flags.dirty && count <= 3 && Widget.optimizePrepare && now() - this.timings.start < 8) {
286
- forceContinue = true;
287
- continue;
288
- }
289
-
290
- if (visible) {
291
- this.timings.afterExplore = now();
292
-
293
- for (let i = 0; i < (context as any).prepareList.length; i++)
294
- (context as any).prepareList[i].prepare(context);
295
- this.timings.afterPrepare = now();
296
- }
297
- } while (
298
- forceContinue ||
299
- (this.props.flags.dirty && count <= 3 && Widget.optimizePrepare && now() - this.timings.start < 8)
300
- );
301
-
302
- if (visible) {
303
- //walk in reverse order so children get rendered first
304
- let renderList = (context as any).getRootRenderList();
305
- while (renderList) {
306
- for (let i = renderList.data.length - 1; i >= 0; i--) {
307
- renderList.data[i].render(context);
308
- }
309
- renderList = renderList.right;
310
- }
311
-
312
- this.content = getContent((instance as any).vdom);
313
- if (contentFactory) this.content = contentFactory({ children: this.content });
314
- this.timings.afterRender = now();
315
- for (let i = 0; i < (context as any).cleanupList.length; i++)
316
- (context as any).cleanupList[i].cleanup(context);
317
- } else {
318
- this.content = null;
319
- this.timings.afterExplore = this.timings.afterPrepare = this.timings.afterRender = now();
320
- }
321
- } finally {
322
- if (this.props.cultureInfo) popCulture(this.props.cultureInfo);
323
- }
324
-
325
- this.timings.beforeVDOMRender = now();
326
- this.props.flags.preparing = false;
327
- this.props.flags.rendering = true;
328
- this.renderingContext = context;
329
- }
330
-
331
- render() {
332
- return this.content;
333
- }
334
-
335
- componentDidMount(): void {
336
- this.componentDidUpdate();
337
- }
338
-
339
- componentDidUpdate(): void {
340
- this.props.flags.rendering = false;
341
- this.timings.afterVDOMRender = now();
342
-
343
- //let {instance} = this.props;
344
- //instance.cleanup(this.renderingContext);
345
-
346
- this.timings.afterCleanup = now();
347
- this.renderCount++;
348
-
349
- if (process.env.NODE_ENV !== "production") {
350
- let { start, beforeVDOMRender, afterVDOMRender, afterPrepare, afterExplore, afterRender, afterCleanup } =
351
- this.timings;
352
-
353
- Timing.log(
354
- vdomRenderFlag,
355
- this.renderCount,
356
- "cx",
357
- (beforeVDOMRender - start + afterCleanup - afterVDOMRender).toFixed(2) + "ms",
358
- "vdom",
359
- (afterVDOMRender - beforeVDOMRender).toFixed(2) + "ms",
360
- );
361
-
362
- Timing.log(
363
- appLoopFlag,
364
- this.renderCount,
365
- (this.renderingContext as any).options.name || "main",
366
- "total",
367
- (afterCleanup - start).toFixed(1) + "ms",
368
- "explore",
369
- (afterExplore - start).toFixed(1) + "ms",
370
- "prepare",
371
- (afterPrepare - afterExplore).toFixed(1),
372
- "render",
373
- (afterRender - afterPrepare).toFixed(1),
374
- "vdom",
375
- (afterVDOMRender - beforeVDOMRender).toFixed(1),
376
- "cleanup",
377
- (afterCleanup - afterVDOMRender).toFixed(1),
378
- );
379
- }
380
- }
381
-
382
- componentWillUnmount(): void {
383
- let { instance } = this.props;
384
- if ((instance as any).destroyTracked) (instance as any).destroy();
385
- }
386
- }
1
+ /** @jsxImportSource react */
2
+
3
+ import { Widget, VDOM, getContent } from "./Widget";
4
+ import { Instance } from "./Instance";
5
+ import { RenderingContext } from "./RenderingContext";
6
+ import { debug, appDataFlag } from "../util/Debug";
7
+ import { Timing, now, appLoopFlag, vdomRenderFlag } from "../util/Timing";
8
+ import { isBatchingUpdates, notifyBatchedUpdateStarting, notifyBatchedUpdateCompleted } from "./batchUpdates";
9
+ import { shallowEquals } from "../util/shallowEquals";
10
+ import { PureContainer } from "./PureContainer";
11
+ import { onIdleCallback } from "../util/onIdleCallback";
12
+ import { getCurrentCulture, pushCulture, popCulture, CultureInfo, ResolvedCultureInfo } from "./Culture";
13
+ import { View } from "../data/View";
14
+ import { Config } from "./Prop";
15
+
16
+ export interface CxProps {
17
+ widget?: Config;
18
+ items?: Config;
19
+ store?: View;
20
+ instance?: Instance;
21
+ parentInstance?: Instance;
22
+ subscribe?: boolean;
23
+ immediate?: boolean;
24
+ deferredUntilIdle?: boolean;
25
+ idleTimeout?: number;
26
+ options?: any;
27
+ onError?: (error: Error, instance: Instance, info: any) => void;
28
+ params?: any;
29
+ contentFactory?: (props: { children: any }) => any;
30
+ cultureInfo?: ResolvedCultureInfo;
31
+ }
32
+
33
+ export interface CxState {
34
+ deferToken: number;
35
+ data?: any;
36
+ }
37
+
38
+ export class Cx extends VDOM.Component<CxProps, CxState> {
39
+ widget: Widget;
40
+ store: View;
41
+ parentInstance?: Instance;
42
+ instance?: Instance;
43
+ flags: { preparing?: boolean; dirty?: boolean; rendering?: boolean };
44
+ renderCount: number;
45
+ unsubscribe?: () => void;
46
+ componentDidCatch?: (error: Error, info: any) => void;
47
+ forceUpdateCallback: () => void;
48
+ deferCounter: number;
49
+ pendingUpdateTimer?: NodeJS.Timeout;
50
+ unsubscribeIdleRequest?: () => void;
51
+ // 0 when no synchronous setState is in flight; >0 while one is pending (re-entrancy guard for update())
52
+ stateUpdateDepth: number = 0;
53
+
54
+ constructor(props: CxProps) {
55
+ super(props);
56
+
57
+ if (props.instance) {
58
+ this.widget = (props.instance as any).widget;
59
+ this.store = (props.instance as any).store;
60
+ } else {
61
+ this.widget = PureContainer.create({ items: props.widget || props.items });
62
+
63
+ if (props.parentInstance) {
64
+ this.parentInstance = props.parentInstance;
65
+ this.store = props.store || (this.parentInstance as any).store;
66
+ } else {
67
+ this.parentInstance = new Instance(this.widget, "0", undefined, props.store);
68
+ this.store = props.store!;
69
+ }
70
+
71
+ if (!this.store) throw new Error("Cx component requires a store.");
72
+ }
73
+
74
+ this.state = {
75
+ deferToken: 0,
76
+ };
77
+
78
+ if (props.subscribe) {
79
+ this.unsubscribe = this.store.subscribe(this.update.bind(this));
80
+ (this.state as any).data = this.store.getData();
81
+ }
82
+
83
+ this.onStateUpdateCompleted = this.onStateUpdateCompleted.bind(this);
84
+
85
+ this.flags = {};
86
+ this.renderCount = 0;
87
+
88
+ if (props.onError) this.componentDidCatch = this.componentDidCatchHandler.bind(this);
89
+
90
+ this.forceUpdateCallback = this.forceUpdate.bind(this);
91
+
92
+ this.deferCounter = 0;
93
+ this.waitForIdle();
94
+ }
95
+
96
+ UNSAFE_componentWillReceiveProps(props: CxProps): void {
97
+ let newStore = props.instance
98
+ ? (props.instance as any).store
99
+ : props.store
100
+ ? props.store
101
+ : (props.parentInstance as any).store;
102
+
103
+ if (newStore != this.store) {
104
+ this.store = newStore;
105
+ if (this.unsubscribe) this.unsubscribe();
106
+ if (props.subscribe) this.unsubscribe = this.store.subscribe(this.update.bind(this));
107
+ }
108
+
109
+ if (props.subscribe) {
110
+ let data = this.store.getData();
111
+ if (data !== this.state.data) {
112
+ this.waitForIdle();
113
+ this.setState({ data });
114
+ }
115
+ }
116
+ }
117
+
118
+ getInstance(): Instance {
119
+ if (this.props.instance) return this.props.instance;
120
+
121
+ if (this.instance && this.instance.widget === this.widget) {
122
+ if (this.instance.parentStore != this.store) this.instance.setParentStore(this.store);
123
+ return this.instance;
124
+ }
125
+
126
+ if (this.widget && this.parentInstance)
127
+ return (this.instance = this.parentInstance.getDetachedChild(this.widget, "0", this.store));
128
+
129
+ throw new Error("Could not resolve a widget instance in the Cx component.");
130
+ }
131
+
132
+ render() {
133
+ if (this.props.deferredUntilIdle && this.state.deferToken < this.deferCounter) return null;
134
+
135
+ let cultureInfo = this.props.cultureInfo ?? getCurrentCulture();
136
+
137
+ return (
138
+ <CxContext
139
+ instance={this.getInstance()}
140
+ flags={this.flags}
141
+ options={this.props.options}
142
+ buster={++this.renderCount}
143
+ contentFactory={this.props.contentFactory}
144
+ forceUpdate={this.forceUpdateCallback}
145
+ cultureInfo={cultureInfo}
146
+ />
147
+ );
148
+ }
149
+
150
+ componentDidMount(): void {
151
+ this.componentDidUpdate();
152
+
153
+ if (this.props.options && this.props.options.onPipeUpdate)
154
+ this.props.options.onPipeUpdate(this.update.bind(this));
155
+ }
156
+
157
+ componentDidUpdate(): void {
158
+ if (this.flags.dirty) {
159
+ this.update();
160
+ }
161
+ }
162
+
163
+ update(): void {
164
+ let data = this.store.getData();
165
+ debug(appDataFlag, data);
166
+ if (this.flags.preparing) this.flags.dirty = true;
167
+ // Synchronous path (while batching, or for `immediate` instances). At most one setState may be in flight
168
+ // per Cx: if one is already pending (stateUpdateDepth != 0) skip issuing another. Re-entrant setStates
169
+ // nested deep -- e.g. a large initial render that writes to the store while it renders -- are what trip
170
+ // React's "Maximum update depth exceeded"; coalescing them into a single in-flight update avoids that.
171
+ // The pending update's completion callback re-reads the store and renders again if it changed
172
+ // (see onStateUpdateCompleted), so no change is dropped.
173
+ else if (this.props.immediate || isBatchingUpdates()) {
174
+ if (this.stateUpdateDepth == 0) {
175
+ this.stateUpdateDepth = 1;
176
+ notifyBatchedUpdateStarting();
177
+ this.setState({ data: data }, this.onStateUpdateCompleted);
178
+ }
179
+ } else {
180
+ // standard mode: coalesce sequential store commands into a single deferred update
181
+ if (!this.pendingUpdateTimer) {
182
+ notifyBatchedUpdateStarting();
183
+ this.pendingUpdateTimer = setTimeout(() => {
184
+ delete this.pendingUpdateTimer;
185
+ // read fresh data at fire time so the coalesced update renders the latest store state
186
+ this.setState({ data: this.store.getData() }, notifyBatchedUpdateCompleted);
187
+ }, 0);
188
+ }
189
+ }
190
+ }
191
+
192
+ // Completion callback for the synchronous setState above. If the store changed while that update was
193
+ // rendering -- i.e. the render itself wrote to the store, as happens throughout a large initial render --
194
+ // render once more with the latest data, keeping this callback armed; otherwise the burst has settled, so
195
+ // clear the in-flight flag and report completion. Re-arming coalesces until the store reaches a fixpoint;
196
+ // a genuinely non-converging update loop is still caught by React's own max-update-depth guard.
197
+ onStateUpdateCompleted() {
198
+ let latestData = this.store.getData();
199
+ if (this.state.data === latestData) {
200
+ this.stateUpdateDepth = 0;
201
+ notifyBatchedUpdateCompleted();
202
+ } else {
203
+ this.stateUpdateDepth++;
204
+ this.setState({ data: latestData }, this.onStateUpdateCompleted);
205
+ }
206
+ }
207
+
208
+ waitForIdle(): void {
209
+ if (!this.props.deferredUntilIdle) return;
210
+
211
+ if (this.unsubscribeIdleRequest) this.unsubscribeIdleRequest();
212
+
213
+ let token = ++this.deferCounter;
214
+ this.unsubscribeIdleRequest = onIdleCallback(
215
+ () => {
216
+ this.setState({ deferToken: token }, this.onStateUpdateCompleted);
217
+ },
218
+ {
219
+ timeout: this.props.idleTimeout || 30000,
220
+ },
221
+ );
222
+ }
223
+
224
+ componentWillUnmount(): void {
225
+ if (this.pendingUpdateTimer) clearTimeout(this.pendingUpdateTimer);
226
+ if (this.unsubscribeIdleRequest) this.unsubscribeIdleRequest();
227
+ if (this.unsubscribe) this.unsubscribe();
228
+ if (this.props.options && this.props.options.onPipeUpdate) this.props.options.onPipeUpdate(null);
229
+ }
230
+
231
+ shouldComponentUpdate(props: CxProps, state: CxState): boolean {
232
+ if (props.deferredUntilIdle && state.deferToken != this.deferCounter) return false;
233
+
234
+ return (
235
+ state !== this.state ||
236
+ !props.params ||
237
+ !shallowEquals(props.params, this.props.params) ||
238
+ props.instance !== this.props.instance ||
239
+ props.widget !== this.props.widget ||
240
+ props.store !== this.props.store ||
241
+ props.parentInstance !== this.props.parentInstance ||
242
+ props.cultureInfo !== this.props.cultureInfo
243
+ );
244
+ }
245
+
246
+ componentDidCatchHandler(error: Error, info: any): void {
247
+ this.flags.preparing = false;
248
+ this.props.onError!(error, this.getInstance(), info);
249
+ }
250
+ }
251
+
252
+ interface CxContextProps {
253
+ instance: Instance;
254
+ flags: { preparing?: boolean; dirty?: boolean; rendering?: boolean };
255
+ options?: any;
256
+ buster: number;
257
+ contentFactory?: (props: { children: any }) => any;
258
+ forceUpdate: () => void;
259
+ cultureInfo?: ResolvedCultureInfo;
260
+ }
261
+
262
+ class CxContext extends VDOM.Component<CxContextProps, {}> {
263
+ renderCount: number;
264
+ timings: any;
265
+ content: any;
266
+ renderingContext?: RenderingContext;
267
+
268
+ constructor(props: CxContextProps) {
269
+ super(props);
270
+ this.renderCount = 0;
271
+ this.UNSAFE_componentWillReceiveProps(props);
272
+ }
273
+
274
+ UNSAFE_componentWillReceiveProps(props: CxContextProps): void {
275
+ this.timings = {
276
+ start: now(),
277
+ };
278
+
279
+ let { instance, options, contentFactory } = props;
280
+ let count = 0,
281
+ visible,
282
+ context,
283
+ forceContinue;
284
+
285
+ //should not be tracked by parents for destroy
286
+ if (!(instance as any).detached)
287
+ throw new Error("The instance passed to a Cx component should be detached from its parent.");
288
+
289
+ if (this.props.instance !== instance && (this.props.instance as any).destroyTracked)
290
+ (this.props.instance as any).destroy();
291
+
292
+ this.props.flags.preparing = true;
293
+
294
+ if (this.props.cultureInfo) pushCulture(this.props.cultureInfo);
295
+
296
+ try {
297
+ do {
298
+ count++;
299
+ forceContinue = false;
300
+ context = new RenderingContext(options);
301
+ (context as any).forceUpdate = this.props.forceUpdate;
302
+ this.props.flags.dirty = false;
303
+ (instance as any).assignedRenderList = (context as any).getRootRenderList();
304
+ visible = (instance as any).scheduleExploreIfVisible(context);
305
+ if (visible) {
306
+ while (!(context as any).exploreStack.empty()) {
307
+ let inst = (context as any).exploreStack.pop();
308
+ //console.log("EXPLORE", inst.widget.constructor.name, inst.widget.tag, inst.widget.widgetId);
309
+ (inst as any).explore(context);
310
+ }
311
+ } else if ((instance as any).destroyTracked) {
312
+ (instance as any).destroy();
313
+ }
314
+
315
+ if (this.props.flags.dirty && count <= 3 && Widget.optimizePrepare && now() - this.timings.start < 8) {
316
+ forceContinue = true;
317
+ continue;
318
+ }
319
+
320
+ if (visible) {
321
+ this.timings.afterExplore = now();
322
+
323
+ for (let i = 0; i < (context as any).prepareList.length; i++)
324
+ (context as any).prepareList[i].prepare(context);
325
+ this.timings.afterPrepare = now();
326
+ }
327
+ } while (
328
+ forceContinue ||
329
+ (this.props.flags.dirty && count <= 3 && Widget.optimizePrepare && now() - this.timings.start < 8)
330
+ );
331
+
332
+ if (visible) {
333
+ //walk in reverse order so children get rendered first
334
+ let renderList = (context as any).getRootRenderList();
335
+ while (renderList) {
336
+ for (let i = renderList.data.length - 1; i >= 0; i--) {
337
+ renderList.data[i].render(context);
338
+ }
339
+ renderList = renderList.right;
340
+ }
341
+
342
+ this.content = getContent((instance as any).vdom);
343
+ if (contentFactory) this.content = contentFactory({ children: this.content });
344
+ this.timings.afterRender = now();
345
+ for (let i = 0; i < (context as any).cleanupList.length; i++)
346
+ (context as any).cleanupList[i].cleanup(context);
347
+ } else {
348
+ this.content = null;
349
+ this.timings.afterExplore = this.timings.afterPrepare = this.timings.afterRender = now();
350
+ }
351
+ } finally {
352
+ if (this.props.cultureInfo) popCulture(this.props.cultureInfo);
353
+ }
354
+
355
+ this.timings.beforeVDOMRender = now();
356
+ this.props.flags.preparing = false;
357
+ this.props.flags.rendering = true;
358
+ this.renderingContext = context;
359
+ }
360
+
361
+ render() {
362
+ return this.content;
363
+ }
364
+
365
+ componentDidMount(): void {
366
+ this.componentDidUpdate();
367
+ }
368
+
369
+ componentDidUpdate(): void {
370
+ this.props.flags.rendering = false;
371
+ this.timings.afterVDOMRender = now();
372
+
373
+ //let {instance} = this.props;
374
+ //instance.cleanup(this.renderingContext);
375
+
376
+ this.timings.afterCleanup = now();
377
+ this.renderCount++;
378
+
379
+ if (process.env.NODE_ENV !== "production") {
380
+ let { start, beforeVDOMRender, afterVDOMRender, afterPrepare, afterExplore, afterRender, afterCleanup } =
381
+ this.timings;
382
+
383
+ Timing.log(
384
+ vdomRenderFlag,
385
+ this.renderCount,
386
+ "cx",
387
+ (beforeVDOMRender - start + afterCleanup - afterVDOMRender).toFixed(2) + "ms",
388
+ "vdom",
389
+ (afterVDOMRender - beforeVDOMRender).toFixed(2) + "ms",
390
+ );
391
+
392
+ Timing.log(
393
+ appLoopFlag,
394
+ this.renderCount,
395
+ (this.renderingContext as any).options.name || "main",
396
+ "total",
397
+ (afterCleanup - start).toFixed(1) + "ms",
398
+ "explore",
399
+ (afterExplore - start).toFixed(1) + "ms",
400
+ "prepare",
401
+ (afterPrepare - afterExplore).toFixed(1),
402
+ "render",
403
+ (afterRender - afterPrepare).toFixed(1),
404
+ "vdom",
405
+ (afterVDOMRender - beforeVDOMRender).toFixed(1),
406
+ "cleanup",
407
+ (afterCleanup - afterVDOMRender).toFixed(1),
408
+ );
409
+ }
410
+ }
411
+
412
+ componentWillUnmount(): void {
413
+ let { instance } = this.props;
414
+ if ((instance as any).destroyTracked) (instance as any).destroy();
415
+ }
416
+ }