react-store-input 0.4.0 → 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.
package/dist/index.js CHANGED
@@ -15,7 +15,6 @@ 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
@@ -34,45 +33,13 @@ __export(src_exports, {
34
33
  err: () => import_gw_result2.err,
35
34
  ok: () => import_gw_result2.ok,
36
35
  stateLens: () => stateLens,
37
- useFormStore: () => useFormStore,
38
- useStoreComponent: () => useStoreComponent,
39
- useStoreController: () => useStoreController,
36
+ useStoreBinding: () => useStoreBinding,
37
+ useStoreHTMLElement: () => useStoreHTMLElement,
40
38
  useStoreInput: () => useStoreInput,
41
39
  useStoreInputWithName: () => useStoreInputWithName
42
40
  });
43
41
  module.exports = __toCommonJS(src_exports);
44
42
 
45
- // src/store/use_store_controller.tsx
46
- var import_react = require("react");
47
- function useStoreController(store, props) {
48
- const dispatchKey = (0, import_react.useId)();
49
- const propsRef = (0, import_react.useRef)(props);
50
- const subscribedStoreRef = (0, import_react.useRef)(null);
51
- (0, import_react.useEffect)(() => {
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);
60
- }
61
- });
62
- if (isReplacement) {
63
- propsRef.current.onSubscribe(store.state);
64
- }
65
- return unsubscribe;
66
- }, [dispatchKey, store]);
67
- const dispatch = (0, import_react.useCallback)(() => {
68
- store.dispatch(
69
- (state) => propsRef.current.onDispatch(state),
70
- { key: dispatchKey }
71
- );
72
- }, [dispatchKey, store]);
73
- return { dispatch };
74
- }
75
-
76
43
  // src/store/create_render.tsx
77
44
  var import_gw_store = require("gw-store");
78
45
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -98,16 +65,10 @@ function defineLens(lens) {
98
65
  return lens;
99
66
  }
100
67
  function readPath(value, path) {
101
- return path.reduce(
102
- (current, key) => current[key],
103
- value
104
- );
68
+ return path.reduce((current, key) => current[key], value);
105
69
  }
106
70
  function writePath(state, path, value) {
107
- const parent = path.slice(0, -1).reduce(
108
- (current, key) => current[key],
109
- state
110
- );
71
+ const parent = path.slice(0, -1).reduce((current, key) => current[key], state);
111
72
  parent[path.at(-1)] = value;
112
73
  }
113
74
  function createPathLens(path) {
@@ -160,10 +121,7 @@ function assertLensLaws(lens, options) {
160
121
  const equalsState = options.equalsState ?? structuralEqual;
161
122
  const equalsValue = options.equalsValue ?? structuralEqual;
162
123
  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
- )) {
124
+ if (!equalsState(set(options.state, lens.get(options.state)), options.state)) {
167
125
  throw new Error("Lens law failed: setting the current value changed state.");
168
126
  }
169
127
  for (const value of options.values) {
@@ -174,10 +132,7 @@ function assertLensLaws(lens, options) {
174
132
  }
175
133
  for (const previous of options.values) {
176
134
  for (const next of options.values) {
177
- if (!equalsState(
178
- set(set(options.state, previous), next),
179
- set(options.state, next)
180
- )) {
135
+ if (!equalsState(set(set(options.state, previous), next), set(options.state, next))) {
181
136
  throw new Error("Lens law failed: the last set did not win.");
182
137
  }
183
138
  }
@@ -198,17 +153,109 @@ function assertCodecLaws(codec, options) {
198
153
  for (const input of options.inputs ?? []) {
199
154
  const result = codec.parse(input);
200
155
  if (result.isOk && !equalsInput(codec.format(result.value), input)) {
201
- throw new Error(
202
- "Codec law failed: format(parse(input).value) !== input."
203
- );
156
+ throw new Error("Codec law failed: format(parse(input).value) !== input.");
204
157
  }
205
158
  }
206
159
  }
207
160
 
208
- // src/input/use_store_input.tsx
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) {
192
+ return;
193
+ }
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);
200
+ }
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)) {
212
+ return;
213
+ }
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;
229
+ }
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
+ );
247
+ return {
248
+ initialValue,
249
+ meta,
250
+ commit,
251
+ reset
252
+ };
253
+ }
254
+
255
+ // src/html_element/use_store_html_element.tsx
209
256
  var import_react2 = require("react");
210
257
 
211
- // src/input/dom_value.ts
258
+ // src/html_element/dom_value.ts
212
259
  function pad(value) {
213
260
  return String(value).padStart(2, "0");
214
261
  }
@@ -242,9 +289,7 @@ function writeElementValue(element, value) {
242
289
  return;
243
290
  }
244
291
  if ("options" in element && element.multiple) {
245
- const selectedValues = new Set(
246
- (Array.isArray(value) ? value : [value]).map(String)
247
- );
292
+ const selectedValues = new Set((Array.isArray(value) ? value : [value]).map(String));
248
293
  for (const option of element.options) {
249
294
  const selected = selectedValues.has(option.value);
250
295
  if (option.selected !== selected) {
@@ -258,20 +303,8 @@ function writeElementValue(element, value) {
258
303
  element.value = nextValue;
259
304
  }
260
305
  }
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
306
 
274
- // src/input/reset_coordinator.ts
307
+ // src/html_element/reset_coordinator.ts
275
308
  var resettingForms = /* @__PURE__ */ new WeakSet();
276
309
  var resetCoordinators = /* @__PURE__ */ new WeakMap();
277
310
  function isFormResetting(form) {
@@ -283,10 +316,7 @@ function registerResetBinding(form, batch, reset) {
283
316
  const groups = /* @__PURE__ */ new Map();
284
317
  const handleReset = () => {
285
318
  resettingForms.add(form);
286
- const groupSnapshots = Array.from(groups, ([groupBatch, bindings2]) => [
287
- groupBatch,
288
- [...bindings2]
289
- ]);
319
+ const groupSnapshots = Array.from(groups, ([groupBatch, bindings2]) => [groupBatch, [...bindings2]]);
290
320
  setTimeout(() => {
291
321
  try {
292
322
  for (const [groupBatch, bindings2] of groupSnapshots) {
@@ -328,7 +358,7 @@ function registerResetBinding(form, batch, reset) {
328
358
  };
329
359
  }
330
360
 
331
- // src/input/value_conversion.ts
361
+ // src/html_element/value_conversion.ts
332
362
  function convertToInputValue(value, type) {
333
363
  if (value === void 0 || value === null) {
334
364
  return "";
@@ -383,10 +413,7 @@ function resolveResetStateValue(props, stateValue, parse) {
383
413
  if (props.value !== void 0) {
384
414
  return props.value;
385
415
  }
386
- return convertToStateValue(
387
- String(props.defaultValue ?? ""),
388
- props.type
389
- );
416
+ return convertToStateValue(String(props.defaultValue ?? ""), props.type);
390
417
  }
391
418
  if (props.type !== "file" && props.type !== "checkbox" && props.type !== "radio" && props.defaultValue !== void 0) {
392
419
  const defaultValue = Array.isArray(props.defaultValue) ? props.defaultValue.map(String) : String(props.defaultValue);
@@ -395,7 +422,7 @@ function resolveResetStateValue(props, stateValue, parse) {
395
422
  return stateValue;
396
423
  }
397
424
 
398
- // src/input/use_store_input.tsx
425
+ // src/html_element/use_store_html_element.tsx
399
426
  function isDisplayValue(value) {
400
427
  return typeof value === "string" || typeof value === "number" || Array.isArray(value) && value.every((item) => typeof item === "string");
401
428
  }
@@ -411,20 +438,10 @@ function readControlValue(element, type, radioValue) {
411
438
  }
412
439
  return readElementValue(element);
413
440
  }
414
- function useStoreInput(ref, store, binding, options = {}) {
441
+ function useStoreHTMLElement(ref, store, binding, options = {}) {
415
442
  const props = options;
416
443
  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
- );
444
+ const formatValue = (0, import_react2.useCallback)((value) => binding.codec.format(value), [binding]);
428
445
  const toChecked = (0, import_react2.useCallback)(
429
446
  (value) => props.type === "radio" ? Object.is(formatValue(value), props.value) : Boolean(formatValue(value)),
430
447
  [formatValue, props.type, props.value]
@@ -433,35 +450,6 @@ function useStoreInput(ref, store, binding, options = {}) {
433
450
  const resetValueRef = (0, import_react2.useRef)(
434
451
  Object.prototype.hasOwnProperty.call(options, "resetValue") ? options.resetValue : initialStateValue
435
452
  );
436
- const setStateValue = (0, import_react2.useCallback)(
437
- (state, value) => {
438
- const equals = binding.codec.equals ?? equalStateValue;
439
- if (equals(getStateValue(store.state), value)) {
440
- return;
441
- }
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;
457
- }
458
- const validMeta = { valid: true };
459
- metaRef.current = validMeta;
460
- setMeta((current) => current.valid ? current : validMeta);
461
- setStateValue(state, result.value);
462
- },
463
- [parseValue, setStateValue]
464
- );
465
453
  const writeValue = (0, import_react2.useCallback)(
466
454
  (element, value) => {
467
455
  if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
@@ -478,26 +466,16 @@ function useStoreInput(ref, store, binding, options = {}) {
478
466
  },
479
467
  [formatValue, props.type, toChecked]
480
468
  );
481
- const { dispatch } = useStoreController(store, {
482
- onSubscribe: (state) => {
469
+ const { commit, meta, reset } = useStoreBinding(store, binding, {
470
+ onStoreChange: (_input, value) => {
483
471
  const element = ref.current;
484
- if (!element || !metaRef.current.valid || element.form && isFormResetting(element.form) || props.type !== "radio" && props.value !== void 0) {
472
+ if (!element || element.form && isFormResetting(element.form) || props.type !== "radio" && props.value !== void 0) {
485
473
  return;
486
474
  }
487
475
  if ("checked" in element && (props.type === "checkbox" || props.type === "radio") && props.checked !== void 0) {
488
476
  return;
489
477
  }
490
- writeValue(element, getStateValue(state));
491
- },
492
- onDispatch: (state) => {
493
- const element = ref.current;
494
- if (!element) {
495
- return;
496
- }
497
- const controlValue = readControlValue(element, props.type, props.value);
498
- if (controlValue !== void 0) {
499
- commitValue(state, controlValue);
500
- }
478
+ writeValue(element, value);
501
479
  }
502
480
  });
503
481
  (0, import_react2.useEffect)(() => {
@@ -513,36 +491,35 @@ function useStoreInput(ref, store, binding, options = {}) {
513
491
  const resetValue = resetValueRef.current;
514
492
  const writeResetValue = () => writeValue(element, resetValue);
515
493
  writeResetValue();
516
- const validMeta = { valid: true };
517
- metaRef.current = validMeta;
518
- setMeta((current) => current.valid ? current : validMeta);
519
- store.dispatch((state) => setStateValue(state, resetValue));
494
+ reset(resetValue);
520
495
  setTimeout(() => {
521
496
  if (element.isConnected) {
522
497
  writeResetValue();
523
498
  }
524
499
  }, 0);
525
500
  });
526
- }, [ref, setStateValue, store, store.batch, writeValue]);
501
+ }, [ref, reset, store.batch, writeValue]);
527
502
  const inputProps = {
528
503
  defaultValue: resolveDefaultValue(props, initialStateValue, (value) => {
529
504
  const formatted = formatValue(value);
530
505
  return isDisplayValue(formatted) ? formatted : "";
531
506
  }),
532
- defaultChecked: resolveDefaultChecked(
533
- props,
534
- initialStateValue,
535
- toChecked
536
- ),
507
+ defaultChecked: resolveDefaultChecked(props, initialStateValue, toChecked),
537
508
  onChange: (event) => {
538
- dispatch();
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
+ }
515
+ }
539
516
  props.onChange?.(event);
540
517
  }
541
518
  };
542
519
  return { inputProps, meta };
543
520
  }
544
521
 
545
- // src/input/use_store_input_with_name.tsx
522
+ // src/html_element/use_store_input_with_name.tsx
546
523
  var import_react3 = require("react");
547
524
  var import_gw_result = require("gw-result");
548
525
  function formatControlValue(value, type) {
@@ -583,7 +560,7 @@ function useStoreInputWithName(ref, store, props) {
583
560
  initialStateValue,
584
561
  (value) => parseControlValue(value, props.type)
585
562
  );
586
- const { inputProps } = useStoreInput(ref, store, binding, {
563
+ const { inputProps } = useStoreHTMLElement(ref, store, binding, {
587
564
  ...props,
588
565
  resetValue
589
566
  });
@@ -593,7 +570,7 @@ function useStoreInputWithName(ref, store, props) {
593
570
  };
594
571
  }
595
572
 
596
- // src/form/store_components.tsx
573
+ // src/input/store_input.tsx
597
574
  var import_react4 = require("react");
598
575
  var import_jsx_runtime2 = require("react/jsx-runtime");
599
576
  function Input({
@@ -620,7 +597,7 @@ function Textarea({
620
597
  const storeProps = useStoreInputWithName(ref, store, props);
621
598
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("textarea", { ref, ...props, ...storeProps });
622
599
  }
623
- function useStoreComponent(store) {
600
+ function useStoreInput(store) {
624
601
  const input = (0, import_react4.useCallback)(
625
602
  function Component(props) {
626
603
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Input, { store, ...props });
@@ -642,26 +619,8 @@ function useStoreComponent(store) {
642
619
  return { input, select, textarea };
643
620
  }
644
621
 
645
- // src/form/use_form_store.tsx
646
- var import_gw_store2 = require("gw-store");
647
- function useFormStore(initialState) {
648
- const store = (0, import_gw_store2.useStore)(initialState);
649
- const storeComponent = useStoreComponent(store);
650
- return {
651
- get state() {
652
- return store.state;
653
- },
654
- dispatch: store.dispatch,
655
- batch: store.batch,
656
- subscribe: store.subscribe,
657
- render: createRenderWithStore(store),
658
- ...storeComponent
659
- };
660
- }
661
-
662
622
  // src/index.tsx
663
623
  var import_gw_result2 = require("gw-result");
664
- __reExport(src_exports, require("gw-store"), module.exports);
665
624
  // Annotate the CommonJS export names for ESM import in node:
666
625
  0 && (module.exports = {
667
626
  Input,
@@ -677,11 +636,9 @@ __reExport(src_exports, require("gw-store"), module.exports);
677
636
  err,
678
637
  ok,
679
638
  stateLens,
680
- useFormStore,
681
- useStoreComponent,
682
- useStoreController,
639
+ useStoreBinding,
640
+ useStoreHTMLElement,
683
641
  useStoreInput,
684
- useStoreInputWithName,
685
- ...require("gw-store")
642
+ useStoreInputWithName
686
643
  });
687
644
  //# sourceMappingURL=index.js.map