react-store-input 0.2.6 → 0.5.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/CHANGELOG.md +136 -0
- package/README.md +211 -199
- package/dist/index.d.mts +127 -11
- package/dist/index.d.ts +127 -11
- package/dist/index.js +542 -280
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +529 -272
- package/dist/index.mjs.map +1 -0
- package/dist/text-editor.d.mts +11 -0
- package/dist/text-editor.d.ts +11 -0
- package/dist/text-editor.js +203 -0
- package/dist/text-editor.js.map +1 -0
- package/dist/text-editor.mjs +178 -0
- package/dist/text-editor.mjs.map +1 -0
- package/package.json +78 -18
- package/dist/create_render.d.mts +0 -9
- package/dist/create_render.d.ts +0 -9
- package/dist/create_render.js +0 -51
- package/dist/create_render.mjs +0 -25
- package/dist/text_editor.d.mts +0 -13
- package/dist/text_editor.d.ts +0 -13
- package/dist/text_editor.js +0 -135
- package/dist/text_editor.mjs +0 -112
- package/dist/use_form_store.d.mts +0 -16
- package/dist/use_form_store.d.ts +0 -16
- package/dist/use_form_store.js +0 -365
- package/dist/use_form_store.mjs +0 -340
- package/dist/use_store_component.d.mts +0 -23
- package/dist/use_store_component.d.ts +0 -23
- package/dist/use_store_component.js +0 -337
- package/dist/use_store_component.mjs +0 -311
- package/dist/use_store_controller.d.mts +0 -11
- package/dist/use_store_controller.d.ts +0 -11
- package/dist/use_store_controller.js +0 -57
- package/dist/use_store_controller.mjs +0 -32
- package/dist/use_store_input.d.mts +0 -22
- package/dist/use_store_input.d.ts +0 -22
- package/dist/use_store_input.js +0 -151
- package/dist/use_store_input.mjs +0 -126
- package/dist/use_store_input_with_name.d.mts +0 -17
- package/dist/use_store_input_with_name.d.ts +0 -17
- package/dist/use_store_input_with_name.js +0 -177
- package/dist/use_store_input_with_name.mjs +0 -150
- package/tsup.config.ts +0 -9
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project uses [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.5.0] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Replaced `useStoreController` with `useStoreBinding`, a Lens/Codec-based
|
|
13
|
+
adapter for synchronizing external controls without exposing whole-state
|
|
14
|
+
subscription and draft callbacks.
|
|
15
|
+
- Reimplemented `useStoreHTMLElement` and the optional `TextEditor` integration
|
|
16
|
+
on top of `useStoreBinding`.
|
|
17
|
+
- Renamed `useStoreComponent` to `useStoreInput`. It accepts an existing
|
|
18
|
+
`gw-store` `Store` and returns bound `input`, `select`, and `textarea`
|
|
19
|
+
components.
|
|
20
|
+
- Renamed the low-level DOM binding hook from `useStoreInput` to
|
|
21
|
+
`useStoreHTMLElement`.
|
|
22
|
+
- Removed `gw-store` API re-exports. Import stores, selectors, and their types
|
|
23
|
+
directly from `gw-store`.
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- Removed the deprecated `useFormStore`, `FormStore`, and `FormComponents`
|
|
28
|
+
APIs.
|
|
29
|
+
- Removed `useStoreController`. Non-input actions should call
|
|
30
|
+
`store.dispatch` directly.
|
|
31
|
+
|
|
32
|
+
## [0.4.0] - 2026-07-22
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Typed `Lens`, `Codec`, and `InputBinding` primitives for custom controls.
|
|
37
|
+
- `stateLens` for generating a getter and setter from one typed state path.
|
|
38
|
+
- `gw-result@0.3.0`-based codec parsing with typed failure metadata from
|
|
39
|
+
`useStoreInput`.
|
|
40
|
+
- `assertLensLaws` and `assertCodecLaws` helpers for testing mapping and
|
|
41
|
+
round-trip invariants.
|
|
42
|
+
- Top-level `ok`, `err`, and `Result` re-exports for codec implementations.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Removed the deprecated `getter`, `setter`, `toInputValue`, and
|
|
47
|
+
`toStateValue` APIs. Nested and converted controls now use bindings.
|
|
48
|
+
- Reimplemented named controls internally with a generated Lens and default
|
|
49
|
+
Codec while preserving built-in conversion and reset behavior.
|
|
50
|
+
- Expanded the custom currency example to demonstrate validation failures and
|
|
51
|
+
accessible error feedback.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Preserved invalid raw control input while unrelated store fields update.
|
|
56
|
+
- Cleared binding parse errors and restored the initial domain value on native
|
|
57
|
+
form reset.
|
|
58
|
+
|
|
59
|
+
## [0.3.0] - 2026-07-22
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- Standalone `Input`, `Select`, and `Textarea` components with typed named or
|
|
64
|
+
custom getter/setter bindings.
|
|
65
|
+
- `useStoreInput`, `useStoreController`, `createRender`, and complete
|
|
66
|
+
`gw-store` API re-exports.
|
|
67
|
+
- An optional `react-store-input/text-editor` entry point that keeps editor
|
|
68
|
+
dependencies out of the core bundle.
|
|
69
|
+
- Automatic native form reset synchronization, including batched store
|
|
70
|
+
notifications and multiple-select, date, checkbox, radio, and file values.
|
|
71
|
+
- Runtime, package-consumer, type, and example build tests.
|
|
72
|
+
- A complete interactive example covering the public components, hooks, and
|
|
73
|
+
supported input value types.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- Updated the store integration to use exactly `gw-store@0.2.0`.
|
|
78
|
+
- Published separate CommonJS and ESM entry points with matching declaration
|
|
79
|
+
files.
|
|
80
|
+
- Made `gw-react-text-editor` an optional peer dependency.
|
|
81
|
+
- Expanded React peer support to React 18 and 19.
|
|
82
|
+
- Organized package source, examples, styles, and runtime tests by
|
|
83
|
+
responsibility.
|
|
84
|
+
|
|
85
|
+
### Fixed
|
|
86
|
+
|
|
87
|
+
- Prevented bundled duplicate React copies from causing invalid hook calls.
|
|
88
|
+
- Preserved numeric radio values and mapped empty numeric inputs to
|
|
89
|
+
`undefined`.
|
|
90
|
+
- Stored all selected values from multiple selects and normalized empty file
|
|
91
|
+
inputs to `null`.
|
|
92
|
+
- Kept rendered JSON synchronized after a native form reset.
|
|
93
|
+
- Reduced reset notifications to one batch and eliminated notifications for
|
|
94
|
+
semantically unchanged arrays and `Date` values.
|
|
95
|
+
|
|
96
|
+
## [0.2.6] - 2026-04-28
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- Re-exported the `gw-store` API.
|
|
101
|
+
|
|
102
|
+
## [0.2.5] - 2026-04-27
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
|
|
106
|
+
- Added `gw-store` as a package dependency.
|
|
107
|
+
|
|
108
|
+
## [0.2.4] - 2026-03-16
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- Improved store value comparison behavior.
|
|
113
|
+
|
|
114
|
+
## [0.2.3] - 2026-03-09
|
|
115
|
+
|
|
116
|
+
### Changed
|
|
117
|
+
|
|
118
|
+
- Applied package maintenance updates.
|
|
119
|
+
|
|
120
|
+
## [0.2.2] - 2026-03-09
|
|
121
|
+
|
|
122
|
+
### Changed
|
|
123
|
+
|
|
124
|
+
- Added compatible React 18 type and peer dependency ranges.
|
|
125
|
+
|
|
126
|
+
## [0.2.1] - 2026-01-21
|
|
127
|
+
|
|
128
|
+
### Changed
|
|
129
|
+
|
|
130
|
+
- Updated the JSX runtime configuration.
|
|
131
|
+
|
|
132
|
+
## [0.2.0] - 2026-01-13
|
|
133
|
+
|
|
134
|
+
### Fixed
|
|
135
|
+
|
|
136
|
+
- Corrected forwarded ref behavior.
|
package/README.md
CHANGED
|
@@ -1,285 +1,297 @@
|
|
|
1
1
|
# React Store Input
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed, store-backed form controls for React 18 and 19. The package uses
|
|
4
|
+
[`gw-store`](https://www.npmjs.com/package/gw-store) 0.2.0, so state snapshots
|
|
5
|
+
are immutable and updates are made through Immer recipes.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
```sh
|
|
10
|
+
npm install react-store-input
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Quick start
|
|
12
14
|
|
|
13
15
|
```tsx
|
|
14
|
-
import {
|
|
16
|
+
import { useStore } from "gw-store";
|
|
17
|
+
import { Input } from "react-store-input";
|
|
15
18
|
|
|
16
|
-
export default function
|
|
17
|
-
const store =
|
|
19
|
+
export default function LoginForm() {
|
|
20
|
+
const store = useStore({
|
|
18
21
|
email: "",
|
|
19
22
|
password: "",
|
|
23
|
+
rememberMe: false,
|
|
20
24
|
});
|
|
21
25
|
|
|
22
|
-
const submit = async () => {
|
|
23
|
-
const { email, password } = store.state;
|
|
24
|
-
|
|
25
|
-
alert(`Email: ${email}\nPassword: ${password}`);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
26
|
return (
|
|
29
27
|
<form
|
|
30
|
-
onSubmit={(
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
onSubmit={(event) => {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
console.log(store.state);
|
|
33
31
|
}}
|
|
34
32
|
>
|
|
35
|
-
<store
|
|
36
|
-
<store
|
|
37
|
-
<
|
|
33
|
+
<Input store={store} name="email" type="email" />
|
|
34
|
+
<Input store={store} name="password" type="password" />
|
|
35
|
+
<Input store={store} name="rememberMe" type="checkbox" />
|
|
36
|
+
<button type="submit">Sign in</button>
|
|
38
37
|
</form>
|
|
39
38
|
);
|
|
40
39
|
}
|
|
41
40
|
```
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
Create and own stores with `gw-store`. This package only binds that store to
|
|
43
|
+
input controls.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
## Components
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
function Component() {
|
|
49
|
-
...
|
|
50
|
-
|
|
51
|
-
const store = useStore({
|
|
52
|
-
email: "",
|
|
53
|
-
password: "",
|
|
54
|
-
rememberMe: false,
|
|
55
|
-
});
|
|
47
|
+
You can use the standalone components when the store is passed from elsewhere:
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
}
|
|
49
|
+
```tsx
|
|
50
|
+
import { useStore } from "gw-store";
|
|
51
|
+
import { Input, Select, Textarea } from "react-store-input";
|
|
52
|
+
|
|
53
|
+
const store = useStore({ role: "user", bio: "" });
|
|
54
|
+
|
|
55
|
+
<Input store={store} name="role" type="radio" value="admin" />;
|
|
56
|
+
<Input store={store} name="role" type="radio" value="user" />;
|
|
57
|
+
<Select store={store} name="role">
|
|
58
|
+
<option value="admin">Admin</option>
|
|
59
|
+
<option value="user">User</option>
|
|
60
|
+
</Select>;
|
|
61
|
+
<Textarea store={store} name="bio" rows={5} />;
|
|
59
62
|
```
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## How to get input values?
|
|
64
|
-
|
|
65
|
-
You can access the current values of the input elements through the `state` property of the store.
|
|
64
|
+
When passing `store` to every control would be repetitive, `useStoreInput`
|
|
65
|
+
returns stable components already bound to an existing store:
|
|
66
66
|
|
|
67
67
|
```tsx
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
import { useStore } from "gw-store";
|
|
69
|
+
import { useStoreInput } from "react-store-input";
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
71
|
+
const store = useStore({ role: "user", bio: "" });
|
|
72
|
+
const controls = useStoreInput(store);
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
<controls.input name="role" />;
|
|
75
|
+
<controls.select name="role">...</controls.select>;
|
|
76
|
+
<controls.textarea name="bio" />;
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
A named `Input`, `Select`, or `Textarea` requires a valid top-level state key.
|
|
80
|
+
Use `useStoreHTMLElement` with a typed binding for nested or converted values.
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
## Value conversion
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
import { Input } from "dn-react-input";
|
|
85
|
-
|
|
86
|
-
function Component() {
|
|
87
|
-
...
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<form>
|
|
91
|
-
<Input store={store} name="email" type="email" />
|
|
92
|
-
<Input store={store} name="password" type="password" />
|
|
93
|
-
<Input store={store} name="rememberMe" type="checkbox" />
|
|
94
|
-
</form>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
```
|
|
84
|
+
The default conversions are:
|
|
98
85
|
|
|
99
|
-
|
|
86
|
+
- checkbox → `boolean`
|
|
87
|
+
- radio → the original `value` prop, preserving numbers and strings
|
|
88
|
+
- number/range → `number`, or `undefined` when empty
|
|
89
|
+
- datetime-local → `Date`, or `undefined` when empty or invalid
|
|
90
|
+
- multiple select → `string[]`
|
|
91
|
+
- file → `FileList | null`
|
|
92
|
+
- other controls → `string`
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
import { useStoreComponent } from "dn-react-input";
|
|
103
|
-
|
|
104
|
-
function Component() {
|
|
105
|
-
...
|
|
106
|
-
const component = useStoreComponent(store);
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<form>
|
|
110
|
-
<component.input name="email" type="email" />
|
|
111
|
-
<component.input name="password" type="password" />
|
|
112
|
-
<component.input name="rememberMe" type="checkbox" />
|
|
113
|
-
</form>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
```
|
|
94
|
+
Domain-specific conversions use a Codec as described below.
|
|
117
95
|
|
|
118
|
-
|
|
96
|
+
The controls are store-backed uncontrolled inputs. An explicit `value` or
|
|
97
|
+
`checked` prop is respected as an externally controlled value and is not
|
|
98
|
+
overwritten by store subscriptions. Native form reset is synchronized back to
|
|
99
|
+
the store.
|
|
119
100
|
|
|
120
|
-
|
|
121
|
-
import { useFormStore } from "dn-react-input";
|
|
122
|
-
|
|
123
|
-
function Component() {
|
|
124
|
-
...
|
|
125
|
-
const store = useFormStore({
|
|
126
|
-
email: "",
|
|
127
|
-
password: "",
|
|
128
|
-
rememberMe: false,
|
|
129
|
-
});
|
|
101
|
+
## Lens, codec, and binding
|
|
130
102
|
|
|
131
|
-
|
|
132
|
-
<form>
|
|
133
|
-
<store.input name="email" type="email" />
|
|
134
|
-
<store.input name="password" type="password" />
|
|
135
|
-
<store.input name="rememberMe" type="checkbox" />
|
|
136
|
-
</form>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
```
|
|
103
|
+
A custom control is defined from three small pieces:
|
|
140
104
|
|
|
141
|
-
|
|
105
|
+
- A `Lens<TState, TValue>` selects and updates one domain value in the store.
|
|
106
|
+
- A `Codec<TValue, TInput, TError>` formats that domain value for the control
|
|
107
|
+
and parses input back to a `gw-result` `Result`.
|
|
108
|
+
- An `InputBinding` combines a lens and codec whose `TValue` types must match.
|
|
142
109
|
|
|
143
|
-
|
|
110
|
+
Keeping the lens and codec separate lets one state field use different UI
|
|
111
|
+
representations, and lets one codec be reused for the same domain type in
|
|
112
|
+
different stores. `InputBinding` itself is not tied to an HTML element.
|
|
144
113
|
|
|
145
114
|
```tsx
|
|
146
|
-
import {
|
|
115
|
+
import {
|
|
116
|
+
defineBinding,
|
|
117
|
+
defineCodec,
|
|
118
|
+
err,
|
|
119
|
+
ok,
|
|
120
|
+
stateLens,
|
|
121
|
+
} from "react-store-input";
|
|
122
|
+
|
|
123
|
+
type FormState = { profile: { budget?: number } };
|
|
124
|
+
type BudgetError = { code: "INVALID_BUDGET"; input: string };
|
|
125
|
+
|
|
126
|
+
const budgetBinding = defineBinding({
|
|
127
|
+
lens: stateLens<FormState>().prop("profile").prop("budget"),
|
|
128
|
+
codec: defineCodec<number | undefined, string, BudgetError>({
|
|
129
|
+
format: (value) => value?.toString() ?? "",
|
|
130
|
+
parse: (input) => {
|
|
131
|
+
if (input === "") return ok(undefined);
|
|
132
|
+
|
|
133
|
+
const value = Number(input);
|
|
134
|
+
return Number.isFinite(value)
|
|
135
|
+
? ok(value)
|
|
136
|
+
: err({ code: "INVALID_BUDGET", input });
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
```
|
|
147
141
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
`stateLens().prop(...)` creates `get` and `set` from the same typed path, so
|
|
143
|
+
they cannot accidentally target different fields. `defineLens` is also
|
|
144
|
+
available for computed or otherwise non-path mappings.
|
|
151
145
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
`ok`, `err`, and the `Result` type are re-exported from `gw-result@0.3.0` for
|
|
147
|
+
codec implementations.
|
|
148
|
+
|
|
149
|
+
## Rendering selected state
|
|
155
150
|
|
|
156
|
-
|
|
151
|
+
Use `gw-store` selectors with this package's rendering helper.
|
|
157
152
|
|
|
158
153
|
```tsx
|
|
159
|
-
import {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
}
|
|
154
|
+
import { useSelector } from "gw-store";
|
|
155
|
+
import { createRender } from "react-store-input";
|
|
156
|
+
|
|
157
|
+
const email = useSelector(store, (state) => state.email);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
<p>{email}</p>
|
|
162
|
+
{createRender(store, (state) => <p>{state.password.length} characters</p>)}
|
|
163
|
+
{createRender(store, (state) => (
|
|
164
|
+
<p>{state.rememberMe ? "Remember" : "Forget"}</p>
|
|
165
|
+
))}
|
|
166
|
+
</>
|
|
167
|
+
);
|
|
170
168
|
```
|
|
171
169
|
|
|
172
|
-
|
|
170
|
+
## Custom controls
|
|
171
|
+
|
|
172
|
+
Use `useStoreHTMLElement` with a binding for custom elements that expose a normal
|
|
173
|
+
form-control DOM node. The ref is deliberately explicit. A parse failure keeps
|
|
174
|
+
the last valid store value, preserves the user's raw input, and exposes the
|
|
175
|
+
typed error through `meta`.
|
|
173
176
|
|
|
174
177
|
```tsx
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
import { useRef } from "react";
|
|
179
|
+
import type { Store } from "gw-store";
|
|
180
|
+
import { useStoreHTMLElement } from "react-store-input";
|
|
181
|
+
|
|
182
|
+
function BudgetInput({ store }: { store: Store<FormState> }) {
|
|
183
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
184
|
+
const field = useStoreHTMLElement(ref, store, budgetBinding, {
|
|
185
|
+
type: "text",
|
|
179
186
|
});
|
|
180
187
|
|
|
181
188
|
return (
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
<label>
|
|
190
|
+
Budget
|
|
191
|
+
<input
|
|
192
|
+
ref={ref}
|
|
193
|
+
type="text"
|
|
194
|
+
inputMode="decimal"
|
|
195
|
+
aria-invalid={!field.meta.valid}
|
|
196
|
+
{...field.inputProps}
|
|
197
|
+
/>
|
|
198
|
+
{!field.meta.valid && <span>{field.meta.error.code}</span>}
|
|
199
|
+
</label>
|
|
190
200
|
);
|
|
191
201
|
}
|
|
192
202
|
```
|
|
193
203
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
204
|
+
For an imperative editor or another non-DOM control, use the same binding
|
|
205
|
+
through `useStoreBinding`. It parses commits, exposes validation metadata, and
|
|
206
|
+
synchronizes external store changes without echoing its own dispatch:
|
|
197
207
|
|
|
198
208
|
```tsx
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
unsubscribe();
|
|
208
|
-
};
|
|
209
|
-
}, []);
|
|
210
|
-
|
|
211
|
-
...
|
|
212
|
-
}
|
|
209
|
+
const field = useStoreBinding(store, budgetBinding, {
|
|
210
|
+
onStoreChange: (input) => editorRef.current?.setValue(input),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
<ExternalEditor
|
|
214
|
+
defaultValue={field.initialValue}
|
|
215
|
+
onChange={(input) => field.commit(input)}
|
|
216
|
+
/>;
|
|
213
217
|
```
|
|
214
218
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
You can update the state manually using the `dispatch` method of the store.
|
|
219
|
+
Generated lenses and codecs can be checked with the exported law assertions in
|
|
220
|
+
unit tests:
|
|
218
221
|
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
};
|
|
222
|
+
```ts
|
|
223
|
+
assertLensLaws(budgetBinding.lens, {
|
|
224
|
+
state: { profile: { budget: 10 } },
|
|
225
|
+
values: [undefined, 0, 25],
|
|
226
|
+
});
|
|
225
227
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
assertCodecLaws(budgetBinding.codec, {
|
|
229
|
+
values: [undefined, 0, 25],
|
|
230
|
+
inputs: ["", "0", "25"],
|
|
231
|
+
});
|
|
228
232
|
```
|
|
229
233
|
|
|
230
|
-
The
|
|
234
|
+
The lens assertions verify get-after-set, set-current-value, and last-set-wins.
|
|
235
|
+
The codec assertion verifies `parse(format(value))` for representative domain
|
|
236
|
+
values, plus `format(parse(input).value)` for successful canonical inputs when
|
|
237
|
+
`inputs` are supplied. Normalizing or lossy codecs may supply domain-specific
|
|
238
|
+
`equals` and `equalsInput` functions.
|
|
239
|
+
|
|
240
|
+
For non-input actions, dispatch directly to the store:
|
|
231
241
|
|
|
232
242
|
```tsx
|
|
233
|
-
|
|
234
|
-
...
|
|
243
|
+
import type { Store } from "gw-store";
|
|
235
244
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
};
|
|
245
|
+
function Counter({ store }: { store: Store<{ count: number }> }) {
|
|
246
|
+
const increment = () =>
|
|
247
|
+
store.dispatch((state) => {
|
|
248
|
+
state.count += 1;
|
|
249
|
+
});
|
|
241
250
|
|
|
242
|
-
|
|
251
|
+
return <button onClick={increment}>Increment</button>;
|
|
243
252
|
}
|
|
244
253
|
```
|
|
245
254
|
|
|
246
|
-
##
|
|
255
|
+
## Optional text editor
|
|
247
256
|
|
|
248
|
-
|
|
257
|
+
The ProseMirror-based editor is a separate entry point so normal forms do not
|
|
258
|
+
download or bundle editor dependencies. It requires React 19 and an explicit
|
|
259
|
+
optional peer installation:
|
|
249
260
|
|
|
250
|
-
```
|
|
251
|
-
|
|
261
|
+
```sh
|
|
262
|
+
npm install gw-react-text-editor
|
|
263
|
+
```
|
|
252
264
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
name: "email",
|
|
256
|
-
});
|
|
265
|
+
```tsx
|
|
266
|
+
import { TextEditor } from "react-store-input/text-editor";
|
|
257
267
|
|
|
258
|
-
|
|
259
|
-
}
|
|
268
|
+
<TextEditor store={store} name="content" />;
|
|
260
269
|
```
|
|
261
270
|
|
|
262
|
-
##
|
|
271
|
+
## Development
|
|
263
272
|
|
|
264
|
-
|
|
273
|
+
Release history is tracked in [CHANGELOG.md](./CHANGELOG.md). The manual
|
|
274
|
+
pre-release checks are documented in [docs/PUBLISHING.md](./docs/PUBLISHING.md).
|
|
265
275
|
|
|
266
|
-
|
|
267
|
-
import { useStoreController } from "dn-react-input";
|
|
276
|
+
Source code is grouped by responsibility:
|
|
268
277
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
```text
|
|
279
|
+
src/
|
|
280
|
+
├─ binding/ Lens, Codec, Binding, and law assertions
|
|
281
|
+
├─ input/ DOM value conversion, reset coordination, and element hooks
|
|
282
|
+
├─ form/ standalone and store-bound input components
|
|
283
|
+
├─ store/ controller and render helpers
|
|
284
|
+
└─ editor/ optional text-editor integration
|
|
272
285
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
state.count += Number(element.textContent.replace("Count: ", ""));
|
|
280
|
-
},
|
|
281
|
-
});
|
|
286
|
+
example/src/
|
|
287
|
+
├─ components/ reusable demo UI
|
|
288
|
+
├─ sections/ one catalog section per capability group
|
|
289
|
+
├─ demo/ state model and initial data
|
|
290
|
+
└─ styles/ layout, fields, toolbar, state panel, and responsive rules
|
|
291
|
+
```
|
|
282
292
|
|
|
283
|
-
|
|
284
|
-
|
|
293
|
+
```sh
|
|
294
|
+
npm run typecheck
|
|
295
|
+
npm test
|
|
296
|
+
npm pack --dry-run
|
|
285
297
|
```
|