cogsbox-state 0.5.432 → 0.5.434
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/README.md +596 -238
- package/dist/CogsState.d.ts +93 -104
- package/dist/CogsState.jsx +1529 -1058
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Functions.d.ts +1 -15
- package/dist/Functions.jsx +40 -187
- package/dist/Functions.jsx.map +1 -1
- package/dist/index.js +18 -19
- package/dist/store.d.ts +94 -92
- package/dist/store.js +230 -295
- package/dist/store.js.map +1 -1
- package/dist/useValidateZodPath.d.ts +1 -1
- package/dist/utility.d.ts +2 -2
- package/dist/utility.js +152 -169
- package/dist/utility.js.map +1 -1
- package/package.json +2 -1
- package/src/CogsState.tsx +2847 -1685
- package/src/Functions.tsx +167 -303
- package/src/store.ts +440 -440
- package/src/utility.ts +76 -95
- package/dist/useValidateZodPath.js +0 -59
- package/dist/useValidateZodPath.js.map +0 -1
package/src/Functions.tsx
CHANGED
|
@@ -1,130 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
notifyComponent,
|
|
3
|
-
type EffectiveSetState,
|
|
4
|
-
type FormElementParams,
|
|
5
|
-
type FormOptsType,
|
|
6
|
-
type UpdateArg,
|
|
7
|
-
type UpdateOpts,
|
|
8
|
-
} from "./CogsState";
|
|
1
|
+
import { type FormOptsType } from './CogsState';
|
|
9
2
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
updateNestedProperty,
|
|
14
|
-
updateNestedPropertyIds,
|
|
15
|
-
} from "./utility";
|
|
16
|
-
import { useEffect, useRef, useState } from "react";
|
|
17
|
-
import React from "react";
|
|
18
|
-
import { getGlobalStore, formRefStore } from "./store";
|
|
19
|
-
import { validateZodPathFunc } from "./useValidateZodPath";
|
|
20
|
-
import { ulid } from "ulid";
|
|
21
|
-
|
|
22
|
-
export function updateFn<U>(
|
|
23
|
-
setState: EffectiveSetState<U>,
|
|
24
|
-
payload: UpdateArg<U>,
|
|
25
|
-
path: string[],
|
|
26
|
-
validationKey?: string
|
|
27
|
-
): void {
|
|
28
|
-
setState(
|
|
29
|
-
(prevState) => {
|
|
30
|
-
if (isFunction<U>(payload)) {
|
|
31
|
-
const nestedValue = payload(getNestedValue(prevState, path));
|
|
32
|
-
console.group("nestedValue", path, nestedValue);
|
|
33
|
-
let value = updateNestedPropertyIds(path, prevState, nestedValue);
|
|
34
|
-
console.group("updateFn", value);
|
|
35
|
-
if (typeof value == "string") {
|
|
36
|
-
value = value.trim();
|
|
37
|
-
}
|
|
38
|
-
return value;
|
|
39
|
-
} else {
|
|
40
|
-
let value =
|
|
41
|
-
!path || path.length == 0
|
|
42
|
-
? payload
|
|
43
|
-
: updateNestedPropertyIds(path, prevState, payload);
|
|
44
|
-
if (typeof value == "string") {
|
|
45
|
-
value = value.trim();
|
|
46
|
-
}
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
path,
|
|
51
|
-
{ updateType: "update" },
|
|
52
|
-
validationKey
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
export function pushFunc<U>(
|
|
56
|
-
setState: EffectiveSetState<U>,
|
|
57
|
-
payload: UpdateArg<U>,
|
|
58
|
-
path: string[],
|
|
59
|
-
stateKey: string,
|
|
60
|
-
index?: number
|
|
61
|
-
): void {
|
|
62
|
-
// --- THE FIX ---
|
|
63
|
-
// 1. Determine the newItem and its ID BEFORE calling setState.
|
|
64
|
-
const arrayBeforeUpdate =
|
|
65
|
-
(getGlobalStore.getState().getNestedState(stateKey, path) as any[]) || [];
|
|
66
|
-
|
|
67
|
-
const newItem = isFunction<U>(payload)
|
|
68
|
-
? payload(arrayBeforeUpdate as any)
|
|
69
|
-
: payload;
|
|
70
|
-
|
|
71
|
-
// 2. Ensure it has an ID.
|
|
72
|
-
if (typeof newItem === "object" && newItem !== null && !(newItem as any).id) {
|
|
73
|
-
(newItem as any).id = ulid();
|
|
74
|
-
}
|
|
75
|
-
const finalId = (newItem as any).id;
|
|
76
|
-
// --- END OF FIX ---
|
|
77
|
-
|
|
78
|
-
setState(
|
|
79
|
-
(prevState) => {
|
|
80
|
-
// The logic inside here is now much simpler.
|
|
81
|
-
// We already have the final `newItem`.
|
|
82
|
-
const arrayToUpdate = getNestedValue(prevState, [...path]) || [];
|
|
83
|
-
const newArray = [...arrayToUpdate];
|
|
84
|
-
newArray.splice(index ?? newArray.length, 0, newItem);
|
|
85
|
-
return updateNestedPropertyIds([...path], prevState, newArray);
|
|
86
|
-
},
|
|
87
|
-
[...path, `id:${finalId}`], // Now we use the ID that is guaranteed to be correct.
|
|
88
|
-
{
|
|
89
|
-
updateType: "insert",
|
|
90
|
-
}
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
export function cutFunc<U>(
|
|
94
|
-
setState: EffectiveSetState<U>,
|
|
95
|
-
path: string[],
|
|
96
|
-
stateKey: string,
|
|
97
|
-
index: number
|
|
98
|
-
): void {
|
|
99
|
-
// Get the ordered IDs to find the ID for this index
|
|
100
|
-
const arrayKey = [stateKey, ...path].join(".");
|
|
101
|
-
const arrayMeta = getGlobalStore.getState().shadowStateStore.get(arrayKey);
|
|
102
|
-
const itemId = arrayMeta?.arrayKeys?.[index];
|
|
103
|
-
|
|
104
|
-
if (!itemId) {
|
|
105
|
-
throw new Error(`No ID found for index ${index} in array`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
setState(
|
|
109
|
-
(prevState) => {
|
|
110
|
-
const arrayToUpdate = getNestedValue(prevState, [...path]);
|
|
111
|
-
if (index < 0 || index >= arrayToUpdate?.length) {
|
|
112
|
-
throw new Error(`Index ${index} does not exist in the array.`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const updatedArray = [
|
|
116
|
-
...arrayToUpdate.slice(0, index),
|
|
117
|
-
...arrayToUpdate.slice(index + 1),
|
|
118
|
-
] as U;
|
|
119
|
-
|
|
120
|
-
return path.length == 0
|
|
121
|
-
? updatedArray
|
|
122
|
-
: updateNestedPropertyIds([...path], prevState, updatedArray);
|
|
123
|
-
},
|
|
124
|
-
[...path, itemId], // Use the ID here!
|
|
125
|
-
{ updateType: "cut" }
|
|
126
|
-
);
|
|
127
|
-
}
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { getGlobalStore } from './store';
|
|
128
6
|
|
|
129
7
|
export const useStoreSubscription = <T,>(
|
|
130
8
|
fullPath: string,
|
|
@@ -167,9 +45,9 @@ export const useGetValidationErrors = (
|
|
|
167
45
|
) => {
|
|
168
46
|
const fullPath =
|
|
169
47
|
validationKey +
|
|
170
|
-
|
|
171
|
-
(path.length > 0 ? [path.join(
|
|
172
|
-
(validIndices && validIndices.length > 0 ?
|
|
48
|
+
'.' +
|
|
49
|
+
(path.length > 0 ? [path.join('.')] : []) +
|
|
50
|
+
(validIndices && validIndices.length > 0 ? '.' + validIndices : '');
|
|
173
51
|
|
|
174
52
|
const returnresult = useStoreSubscription(
|
|
175
53
|
fullPath,
|
|
@@ -179,179 +57,165 @@ export const useGetValidationErrors = (
|
|
|
179
57
|
return returnresult;
|
|
180
58
|
};
|
|
181
59
|
|
|
182
|
-
export const useGetSyncInfo = (key: string, path: string[]) => {
|
|
183
|
-
const syncKey = `${key}:${path.join(".")}`;
|
|
184
|
-
return useStoreSubscription(syncKey, (store, path) =>
|
|
185
|
-
store.getSyncInfo(path)
|
|
186
|
-
);
|
|
187
|
-
};
|
|
188
|
-
export const useGetKeyState = (key: string, path: string[]) => {
|
|
189
|
-
return useStoreSubscription(`${key}:${path.join(".")}`, (store, fullPath) =>
|
|
190
|
-
store.getNestedState(key, path)
|
|
191
|
-
);
|
|
192
|
-
};
|
|
193
|
-
interface FormControlComponentProps<TStateObject> {
|
|
194
|
-
setState: EffectiveSetState<TStateObject>;
|
|
195
|
-
|
|
196
|
-
path: string[];
|
|
197
|
-
child: (obj: FormElementParams<TStateObject>) => JSX.Element;
|
|
198
|
-
formOpts?: FormOptsType;
|
|
199
|
-
stateKey: string;
|
|
200
|
-
}
|
|
201
60
|
// Find FormControlComponent in your Functions.ts or equivalent file
|
|
202
61
|
|
|
203
|
-
export const FormControlComponent = <TStateObject,>({
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
62
|
+
// export const FormControlComponent = <TStateObject,>({
|
|
63
|
+
// setState, // This is the real effectiveSetState from the hook
|
|
64
|
+
// path,
|
|
65
|
+
// child,
|
|
66
|
+
// formOpts,
|
|
67
|
+
// stateKey,
|
|
68
|
+
// rebuildStateShape,
|
|
69
|
+
// }: FormControlComponentProps<TStateObject>) => {
|
|
70
|
+
// const { registerFormRef, getFormRef } = formRefStore.getState();
|
|
71
|
+
// const {
|
|
72
|
+
// getValidationErrors,
|
|
73
|
+
// addValidationError,
|
|
74
|
+
// getInitialOptions,
|
|
75
|
+
// removeValidationError,
|
|
76
|
+
// } = getGlobalStore.getState();
|
|
77
|
+
// const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
78
|
+
// const [, forceUpdate] = useState<any>();
|
|
79
|
+
// getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
|
|
80
|
+
// forceUpdate({});
|
|
81
|
+
// });
|
|
82
|
+
|
|
83
|
+
// const refKey = stateKey + '.' + path.join('.');
|
|
84
|
+
// const localFormRef = useRef<HTMLInputElement>(null);
|
|
85
|
+
// const existingRef = getFormRef(refKey);
|
|
86
|
+
// if (!existingRef) {
|
|
87
|
+
// registerFormRef(refKey, localFormRef);
|
|
88
|
+
// }
|
|
89
|
+
// const formRef = existingRef || localFormRef;
|
|
90
|
+
|
|
91
|
+
// // --- START CHANGES ---
|
|
92
|
+
|
|
93
|
+
// const globalStateValue = getGlobalStore
|
|
94
|
+
// .getState()
|
|
95
|
+
// .getShadowValue(stateKeyPathKey);
|
|
96
|
+
// const [localValue, setLocalValue] = useState<any>(globalStateValue);
|
|
97
|
+
// const isCurrentlyDebouncing = useRef(false);
|
|
98
|
+
// const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
99
|
+
|
|
100
|
+
// // Effect to sync local state if global state changes externally
|
|
101
|
+
// useEffect(() => {
|
|
102
|
+
// // Only update local if not actively debouncing a local change
|
|
103
|
+
// if (!isCurrentlyDebouncing.current && globalStateValue !== localValue) {
|
|
104
|
+
// setLocalValue(globalStateValue);
|
|
105
|
+
// }
|
|
106
|
+
// }, [globalStateValue]); // Removed localValue dependency
|
|
107
|
+
|
|
108
|
+
// // Effect for cleanup
|
|
109
|
+
// useEffect(() => {
|
|
110
|
+
// return () => {
|
|
111
|
+
// if (debounceTimeoutRef.current) {
|
|
112
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
113
|
+
// debounceTimeoutRef.current = null; // Explicitly nullify
|
|
114
|
+
// isCurrentlyDebouncing.current = false;
|
|
115
|
+
// }
|
|
116
|
+
// };
|
|
117
|
+
// }, []);
|
|
118
|
+
|
|
119
|
+
// const debouncedUpdater = (payload: UpdateArg<TStateObject>) => {
|
|
120
|
+
// setLocalValue(payload); // Update local state immediately
|
|
121
|
+
// isCurrentlyDebouncing.current = true;
|
|
122
|
+
|
|
123
|
+
// if (payload === '') {
|
|
124
|
+
// if (debounceTimeoutRef.current) {
|
|
125
|
+
// clearTimeout(debounceTimeoutRef.current); // Clear pending timer
|
|
126
|
+
// debounceTimeoutRef.current = null;
|
|
127
|
+
// }
|
|
128
|
+
|
|
129
|
+
// setState(payload, path, { updateType: 'update' });
|
|
130
|
+
// isCurrentlyDebouncing.current = false; // No longer debouncing
|
|
131
|
+
// return; // Don't proceed to set another timeout
|
|
132
|
+
// }
|
|
133
|
+
|
|
134
|
+
// // If not empty, proceed with normal debouncing
|
|
135
|
+
// if (debounceTimeoutRef.current) {
|
|
136
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
137
|
+
// }
|
|
138
|
+
|
|
139
|
+
// debounceTimeoutRef.current = setTimeout(
|
|
140
|
+
// () => {
|
|
141
|
+
// isCurrentlyDebouncing.current = false;
|
|
142
|
+
// console.log('debouncedUpdater', payload);
|
|
143
|
+
// setState(payload, path, { updateType: 'update' });
|
|
144
|
+
// },
|
|
145
|
+
// formOpts?.debounceTime ??
|
|
146
|
+
// (typeof globalStateValue == 'boolean' ? 20 : 200)
|
|
147
|
+
// );
|
|
148
|
+
// };
|
|
149
|
+
|
|
150
|
+
// const initialOptions = getInitialOptions(stateKey);
|
|
151
|
+
|
|
152
|
+
// const validationKey = initialOptions?.validation?.key;
|
|
153
|
+
// const validateOnBlur = initialOptions?.validation?.onBlur === true;
|
|
154
|
+
|
|
155
|
+
// const handleBlur = async () => {
|
|
156
|
+
// // --- Ensure latest value is flushed if debouncing ---
|
|
157
|
+
// if (debounceTimeoutRef.current) {
|
|
158
|
+
// clearTimeout(debounceTimeoutRef.current); // Clear pending timer
|
|
159
|
+
// debounceTimeoutRef.current = null;
|
|
160
|
+
// isCurrentlyDebouncing.current = false;
|
|
161
|
+
// // Ensure the absolute latest local value is committed on blur
|
|
162
|
+
// setState(localValue, path, { updateType: 'update' });
|
|
163
|
+
// }
|
|
164
|
+
// // --- End modification ---
|
|
165
|
+
|
|
166
|
+
// if (!initialOptions?.validation?.zodSchema || !validateOnBlur) return;
|
|
167
|
+
// removeValidationError(validationKey + '.' + path.join('.'));
|
|
168
|
+
// try {
|
|
169
|
+
// // Use the potentially just flushed value
|
|
170
|
+
// if (!validationKey) return;
|
|
171
|
+
// const fieldValue = getGlobalStore
|
|
172
|
+
// .getState()
|
|
173
|
+
// .getShadowValue(stateKeyPathKey);
|
|
174
|
+
// await validateZodPathFunc(
|
|
175
|
+
// validationKey,
|
|
176
|
+
// initialOptions.validation.zodSchema,
|
|
177
|
+
// path,
|
|
178
|
+
// fieldValue
|
|
179
|
+
// );
|
|
180
|
+
// // forceUpdate might be needed if validation state update doesn't trigger render
|
|
181
|
+
// // Consider using useGetValidationErrors hook result directly for validation display
|
|
182
|
+
// } catch (error) {
|
|
183
|
+
// console.error('Validation error on blur:', error);
|
|
184
|
+
// }
|
|
185
|
+
// };
|
|
186
|
+
|
|
187
|
+
// const childElement = child({
|
|
188
|
+
// state: setter,
|
|
189
|
+
// // --- START CHANGES ---
|
|
190
|
+
// get: () => localValue, // Get should return the immediate local value
|
|
191
|
+
// set: debouncedUpdater, // Use the new debounced updater
|
|
192
|
+
// // --- END CHANGES ---
|
|
193
|
+
|
|
194
|
+
// path: path,
|
|
195
|
+
// validationErrors: () =>
|
|
196
|
+
// getValidationErrors(validationKey + '.' + path.join('.')),
|
|
197
|
+
// addValidationError: (message?: string) => {
|
|
198
|
+
// removeValidationError(validationKey + '.' + path.join('.'));
|
|
199
|
+
// addValidationError(validationKey + '.' + path.join('.'), message ?? '');
|
|
200
|
+
// },
|
|
201
|
+
// inputProps: {
|
|
202
|
+
// // --- START CHANGES ---
|
|
203
|
+
// value: localValue ?? '', // Input value is always the local state
|
|
204
|
+
// onChange: (e: any) => debouncedUpdater(e.target.value), // Use debounced updater
|
|
205
|
+
// // --- END CHANGES ---
|
|
206
|
+
// onBlur: handleBlur,
|
|
207
|
+
// ref: formRef,
|
|
208
|
+
// },
|
|
209
|
+
// });
|
|
210
|
+
|
|
211
|
+
// return (
|
|
212
|
+
// <>
|
|
213
|
+
// <ValidationWrapper {...{ formOpts, path, stateKey }}>
|
|
214
|
+
// {childElement}
|
|
215
|
+
// </ValidationWrapper>
|
|
216
|
+
// </>
|
|
217
|
+
// );
|
|
218
|
+
// };
|
|
355
219
|
export type ValidationWrapperProps = {
|
|
356
220
|
formOpts?: FormOptsType;
|
|
357
221
|
path: string[];
|
|
@@ -385,7 +249,7 @@ export function ValidationWrapper({
|
|
|
385
249
|
const thesMessages: string[] = [];
|
|
386
250
|
|
|
387
251
|
if (validationErrors) {
|
|
388
|
-
const newMessage = validationErrors!.join(
|
|
252
|
+
const newMessage = validationErrors!.join(', ');
|
|
389
253
|
if (!thesMessages.includes(newMessage)) {
|
|
390
254
|
thesMessages.push(newMessage);
|
|
391
255
|
}
|
|
@@ -401,10 +265,10 @@ export function ValidationWrapper({
|
|
|
401
265
|
),
|
|
402
266
|
active: validationErrors.length > 0 ? true : false,
|
|
403
267
|
message: formOpts?.validation?.hideMessage
|
|
404
|
-
?
|
|
268
|
+
? ''
|
|
405
269
|
: formOpts?.validation?.message
|
|
406
270
|
? formOpts?.validation?.message
|
|
407
|
-
: thesMessages.map((m) => m).join(
|
|
271
|
+
: thesMessages.map((m) => m).join(', '),
|
|
408
272
|
path: path,
|
|
409
273
|
})
|
|
410
274
|
) : (
|