@view-models/react 1.1.0 → 1.3.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
@@ -1,5 +1,8 @@
1
1
  # @view-models/react
2
2
 
3
+ [![CI](https://github.com/sunesimonsen/view-models-react/actions/workflows/ci.yml/badge.svg)](https://github.com/sunesimonsen/view-models-react/actions/workflows/ci.yml)
4
+ [![Bundle Size](https://img.badgesize.io/https://unpkg.com/@view-models/react@latest/dist/index.js?label=gzip&compression=gzip)](https://unpkg.com/@view-models/react@latest/dist/index.js)
5
+
3
6
  React integration for [@view-models/core](https://github.com/sunesimonsen/view-models-core).
4
7
 
5
8
  ## Installation
@@ -20,11 +23,11 @@ type CounterState = Readonly<{ count: number }>;
20
23
 
21
24
  class CounterViewModel extends ViewModel<CounterState> {
22
25
  increment = () => {
23
- this.update(({ count }) => ({ count: count + 1 }));
26
+ super.update({ count: super.state.count + 1 });
24
27
  };
25
28
 
26
29
  decrement = () => {
27
- this.update(({ count }) => ({ count: count - 1 }));
30
+ super.update({ count: super.state.count - 1 });
28
31
  };
29
32
  }
30
33
 
@@ -58,11 +61,11 @@ type CounterState = Readonly<{ count: number }>;
58
61
 
59
62
  class CounterViewModel extends ViewModel<CounterState> {
60
63
  increment = () => {
61
- this.update(({ count }) => ({ count: count + 1 }));
64
+ super.update({ count: super.state.count + 1 });
62
65
  };
63
66
 
64
67
  decrement = () => {
65
- this.update(({ count }) => ({ count: count - 1 }));
68
+ super.update({ count: super.state.count - 1 });
66
69
  };
67
70
  }
68
71
 
@@ -86,26 +89,9 @@ function Counter() {
86
89
  }
87
90
  ```
88
91
 
89
- ## API
90
-
91
- ### `useModelState<T>(model: ViewModel<T>): T`
92
-
93
- A React hook that subscribes to state updates from a ViewModel instance.
94
-
95
- **Parameters:**
96
-
97
- - `model`: A ViewModel instance to subscribe to
98
-
99
- **Returns:**
100
-
101
- - The current state of the ViewModel
102
-
103
- **Features:**
92
+ ## API Reference
104
93
 
105
- - Automatically subscribes to state updates when the component mounts
106
- - Automatically unsubscribes when the component unmounts
107
- - Re-renders the component when the ViewModel state changes
108
- - Multiple components can subscribe to the same ViewModel
94
+ For detailed API documentation, see [docs](./docs).
109
95
 
110
96
  ## License
111
97
 
package/dist/index.d.ts CHANGED
@@ -1,2 +1,90 @@
1
- export * from "./useModelState.js";
2
- //# sourceMappingURL=index.d.ts.map
1
+ /**
2
+ * Function that gets called when the state changes.
3
+ *
4
+ * @template T - The state type
5
+ * @param state - The new state
6
+ */
7
+ type ViewModelListener = () => void;
8
+ /**
9
+ * Interface for a ViewModel that manages state and notifies subscribers of changes.
10
+ *
11
+ * ViewModels provide a way to manage component state outside of the component tree,
12
+ * allowing multiple components to share and react to the same state. They follow
13
+ * the observer pattern, where components can subscribe to state changes and receive
14
+ * notifications when updates occur.
15
+ *
16
+ * @template T - The type of state managed by this ViewModel
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Implementing a ViewModel
21
+ * class CounterViewModel extends ViewModel<{ count: number }> {
22
+ * increment = () => super.update({ count: super.state.count + 1 });
23
+ * decrement = () => super.update(({ count: super.state.count - 1 }));
24
+ * }
25
+ *
26
+ * // Using in a component
27
+ * const counterModel = new CounterViewModel({ count: 0 });
28
+ * const { count } = useModelState(counterModel);
29
+ * ```
30
+ */
31
+ interface ViewModel<T> {
32
+ /**
33
+ * Subscribe to state changes.
34
+ *
35
+ * The listener will be called immediately after any state update.
36
+ *
37
+ * @param listener - Function to call when state changes
38
+ * @returns Function to unsubscribe the listener
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const unsubscribe = viewModel.subscribe((state) => {
43
+ * console.log('State changed:', state);
44
+ * });
45
+ *
46
+ * // Later, when you want to stop listening:
47
+ * unsubscribe();
48
+ * ```
49
+ */
50
+ subscribe(listener: ViewModelListener): () => void;
51
+ /**
52
+ * Get the current state.
53
+ *
54
+ * @returns The current state
55
+ */
56
+ get state(): T;
57
+ }
58
+ /**
59
+ * A React hook that subscribes a component to a ViewModel's state updates.
60
+ *
61
+ * This hook connects a React component to a ViewModel instance, automatically
62
+ * subscribing to state changes and triggering re-renders when the state updates.
63
+ *
64
+ * @template T - The state type, which must extend the State interface
65
+ * @param model - The ViewModel instance to subscribe to
66
+ * @returns The current state from the ViewModel
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * class CounterViewModel extends ViewModel<{ count: number }> {
71
+ * increment = () => super.update({ count: super.state.count + 1 });
72
+ * }
73
+ *
74
+ * function Counter() {
75
+ * const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);
76
+ * const { count } = useModelState(counterModel);
77
+ *
78
+ * return (
79
+ * <div>
80
+ * <p>Count: {count}</p>
81
+ * <button onClick={counterModel.increment}>+</button>
82
+ * </div>
83
+ * );
84
+ * }
85
+ * ```
86
+ */
87
+ declare function useModelState<T>(model: ViewModel<T>): T;
88
+
89
+ export { useModelState };
90
+ export type { ViewModel, ViewModelListener };
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- export * from "./useModelState.js";
1
+ import{useSyncExternalStore as r}from"react";function t(t){return r(t.subscribe.bind(t),()=>t.state)}export{t as useModelState};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/useModelState.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\n/**\n * Function that gets called when the state changes.\n *\n * @template T - The state type\n * @param state - The new state\n */\nexport type ViewModelListener = () => void;\n\n/**\n * Interface for a ViewModel that manages state and notifies subscribers of changes.\n *\n * ViewModels provide a way to manage component state outside of the component tree,\n * allowing multiple components to share and react to the same state. They follow\n * the observer pattern, where components can subscribe to state changes and receive\n * notifications when updates occur.\n *\n * @template T - The type of state managed by this ViewModel\n *\n * @example\n * ```typescript\n * // Implementing a ViewModel\n * class CounterViewModel extends ViewModel<{ count: number }> {\n * increment = () => super.update({ count: super.state.count + 1 });\n * decrement = () => super.update(({ count: super.state.count - 1 }));\n * }\n *\n * // Using in a component\n * const counterModel = new CounterViewModel({ count: 0 });\n * const { count } = useModelState(counterModel);\n * ```\n */\nexport interface ViewModel<T> {\n /**\n * Subscribe to state changes.\n *\n * The listener will be called immediately after any state update.\n *\n * @param listener - Function to call when state changes\n * @returns Function to unsubscribe the listener\n *\n * @example\n * ```typescript\n * const unsubscribe = viewModel.subscribe((state) => {\n * console.log('State changed:', state);\n * });\n *\n * // Later, when you want to stop listening:\n * unsubscribe();\n * ```\n */\n subscribe(listener: ViewModelListener): () => void;\n\n /**\n * Get the current state.\n *\n * @returns The current state\n */\n get state(): T;\n}\n\n/**\n * A React hook that subscribes a component to a ViewModel's state updates.\n *\n * This hook connects a React component to a ViewModel instance, automatically\n * subscribing to state changes and triggering re-renders when the state updates.\n *\n * @template T - The state type, which must extend the State interface\n * @param model - The ViewModel instance to subscribe to\n * @returns The current state from the ViewModel\n *\n * @example\n * ```tsx\n * class CounterViewModel extends ViewModel<{ count: number }> {\n * increment = () => super.update({ count: super.state.count + 1 });\n * }\n *\n * function Counter() {\n * const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);\n * const { count } = useModelState(counterModel);\n *\n * return (\n * <div>\n * <p>Count: {count}</p>\n * <button onClick={counterModel.increment}>+</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useModelState<T>(model: ViewModel<T>): T {\n return useSyncExternalStore(model.subscribe.bind(model), () => model.state);\n}\n"],"names":["useModelState","model","useSyncExternalStore","subscribe","bind","state"],"mappings":"6CA2FM,SAAUA,EAAiBC,GAC/B,OAAOC,EAAqBD,EAAME,UAAUC,KAAKH,GAAQ,IAAMA,EAAMI,MACvE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@view-models/react",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "React integration for @view-models/core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,13 +16,15 @@
16
16
  "src"
17
17
  ],
18
18
  "scripts": {
19
- "build": "tsc",
19
+ "clean": "rm -rf dist",
20
+ "build": "npm run clean && rollup -c",
20
21
  "typecheck": "tsc --noEmit",
21
22
  "test": "vitest run",
22
23
  "test:watch": "vitest",
23
24
  "test:coverage": "vitest run --coverage",
24
25
  "format": "prettier --write .",
25
26
  "format:check": "prettier --check .",
27
+ "docs": "typedoc && npm run format",
26
28
  "prepublishOnly": "npm run build"
27
29
  },
28
30
  "keywords": [
@@ -45,18 +47,24 @@
45
47
  "node": ">=18.0.0"
46
48
  },
47
49
  "peerDependencies": {
48
- "@view-models/core": "^1.1.0",
49
50
  "react": ">=17.0.0"
50
51
  },
51
52
  "devDependencies": {
53
+ "@rollup/plugin-terser": "^0.4.4",
54
+ "@rollup/plugin-typescript": "^12.3.0",
52
55
  "@testing-library/react": "^16.1.0",
53
56
  "@types/react": "^18.3.18",
54
- "@view-models/core": "^1.1.0",
57
+ "@view-models/core": "^3.0.0",
55
58
  "@vitest/coverage-v8": "^2.1.8",
56
59
  "jsdom": "^26.0.0",
57
60
  "prettier": "^3.4.2",
58
61
  "react": "^18.3.1",
59
62
  "react-dom": "^18.3.1",
63
+ "rollup": "^4.57.0",
64
+ "rollup-plugin-dts": "^6.3.0",
65
+ "tslib": "^2.8.1",
66
+ "typedoc": "^0.28.16",
67
+ "typedoc-plugin-markdown": "^4.9.0",
60
68
  "typescript": "^5.7.3",
61
69
  "vitest": "^2.1.8"
62
70
  },
@@ -1,5 +1,64 @@
1
1
  import { useSyncExternalStore } from "react";
2
- import type { ViewModel, State } from "@view-models/core";
2
+
3
+ /**
4
+ * Function that gets called when the state changes.
5
+ *
6
+ * @template T - The state type
7
+ * @param state - The new state
8
+ */
9
+ export type ViewModelListener = () => void;
10
+
11
+ /**
12
+ * Interface for a ViewModel that manages state and notifies subscribers of changes.
13
+ *
14
+ * ViewModels provide a way to manage component state outside of the component tree,
15
+ * allowing multiple components to share and react to the same state. They follow
16
+ * the observer pattern, where components can subscribe to state changes and receive
17
+ * notifications when updates occur.
18
+ *
19
+ * @template T - The type of state managed by this ViewModel
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Implementing a ViewModel
24
+ * class CounterViewModel extends ViewModel<{ count: number }> {
25
+ * increment = () => super.update({ count: super.state.count + 1 });
26
+ * decrement = () => super.update(({ count: super.state.count - 1 }));
27
+ * }
28
+ *
29
+ * // Using in a component
30
+ * const counterModel = new CounterViewModel({ count: 0 });
31
+ * const { count } = useModelState(counterModel);
32
+ * ```
33
+ */
34
+ export interface ViewModel<T> {
35
+ /**
36
+ * Subscribe to state changes.
37
+ *
38
+ * The listener will be called immediately after any state update.
39
+ *
40
+ * @param listener - Function to call when state changes
41
+ * @returns Function to unsubscribe the listener
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const unsubscribe = viewModel.subscribe((state) => {
46
+ * console.log('State changed:', state);
47
+ * });
48
+ *
49
+ * // Later, when you want to stop listening:
50
+ * unsubscribe();
51
+ * ```
52
+ */
53
+ subscribe(listener: ViewModelListener): () => void;
54
+
55
+ /**
56
+ * Get the current state.
57
+ *
58
+ * @returns The current state
59
+ */
60
+ get state(): T;
61
+ }
3
62
 
4
63
  /**
5
64
  * A React hook that subscribes a component to a ViewModel's state updates.
@@ -14,7 +73,7 @@ import type { ViewModel, State } from "@view-models/core";
14
73
  * @example
15
74
  * ```tsx
16
75
  * class CounterViewModel extends ViewModel<{ count: number }> {
17
- * increment = () => this.update(({ count }) => ({ count: count + 1 }));
76
+ * increment = () => super.update({ count: super.state.count + 1 });
18
77
  * }
19
78
  *
20
79
  * function Counter() {
@@ -30,6 +89,6 @@ import type { ViewModel, State } from "@view-models/core";
30
89
  * }
31
90
  * ```
32
91
  */
33
- export function useModelState<T extends State>(model: ViewModel<T>): T {
92
+ export function useModelState<T>(model: ViewModel<T>): T {
34
93
  return useSyncExternalStore(model.subscribe.bind(model), () => model.state);
35
94
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
@@ -1,32 +0,0 @@
1
- import type { ViewModel, State } from "@view-models/core";
2
- /**
3
- * A React hook that subscribes a component to a ViewModel's state updates.
4
- *
5
- * This hook connects a React component to a ViewModel instance, automatically
6
- * subscribing to state changes and triggering re-renders when the state updates.
7
- *
8
- * @template T - The state type, which must extend the State interface
9
- * @param model - The ViewModel instance to subscribe to
10
- * @returns The current state from the ViewModel
11
- *
12
- * @example
13
- * ```tsx
14
- * class CounterViewModel extends ViewModel<{ count: number }> {
15
- * increment = () => this.update(({ count }) => ({ count: count + 1 }));
16
- * }
17
- *
18
- * function Counter() {
19
- * const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);
20
- * const { count } = useModelState(counterModel);
21
- *
22
- * return (
23
- * <div>
24
- * <p>Count: {count}</p>
25
- * <button onClick={counterModel.increment}>+</button>
26
- * </div>
27
- * );
28
- * }
29
- * ```
30
- */
31
- export declare function useModelState<T extends State>(model: ViewModel<T>): T;
32
- //# sourceMappingURL=useModelState.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useModelState.d.ts","sourceRoot":"","sources":["../src/useModelState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAErE"}
@@ -1,33 +0,0 @@
1
- import { useSyncExternalStore } from "react";
2
- /**
3
- * A React hook that subscribes a component to a ViewModel's state updates.
4
- *
5
- * This hook connects a React component to a ViewModel instance, automatically
6
- * subscribing to state changes and triggering re-renders when the state updates.
7
- *
8
- * @template T - The state type, which must extend the State interface
9
- * @param model - The ViewModel instance to subscribe to
10
- * @returns The current state from the ViewModel
11
- *
12
- * @example
13
- * ```tsx
14
- * class CounterViewModel extends ViewModel<{ count: number }> {
15
- * increment = () => this.update(({ count }) => ({ count: count + 1 }));
16
- * }
17
- *
18
- * function Counter() {
19
- * const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);
20
- * const { count } = useModelState(counterModel);
21
- *
22
- * return (
23
- * <div>
24
- * <p>Count: {count}</p>
25
- * <button onClick={counterModel.increment}>+</button>
26
- * </div>
27
- * );
28
- * }
29
- * ```
30
- */
31
- export function useModelState(model) {
32
- return useSyncExternalStore(model.subscribe.bind(model), () => model.state);
33
- }