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 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
  {
@@ -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 build # Compile TypeScript sources to dist/ and emit .d.ts files
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;
@@ -17,7 +17,6 @@ export interface HistoryLinkProps extends Props {
17
17
  to: string;
18
18
  replace?: boolean;
19
19
  ariaLabel?: string;
20
- children?: AnuElement[];
21
20
  }
22
21
  export interface HistoryRedirectProps extends Props {
23
22
  to: string;
@@ -3,7 +3,6 @@ export interface IntlProviderProps extends Props {
3
3
  locale: string | null;
4
4
  messages: Record<string, Record<string, string>>;
5
5
  defaultLocale?: string;
6
- children?: AnuElement[];
7
6
  }
8
7
  export interface FormattedMessageProps extends Props {
9
8
  id: string;
@@ -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?: AnuChild | AnuChild[];
6
+ children?: AnuNode;
5
7
  ref?: Ref<any>;
6
8
  key?: string | number;
7
- style?: Partial<CSSStyleDeclaration & Record<string, string>>;
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.8.0",
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
- "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;