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 +5 -1
- package/build/ui/Cx.d.ts.map +1 -1
- package/build/ui/Cx.js +102 -34
- package/build/ui/batchUpdates.d.ts +1 -0
- package/build/ui/batchUpdates.d.ts.map +1 -1
- package/build/ui/batchUpdates.js +6 -0
- package/dist/manifest.js +716 -707
- package/dist/ui.js +118 -42
- package/package.json +1 -1
- package/src/ui/Cx.spec.tsx +58 -0
- package/src/ui/Cx.tsx +492 -416
- package/src/ui/Restate.tsx +217 -217
- package/src/ui/batchUpdates.ts +7 -0
package/src/ui/Restate.tsx
CHANGED
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import { PureContainer, PureContainerBase, PureContainerConfig } from "./PureContainer";
|
|
3
|
-
import { Store } from "../data/Store";
|
|
4
|
-
import { View } from "../data/View";
|
|
5
|
-
import { Cx } from "./Cx";
|
|
6
|
-
import { VDOM } from "./VDOM";
|
|
7
|
-
import { isObject } from "../util/isObject";
|
|
8
|
-
import { isUndefined } from "../util/isUndefined";
|
|
9
|
-
import { Binding } from "../data/Binding";
|
|
10
|
-
import { StructuredSelector } from "../data/StructuredSelector";
|
|
11
|
-
import { getCurrentCulture, CultureInfo } from "./Culture";
|
|
12
|
-
import { BooleanProp, NumberProp, StringProp, StructuredProp } from "./Prop";
|
|
13
|
-
import { Instance } from "./Instance";
|
|
14
|
-
|
|
15
|
-
let persistenceCache: any = {};
|
|
16
|
-
|
|
17
|
-
export interface RestateConfig extends PureContainerConfig {
|
|
18
|
-
/** Data object to be exposed in the local store. */
|
|
19
|
-
data?: StructuredProp;
|
|
20
|
-
|
|
21
|
-
/** Set to `true` to render the content in a separate, detached rendering context. */
|
|
22
|
-
detached?: boolean;
|
|
23
|
-
|
|
24
|
-
/** Set to `true` to defer updates until the browser is idle. */
|
|
25
|
-
deferredUntilIdle?: BooleanProp;
|
|
26
|
-
|
|
27
|
-
/** Timeout in milliseconds for idle updates. */
|
|
28
|
-
idleTimeout?: NumberProp;
|
|
29
|
-
|
|
30
|
-
/** Key used to cache and persist the local store data. */
|
|
31
|
-
cacheKey?: StringProp;
|
|
32
|
-
|
|
33
|
-
/** Set to `true` to disable batching of updates. */
|
|
34
|
-
immediate?: boolean;
|
|
35
|
-
|
|
36
|
-
/** Error handler callback. */
|
|
37
|
-
onError?: (error: Error, instance: Instance) => void;
|
|
38
|
-
|
|
39
|
-
/** Culture info to be used for formatting. */
|
|
40
|
-
culture?: CultureInfo;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Legacy alias for backward compatibility
|
|
44
|
-
export interface RestateProps extends RestateConfig {}
|
|
45
|
-
|
|
46
|
-
export class Restate<Config extends RestateConfig = RestateConfig> extends PureContainerBase<Config> {
|
|
47
|
-
declare container: any;
|
|
48
|
-
declare privateDataSelector: any;
|
|
49
|
-
declare detached: boolean;
|
|
50
|
-
declare data?: any;
|
|
51
|
-
declare culture?: any;
|
|
52
|
-
declare options?: any;
|
|
53
|
-
declare onError?: (error: Error, instance: Instance) => void;
|
|
54
|
-
declare waitForIdle: boolean;
|
|
55
|
-
declare immediate: boolean;
|
|
56
|
-
|
|
57
|
-
declareData(...args: any[]) {
|
|
58
|
-
return super.declareData(...args, {
|
|
59
|
-
deferredUntilIdle: undefined,
|
|
60
|
-
idleTimeout: undefined,
|
|
61
|
-
cacheKey: undefined,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
init() {
|
|
66
|
-
this.container = PureContainer.create({
|
|
67
|
-
items: this.children || this.items,
|
|
68
|
-
layout: this.layout,
|
|
69
|
-
controller: this.controller,
|
|
70
|
-
outerLayout: this.outerLayout,
|
|
71
|
-
useParentLayout: !this.detached,
|
|
72
|
-
ws: this.ws,
|
|
73
|
-
});
|
|
74
|
-
this.privateDataSelector = new StructuredSelector({
|
|
75
|
-
props: this.data || {},
|
|
76
|
-
values: this.data,
|
|
77
|
-
});
|
|
78
|
-
this.items = [];
|
|
79
|
-
delete this.children;
|
|
80
|
-
delete this.controller;
|
|
81
|
-
delete this.outerLayout;
|
|
82
|
-
delete this.layout;
|
|
83
|
-
if (this.useParentLayout == null) this.useParentLayout = !this.detached;
|
|
84
|
-
super.init();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
initSubStore(context: any, instance: any) {
|
|
88
|
-
let { cacheKey } = instance.data;
|
|
89
|
-
this.privateDataSelector.init(instance.store);
|
|
90
|
-
instance.subStore = new RestateStore({
|
|
91
|
-
store: instance.store,
|
|
92
|
-
detached: this.detached,
|
|
93
|
-
privateData: this.data || {},
|
|
94
|
-
data: cacheKey ? persistenceCache[cacheKey] || {} : {},
|
|
95
|
-
dataSelector: this.privateDataSelector.create(),
|
|
96
|
-
onSet: (path: string, value: any) => instance.nestedDataSet(path, value, this.data),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (cacheKey) {
|
|
100
|
-
instance.subscribeOnDestroy(() => {
|
|
101
|
-
persistenceCache[cacheKey] = instance.subStore.getData();
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
applyParentStore(instance: any) {
|
|
107
|
-
if (instance.subStore) instance.subStore.setStore(instance.parentStore);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
explore(context: any, instance: any) {
|
|
111
|
-
if (!instance.subStore) this.initSubStore(context, instance);
|
|
112
|
-
if (instance.subStore.parentDataCheck()) instance.markShouldUpdate();
|
|
113
|
-
instance.cultureInfo = this.culture ?? getCurrentCulture();
|
|
114
|
-
if (instance.cache("cultureInfo", instance.culture)) instance.markShouldUpdate();
|
|
115
|
-
super.explore(context, instance);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
exploreItems(context: any, instance: any, items: any) {
|
|
119
|
-
if (!this.detached) {
|
|
120
|
-
instance.container = instance.getChild(context, this.container, "container", instance.subStore);
|
|
121
|
-
instance.container.scheduleExploreIfVisible(context);
|
|
122
|
-
instance.children = [instance.container];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
render(context: any, instance: any, key: string) {
|
|
127
|
-
if (!this.detached) return instance.container.render(context);
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<Cx
|
|
131
|
-
key={key}
|
|
132
|
-
widget={this.container}
|
|
133
|
-
parentInstance={instance}
|
|
134
|
-
store={instance.subStore}
|
|
135
|
-
subscribe
|
|
136
|
-
options={this.options}
|
|
137
|
-
onError={this.onError}
|
|
138
|
-
deferredUntilIdle={instance.data.deferredUntilIdle}
|
|
139
|
-
idleTimeout={instance.data.idleTimeout}
|
|
140
|
-
immediate={this.immediate}
|
|
141
|
-
cultureInfo={instance.cultureInfo}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
Restate.prototype.detached = false;
|
|
148
|
-
Restate.prototype.waitForIdle = false;
|
|
149
|
-
Restate.prototype.immediate = false;
|
|
150
|
-
Restate.prototype.culture = null;
|
|
151
|
-
|
|
152
|
-
export const PrivateStore = Restate;
|
|
153
|
-
|
|
154
|
-
class RestateStore extends Store {
|
|
155
|
-
declare parentDataVersion: number;
|
|
156
|
-
declare parentData: any;
|
|
157
|
-
declare dataSelector: any;
|
|
158
|
-
declare privateData: any;
|
|
159
|
-
declare onSet: any;
|
|
160
|
-
declare detached: any;
|
|
161
|
-
declare store: View; // Parent store reference
|
|
162
|
-
|
|
163
|
-
constructor(config: any) {
|
|
164
|
-
super(config);
|
|
165
|
-
this.parentDataVersion = -1;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
getData() {
|
|
169
|
-
this.silently(() => {
|
|
170
|
-
this.parentDataCheck();
|
|
171
|
-
});
|
|
172
|
-
return super.getData();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
parentDataCheck() {
|
|
176
|
-
if (this.parentDataVersion == this.store.meta.version) return false;
|
|
177
|
-
this.parentDataVersion = this.store.meta.version;
|
|
178
|
-
this.parentData = this.dataSelector(this.store.getData());
|
|
179
|
-
return this.batch(() => {
|
|
180
|
-
for (let key in this.parentData) {
|
|
181
|
-
super.setItem(key, this.parentData[key]);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
setItem(path: string, value: any) {
|
|
187
|
-
let binding = Binding.get(path);
|
|
188
|
-
let bindingRoot = binding.parts[0];
|
|
189
|
-
if (!isObject(this.privateData) || !this.privateData.hasOwnProperty(bindingRoot)) {
|
|
190
|
-
let changed = isUndefined(value) ? super.deleteItem(path) : super.setItem(path, value);
|
|
191
|
-
return changed;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
let newValue = value;
|
|
195
|
-
if (binding.parts.length > 1) newValue = binding.set(this.getData(), value)[bindingRoot];
|
|
196
|
-
this.onSet(bindingRoot, newValue);
|
|
197
|
-
this.batch(() => {
|
|
198
|
-
super.setItem(bindingRoot, newValue);
|
|
199
|
-
this.parentDataCheck();
|
|
200
|
-
});
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
deleteItem(path: string) {
|
|
205
|
-
return this.setItem(path, undefined);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
doNotify() {
|
|
209
|
-
if (!this.detached) this.store.notify(undefined!);
|
|
210
|
-
super.doNotify(undefined!);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// override the default implementation to avoid meta overwrites
|
|
214
|
-
setStore(store: any) {
|
|
215
|
-
this.store = store;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { PureContainer, PureContainerBase, PureContainerConfig } from "./PureContainer";
|
|
3
|
+
import { Store } from "../data/Store";
|
|
4
|
+
import { View } from "../data/View";
|
|
5
|
+
import { Cx } from "./Cx";
|
|
6
|
+
import { VDOM } from "./VDOM";
|
|
7
|
+
import { isObject } from "../util/isObject";
|
|
8
|
+
import { isUndefined } from "../util/isUndefined";
|
|
9
|
+
import { Binding } from "../data/Binding";
|
|
10
|
+
import { StructuredSelector } from "../data/StructuredSelector";
|
|
11
|
+
import { getCurrentCulture, CultureInfo } from "./Culture";
|
|
12
|
+
import { BooleanProp, NumberProp, StringProp, StructuredProp } from "./Prop";
|
|
13
|
+
import { Instance } from "./Instance";
|
|
14
|
+
|
|
15
|
+
let persistenceCache: any = {};
|
|
16
|
+
|
|
17
|
+
export interface RestateConfig extends PureContainerConfig {
|
|
18
|
+
/** Data object to be exposed in the local store. */
|
|
19
|
+
data?: StructuredProp;
|
|
20
|
+
|
|
21
|
+
/** Set to `true` to render the content in a separate, detached rendering context. */
|
|
22
|
+
detached?: boolean;
|
|
23
|
+
|
|
24
|
+
/** Set to `true` to defer updates until the browser is idle. */
|
|
25
|
+
deferredUntilIdle?: BooleanProp;
|
|
26
|
+
|
|
27
|
+
/** Timeout in milliseconds for idle updates. */
|
|
28
|
+
idleTimeout?: NumberProp;
|
|
29
|
+
|
|
30
|
+
/** Key used to cache and persist the local store data. */
|
|
31
|
+
cacheKey?: StringProp;
|
|
32
|
+
|
|
33
|
+
/** Set to `true` to disable batching of updates. */
|
|
34
|
+
immediate?: boolean;
|
|
35
|
+
|
|
36
|
+
/** Error handler callback. */
|
|
37
|
+
onError?: (error: Error, instance: Instance) => void;
|
|
38
|
+
|
|
39
|
+
/** Culture info to be used for formatting. */
|
|
40
|
+
culture?: CultureInfo;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Legacy alias for backward compatibility
|
|
44
|
+
export interface RestateProps extends RestateConfig {}
|
|
45
|
+
|
|
46
|
+
export class Restate<Config extends RestateConfig = RestateConfig> extends PureContainerBase<Config> {
|
|
47
|
+
declare container: any;
|
|
48
|
+
declare privateDataSelector: any;
|
|
49
|
+
declare detached: boolean;
|
|
50
|
+
declare data?: any;
|
|
51
|
+
declare culture?: any;
|
|
52
|
+
declare options?: any;
|
|
53
|
+
declare onError?: (error: Error, instance: Instance) => void;
|
|
54
|
+
declare waitForIdle: boolean;
|
|
55
|
+
declare immediate: boolean;
|
|
56
|
+
|
|
57
|
+
declareData(...args: any[]) {
|
|
58
|
+
return super.declareData(...args, {
|
|
59
|
+
deferredUntilIdle: undefined,
|
|
60
|
+
idleTimeout: undefined,
|
|
61
|
+
cacheKey: undefined,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
init() {
|
|
66
|
+
this.container = PureContainer.create({
|
|
67
|
+
items: this.children || this.items,
|
|
68
|
+
layout: this.layout,
|
|
69
|
+
controller: this.controller,
|
|
70
|
+
outerLayout: this.outerLayout,
|
|
71
|
+
useParentLayout: !this.detached,
|
|
72
|
+
ws: this.ws,
|
|
73
|
+
});
|
|
74
|
+
this.privateDataSelector = new StructuredSelector({
|
|
75
|
+
props: this.data || {},
|
|
76
|
+
values: this.data,
|
|
77
|
+
});
|
|
78
|
+
this.items = [];
|
|
79
|
+
delete this.children;
|
|
80
|
+
delete this.controller;
|
|
81
|
+
delete this.outerLayout;
|
|
82
|
+
delete this.layout;
|
|
83
|
+
if (this.useParentLayout == null) this.useParentLayout = !this.detached;
|
|
84
|
+
super.init();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
initSubStore(context: any, instance: any) {
|
|
88
|
+
let { cacheKey } = instance.data;
|
|
89
|
+
this.privateDataSelector.init(instance.store);
|
|
90
|
+
instance.subStore = new RestateStore({
|
|
91
|
+
store: instance.store,
|
|
92
|
+
detached: this.detached,
|
|
93
|
+
privateData: this.data || {},
|
|
94
|
+
data: cacheKey ? persistenceCache[cacheKey] || {} : {},
|
|
95
|
+
dataSelector: this.privateDataSelector.create(),
|
|
96
|
+
onSet: (path: string, value: any) => instance.nestedDataSet(path, value, this.data),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (cacheKey) {
|
|
100
|
+
instance.subscribeOnDestroy(() => {
|
|
101
|
+
persistenceCache[cacheKey] = instance.subStore.getData();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
applyParentStore(instance: any) {
|
|
107
|
+
if (instance.subStore) instance.subStore.setStore(instance.parentStore);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
explore(context: any, instance: any) {
|
|
111
|
+
if (!instance.subStore) this.initSubStore(context, instance);
|
|
112
|
+
if (instance.subStore.parentDataCheck()) instance.markShouldUpdate();
|
|
113
|
+
instance.cultureInfo = this.culture ?? getCurrentCulture();
|
|
114
|
+
if (instance.cache("cultureInfo", instance.culture)) instance.markShouldUpdate();
|
|
115
|
+
super.explore(context, instance);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
exploreItems(context: any, instance: any, items: any) {
|
|
119
|
+
if (!this.detached) {
|
|
120
|
+
instance.container = instance.getChild(context, this.container, "container", instance.subStore);
|
|
121
|
+
instance.container.scheduleExploreIfVisible(context);
|
|
122
|
+
instance.children = [instance.container];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
render(context: any, instance: any, key: string) {
|
|
127
|
+
if (!this.detached) return instance.container.render(context);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Cx
|
|
131
|
+
key={key}
|
|
132
|
+
widget={this.container}
|
|
133
|
+
parentInstance={instance}
|
|
134
|
+
store={instance.subStore}
|
|
135
|
+
subscribe
|
|
136
|
+
options={this.options}
|
|
137
|
+
onError={this.onError}
|
|
138
|
+
deferredUntilIdle={instance.data.deferredUntilIdle}
|
|
139
|
+
idleTimeout={instance.data.idleTimeout}
|
|
140
|
+
immediate={this.immediate}
|
|
141
|
+
cultureInfo={instance.cultureInfo}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
Restate.prototype.detached = false;
|
|
148
|
+
Restate.prototype.waitForIdle = false;
|
|
149
|
+
Restate.prototype.immediate = false;
|
|
150
|
+
Restate.prototype.culture = null;
|
|
151
|
+
|
|
152
|
+
export const PrivateStore = Restate;
|
|
153
|
+
|
|
154
|
+
class RestateStore extends Store {
|
|
155
|
+
declare parentDataVersion: number;
|
|
156
|
+
declare parentData: any;
|
|
157
|
+
declare dataSelector: any;
|
|
158
|
+
declare privateData: any;
|
|
159
|
+
declare onSet: any;
|
|
160
|
+
declare detached: any;
|
|
161
|
+
declare store: View; // Parent store reference
|
|
162
|
+
|
|
163
|
+
constructor(config: any) {
|
|
164
|
+
super(config);
|
|
165
|
+
this.parentDataVersion = -1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getData() {
|
|
169
|
+
this.silently(() => {
|
|
170
|
+
this.parentDataCheck();
|
|
171
|
+
});
|
|
172
|
+
return super.getData();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
parentDataCheck() {
|
|
176
|
+
if (this.parentDataVersion == this.store.meta.version) return false;
|
|
177
|
+
this.parentDataVersion = this.store.meta.version;
|
|
178
|
+
this.parentData = this.dataSelector(this.store.getData());
|
|
179
|
+
return this.batch(() => {
|
|
180
|
+
for (let key in this.parentData) {
|
|
181
|
+
super.setItem(key, this.parentData[key]);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setItem(path: string, value: any) {
|
|
187
|
+
let binding = Binding.get(path);
|
|
188
|
+
let bindingRoot = binding.parts[0];
|
|
189
|
+
if (!isObject(this.privateData) || !this.privateData.hasOwnProperty(bindingRoot)) {
|
|
190
|
+
let changed = isUndefined(value) ? super.deleteItem(path) : super.setItem(path, value);
|
|
191
|
+
return changed;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let newValue = value;
|
|
195
|
+
if (binding.parts.length > 1) newValue = binding.set(this.getData(), value)[bindingRoot];
|
|
196
|
+
this.onSet(bindingRoot, newValue);
|
|
197
|
+
this.batch(() => {
|
|
198
|
+
super.setItem(bindingRoot, newValue);
|
|
199
|
+
this.parentDataCheck();
|
|
200
|
+
});
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
deleteItem(path: string) {
|
|
205
|
+
return this.setItem(path, undefined);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
doNotify() {
|
|
209
|
+
if (!this.detached) this.store.notify(undefined!);
|
|
210
|
+
super.doNotify(undefined!);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// override the default implementation to avoid meta overwrites
|
|
214
|
+
setStore(store: any) {
|
|
215
|
+
this.store = store;
|
|
216
|
+
}
|
|
217
|
+
}
|
package/src/ui/batchUpdates.ts
CHANGED
|
@@ -27,6 +27,13 @@ export function isBatchingUpdates(): boolean {
|
|
|
27
27
|
return isBatching > 0;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// True while a batchUpdatesAndNotify accumulator is subscribed, i.e. some caller is waiting for the
|
|
31
|
+
// current batch's renders to finish. Cx uses this to stay fully synchronous during page-breaking so the
|
|
32
|
+
// notify callback still fires right after the change commits.
|
|
33
|
+
export function hasBatchedUpdateSubscribers(): boolean {
|
|
34
|
+
return !promiseSubscribers.isEmpty();
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
export function notifyBatchedUpdateStarting(): void {
|
|
31
38
|
promiseSubscribers.execute((x: any) => {
|
|
32
39
|
(x as UpdateCallback).pending++;
|