anu-verzum 1.8.0 → 1.9.0
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/README.md +8 -3
- package/package.json +3 -2
- package/dist/core/components/AnulyticsProvider.ts +0 -271
- package/dist/core/components/Component.ts +0 -39
- package/dist/core/components/Connector.ts +0 -190
- package/dist/core/components/Context.ts +0 -106
- package/dist/core/components/Feature.ts +0 -34
- package/dist/core/components/Fragment.ts +0 -20
- package/dist/core/components/History.ts +0 -208
- package/dist/core/components/Intl.ts +0 -214
- package/dist/core/domUtils.ts +0 -196
- package/dist/core/elements.ts +0 -44
- package/dist/core/reconciler.ts +0 -500
- package/dist/index.ts +0 -86
- package/dist/misc/utils.ts +0 -28
- package/dist/server-api/server-api.ts +0 -142
- package/dist/store/store.ts +0 -236
package/README.md
CHANGED
|
@@ -47,8 +47,12 @@ Anu.render(<App />, document.getElementById('root'));
|
|
|
47
47
|
<h3>TypeScript setup</h3>
|
|
48
48
|
|
|
49
49
|
The library is written in TypeScript and ships with declaration files (`.d.ts`) out of the box —
|
|
50
|
-
no extra `@types` package is needed.
|
|
51
|
-
|
|
50
|
+
no extra `@types` package is needed. The `dist/` directory contains only compiled `.js` files and
|
|
51
|
+
`.d.ts` declarations, so the package works correctly under all standard TypeScript `moduleResolution`
|
|
52
|
+
modes including `"node"`, `"node16"`, and `"bundler"`.
|
|
53
|
+
|
|
54
|
+
If your project uses TypeScript, add the following to your `tsconfig.json` so the compiler
|
|
55
|
+
understands JSX produced by ANUVerzum:
|
|
52
56
|
|
|
53
57
|
```json
|
|
54
58
|
{
|
|
@@ -140,7 +144,8 @@ The following types are exported from `anu-verzum` for use in consumer projects:
|
|
|
140
144
|
These scripts are available when working on the library itself:
|
|
141
145
|
|
|
142
146
|
```bash
|
|
143
|
-
npm run
|
|
147
|
+
npm run clean # Delete dist/ entirely
|
|
148
|
+
npm run build # Clean, compile TypeScript sources to dist/, and emit .d.ts files
|
|
144
149
|
npm run typecheck # Type-check without emitting any output
|
|
145
150
|
npm run lint # Run ESLint on all source files
|
|
146
151
|
npm run format # Format all source files with Prettier
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anu-verzum",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "A \"React-like\" UI library that supports JSX syntax, Redux-like state management, array-rendering, i18n, routing and many more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anu-verzum",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"main": "dist/index.js",
|
|
30
30
|
"types": "dist/index.d.ts",
|
|
31
31
|
"scripts": {
|
|
32
|
-
"
|
|
32
|
+
"clean": "node -e \"require('fs').rmSync('dist', {recursive:true, force:true})\"",
|
|
33
|
+
"build": "npm run clean && babel src --out-dir dist --extensions \".ts\" && tsc --emitDeclarationOnly",
|
|
33
34
|
"typecheck": "tsc --noEmit",
|
|
34
35
|
"lint": "eslint src",
|
|
35
36
|
"format": "prettier --write src",
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { Component } from './Component';
|
|
2
|
-
import { AnuElement, Props } from '../elements';
|
|
3
|
-
import ServerAPI from '../../server-api/server-api';
|
|
4
|
-
|
|
5
|
-
const EventTypes = {
|
|
6
|
-
INITIALIZATION: 'initialization',
|
|
7
|
-
USER_ACTION: 'userAction',
|
|
8
|
-
STATE_CHANGE: 'stateChange',
|
|
9
|
-
NAVIGATION: 'navigation',
|
|
10
|
-
PAGE_LEAVE: 'pageLeave'
|
|
11
|
-
} as const;
|
|
12
|
-
|
|
13
|
-
type AnulyticsData = {
|
|
14
|
-
startDate: number;
|
|
15
|
-
events: Array<Record<string, any>>;
|
|
16
|
-
user: Record<string, any>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type UserActionEvent = {
|
|
20
|
-
type: string;
|
|
21
|
-
keyCode?: number | null;
|
|
22
|
-
pageX?: number;
|
|
23
|
-
pageY?: number;
|
|
24
|
-
target?: {
|
|
25
|
-
id?: string;
|
|
26
|
-
name?: string;
|
|
27
|
-
nodeName?: string;
|
|
28
|
-
value?: string;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const AnulyticsState = (() => {
|
|
33
|
-
const initStart = new Date().getTime();
|
|
34
|
-
let _anulyticsInstanceExist = false;
|
|
35
|
-
|
|
36
|
-
const setAnulyticsInstanceExist = (instanceExist: boolean): void => {
|
|
37
|
-
_anulyticsInstanceExist = instanceExist;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const getAnulyticsInstanceExist = (): boolean => _anulyticsInstanceExist;
|
|
41
|
-
|
|
42
|
-
const _anulytics: AnulyticsData = {
|
|
43
|
-
startDate: initStart,
|
|
44
|
-
events: [
|
|
45
|
-
{
|
|
46
|
-
[EventTypes.INITIALIZATION]: {
|
|
47
|
-
eventType: window.location.pathname,
|
|
48
|
-
timestamp: initStart,
|
|
49
|
-
properties: {}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
user: {}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
getAnulyticsInstanceExist,
|
|
58
|
-
setAnulyticsInstanceExist,
|
|
59
|
-
addEvent: (key: string, val: Record<string, any>): void => {
|
|
60
|
-
_anulytics.events.push({ [key]: val });
|
|
61
|
-
},
|
|
62
|
-
trackEvent: (
|
|
63
|
-
{
|
|
64
|
-
type,
|
|
65
|
-
keyCode = null,
|
|
66
|
-
pageX = 0,
|
|
67
|
-
pageY = 0,
|
|
68
|
-
target: { id = '', name = '', nodeName = '', value = '' } = {}
|
|
69
|
-
}: UserActionEvent,
|
|
70
|
-
rawProps: unknown
|
|
71
|
-
): void => {
|
|
72
|
-
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
|
73
|
-
const scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
|
|
74
|
-
const props =
|
|
75
|
-
typeof rawProps === 'object' && !Array.isArray(rawProps) && rawProps !== null
|
|
76
|
-
? (rawProps as Record<string, any>)
|
|
77
|
-
: null;
|
|
78
|
-
|
|
79
|
-
const event = {
|
|
80
|
-
[EventTypes.USER_ACTION]: {
|
|
81
|
-
eventType: type,
|
|
82
|
-
timestamp: new Date().getTime(),
|
|
83
|
-
properties: {
|
|
84
|
-
id,
|
|
85
|
-
keyCode,
|
|
86
|
-
name,
|
|
87
|
-
nodeName,
|
|
88
|
-
value,
|
|
89
|
-
pageX,
|
|
90
|
-
pageY,
|
|
91
|
-
scrollTop,
|
|
92
|
-
scrollLeft,
|
|
93
|
-
url: window.location.pathname,
|
|
94
|
-
props
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
_anulytics.events.push(event);
|
|
100
|
-
},
|
|
101
|
-
trackStateChange: (
|
|
102
|
-
prevState: Record<string, any>,
|
|
103
|
-
action: Record<string, any>,
|
|
104
|
-
nextState: Record<string, any>
|
|
105
|
-
): void => {
|
|
106
|
-
const event = {
|
|
107
|
-
[EventTypes.STATE_CHANGE]: {
|
|
108
|
-
eventType: action['type'],
|
|
109
|
-
timestamp: new Date().getTime(),
|
|
110
|
-
properties: {
|
|
111
|
-
url: window.location.pathname,
|
|
112
|
-
prevState,
|
|
113
|
-
action,
|
|
114
|
-
nextState
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
_anulytics.events.push(event);
|
|
120
|
-
},
|
|
121
|
-
setUser: (user: Record<string, any> | null | undefined): void => {
|
|
122
|
-
_anulytics.user = user || {};
|
|
123
|
-
},
|
|
124
|
-
getAnulyticsData: (): AnulyticsData => _anulytics
|
|
125
|
-
};
|
|
126
|
-
})();
|
|
127
|
-
|
|
128
|
-
const _isBot: boolean = !!(
|
|
129
|
-
(window as any).phantom ||
|
|
130
|
-
(window as any)._phantom ||
|
|
131
|
-
(window as any).__nightmare ||
|
|
132
|
-
window.navigator.webdriver ||
|
|
133
|
-
(window as any).Cypress
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const _isInstalled = (): boolean => {
|
|
137
|
-
if ((window.navigator as any).standalone) {
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (window.matchMedia('(display-mode: standalone)').matches) {
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return false;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
export const trackEvent = (event: UserActionEvent, props?: unknown): void => {
|
|
149
|
-
if (AnulyticsState.getAnulyticsInstanceExist()) {
|
|
150
|
-
AnulyticsState.trackEvent(event, props);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export const trackStateChange = (
|
|
155
|
-
prevState: Record<string, any>,
|
|
156
|
-
action: Record<string, any>,
|
|
157
|
-
nextState: Record<string, any>
|
|
158
|
-
): void => {
|
|
159
|
-
if (AnulyticsState.getAnulyticsInstanceExist()) {
|
|
160
|
-
AnulyticsState.trackStateChange(prevState, action, nextState);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
export const trackRouteChange = (path?: string): void => {
|
|
165
|
-
if (AnulyticsState.getAnulyticsInstanceExist()) {
|
|
166
|
-
const url = path || window.location.pathname;
|
|
167
|
-
const event = {
|
|
168
|
-
eventType: url,
|
|
169
|
-
timestamp: new Date().getTime(),
|
|
170
|
-
properties: {}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
AnulyticsState.addEvent(EventTypes.NAVIGATION, event);
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
export interface AnulyticsProviderProps extends Props {
|
|
178
|
-
analyticsUrl: string;
|
|
179
|
-
userData?: Record<string, any>;
|
|
180
|
-
onSuccess: (response: any) => void;
|
|
181
|
-
onFail: (status: number) => void;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
class AnulyticsProvider extends Component<AnulyticsProviderProps> {
|
|
185
|
-
constructor(props: AnulyticsProviderProps) {
|
|
186
|
-
super(props);
|
|
187
|
-
AnulyticsState.setAnulyticsInstanceExist(true);
|
|
188
|
-
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
componentDidMount(): void {
|
|
192
|
-
const { userData } = this.props;
|
|
193
|
-
AnulyticsState.setUser(userData || null);
|
|
194
|
-
|
|
195
|
-
document.addEventListener('visibilitychange', this.handleVisibilityChange, {
|
|
196
|
-
passive: true
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
componentWillUnmount(): void {
|
|
201
|
-
document.removeEventListener('visibilitychange', this.handleVisibilityChange as EventListener);
|
|
202
|
-
AnulyticsState.setAnulyticsInstanceExist(false);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
handleVisibilityChange(): void {
|
|
206
|
-
const { analyticsUrl, onSuccess, onFail } = this.props;
|
|
207
|
-
|
|
208
|
-
if (_isBot) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (document.visibilityState === 'hidden') {
|
|
213
|
-
const url = window.location.pathname;
|
|
214
|
-
const event = {
|
|
215
|
-
eventType: url,
|
|
216
|
-
timestamp: new Date().getTime(),
|
|
217
|
-
properties: {}
|
|
218
|
-
};
|
|
219
|
-
let ua: { userAgent: any; mobile: boolean; platform: string };
|
|
220
|
-
|
|
221
|
-
AnulyticsState.addEvent(EventTypes.PAGE_LEAVE, event);
|
|
222
|
-
|
|
223
|
-
if ((window.navigator as any).userAgentData) {
|
|
224
|
-
const { brands, mobile, platform } = (window.navigator as any).userAgentData;
|
|
225
|
-
|
|
226
|
-
ua = { userAgent: brands, mobile, platform };
|
|
227
|
-
} else {
|
|
228
|
-
ua = {
|
|
229
|
-
userAgent: window.navigator.userAgent,
|
|
230
|
-
mobile: false,
|
|
231
|
-
platform: ''
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const data = {
|
|
236
|
-
...AnulyticsState.getAnulyticsData(),
|
|
237
|
-
endDate: new Date().getTime(),
|
|
238
|
-
system: {
|
|
239
|
-
referrer: document.referrer || null,
|
|
240
|
-
innerWidth: window.innerWidth,
|
|
241
|
-
isMobileAppInstalled: _isInstalled(),
|
|
242
|
-
userAgentData: ua
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
ServerAPI.post(analyticsUrl, data)
|
|
247
|
-
.then(({ response }) => onSuccess(response))
|
|
248
|
-
.catch(({ status }) => onFail(status));
|
|
249
|
-
} else {
|
|
250
|
-
this.setState();
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
render(): AnuElement | AnuElement[] | null {
|
|
255
|
-
const { children } = this.props;
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
if (!children || children.length !== 1) {
|
|
259
|
-
throw new Error('Provider must have one child element!');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return children;
|
|
263
|
-
} catch (err) {
|
|
264
|
-
console.error(err);
|
|
265
|
-
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export default AnulyticsProvider;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { scheduleUpdate } from '../reconciler';
|
|
2
|
-
import { AnuElement, Props } from '../elements';
|
|
3
|
-
|
|
4
|
-
export abstract class Component<P extends Props = Props, S extends Record<string, any> = Record<string, any>> {
|
|
5
|
-
props: P;
|
|
6
|
-
state: S;
|
|
7
|
-
context: Record<string, any>;
|
|
8
|
-
/** @internal Set by the reconciler. */
|
|
9
|
-
__fiber?: any;
|
|
10
|
-
|
|
11
|
-
static isAnuComponent?: boolean;
|
|
12
|
-
|
|
13
|
-
constructor(props: P, context?: Record<string, any>) {
|
|
14
|
-
this.props = props || ({} as P);
|
|
15
|
-
this.context = { ...(this as any).context, ...context };
|
|
16
|
-
this.state = (this as any).state || ({} as S);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
setState(partialState: Partial<S> | ((prevState: S, prevProps: P) => S) = {}): void {
|
|
20
|
-
let partialStateObject: Partial<S> = this.state;
|
|
21
|
-
let partialStateCallback: ((prevState: S, prevProps: P) => S) | undefined;
|
|
22
|
-
|
|
23
|
-
if (typeof partialState === 'object') {
|
|
24
|
-
partialStateObject = { ...partialStateObject, ...partialState };
|
|
25
|
-
} else if (typeof partialState === 'function') {
|
|
26
|
-
partialStateCallback = partialState;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
scheduleUpdate(this, partialStateObject, partialStateCallback);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
abstract render(): AnuElement | AnuElement[] | null | undefined;
|
|
33
|
-
|
|
34
|
-
componentDidMount(): void {}
|
|
35
|
-
|
|
36
|
-
componentDidUpdate(_prevProps: P, _prevState: S): void {}
|
|
37
|
-
|
|
38
|
-
componentWillUnmount(): void {}
|
|
39
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { createElement } from '../elements';
|
|
2
|
-
import { Component } from './Component';
|
|
3
|
-
import { AnuElement, Props, ComponentConstructor } from '../elements';
|
|
4
|
-
|
|
5
|
-
type StoreShape = {
|
|
6
|
-
getState: () => any;
|
|
7
|
-
dispatch: (action: any) => any;
|
|
8
|
-
subscribe: (listener: () => void) => void;
|
|
9
|
-
unsubscribe: (listener: () => void) => void;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type MapStateToProps<TState = any, TOwnProps extends Props = Props, TStateProps extends Props = Props> = (
|
|
13
|
-
state: TState,
|
|
14
|
-
ownProps: TOwnProps
|
|
15
|
-
) => TStateProps;
|
|
16
|
-
|
|
17
|
-
type MapDispatchToProps<TDispatch = any, TOwnProps extends Props = Props, TDispatchProps extends Props = Props> = (
|
|
18
|
-
dispatch: TDispatch,
|
|
19
|
-
ownProps: TOwnProps
|
|
20
|
-
) => TDispatchProps;
|
|
21
|
-
|
|
22
|
-
const providedStore = (context: Record<string, any> = {}) => {
|
|
23
|
-
let providerContext = { ...context };
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
getContext: (): Record<string, any> => providerContext,
|
|
27
|
-
setContext: (ctx: Record<string, any>): void => {
|
|
28
|
-
providerContext = { ...providerContext, ...ctx };
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const providerStore = providedStore();
|
|
34
|
-
|
|
35
|
-
export interface ConnectorProviderProps extends Props {
|
|
36
|
-
store: StoreShape;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
class Provider extends Component<ConnectorProviderProps> {
|
|
40
|
-
store: StoreShape;
|
|
41
|
-
|
|
42
|
-
constructor(props: ConnectorProviderProps, context?: Record<string, any>) {
|
|
43
|
-
super(props, context);
|
|
44
|
-
this.store = props.store;
|
|
45
|
-
this.context['store'] = props.store;
|
|
46
|
-
this.context['parentSub'] = null;
|
|
47
|
-
providerStore.setContext({ ...this.context });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
render(): AnuElement | AnuElement[] | null {
|
|
51
|
-
const { children } = this.props;
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
if (!children || children.length !== 1) {
|
|
55
|
-
throw new Error('Provider must have one child element!');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return children;
|
|
59
|
-
} catch (err) {
|
|
60
|
-
console.error(err);
|
|
61
|
-
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
class Subscription {
|
|
68
|
-
store: StoreShape;
|
|
69
|
-
parentSub: Subscription | null;
|
|
70
|
-
onStateChange: () => void;
|
|
71
|
-
subscribed: boolean;
|
|
72
|
-
listeners: Array<() => void>;
|
|
73
|
-
|
|
74
|
-
constructor(store: StoreShape, parentSub: Subscription | null, onStateChange: () => void) {
|
|
75
|
-
this.store = store;
|
|
76
|
-
this.parentSub = parentSub;
|
|
77
|
-
this.onStateChange = onStateChange;
|
|
78
|
-
this.subscribed = false;
|
|
79
|
-
this.listeners = [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
notifyNestedSubs(): void {
|
|
83
|
-
this.listeners.forEach((listener) => listener());
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
trySubscribe(): void {
|
|
87
|
-
if (!this.subscribed) {
|
|
88
|
-
if (this.parentSub !== null) {
|
|
89
|
-
this.parentSub.addNestedSub(this.onStateChange);
|
|
90
|
-
} else {
|
|
91
|
-
this.store.subscribe(this.onStateChange);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
this.subscribed = true;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
addNestedSub(listener: () => void): void {
|
|
99
|
-
this.trySubscribe();
|
|
100
|
-
this.listeners.push(listener);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
tryUnsubscribe(): void {
|
|
104
|
-
if (this.subscribed) {
|
|
105
|
-
if (this.parentSub !== null) {
|
|
106
|
-
this.parentSub.removeNestedSub(this.onStateChange);
|
|
107
|
-
} else {
|
|
108
|
-
this.store.unsubscribe(this.onStateChange);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.subscribed = false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
removeNestedSub(listener: () => void): void {
|
|
116
|
-
this.tryUnsubscribe();
|
|
117
|
-
const index = this.listeners.indexOf(listener);
|
|
118
|
-
|
|
119
|
-
if (index >= 0) {
|
|
120
|
-
this.listeners.splice(index, 1);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const connectHOC =
|
|
126
|
-
<
|
|
127
|
-
TState = any,
|
|
128
|
-
TOwnProps extends Props = Props,
|
|
129
|
-
TStateProps extends Props = Props,
|
|
130
|
-
TDispatchProps extends Props = Props
|
|
131
|
-
>(
|
|
132
|
-
mapStateToProps?: MapStateToProps<TState, TOwnProps, TStateProps> | null,
|
|
133
|
-
mapDispatchToProps?: MapDispatchToProps<any, TOwnProps, TDispatchProps> | null
|
|
134
|
-
) =>
|
|
135
|
-
(WrappedComponent: ComponentConstructor<TStateProps & TDispatchProps & TOwnProps>): ComponentConstructor => {
|
|
136
|
-
class Connect extends Component<TOwnProps> {
|
|
137
|
-
store: StoreShape;
|
|
138
|
-
subscription: Subscription;
|
|
139
|
-
|
|
140
|
-
constructor(props: TOwnProps, context?: Record<string, any>) {
|
|
141
|
-
super(props, context);
|
|
142
|
-
this.context = { ...this.context, ...providerStore.getContext() };
|
|
143
|
-
this.store = this.context['store'] as StoreShape;
|
|
144
|
-
const parentSub = this.context['parentSub'] as Subscription | null;
|
|
145
|
-
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this));
|
|
146
|
-
this.context['parentSub'] = this.subscription;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
componentDidMount(): void {
|
|
150
|
-
this.subscription.trySubscribe();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
componentWillUnmount(): void {
|
|
154
|
-
this.subscription.tryUnsubscribe();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
componentDidUpdate(): void {
|
|
158
|
-
this.subscription.notifyNestedSubs();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
onStateChange(): void {
|
|
162
|
-
this.setState({});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
render(): AnuElement {
|
|
166
|
-
const stateProps = mapStateToProps
|
|
167
|
-
? mapStateToProps(this.store.getState(), this.props)
|
|
168
|
-
: ({} as TStateProps);
|
|
169
|
-
const dispatchProps = mapDispatchToProps
|
|
170
|
-
? mapDispatchToProps(this.store.dispatch, this.props)
|
|
171
|
-
: ({} as TDispatchProps);
|
|
172
|
-
const passedProps = {
|
|
173
|
-
...this.props,
|
|
174
|
-
...stateProps,
|
|
175
|
-
...dispatchProps
|
|
176
|
-
} as TStateProps & TDispatchProps & TOwnProps;
|
|
177
|
-
|
|
178
|
-
return createElement(WrappedComponent as any, passedProps as any);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return Connect as unknown as ComponentConstructor;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const Connector = {
|
|
186
|
-
connect: connectHOC,
|
|
187
|
-
Provider
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
export default Connector;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { deepEqual } from '../../misc/utils';
|
|
2
|
-
import { Component } from './Component';
|
|
3
|
-
import { AnuElement, Props } from '../elements';
|
|
4
|
-
|
|
5
|
-
export type ContextValue<T> = {
|
|
6
|
-
value: Partial<T>;
|
|
7
|
-
defaultContext: { value: T };
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type Context = {
|
|
11
|
-
Provider: new (props: Props) => Component;
|
|
12
|
-
Consumer: new (props: Props) => Component;
|
|
13
|
-
ContextProvider: new (props: Props) => Component;
|
|
14
|
-
ContextConsumer: new (props: Props) => Component;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const createContext = <T extends Record<string, any> = Record<string, any>>(context: T): Context => {
|
|
18
|
-
const providerContext: { defaultContext: { value: T }; value: Partial<T>; __notifySub?: boolean } = {
|
|
19
|
-
defaultContext: { value: context },
|
|
20
|
-
value: {}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const getPureProps = (props: Props): Partial<T> => {
|
|
24
|
-
const pureProps: Record<string, any> = {};
|
|
25
|
-
|
|
26
|
-
Object.keys(props).forEach((key) => {
|
|
27
|
-
if (key !== 'children') {
|
|
28
|
-
pureProps[key] = props[key];
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return pureProps as Partial<T>;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
class ContextProvider extends Component<Props> {
|
|
36
|
-
constructor(props: Props) {
|
|
37
|
-
super(props);
|
|
38
|
-
providerContext.value = { ...getPureProps(props) };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
componentDidUpdate(): void {
|
|
42
|
-
const pureProps = getPureProps(this.props);
|
|
43
|
-
|
|
44
|
-
if (!deepEqual(providerContext.value as Record<string, any>, pureProps as Record<string, any>)) {
|
|
45
|
-
providerContext.value = { ...pureProps };
|
|
46
|
-
|
|
47
|
-
this.setState();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
render(): AnuElement | AnuElement[] | null {
|
|
52
|
-
const { children } = this.props;
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
if (!children || children.length !== 1) {
|
|
56
|
-
throw new Error('Context Component must have exactly one child element!');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return children;
|
|
60
|
-
} catch (err) {
|
|
61
|
-
console.error(err);
|
|
62
|
-
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
class ContextConsumer extends Component<Props> {
|
|
69
|
-
componentDidUpdate(): void {
|
|
70
|
-
if (providerContext.__notifySub) {
|
|
71
|
-
this.setState();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
render(): AnuElement | AnuElement[] | null {
|
|
76
|
-
const { children } = this.props;
|
|
77
|
-
const { value, defaultContext } = providerContext;
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
if (!children || children.length !== 1) {
|
|
81
|
-
throw new Error('Context Component must have exactly one child element!');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const { type } = children[0];
|
|
85
|
-
const childProps: ContextValue<T> = { value, defaultContext };
|
|
86
|
-
|
|
87
|
-
if (typeof type === 'function') {
|
|
88
|
-
return (type as (ctx: ContextValue<T>) => AnuElement | AnuElement[] | null)(childProps);
|
|
89
|
-
} else {
|
|
90
|
-
throw new Error('Context component child element must be a function!');
|
|
91
|
-
}
|
|
92
|
-
} catch (err) {
|
|
93
|
-
console.error(err);
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
Provider: ContextProvider,
|
|
102
|
-
Consumer: ContextConsumer,
|
|
103
|
-
ContextProvider,
|
|
104
|
-
ContextConsumer
|
|
105
|
-
};
|
|
106
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createElement, AnuElement, Props } from '../elements';
|
|
2
|
-
import { createContext } from './Context';
|
|
3
|
-
|
|
4
|
-
type FeaturesMap = Record<string, boolean>;
|
|
5
|
-
|
|
6
|
-
const defaultFeatures: FeaturesMap = {};
|
|
7
|
-
|
|
8
|
-
const FeaturesContext = createContext<{ features?: FeaturesMap }>(defaultFeatures as any);
|
|
9
|
-
|
|
10
|
-
export interface FeatureProviderProps extends Props {
|
|
11
|
-
features: FeaturesMap;
|
|
12
|
-
children?: AnuElement[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface FeatureToggleProps extends Props {
|
|
16
|
-
name: string;
|
|
17
|
-
defaultComponent?: AnuElement | null;
|
|
18
|
-
children?: AnuElement[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const FeatureToggle = ({ name, children, defaultComponent = null }: FeatureToggleProps): AnuElement =>
|
|
22
|
-
createElement(
|
|
23
|
-
FeaturesContext.ContextConsumer,
|
|
24
|
-
{},
|
|
25
|
-
({ value: { features } }: { value: { features?: FeaturesMap } }) =>
|
|
26
|
-
features && features[name] === true ? children : defaultComponent
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const Feature = {
|
|
30
|
-
Provider: FeaturesContext.ContextProvider,
|
|
31
|
-
Toggle: FeatureToggle
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export default Feature;
|