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 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. If your project uses TypeScript, add the following to your
51
- `tsconfig.json` so the compiler understands JSX produced by ANUVerzum:
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 build # Compile TypeScript sources to dist/ and emit .d.ts files
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.8.0",
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
- "build": "babel src --out-dir dist --extensions \".ts\" --copy-files && tsc --emitDeclarationOnly",
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;