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