react-store-input 0.4.0 → 0.6.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.mjs CHANGED
@@ -1,34 +1,3 @@
1
- // src/store/use_store_controller.tsx
2
- import { useCallback, useEffect, useId, useRef } from "react";
3
- function useStoreController(store, props) {
4
- const dispatchKey = useId();
5
- const propsRef = useRef(props);
6
- const subscribedStoreRef = useRef(null);
7
- useEffect(() => {
8
- propsRef.current = props;
9
- }, [props]);
10
- useEffect(() => {
11
- const isReplacement = subscribedStoreRef.current !== null && subscribedStoreRef.current !== store;
12
- subscribedStoreRef.current = store;
13
- const unsubscribe = store.subscribe((state, key) => {
14
- if (key !== dispatchKey) {
15
- propsRef.current.onSubscribe(state);
16
- }
17
- });
18
- if (isReplacement) {
19
- propsRef.current.onSubscribe(store.state);
20
- }
21
- return unsubscribe;
22
- }, [dispatchKey, store]);
23
- const dispatch = useCallback(() => {
24
- store.dispatch(
25
- (state) => propsRef.current.onDispatch(state),
26
- { key: dispatchKey }
27
- );
28
- }, [dispatchKey, store]);
29
- return { dispatch };
30
- }
31
-
32
1
  // src/store/create_render.tsx
33
2
  import { useSelector } from "gw-store";
34
3
  import { Fragment, jsx } from "react/jsx-runtime";
@@ -54,16 +23,10 @@ function defineLens(lens) {
54
23
  return lens;
55
24
  }
56
25
  function readPath(value, path) {
57
- return path.reduce(
58
- (current, key) => current[key],
59
- value
60
- );
26
+ return path.reduce((current, key) => current[key], value);
61
27
  }
62
28
  function writePath(state, path, value) {
63
- const parent = path.slice(0, -1).reduce(
64
- (current, key) => current[key],
65
- state
66
- );
29
+ const parent = path.slice(0, -1).reduce((current, key) => current[key], state);
67
30
  parent[path.at(-1)] = value;
68
31
  }
69
32
  function createPathLens(path) {
@@ -116,10 +79,7 @@ function assertLensLaws(lens, options) {
116
79
  const equalsState = options.equalsState ?? structuralEqual;
117
80
  const equalsValue = options.equalsValue ?? structuralEqual;
118
81
  const set = (state, value) => produce(state, (draft) => lens.set(draft, value));
119
- if (!equalsState(
120
- set(options.state, lens.get(options.state)),
121
- options.state
122
- )) {
82
+ if (!equalsState(set(options.state, lens.get(options.state)), options.state)) {
123
83
  throw new Error("Lens law failed: setting the current value changed state.");
124
84
  }
125
85
  for (const value of options.values) {
@@ -130,10 +90,7 @@ function assertLensLaws(lens, options) {
130
90
  }
131
91
  for (const previous of options.values) {
132
92
  for (const next of options.values) {
133
- if (!equalsState(
134
- set(set(options.state, previous), next),
135
- set(options.state, next)
136
- )) {
93
+ if (!equalsState(set(set(options.state, previous), next), set(options.state, next))) {
137
94
  throw new Error("Lens law failed: the last set did not win.");
138
95
  }
139
96
  }
@@ -154,22 +111,109 @@ function assertCodecLaws(codec, options) {
154
111
  for (const input of options.inputs ?? []) {
155
112
  const result = codec.parse(input);
156
113
  if (result.isOk && !equalsInput(codec.format(result.value), input)) {
157
- throw new Error(
158
- "Codec law failed: format(parse(input).value) !== input."
159
- );
114
+ throw new Error("Codec law failed: format(parse(input).value) !== input.");
160
115
  }
161
116
  }
162
117
  }
163
118
 
164
- // src/input/use_store_input.tsx
165
- import {
166
- useCallback as useCallback2,
167
- useEffect as useEffect2,
168
- useRef as useRef2,
169
- useState
170
- } from "react";
119
+ // src/binding/use_store_binding.tsx
120
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
121
+ function equalStoreValue(previous, next) {
122
+ if (Object.is(previous, next)) {
123
+ return true;
124
+ }
125
+ if (previous instanceof Date && next instanceof Date) {
126
+ return Object.is(previous.getTime(), next.getTime());
127
+ }
128
+ if (Array.isArray(previous) && Array.isArray(next)) {
129
+ return previous.length === next.length && previous.every((value, index) => Object.is(value, next[index]));
130
+ }
131
+ return false;
132
+ }
133
+ function useStoreBinding(store, binding, options = {}) {
134
+ const dispatchKey = useId();
135
+ const [initialValue] = useState(() => binding.codec.format(binding.lens.get(store.state)));
136
+ const [meta, setMeta] = useState({ valid: true });
137
+ const metaRef = useRef(meta);
138
+ const optionsRef = useRef(options);
139
+ const subscribedStoreRef = useRef(null);
140
+ const subscribedBindingRef = useRef(binding);
141
+ useEffect(() => {
142
+ optionsRef.current = options;
143
+ }, [options]);
144
+ useEffect(() => {
145
+ const isReplacement = subscribedStoreRef.current !== null && (subscribedStoreRef.current !== store || subscribedBindingRef.current !== binding);
146
+ subscribedStoreRef.current = store;
147
+ subscribedBindingRef.current = binding;
148
+ const sync = (state) => {
149
+ if (!metaRef.current.valid) {
150
+ return;
151
+ }
152
+ const value = binding.lens.get(state);
153
+ optionsRef.current.onStoreChange?.(binding.codec.format(value), value);
154
+ };
155
+ const unsubscribe = store.subscribe((state, key) => {
156
+ if (key !== dispatchKey) {
157
+ sync(state);
158
+ }
159
+ });
160
+ if (isReplacement) {
161
+ sync(store.state);
162
+ }
163
+ return unsubscribe;
164
+ }, [binding, dispatchKey, store]);
165
+ const setValue = useCallback(
166
+ (value) => {
167
+ const currentValue = binding.lens.get(store.state);
168
+ const equals = binding.codec.equals ?? equalStoreValue;
169
+ if (equals(currentValue, value)) {
170
+ return;
171
+ }
172
+ store.dispatch((state) => binding.lens.set(state, value), { key: dispatchKey });
173
+ },
174
+ [binding, dispatchKey, store]
175
+ );
176
+ const commit = useCallback(
177
+ (input) => {
178
+ const result = binding.codec.parse(input);
179
+ if (result.isErr) {
180
+ const invalidMeta = {
181
+ valid: false,
182
+ error: result.error
183
+ };
184
+ metaRef.current = invalidMeta;
185
+ setMeta(invalidMeta);
186
+ return result;
187
+ }
188
+ const validMeta = { valid: true };
189
+ metaRef.current = validMeta;
190
+ setMeta((current) => current.valid ? current : validMeta);
191
+ setValue(result.value);
192
+ return result;
193
+ },
194
+ [binding, setValue]
195
+ );
196
+ const reset = useCallback(
197
+ (value) => {
198
+ const validMeta = { valid: true };
199
+ metaRef.current = validMeta;
200
+ setMeta((current) => current.valid ? current : validMeta);
201
+ setValue(value);
202
+ },
203
+ [setValue]
204
+ );
205
+ return {
206
+ initialValue,
207
+ meta,
208
+ commit,
209
+ reset
210
+ };
211
+ }
171
212
 
172
- // src/input/dom_value.ts
213
+ // src/html_element/use_store_html_element.tsx
214
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
215
+
216
+ // src/html_element/dom_value.ts
173
217
  function pad(value) {
174
218
  return String(value).padStart(2, "0");
175
219
  }
@@ -203,9 +247,7 @@ function writeElementValue(element, value) {
203
247
  return;
204
248
  }
205
249
  if ("options" in element && element.multiple) {
206
- const selectedValues = new Set(
207
- (Array.isArray(value) ? value : [value]).map(String)
208
- );
250
+ const selectedValues = new Set((Array.isArray(value) ? value : [value]).map(String));
209
251
  for (const option of element.options) {
210
252
  const selected = selectedValues.has(option.value);
211
253
  if (option.selected !== selected) {
@@ -219,20 +261,8 @@ function writeElementValue(element, value) {
219
261
  element.value = nextValue;
220
262
  }
221
263
  }
222
- function equalStateValue(previous, next) {
223
- if (Object.is(previous, next)) {
224
- return true;
225
- }
226
- if (previous instanceof Date && next instanceof Date) {
227
- return Object.is(previous.getTime(), next.getTime());
228
- }
229
- if (Array.isArray(previous) && Array.isArray(next)) {
230
- return previous.length === next.length && previous.every((value, index) => Object.is(value, next[index]));
231
- }
232
- return false;
233
- }
234
264
 
235
- // src/input/reset_coordinator.ts
265
+ // src/html_element/reset_coordinator.ts
236
266
  var resettingForms = /* @__PURE__ */ new WeakSet();
237
267
  var resetCoordinators = /* @__PURE__ */ new WeakMap();
238
268
  function isFormResetting(form) {
@@ -244,10 +274,7 @@ function registerResetBinding(form, batch, reset) {
244
274
  const groups = /* @__PURE__ */ new Map();
245
275
  const handleReset = () => {
246
276
  resettingForms.add(form);
247
- const groupSnapshots = Array.from(groups, ([groupBatch, bindings2]) => [
248
- groupBatch,
249
- [...bindings2]
250
- ]);
277
+ const groupSnapshots = Array.from(groups, ([groupBatch, bindings2]) => [groupBatch, [...bindings2]]);
251
278
  setTimeout(() => {
252
279
  try {
253
280
  for (const [groupBatch, bindings2] of groupSnapshots) {
@@ -289,7 +316,7 @@ function registerResetBinding(form, batch, reset) {
289
316
  };
290
317
  }
291
318
 
292
- // src/input/value_conversion.ts
319
+ // src/html_element/value_conversion.ts
293
320
  function convertToInputValue(value, type) {
294
321
  if (value === void 0 || value === null) {
295
322
  return "";
@@ -344,10 +371,7 @@ function resolveResetStateValue(props, stateValue, parse) {
344
371
  if (props.value !== void 0) {
345
372
  return props.value;
346
373
  }
347
- return convertToStateValue(
348
- String(props.defaultValue ?? ""),
349
- props.type
350
- );
374
+ return convertToStateValue(String(props.defaultValue ?? ""), props.type);
351
375
  }
352
376
  if (props.type !== "file" && props.type !== "checkbox" && props.type !== "radio" && props.defaultValue !== void 0) {
353
377
  const defaultValue = Array.isArray(props.defaultValue) ? props.defaultValue.map(String) : String(props.defaultValue);
@@ -356,7 +380,7 @@ function resolveResetStateValue(props, stateValue, parse) {
356
380
  return stateValue;
357
381
  }
358
382
 
359
- // src/input/use_store_input.tsx
383
+ // src/html_element/use_store_html_element.tsx
360
384
  function isDisplayValue(value) {
361
385
  return typeof value === "string" || typeof value === "number" || Array.isArray(value) && value.every((item) => typeof item === "string");
362
386
  }
@@ -372,20 +396,10 @@ function readControlValue(element, type, radioValue) {
372
396
  }
373
397
  return readElementValue(element);
374
398
  }
375
- function useStoreInput(ref, store, binding, options = {}) {
399
+ function useStoreHTMLElement(ref, store, binding, options = {}) {
376
400
  const props = options;
377
401
  const getStateValue = binding.lens.get;
378
- const setStoreValue = binding.lens.set;
379
- const [meta, setMeta] = useState({ valid: true });
380
- const metaRef = useRef2(meta);
381
- const formatValue = useCallback2(
382
- (value) => binding.codec.format(value),
383
- [binding]
384
- );
385
- const parseValue = useCallback2(
386
- (value) => binding.codec.parse(value),
387
- [binding]
388
- );
402
+ const formatValue = useCallback2((value) => binding.codec.format(value), [binding]);
389
403
  const toChecked = useCallback2(
390
404
  (value) => props.type === "radio" ? Object.is(formatValue(value), props.value) : Boolean(formatValue(value)),
391
405
  [formatValue, props.type, props.value]
@@ -394,35 +408,6 @@ function useStoreInput(ref, store, binding, options = {}) {
394
408
  const resetValueRef = useRef2(
395
409
  Object.prototype.hasOwnProperty.call(options, "resetValue") ? options.resetValue : initialStateValue
396
410
  );
397
- const setStateValue = useCallback2(
398
- (state, value) => {
399
- const equals = binding.codec.equals ?? equalStateValue;
400
- if (equals(getStateValue(store.state), value)) {
401
- return;
402
- }
403
- setStoreValue(state, value);
404
- },
405
- [binding, getStateValue, setStoreValue, store]
406
- );
407
- const commitValue = useCallback2(
408
- (state, controlValue) => {
409
- const result = parseValue(controlValue);
410
- if (result.isErr) {
411
- const invalidMeta = {
412
- valid: false,
413
- error: result.error
414
- };
415
- metaRef.current = invalidMeta;
416
- setMeta(invalidMeta);
417
- return;
418
- }
419
- const validMeta = { valid: true };
420
- metaRef.current = validMeta;
421
- setMeta((current) => current.valid ? current : validMeta);
422
- setStateValue(state, result.value);
423
- },
424
- [parseValue, setStateValue]
425
- );
426
411
  const writeValue = useCallback2(
427
412
  (element, value) => {
428
413
  if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
@@ -439,26 +424,16 @@ function useStoreInput(ref, store, binding, options = {}) {
439
424
  },
440
425
  [formatValue, props.type, toChecked]
441
426
  );
442
- const { dispatch } = useStoreController(store, {
443
- onSubscribe: (state) => {
427
+ const { commit, meta, reset } = useStoreBinding(store, binding, {
428
+ onStoreChange: (_input, value) => {
444
429
  const element = ref.current;
445
- if (!element || !metaRef.current.valid || element.form && isFormResetting(element.form) || props.type !== "radio" && props.value !== void 0) {
430
+ if (!element || element.form && isFormResetting(element.form) || props.type !== "radio" && props.value !== void 0) {
446
431
  return;
447
432
  }
448
433
  if ("checked" in element && (props.type === "checkbox" || props.type === "radio") && props.checked !== void 0) {
449
434
  return;
450
435
  }
451
- writeValue(element, getStateValue(state));
452
- },
453
- onDispatch: (state) => {
454
- const element = ref.current;
455
- if (!element) {
456
- return;
457
- }
458
- const controlValue = readControlValue(element, props.type, props.value);
459
- if (controlValue !== void 0) {
460
- commitValue(state, controlValue);
461
- }
436
+ writeValue(element, value);
462
437
  }
463
438
  });
464
439
  useEffect2(() => {
@@ -474,36 +449,35 @@ function useStoreInput(ref, store, binding, options = {}) {
474
449
  const resetValue = resetValueRef.current;
475
450
  const writeResetValue = () => writeValue(element, resetValue);
476
451
  writeResetValue();
477
- const validMeta = { valid: true };
478
- metaRef.current = validMeta;
479
- setMeta((current) => current.valid ? current : validMeta);
480
- store.dispatch((state) => setStateValue(state, resetValue));
452
+ reset(resetValue);
481
453
  setTimeout(() => {
482
454
  if (element.isConnected) {
483
455
  writeResetValue();
484
456
  }
485
457
  }, 0);
486
458
  });
487
- }, [ref, setStateValue, store, store.batch, writeValue]);
459
+ }, [ref, reset, store.batch, writeValue]);
488
460
  const inputProps = {
489
461
  defaultValue: resolveDefaultValue(props, initialStateValue, (value) => {
490
462
  const formatted = formatValue(value);
491
463
  return isDisplayValue(formatted) ? formatted : "";
492
464
  }),
493
- defaultChecked: resolveDefaultChecked(
494
- props,
495
- initialStateValue,
496
- toChecked
497
- ),
465
+ defaultChecked: resolveDefaultChecked(props, initialStateValue, toChecked),
498
466
  onChange: (event) => {
499
- dispatch();
467
+ const element = ref.current;
468
+ if (element) {
469
+ const controlValue = readControlValue(element, props.type, props.value);
470
+ if (controlValue !== void 0) {
471
+ commit(controlValue);
472
+ }
473
+ }
500
474
  props.onChange?.(event);
501
475
  }
502
476
  };
503
477
  return { inputProps, meta };
504
478
  }
505
479
 
506
- // src/input/use_store_input_with_name.tsx
480
+ // src/html_element/use_store_input_with_name.tsx
507
481
  import { useMemo } from "react";
508
482
  import { ok } from "gw-result";
509
483
  function formatControlValue(value, type) {
@@ -544,7 +518,7 @@ function useStoreInputWithName(ref, store, props) {
544
518
  initialStateValue,
545
519
  (value) => parseControlValue(value, props.type)
546
520
  );
547
- const { inputProps } = useStoreInput(ref, store, binding, {
521
+ const { inputProps } = useStoreHTMLElement(ref, store, binding, {
548
522
  ...props,
549
523
  resetValue
550
524
  });
@@ -554,7 +528,7 @@ function useStoreInputWithName(ref, store, props) {
554
528
  };
555
529
  }
556
530
 
557
- // src/form/store_components.tsx
531
+ // src/input/store_input.tsx
558
532
  import {
559
533
  useCallback as useCallback3,
560
534
  useRef as useRef3
@@ -584,7 +558,7 @@ function Textarea({
584
558
  const storeProps = useStoreInputWithName(ref, store, props);
585
559
  return /* @__PURE__ */ jsx2("textarea", { ref, ...props, ...storeProps });
586
560
  }
587
- function useStoreComponent(store) {
561
+ function useStoreInput(store) {
588
562
  const input = useCallback3(
589
563
  function Component(props) {
590
564
  return /* @__PURE__ */ jsx2(Input, { store, ...props });
@@ -606,26 +580,8 @@ function useStoreComponent(store) {
606
580
  return { input, select, textarea };
607
581
  }
608
582
 
609
- // src/form/use_form_store.tsx
610
- import { useStore } from "gw-store";
611
- function useFormStore(initialState) {
612
- const store = useStore(initialState);
613
- const storeComponent = useStoreComponent(store);
614
- return {
615
- get state() {
616
- return store.state;
617
- },
618
- dispatch: store.dispatch,
619
- batch: store.batch,
620
- subscribe: store.subscribe,
621
- render: createRenderWithStore(store),
622
- ...storeComponent
623
- };
624
- }
625
-
626
583
  // src/index.tsx
627
584
  import { err, ok as ok2 } from "gw-result";
628
- export * from "gw-store";
629
585
  export {
630
586
  Input,
631
587
  Select,
@@ -640,9 +596,8 @@ export {
640
596
  err,
641
597
  ok2 as ok,
642
598
  stateLens,
643
- useFormStore,
644
- useStoreComponent,
645
- useStoreController,
599
+ useStoreBinding,
600
+ useStoreHTMLElement,
646
601
  useStoreInput,
647
602
  useStoreInputWithName
648
603
  };