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