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