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.js CHANGED
@@ -15,340 +15,634 @@ var __copyProps = (to, from, except, desc) => {
15
15
  }
16
16
  return to;
17
17
  };
18
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
18
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
20
 
20
21
  // src/index.tsx
21
- var index_exports = {};
22
- __export(index_exports, {
22
+ var src_exports = {};
23
+ __export(src_exports, {
23
24
  Input: () => Input,
24
25
  Select: () => Select,
25
- TextEditor: () => TextEditor,
26
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,
27
37
  useFormStore: () => useFormStore,
28
38
  useStoreComponent: () => useStoreComponent,
29
39
  useStoreController: () => useStoreController,
30
40
  useStoreInput: () => useStoreInput,
31
41
  useStoreInputWithName: () => useStoreInputWithName
32
42
  });
33
- module.exports = __toCommonJS(index_exports);
43
+ module.exports = __toCommonJS(src_exports);
34
44
 
35
- // src/use_store_controller.tsx
45
+ // src/store/use_store_controller.tsx
36
46
  var import_react = require("react");
37
47
  function useStoreController(store, props) {
38
48
  const dispatchKey = (0, import_react.useId)();
49
+ const propsRef = (0, import_react.useRef)(props);
50
+ const subscribedStoreRef = (0, import_react.useRef)(null);
39
51
  (0, import_react.useEffect)(() => {
40
- const unsub = store.subscribe((state, key) => {
41
- if (key === dispatchKey) {
42
- 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);
43
60
  }
44
- props.onSubscribe(state);
45
61
  });
46
- return () => {
47
- unsub();
48
- };
49
- }, []);
50
- 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)(() => {
51
68
  store.dispatch(
52
- (state) => {
53
- props.onDispatch(state);
54
- },
55
- {
56
- key: dispatchKey
57
- }
69
+ (state) => propsRef.current.onDispatch(state),
70
+ { key: dispatchKey }
58
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, {});
59
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() {
60
121
  return {
61
- dispatch
122
+ prop: (key) => createPathLens([key])
62
123
  };
63
124
  }
64
125
 
65
- // src/use_store_input.tsx
66
- var import_date_fns = require("date-fns");
67
- function useStoreInput(ref, store, props) {
68
- const toInputValue = props.toInputValue || ((value) => {
69
- if (value === void 0 || value === null) {
70
- 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.");
71
173
  }
72
- if (props.type === "datetime-local") {
73
- const date = new Date(value);
74
- if (!isNaN(date.getTime())) {
75
- 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.");
76
182
  }
77
183
  }
78
- return String(value);
79
- });
80
- const getDefaultValue = () => {
81
- if (props.defaultValue !== void 0) {
82
- 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.");
83
193
  }
84
- if (props.type === "checkbox" || props.type === "radio") {
85
- return void 0;
194
+ if (!equals(result.value, value)) {
195
+ throw new Error("Codec law failed: parse(format(value)) !== value.");
86
196
  }
87
- return toInputValue(props.getter(store.state));
88
- };
89
- const toInputChecked = (value) => {
90
- if (props.type === "radio") {
91
- 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
+ );
92
204
  }
93
- return Boolean(value);
94
- };
95
- const getDefaultChecked = () => {
96
- if (props.defaultChecked) {
97
- 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
+ }
98
253
  }
99
- if (props.type !== "checkbox" && props.type !== "radio") {
100
- 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;
101
318
  }
102
- return toInputChecked(props.getter(store.state));
103
- };
104
- const toStateValue = props.toStateValue || ((value) => {
105
- if (props.type === "number" || props.type === "range") {
106
- return Number(value);
319
+ const currentBindings = currentCoordinator.groups.get(batch);
320
+ currentBindings?.delete(reset);
321
+ if (currentBindings?.size === 0) {
322
+ currentCoordinator.groups.delete(batch);
107
323
  }
108
- if (props.type === "datetime-local") {
109
- return new Date(value);
324
+ if (currentCoordinator.groups.size === 0) {
325
+ form.removeEventListener("reset", currentCoordinator.handleReset);
326
+ resetCoordinators.delete(form);
110
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)) {
111
343
  return value;
112
- });
113
- const inputProps = useStoreController(store, {
114
- onSubscribe: (state) => {
115
- const element = ref.current;
116
- 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)) {
117
440
  return;
118
441
  }
119
- if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
120
- const checked = toInputChecked(props.getter(state));
121
- if (element.checked === checked) {
122
- return;
123
- }
124
- element.checked = checked;
125
- } else {
126
- const value = toInputValue(props.getter(state));
127
- if (element.value === value) {
128
- return;
129
- }
130
- 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;
131
457
  }
132
- const event = new Event("input", { bubbles: true });
133
- element.dispatchEvent(event);
458
+ const validMeta = { valid: true };
459
+ metaRef.current = validMeta;
460
+ setMeta((current) => current.valid ? current : validMeta);
461
+ setStateValue(state, result.value);
134
462
  },
135
- onDispatch: (state) => {
136
- const element = ref.current;
137
- 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);
138
469
  return;
139
470
  }
140
- if ("checked" in element && props.type === "checkbox") {
141
- props.setter(state, element.checked);
142
- } else {
143
- props.setter(state, toStateValue(element.value));
471
+ if ("files" in element && props.type === "file") {
472
+ return;
144
473
  }
145
- }
146
- });
147
- return {
148
- defaultValue: getDefaultValue(),
149
- defaultChecked: getDefaultChecked(),
150
- onChange: (event) => {
151
- inputProps.dispatch();
152
- props.onChange?.(event);
153
- }
154
- };
155
- }
156
-
157
- // src/use_store_input_with_name.tsx
158
- function useStoreInputWithName(ref, store, props) {
159
- const inputProps = useStoreInput(ref, store, {
160
- ...props,
161
- getter: (state) => {
162
- if (props.getter) {
163
- return props.getter(state);
474
+ const formatted = formatValue(value);
475
+ if (isDisplayValue(formatted)) {
476
+ writeElementValue(element, formatted);
164
477
  }
165
- return state[props.name];
166
478
  },
167
- setter: (state, value) => {
168
- if (props.setter) {
169
- props.setter(state, value);
170
- return;
171
- }
172
- state[props.name] = value;
173
- }
174
- });
175
- return {
176
- ...inputProps,
177
- name: "name" in props ? String(props.name) : void 0
178
- };
179
- }
180
-
181
- // src/use_store_component.tsx
182
- var import_react3 = require("react");
183
-
184
- // src/text_editor.tsx
185
- var import_gw_react_text_editor = require("gw-react-text-editor");
186
- var import_react2 = require("react");
187
- var import_jsx_runtime = require("react/jsx-runtime");
188
- function TextEditor({
189
- store,
190
- name,
191
- getter,
192
- setter,
193
- defaultValue,
194
- ref,
195
- ...props
196
- }) {
197
- const controllerRef = (0, import_react2.useRef)(null);
198
- (0, import_react2.useImperativeHandle)(
199
- ref,
200
- () => controllerRef.current,
201
- []
479
+ [formatValue, props.type, toChecked]
202
480
  );
203
481
  const { dispatch } = useStoreController(store, {
204
482
  onSubscribe: (state) => {
205
- const controller = controllerRef.current;
206
- 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) {
207
485
  return;
208
486
  }
209
- const getResult = () => {
210
- if (getter) {
211
- return getter(state) || "";
212
- }
213
- if (name) {
214
- return state[name] || "";
215
- }
216
- return "";
217
- };
218
- const result = getResult();
219
- if (controller.value !== result) {
220
- controller.value = result;
487
+ if ("checked" in element && (props.type === "checkbox" || props.type === "radio") && props.checked !== void 0) {
488
+ return;
221
489
  }
490
+ writeValue(element, getStateValue(state));
222
491
  },
223
492
  onDispatch: (state) => {
224
- const controller = controllerRef.current;
225
- if (!controller) {
226
- return;
227
- }
228
- if (setter) {
229
- setter(state, controller.value);
493
+ const element = ref.current;
494
+ if (!element) {
230
495
  return;
231
496
  }
232
- if (name) {
233
- state[name] = controller.value;
497
+ const controlValue = readControlValue(element, props.type, props.value);
498
+ if (controlValue !== void 0) {
499
+ commitValue(state, controlValue);
234
500
  }
235
501
  }
236
502
  });
237
- const getDefaultValue = () => {
238
- if (getter) {
239
- return getter(store.state);
240
- }
241
- if (name) {
242
- return store.state[name];
503
+ (0, import_react2.useEffect)(() => {
504
+ const element = ref.current;
505
+ const form = element?.form;
506
+ if (!element || !form) {
507
+ return;
243
508
  }
244
- return void 0;
245
- };
246
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
247
- import_gw_react_text_editor.TextEditor,
248
- {
249
- ...props,
250
- ref: controllerRef,
251
- defaultValue: defaultValue ?? getDefaultValue(),
252
- onChange: (e) => {
253
- dispatch();
254
- props.onChange?.(e);
509
+ return registerResetBinding(form, store.batch, () => {
510
+ if (!element.isConnected) {
511
+ return;
255
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);
256
540
  }
257
- );
541
+ };
542
+ return { inputProps, meta };
258
543
  }
259
544
 
260
- // src/use_store_component.tsx
261
- var import_jsx_runtime2 = require("react/jsx-runtime");
262
- function useStoreComponent(store) {
263
- const input = (0, import_react3.useCallback)(function Component(props) {
264
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Input, { store, ...props });
265
- }, []);
266
- const select = (0, import_react3.useCallback)(function Component(props) {
267
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Select, { store, ...props });
268
- }, []);
269
- const textarea = (0, import_react3.useCallback)(function Component(props) {
270
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Textarea, { store, ...props });
271
- }, []);
272
- const textEditor = (0, import_react3.useCallback)(function Component(props) {
273
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TextEditor, { store, ...props });
274
- }, []);
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
+ });
275
590
  return {
276
- input,
277
- select,
278
- textarea,
279
- textEditor
591
+ ...inputProps,
592
+ name: String(props.name)
280
593
  };
281
594
  }
595
+
596
+ // src/form/store_components.tsx
597
+ var import_react4 = require("react");
598
+ var import_jsx_runtime2 = require("react/jsx-runtime");
282
599
  function Input({
283
600
  store,
284
- getter,
285
- setter,
286
- toInputValue,
287
- toStateValue,
288
601
  ...props
289
602
  }) {
290
- const ref = (0, import_react3.useRef)(null);
291
- const storeProps = useStoreInputWithName(ref, store, {
292
- ...props,
293
- getter,
294
- setter,
295
- toInputValue,
296
- toStateValue
297
- });
603
+ const ref = (0, import_react4.useRef)(null);
604
+ const storeProps = useStoreInputWithName(ref, store, props);
298
605
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { ref, ...props, ...storeProps });
299
606
  }
300
607
  function Select({
301
608
  store,
302
- getter,
303
- setter,
304
- toInputValue,
305
- toStateValue,
306
609
  ...props
307
610
  }) {
308
- const ref = (0, import_react3.useRef)(null);
309
- const storeProps = useStoreInputWithName(ref, store, {
310
- ...props,
311
- getter,
312
- setter,
313
- toInputValue,
314
- toStateValue
315
- });
611
+ const ref = (0, import_react4.useRef)(null);
612
+ const storeProps = useStoreInputWithName(ref, store, props);
316
613
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("select", { ref, ...props, ...storeProps });
317
614
  }
318
615
  function Textarea({
319
616
  store,
320
- getter,
321
- setter,
322
- toInputValue,
323
- toStateValue,
324
617
  ...props
325
618
  }) {
326
- const ref = (0, import_react3.useRef)(null);
327
- const storeProps = useStoreInputWithName(ref, store, {
328
- ...props,
329
- getter,
330
- setter,
331
- toInputValue,
332
- toStateValue
333
- });
619
+ const ref = (0, import_react4.useRef)(null);
620
+ const storeProps = useStoreInputWithName(ref, store, props);
334
621
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("textarea", { ref, ...props, ...storeProps });
335
622
  }
336
-
337
- // src/create_render.tsx
338
- var import_gw_store = require("gw-store");
339
- var import_jsx_runtime3 = require("react/jsx-runtime");
340
- function createRenderWithStore(store) {
341
- return function createRender(selector, compare) {
342
- function Component() {
343
- const result = (0, import_gw_store.useSelector)(store, (state) => state, compare);
344
- const content = selector(result);
345
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: content });
346
- }
347
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, {});
348
- };
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 };
349
643
  }
350
644
 
351
- // src/use_form_store.tsx
645
+ // src/form/use_form_store.tsx
352
646
  var import_gw_store2 = require("gw-store");
353
647
  function useFormStore(initialState) {
354
648
  const store = (0, import_gw_store2.useStore)(initialState);
@@ -358,20 +652,36 @@ function useFormStore(initialState) {
358
652
  return store.state;
359
653
  },
360
654
  dispatch: store.dispatch,
655
+ batch: store.batch,
361
656
  subscribe: store.subscribe,
362
657
  render: createRenderWithStore(store),
363
658
  ...storeComponent
364
659
  };
365
660
  }
661
+
662
+ // src/index.tsx
663
+ var import_gw_result2 = require("gw-result");
664
+ __reExport(src_exports, require("gw-store"), module.exports);
366
665
  // Annotate the CommonJS export names for ESM import in node:
367
666
  0 && (module.exports = {
368
667
  Input,
369
668
  Select,
370
- TextEditor,
371
669
  Textarea,
670
+ assertCodecLaws,
671
+ assertLensLaws,
672
+ createRender,
673
+ createRenderWithStore,
674
+ defineBinding,
675
+ defineCodec,
676
+ defineLens,
677
+ err,
678
+ ok,
679
+ stateLens,
372
680
  useFormStore,
373
681
  useStoreComponent,
374
682
  useStoreController,
375
683
  useStoreInput,
376
- useStoreInputWithName
684
+ useStoreInputWithName,
685
+ ...require("gw-store")
377
686
  });
687
+ //# sourceMappingURL=index.js.map