react-store-input 0.2.6 → 0.5.0

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