anu-verzum 1.8.0 → 1.10.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 +10 -3
- package/dist/core/components/Context.d.ts +2 -2
- package/dist/core/components/Feature.d.ts +0 -2
- package/dist/core/components/History.d.ts +0 -1
- package/dist/core/components/Intl.d.ts +0 -1
- package/dist/core/elements.d.ts +4 -2
- 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
|
{
|
|
@@ -117,6 +121,8 @@ The following types are exported from `anu-verzum` for use in consumer projects:
|
|
|
117
121
|
|------|-------------|
|
|
118
122
|
| `AnuElement` | The virtual-DOM element descriptor (return type of `createElement`) |
|
|
119
123
|
| `AnuChild` | Union of all valid JSX child types: `AnuElement \| string \| number \| boolean \| null \| undefined` |
|
|
124
|
+
| `AnuNode` | Recursive child tree type used by `Props.children` — `AnuChild \| AnuNode[]`; accepts single children, arrays, and nested arrays (e.g. from `.map()`) |
|
|
125
|
+
| `AnuCSSProperties` | Style object type used by `Props.style` — `Partial<Record<keyof CSSStyleDeclaration, string \| number>>`; allows numeric values for unitless CSS properties such as `flexShrink`, `zIndex`, `opacity` |
|
|
120
126
|
| `Props` | Base props type — all component prop objects should extend this |
|
|
121
127
|
| `Ref<T>` | Reference object created by `Anu.createRef<T>()` |
|
|
122
128
|
| `Component<P, S>` | Abstract base class for class components |
|
|
@@ -140,7 +146,8 @@ The following types are exported from `anu-verzum` for use in consumer projects:
|
|
|
140
146
|
These scripts are available when working on the library itself:
|
|
141
147
|
|
|
142
148
|
```bash
|
|
143
|
-
npm run
|
|
149
|
+
npm run clean # Delete dist/ entirely
|
|
150
|
+
npm run build # Clean, compile TypeScript sources to dist/, and emit .d.ts files
|
|
144
151
|
npm run typecheck # Type-check without emitting any output
|
|
145
152
|
npm run lint # Run ESLint on all source files
|
|
146
153
|
npm run format # Format all source files with Prettier
|
|
@@ -6,12 +6,12 @@ export type ContextValue<T> = {
|
|
|
6
6
|
value: T;
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
|
-
export type ConsumerProps<T> = {
|
|
9
|
+
export type ConsumerProps<T> = Props & {
|
|
10
10
|
children: (ctx: ContextValue<T>) => AnuElement | null;
|
|
11
11
|
};
|
|
12
12
|
export type Context<T extends Record<string, any> = Record<string, any>> = {
|
|
13
13
|
Provider: new (props: Props & Partial<T>) => Component;
|
|
14
|
-
Consumer: new (props: ConsumerProps<T>) => Component
|
|
14
|
+
Consumer: new (props: ConsumerProps<T>) => Component<ConsumerProps<T>>;
|
|
15
15
|
ContextProvider: new (props: Props) => Component;
|
|
16
16
|
ContextConsumer: new (props: Props) => Component;
|
|
17
17
|
};
|
|
@@ -2,12 +2,10 @@ import { AnuElement, Props } from '../elements';
|
|
|
2
2
|
type FeaturesMap = Record<string, boolean>;
|
|
3
3
|
export interface FeatureProviderProps extends Props {
|
|
4
4
|
features: FeaturesMap;
|
|
5
|
-
children?: AnuElement[];
|
|
6
5
|
}
|
|
7
6
|
export interface FeatureToggleProps extends Props {
|
|
8
7
|
name: string;
|
|
9
8
|
defaultComponent?: AnuElement | null;
|
|
10
|
-
children?: AnuElement[];
|
|
11
9
|
}
|
|
12
10
|
declare const Feature: {
|
|
13
11
|
Provider: new (props: Props) => import("./Component").Component;
|
package/dist/core/elements.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export declare const TEXT_ELEMENT: "TEXT_ELEMENT";
|
|
2
2
|
export type AnuChild = AnuElement | string | number | boolean | null | undefined;
|
|
3
|
+
export type AnuNode = AnuChild | AnuNode[];
|
|
4
|
+
export type AnuCSSProperties = Partial<Record<keyof CSSStyleDeclaration, string | number>>;
|
|
3
5
|
export type Props = {
|
|
4
|
-
children?:
|
|
6
|
+
children?: AnuNode;
|
|
5
7
|
ref?: Ref<any>;
|
|
6
8
|
key?: string | number;
|
|
7
|
-
style?:
|
|
9
|
+
style?: AnuCSSProperties;
|
|
8
10
|
[key: string]: any;
|
|
9
11
|
};
|
|
10
12
|
export type Ref<T> = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anu-verzum",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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;
|