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.
Files changed (44) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +211 -199
  3. package/dist/index.d.mts +127 -11
  4. package/dist/index.d.ts +127 -11
  5. package/dist/index.js +542 -280
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +529 -272
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/text-editor.d.mts +11 -0
  10. package/dist/text-editor.d.ts +11 -0
  11. package/dist/text-editor.js +203 -0
  12. package/dist/text-editor.js.map +1 -0
  13. package/dist/text-editor.mjs +178 -0
  14. package/dist/text-editor.mjs.map +1 -0
  15. package/package.json +78 -18
  16. package/dist/create_render.d.mts +0 -9
  17. package/dist/create_render.d.ts +0 -9
  18. package/dist/create_render.js +0 -51
  19. package/dist/create_render.mjs +0 -25
  20. package/dist/text_editor.d.mts +0 -13
  21. package/dist/text_editor.d.ts +0 -13
  22. package/dist/text_editor.js +0 -135
  23. package/dist/text_editor.mjs +0 -112
  24. package/dist/use_form_store.d.mts +0 -16
  25. package/dist/use_form_store.d.ts +0 -16
  26. package/dist/use_form_store.js +0 -365
  27. package/dist/use_form_store.mjs +0 -340
  28. package/dist/use_store_component.d.mts +0 -23
  29. package/dist/use_store_component.d.ts +0 -23
  30. package/dist/use_store_component.js +0 -337
  31. package/dist/use_store_component.mjs +0 -311
  32. package/dist/use_store_controller.d.mts +0 -11
  33. package/dist/use_store_controller.d.ts +0 -11
  34. package/dist/use_store_controller.js +0 -57
  35. package/dist/use_store_controller.mjs +0 -32
  36. package/dist/use_store_input.d.mts +0 -22
  37. package/dist/use_store_input.d.ts +0 -22
  38. package/dist/use_store_input.js +0 -151
  39. package/dist/use_store_input.mjs +0 -126
  40. package/dist/use_store_input_with_name.d.mts +0 -17
  41. package/dist/use_store_input_with_name.d.ts +0 -17
  42. package/dist/use_store_input_with_name.js +0 -177
  43. package/dist/use_store_input_with_name.mjs +0 -150
  44. package/tsup.config.ts +0 -9
package/dist/index.mjs CHANGED
@@ -1,347 +1,604 @@
1
- // src/use_store_controller.tsx
2
- import { useEffect, useId } from "react";
3
- function useStoreController(store, props) {
4
- const dispatchKey = useId();
5
- useEffect(() => {
6
- const unsub = store.subscribe((state, key) => {
7
- if (key === dispatchKey) {
8
- return;
9
- }
10
- props.onSubscribe(state);
11
- });
12
- return () => {
13
- unsub();
14
- };
15
- }, []);
16
- const dispatch = () => {
17
- store.dispatch(
18
- (state) => {
19
- props.onDispatch(state);
20
- },
21
- {
22
- key: dispatchKey
23
- }
24
- );
1
+ // src/store/create_render.tsx
2
+ import { useSelector } from "gw-store";
3
+ import { Fragment, jsx } from "react/jsx-runtime";
4
+ function createRender(store, selector, compare) {
5
+ function Component() {
6
+ const result = useSelector(store, (state) => state, compare);
7
+ return /* @__PURE__ */ jsx(Fragment, { children: selector(result) });
8
+ }
9
+ return /* @__PURE__ */ jsx(Component, {});
10
+ }
11
+ function createRenderWithStore(store) {
12
+ return function createRender2(selector, compare) {
13
+ function Component() {
14
+ const result = useSelector(store, (state) => state, compare);
15
+ return /* @__PURE__ */ jsx(Fragment, { children: selector(result) });
16
+ }
17
+ return /* @__PURE__ */ jsx(Component, {});
18
+ };
19
+ }
20
+
21
+ // src/binding/lens.ts
22
+ function defineLens(lens) {
23
+ return lens;
24
+ }
25
+ function readPath(value, path) {
26
+ return path.reduce((current, key) => current[key], value);
27
+ }
28
+ function writePath(state, path, value) {
29
+ const parent = path.slice(0, -1).reduce((current, key) => current[key], state);
30
+ parent[path.at(-1)] = value;
31
+ }
32
+ function createPathLens(path) {
33
+ return {
34
+ get: (state) => readPath(state, path),
35
+ set: (state, value) => writePath(state, path, value),
36
+ prop: (key) => createPathLens([...path, key])
25
37
  };
38
+ }
39
+ function stateLens() {
26
40
  return {
27
- dispatch
41
+ prop: (key) => createPathLens([key])
28
42
  };
29
43
  }
30
44
 
31
- // src/use_store_input.tsx
32
- import { format } from "date-fns";
33
- function useStoreInput(ref, store, props) {
34
- const toInputValue = props.toInputValue || ((value) => {
35
- if (value === void 0 || value === null) {
36
- return "";
45
+ // src/binding/codec.ts
46
+ function defineCodec(codec) {
47
+ return codec;
48
+ }
49
+
50
+ // src/binding/binding.ts
51
+ function defineBinding(binding) {
52
+ return binding;
53
+ }
54
+
55
+ // src/binding/laws.ts
56
+ import { produce } from "immer";
57
+ function structuralEqual(previous, next) {
58
+ if (Object.is(previous, next)) {
59
+ return true;
60
+ }
61
+ if (previous instanceof Date && next instanceof Date) {
62
+ return previous.getTime() === next.getTime();
63
+ }
64
+ if (Array.isArray(previous) && Array.isArray(next)) {
65
+ return previous.length === next.length && previous.every((value, index) => structuralEqual(value, next[index]));
66
+ }
67
+ if (previous !== null && next !== null && typeof previous === "object" && typeof next === "object") {
68
+ const previousRecord = previous;
69
+ const nextRecord = next;
70
+ const previousKeys = Reflect.ownKeys(previousRecord);
71
+ const nextKeys = Reflect.ownKeys(nextRecord);
72
+ return previousKeys.length === nextKeys.length && previousKeys.every(
73
+ (key) => Object.prototype.hasOwnProperty.call(nextRecord, key) && structuralEqual(previousRecord[key], nextRecord[key])
74
+ );
75
+ }
76
+ return false;
77
+ }
78
+ function assertLensLaws(lens, options) {
79
+ const equalsState = options.equalsState ?? structuralEqual;
80
+ const equalsValue = options.equalsValue ?? structuralEqual;
81
+ const set = (state, value) => produce(state, (draft) => lens.set(draft, value));
82
+ if (!equalsState(set(options.state, lens.get(options.state)), options.state)) {
83
+ throw new Error("Lens law failed: setting the current value changed state.");
84
+ }
85
+ for (const value of options.values) {
86
+ const updated = set(options.state, value);
87
+ if (!equalsValue(lens.get(updated), value)) {
88
+ throw new Error("Lens law failed: get(set(state, value)) !== value.");
37
89
  }
38
- if (props.type === "datetime-local") {
39
- const date = new Date(value);
40
- if (!isNaN(date.getTime())) {
41
- return format(date, "yyyy-MM-dd'T'HH:mm:ss");
90
+ }
91
+ for (const previous of options.values) {
92
+ for (const next of options.values) {
93
+ if (!equalsState(set(set(options.state, previous), next), set(options.state, next))) {
94
+ throw new Error("Lens law failed: the last set did not win.");
42
95
  }
43
96
  }
44
- return String(value);
45
- });
46
- const getDefaultValue = () => {
47
- if (props.defaultValue !== void 0) {
48
- return props.defaultValue;
49
- }
50
- if (props.type === "checkbox" || props.type === "radio") {
51
- return void 0;
52
- }
53
- return toInputValue(props.getter(store.state));
54
- };
55
- const toInputChecked = (value) => {
56
- if (props.type === "radio") {
57
- return value !== void 0 && value === props.value;
58
- }
59
- return Boolean(value);
60
- };
61
- const getDefaultChecked = () => {
62
- if (props.defaultChecked) {
63
- return props.defaultChecked;
97
+ }
98
+ }
99
+ function assertCodecLaws(codec, options) {
100
+ const equals = options.equals ?? codec.equals ?? structuralEqual;
101
+ const equalsInput = options.equalsInput ?? structuralEqual;
102
+ for (const value of options.values) {
103
+ const result = codec.parse(codec.format(value));
104
+ if (result.isErr) {
105
+ throw new Error("Codec law failed: parse(format(value)) returned Err.");
64
106
  }
65
- if (props.type !== "checkbox" && props.type !== "radio") {
66
- return void 0;
107
+ if (!equals(result.value, value)) {
108
+ throw new Error("Codec law failed: parse(format(value)) !== value.");
67
109
  }
68
- return toInputChecked(props.getter(store.state));
69
- };
70
- const toStateValue = props.toStateValue || ((value) => {
71
- if (props.type === "number" || props.type === "range") {
72
- return Number(value);
110
+ }
111
+ for (const input of options.inputs ?? []) {
112
+ const result = codec.parse(input);
113
+ if (result.isOk && !equalsInput(codec.format(result.value), input)) {
114
+ throw new Error("Codec law failed: format(parse(input).value) !== input.");
73
115
  }
74
- if (props.type === "datetime-local") {
75
- return new Date(value);
76
- }
77
- return value;
78
- });
79
- const inputProps = useStoreController(store, {
80
- onSubscribe: (state) => {
81
- const element = ref.current;
82
- if (!element) {
116
+ }
117
+ }
118
+
119
+ // src/binding/use_store_binding.tsx
120
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
121
+ function equalStoreValue(previous, next) {
122
+ if (Object.is(previous, next)) {
123
+ return true;
124
+ }
125
+ if (previous instanceof Date && next instanceof Date) {
126
+ return Object.is(previous.getTime(), next.getTime());
127
+ }
128
+ if (Array.isArray(previous) && Array.isArray(next)) {
129
+ return previous.length === next.length && previous.every((value, index) => Object.is(value, next[index]));
130
+ }
131
+ return false;
132
+ }
133
+ function useStoreBinding(store, binding, options = {}) {
134
+ const dispatchKey = useId();
135
+ const [initialValue] = useState(() => binding.codec.format(binding.lens.get(store.state)));
136
+ const [meta, setMeta] = useState({ valid: true });
137
+ const metaRef = useRef(meta);
138
+ const optionsRef = useRef(options);
139
+ const subscribedStoreRef = useRef(null);
140
+ const subscribedBindingRef = useRef(binding);
141
+ useEffect(() => {
142
+ optionsRef.current = options;
143
+ }, [options]);
144
+ useEffect(() => {
145
+ const isReplacement = subscribedStoreRef.current !== null && (subscribedStoreRef.current !== store || subscribedBindingRef.current !== binding);
146
+ subscribedStoreRef.current = store;
147
+ subscribedBindingRef.current = binding;
148
+ const sync = (state) => {
149
+ if (!metaRef.current.valid) {
83
150
  return;
84
151
  }
85
- if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
86
- const checked = toInputChecked(props.getter(state));
87
- if (element.checked === checked) {
88
- return;
89
- }
90
- element.checked = checked;
91
- } else {
92
- const value = toInputValue(props.getter(state));
93
- if (element.value === value) {
94
- return;
95
- }
96
- element.value = value;
152
+ const value = binding.lens.get(state);
153
+ optionsRef.current.onStoreChange?.(binding.codec.format(value), value);
154
+ };
155
+ const unsubscribe = store.subscribe((state, key) => {
156
+ if (key !== dispatchKey) {
157
+ sync(state);
97
158
  }
98
- const event = new Event("input", { bubbles: true });
99
- element.dispatchEvent(event);
100
- },
101
- onDispatch: (state) => {
102
- const element = ref.current;
103
- if (!element) {
159
+ });
160
+ if (isReplacement) {
161
+ sync(store.state);
162
+ }
163
+ return unsubscribe;
164
+ }, [binding, dispatchKey, store]);
165
+ const setValue = useCallback(
166
+ (value) => {
167
+ const currentValue = binding.lens.get(store.state);
168
+ const equals = binding.codec.equals ?? equalStoreValue;
169
+ if (equals(currentValue, value)) {
104
170
  return;
105
171
  }
106
- if ("checked" in element && props.type === "checkbox") {
107
- props.setter(state, element.checked);
108
- } else {
109
- props.setter(state, toStateValue(element.value));
172
+ store.dispatch((state) => binding.lens.set(state, value), { key: dispatchKey });
173
+ },
174
+ [binding, dispatchKey, store]
175
+ );
176
+ const commit = useCallback(
177
+ (input) => {
178
+ const result = binding.codec.parse(input);
179
+ if (result.isErr) {
180
+ const invalidMeta = {
181
+ valid: false,
182
+ error: result.error
183
+ };
184
+ metaRef.current = invalidMeta;
185
+ setMeta(invalidMeta);
186
+ return result;
110
187
  }
111
- }
112
- });
188
+ const validMeta = { valid: true };
189
+ metaRef.current = validMeta;
190
+ setMeta((current) => current.valid ? current : validMeta);
191
+ setValue(result.value);
192
+ return result;
193
+ },
194
+ [binding, setValue]
195
+ );
196
+ const reset = useCallback(
197
+ (value) => {
198
+ const validMeta = { valid: true };
199
+ metaRef.current = validMeta;
200
+ setMeta((current) => current.valid ? current : validMeta);
201
+ setValue(value);
202
+ },
203
+ [setValue]
204
+ );
113
205
  return {
114
- defaultValue: getDefaultValue(),
115
- defaultChecked: getDefaultChecked(),
116
- onChange: (event) => {
117
- inputProps.dispatch();
118
- props.onChange?.(event);
119
- }
206
+ initialValue,
207
+ meta,
208
+ commit,
209
+ reset
120
210
  };
121
211
  }
122
212
 
123
- // src/use_store_input_with_name.tsx
124
- function useStoreInputWithName(ref, store, props) {
125
- const inputProps = useStoreInput(ref, store, {
126
- ...props,
127
- getter: (state) => {
128
- if (props.getter) {
129
- return props.getter(state);
130
- }
131
- return state[props.name];
132
- },
133
- setter: (state, value) => {
134
- if (props.setter) {
135
- props.setter(state, value);
136
- return;
213
+ // src/html_element/use_store_html_element.tsx
214
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
215
+
216
+ // src/html_element/dom_value.ts
217
+ function pad(value) {
218
+ return String(value).padStart(2, "0");
219
+ }
220
+ function formatDateTimeLocal(value) {
221
+ const date = new Date(value);
222
+ if (Number.isNaN(date.getTime())) {
223
+ return "";
224
+ }
225
+ return [
226
+ date.getFullYear(),
227
+ "-",
228
+ pad(date.getMonth() + 1),
229
+ "-",
230
+ pad(date.getDate()),
231
+ "T",
232
+ pad(date.getHours()),
233
+ ":",
234
+ pad(date.getMinutes()),
235
+ ":",
236
+ pad(date.getSeconds())
237
+ ].join("");
238
+ }
239
+ function readElementValue(element) {
240
+ if ("options" in element && element.multiple) {
241
+ return Array.from(element.selectedOptions, (option) => option.value);
242
+ }
243
+ return element.value;
244
+ }
245
+ function writeElementValue(element, value) {
246
+ if ("files" in element && element.type === "file") {
247
+ return;
248
+ }
249
+ if ("options" in element && element.multiple) {
250
+ const selectedValues = new Set((Array.isArray(value) ? value : [value]).map(String));
251
+ for (const option of element.options) {
252
+ const selected = selectedValues.has(option.value);
253
+ if (option.selected !== selected) {
254
+ option.selected = selected;
137
255
  }
138
- state[props.name] = value;
139
256
  }
140
- });
141
- return {
142
- ...inputProps,
143
- name: "name" in props ? String(props.name) : void 0
257
+ return;
258
+ }
259
+ const nextValue = String(value);
260
+ if (element.value !== nextValue) {
261
+ element.value = nextValue;
262
+ }
263
+ }
264
+
265
+ // src/html_element/reset_coordinator.ts
266
+ var resettingForms = /* @__PURE__ */ new WeakSet();
267
+ var resetCoordinators = /* @__PURE__ */ new WeakMap();
268
+ function isFormResetting(form) {
269
+ return resettingForms.has(form);
270
+ }
271
+ function registerResetBinding(form, batch, reset) {
272
+ let coordinator = resetCoordinators.get(form);
273
+ if (!coordinator) {
274
+ const groups = /* @__PURE__ */ new Map();
275
+ const handleReset = () => {
276
+ resettingForms.add(form);
277
+ const groupSnapshots = Array.from(groups, ([groupBatch, bindings2]) => [groupBatch, [...bindings2]]);
278
+ setTimeout(() => {
279
+ try {
280
+ for (const [groupBatch, bindings2] of groupSnapshots) {
281
+ groupBatch(() => {
282
+ for (const binding of bindings2) {
283
+ binding();
284
+ }
285
+ });
286
+ }
287
+ } finally {
288
+ setTimeout(() => resettingForms.delete(form), 0);
289
+ }
290
+ }, 0);
291
+ };
292
+ coordinator = { groups, handleReset };
293
+ resetCoordinators.set(form, coordinator);
294
+ form.addEventListener("reset", handleReset);
295
+ }
296
+ let bindings = coordinator.groups.get(batch);
297
+ if (!bindings) {
298
+ bindings = /* @__PURE__ */ new Set();
299
+ coordinator.groups.set(batch, bindings);
300
+ }
301
+ bindings.add(reset);
302
+ return () => {
303
+ const currentCoordinator = resetCoordinators.get(form);
304
+ if (!currentCoordinator) {
305
+ return;
306
+ }
307
+ const currentBindings = currentCoordinator.groups.get(batch);
308
+ currentBindings?.delete(reset);
309
+ if (currentBindings?.size === 0) {
310
+ currentCoordinator.groups.delete(batch);
311
+ }
312
+ if (currentCoordinator.groups.size === 0) {
313
+ form.removeEventListener("reset", currentCoordinator.handleReset);
314
+ resetCoordinators.delete(form);
315
+ }
144
316
  };
145
317
  }
146
318
 
147
- // src/use_store_component.tsx
148
- import { useCallback, useRef as useRef2 } from "react";
319
+ // src/html_element/value_conversion.ts
320
+ function convertToInputValue(value, type) {
321
+ if (value === void 0 || value === null) {
322
+ return "";
323
+ }
324
+ if (type === "datetime-local") {
325
+ return formatDateTimeLocal(value);
326
+ }
327
+ return Array.isArray(value) ? value.map(String) : String(value);
328
+ }
329
+ function convertToStateValue(value, type) {
330
+ if (Array.isArray(value)) {
331
+ return value;
332
+ }
333
+ if (type === "number" || type === "range") {
334
+ return value === "" ? void 0 : Number(value);
335
+ }
336
+ if (type === "datetime-local") {
337
+ if (value === "") {
338
+ return void 0;
339
+ }
340
+ const date = new Date(value);
341
+ return Number.isNaN(date.getTime()) ? void 0 : date;
342
+ }
343
+ return value;
344
+ }
345
+ function resolveDefaultValue(props, stateValue, format) {
346
+ if (props.type !== "radio" && props.value !== void 0) {
347
+ return void 0;
348
+ }
349
+ if (props.defaultValue !== void 0) {
350
+ return props.defaultValue;
351
+ }
352
+ if (props.type === "checkbox" || props.type === "radio" || props.type === "file") {
353
+ return void 0;
354
+ }
355
+ return format(stateValue);
356
+ }
357
+ function resolveDefaultChecked(props, stateValue, toChecked) {
358
+ if (props.checked !== void 0) {
359
+ return void 0;
360
+ }
361
+ if (props.defaultChecked !== void 0) {
362
+ return props.defaultChecked;
363
+ }
364
+ return props.type === "checkbox" || props.type === "radio" ? toChecked(stateValue) : void 0;
365
+ }
366
+ function resolveResetStateValue(props, stateValue, parse) {
367
+ if (props.type === "checkbox" && props.defaultChecked !== void 0) {
368
+ return props.defaultChecked;
369
+ }
370
+ if (props.type === "radio" && props.defaultChecked) {
371
+ if (props.value !== void 0) {
372
+ return props.value;
373
+ }
374
+ return convertToStateValue(String(props.defaultValue ?? ""), props.type);
375
+ }
376
+ if (props.type !== "file" && props.type !== "checkbox" && props.type !== "radio" && props.defaultValue !== void 0) {
377
+ const defaultValue = Array.isArray(props.defaultValue) ? props.defaultValue.map(String) : String(props.defaultValue);
378
+ return parse(defaultValue);
379
+ }
380
+ return stateValue;
381
+ }
149
382
 
150
- // src/text_editor.tsx
151
- import {
152
- TextEditor as GwTextEditor
153
- } from "gw-react-text-editor";
154
- import { useImperativeHandle, useRef } from "react";
155
- import { jsx } from "react/jsx-runtime";
156
- function TextEditor({
157
- store,
158
- name,
159
- getter,
160
- setter,
161
- defaultValue,
162
- ref,
163
- ...props
164
- }) {
165
- const controllerRef = useRef(null);
166
- useImperativeHandle(
167
- ref,
168
- () => controllerRef.current,
169
- []
383
+ // src/html_element/use_store_html_element.tsx
384
+ function isDisplayValue(value) {
385
+ return typeof value === "string" || typeof value === "number" || Array.isArray(value) && value.every((item) => typeof item === "string");
386
+ }
387
+ function readControlValue(element, type, radioValue) {
388
+ if ("files" in element && type === "file") {
389
+ return element.files?.length ? element.files : null;
390
+ }
391
+ if ("checked" in element && type === "checkbox") {
392
+ return element.checked;
393
+ }
394
+ if ("checked" in element && type === "radio") {
395
+ return element.checked ? radioValue ?? element.value : void 0;
396
+ }
397
+ return readElementValue(element);
398
+ }
399
+ function useStoreHTMLElement(ref, store, binding, options = {}) {
400
+ const props = options;
401
+ const getStateValue = binding.lens.get;
402
+ const formatValue = useCallback2((value) => binding.codec.format(value), [binding]);
403
+ const toChecked = useCallback2(
404
+ (value) => props.type === "radio" ? Object.is(formatValue(value), props.value) : Boolean(formatValue(value)),
405
+ [formatValue, props.type, props.value]
406
+ );
407
+ const initialStateValue = getStateValue(store.state);
408
+ const resetValueRef = useRef2(
409
+ Object.prototype.hasOwnProperty.call(options, "resetValue") ? options.resetValue : initialStateValue
170
410
  );
171
- const { dispatch } = useStoreController(store, {
172
- onSubscribe: (state) => {
173
- const controller = controllerRef.current;
174
- if (!controller) {
411
+ const writeValue = useCallback2(
412
+ (element, value) => {
413
+ if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
414
+ element.checked = toChecked(value);
175
415
  return;
176
416
  }
177
- const getResult = () => {
178
- if (getter) {
179
- return getter(state) || "";
180
- }
181
- if (name) {
182
- return state[name] || "";
183
- }
184
- return "";
185
- };
186
- const result = getResult();
187
- if (controller.value !== result) {
188
- controller.value = result;
417
+ if ("files" in element && props.type === "file") {
418
+ return;
419
+ }
420
+ const formatted = formatValue(value);
421
+ if (isDisplayValue(formatted)) {
422
+ writeElementValue(element, formatted);
189
423
  }
190
424
  },
191
- onDispatch: (state) => {
192
- const controller = controllerRef.current;
193
- if (!controller) {
425
+ [formatValue, props.type, toChecked]
426
+ );
427
+ const { commit, meta, reset } = useStoreBinding(store, binding, {
428
+ onStoreChange: (_input, value) => {
429
+ const element = ref.current;
430
+ if (!element || element.form && isFormResetting(element.form) || props.type !== "radio" && props.value !== void 0) {
194
431
  return;
195
432
  }
196
- if (setter) {
197
- setter(state, controller.value);
433
+ if ("checked" in element && (props.type === "checkbox" || props.type === "radio") && props.checked !== void 0) {
198
434
  return;
199
435
  }
200
- if (name) {
201
- state[name] = controller.value;
202
- }
436
+ writeValue(element, value);
203
437
  }
204
438
  });
205
- const getDefaultValue = () => {
206
- if (getter) {
207
- return getter(store.state);
439
+ useEffect2(() => {
440
+ const element = ref.current;
441
+ const form = element?.form;
442
+ if (!element || !form) {
443
+ return;
208
444
  }
209
- if (name) {
210
- return store.state[name];
211
- }
212
- return void 0;
213
- };
214
- return /* @__PURE__ */ jsx(
215
- GwTextEditor,
216
- {
217
- ...props,
218
- ref: controllerRef,
219
- defaultValue: defaultValue ?? getDefaultValue(),
220
- onChange: (e) => {
221
- dispatch();
222
- props.onChange?.(e);
445
+ return registerResetBinding(form, store.batch, () => {
446
+ if (!element.isConnected) {
447
+ return;
448
+ }
449
+ const resetValue = resetValueRef.current;
450
+ const writeResetValue = () => writeValue(element, resetValue);
451
+ writeResetValue();
452
+ reset(resetValue);
453
+ setTimeout(() => {
454
+ if (element.isConnected) {
455
+ writeResetValue();
456
+ }
457
+ }, 0);
458
+ });
459
+ }, [ref, reset, store.batch, writeValue]);
460
+ const inputProps = {
461
+ defaultValue: resolveDefaultValue(props, initialStateValue, (value) => {
462
+ const formatted = formatValue(value);
463
+ return isDisplayValue(formatted) ? formatted : "";
464
+ }),
465
+ defaultChecked: resolveDefaultChecked(props, initialStateValue, toChecked),
466
+ onChange: (event) => {
467
+ const element = ref.current;
468
+ if (element) {
469
+ const controlValue = readControlValue(element, props.type, props.value);
470
+ if (controlValue !== void 0) {
471
+ commit(controlValue);
472
+ }
223
473
  }
474
+ props.onChange?.(event);
224
475
  }
225
- );
476
+ };
477
+ return { inputProps, meta };
226
478
  }
227
479
 
228
- // src/use_store_component.tsx
229
- import { jsx as jsx2 } from "react/jsx-runtime";
230
- function useStoreComponent(store) {
231
- const input = useCallback(function Component(props) {
232
- return /* @__PURE__ */ jsx2(Input, { store, ...props });
233
- }, []);
234
- const select = useCallback(function Component(props) {
235
- return /* @__PURE__ */ jsx2(Select, { store, ...props });
236
- }, []);
237
- const textarea = useCallback(function Component(props) {
238
- return /* @__PURE__ */ jsx2(Textarea, { store, ...props });
239
- }, []);
240
- const textEditor = useCallback(function Component(props) {
241
- return /* @__PURE__ */ jsx2(TextEditor, { store, ...props });
242
- }, []);
480
+ // src/html_element/use_store_input_with_name.tsx
481
+ import { useMemo } from "react";
482
+ import { ok } from "gw-result";
483
+ function formatControlValue(value, type) {
484
+ if (type === "checkbox") {
485
+ return Boolean(value);
486
+ }
487
+ if (type === "radio" || type === "file") {
488
+ return value;
489
+ }
490
+ return convertToInputValue(value, type);
491
+ }
492
+ function parseControlValue(value, type) {
493
+ if (typeof value === "string" || Array.isArray(value)) {
494
+ const stateInputValue = Array.isArray(value) ? [...value] : value;
495
+ return convertToStateValue(stateInputValue, type);
496
+ }
497
+ return value;
498
+ }
499
+ function useStoreInputWithName(ref, store, props) {
500
+ const binding = useMemo(
501
+ () => ({
502
+ lens: {
503
+ get: (state) => state[props.name],
504
+ set: (state, value) => {
505
+ state[props.name] = value;
506
+ }
507
+ },
508
+ codec: {
509
+ format: (value) => formatControlValue(value, props.type),
510
+ parse: (value) => ok(parseControlValue(value, props.type))
511
+ }
512
+ }),
513
+ [props.name, props.type]
514
+ );
515
+ const initialStateValue = binding.lens.get(store.state);
516
+ const resetValue = resolveResetStateValue(
517
+ props,
518
+ initialStateValue,
519
+ (value) => parseControlValue(value, props.type)
520
+ );
521
+ const { inputProps } = useStoreHTMLElement(ref, store, binding, {
522
+ ...props,
523
+ resetValue
524
+ });
243
525
  return {
244
- input,
245
- select,
246
- textarea,
247
- textEditor
526
+ ...inputProps,
527
+ name: String(props.name)
248
528
  };
249
529
  }
530
+
531
+ // src/input/store_input.tsx
532
+ import {
533
+ useCallback as useCallback3,
534
+ useRef as useRef3
535
+ } from "react";
536
+ import { jsx as jsx2 } from "react/jsx-runtime";
250
537
  function Input({
251
538
  store,
252
- getter,
253
- setter,
254
- toInputValue,
255
- toStateValue,
256
539
  ...props
257
540
  }) {
258
- const ref = useRef2(null);
259
- const storeProps = useStoreInputWithName(ref, store, {
260
- ...props,
261
- getter,
262
- setter,
263
- toInputValue,
264
- toStateValue
265
- });
541
+ const ref = useRef3(null);
542
+ const storeProps = useStoreInputWithName(ref, store, props);
266
543
  return /* @__PURE__ */ jsx2("input", { ref, ...props, ...storeProps });
267
544
  }
268
545
  function Select({
269
546
  store,
270
- getter,
271
- setter,
272
- toInputValue,
273
- toStateValue,
274
547
  ...props
275
548
  }) {
276
- const ref = useRef2(null);
277
- const storeProps = useStoreInputWithName(ref, store, {
278
- ...props,
279
- getter,
280
- setter,
281
- toInputValue,
282
- toStateValue
283
- });
549
+ const ref = useRef3(null);
550
+ const storeProps = useStoreInputWithName(ref, store, props);
284
551
  return /* @__PURE__ */ jsx2("select", { ref, ...props, ...storeProps });
285
552
  }
286
553
  function Textarea({
287
554
  store,
288
- getter,
289
- setter,
290
- toInputValue,
291
- toStateValue,
292
555
  ...props
293
556
  }) {
294
- const ref = useRef2(null);
295
- const storeProps = useStoreInputWithName(ref, store, {
296
- ...props,
297
- getter,
298
- setter,
299
- toInputValue,
300
- toStateValue
301
- });
557
+ const ref = useRef3(null);
558
+ const storeProps = useStoreInputWithName(ref, store, props);
302
559
  return /* @__PURE__ */ jsx2("textarea", { ref, ...props, ...storeProps });
303
560
  }
304
-
305
- // src/create_render.tsx
306
- import { useSelector } from "gw-store";
307
- import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
308
- function createRenderWithStore(store) {
309
- return function createRender(selector, compare) {
310
- function Component() {
311
- const result = useSelector(store, (state) => state, compare);
312
- const content = selector(result);
313
- return /* @__PURE__ */ jsx3(Fragment, { children: content });
314
- }
315
- return /* @__PURE__ */ jsx3(Component, {});
316
- };
317
- }
318
-
319
- // src/use_form_store.tsx
320
- import { useStore } from "gw-store";
321
- function useFormStore(initialState) {
322
- const store = useStore(initialState);
323
- const storeComponent = useStoreComponent(store);
324
- return {
325
- get state() {
326
- return store.state;
561
+ function useStoreInput(store) {
562
+ const input = useCallback3(
563
+ function Component(props) {
564
+ return /* @__PURE__ */ jsx2(Input, { store, ...props });
327
565
  },
328
- dispatch: store.dispatch,
329
- subscribe: store.subscribe,
330
- render: createRenderWithStore(store),
331
- ...storeComponent
332
- };
566
+ [store]
567
+ );
568
+ const select = useCallback3(
569
+ function Component(props) {
570
+ return /* @__PURE__ */ jsx2(Select, { store, ...props });
571
+ },
572
+ [store]
573
+ );
574
+ const textarea = useCallback3(
575
+ function Component(props) {
576
+ return /* @__PURE__ */ jsx2(Textarea, { store, ...props });
577
+ },
578
+ [store]
579
+ );
580
+ return { input, select, textarea };
333
581
  }
334
582
 
335
583
  // src/index.tsx
336
- export * from "gw-store";
584
+ import { err, ok as ok2 } from "gw-result";
337
585
  export {
338
586
  Input,
339
587
  Select,
340
- TextEditor,
341
588
  Textarea,
342
- useFormStore,
343
- useStoreComponent,
344
- useStoreController,
589
+ assertCodecLaws,
590
+ assertLensLaws,
591
+ createRender,
592
+ createRenderWithStore,
593
+ defineBinding,
594
+ defineCodec,
595
+ defineLens,
596
+ err,
597
+ ok2 as ok,
598
+ stateLens,
599
+ useStoreBinding,
600
+ useStoreHTMLElement,
345
601
  useStoreInput,
346
602
  useStoreInputWithName
347
603
  };
604
+ //# sourceMappingURL=index.mjs.map