cogsbox-state 0.5.471 → 0.5.473
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 +2 -5
- package/dist/CogsState.d.ts +105 -79
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1082 -987
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +293 -243
- package/dist/Components.jsx.map +1 -1
- package/dist/PluginRunner.d.ts +10 -0
- package/dist/PluginRunner.d.ts.map +1 -0
- package/dist/PluginRunner.jsx +128 -0
- package/dist/PluginRunner.jsx.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -26
- package/dist/index.js.map +1 -1
- package/dist/pluginStore.d.ts +43 -0
- package/dist/pluginStore.d.ts.map +1 -0
- package/dist/pluginStore.js +52 -0
- package/dist/pluginStore.js.map +1 -0
- package/dist/plugins.d.ts +1326 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +76 -0
- package/dist/plugins.js.map +1 -0
- package/dist/store.d.ts +69 -26
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +436 -152
- package/dist/store.js.map +1 -1
- package/dist/utility.d.ts +1 -1
- package/dist/utility.d.ts.map +1 -1
- package/dist/utility.js +12 -12
- package/dist/utility.js.map +1 -1
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +39 -0
- package/dist/validation.js.map +1 -0
- package/package.json +18 -13
- package/src/CogsState.tsx +719 -458
- package/src/Components.tsx +304 -180
- package/src/PluginRunner.tsx +208 -0
- package/src/index.ts +2 -0
- package/src/pluginStore.ts +159 -0
- package/src/plugins.ts +548 -0
- package/src/store.ts +881 -189
- package/src/utility.ts +31 -31
- package/src/validation.ts +84 -0
package/src/Components.tsx
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FormElementParams,
|
|
3
|
+
StateObject,
|
|
4
|
+
UpdateTypeDetail,
|
|
5
|
+
type FormOptsType,
|
|
6
|
+
} from './CogsState';
|
|
7
|
+
import { pluginStore } from './pluginStore';
|
|
8
|
+
import { createMetadataContext, toDeconstructedMethods } from './plugins';
|
|
2
9
|
import React, {
|
|
3
10
|
memo,
|
|
4
11
|
RefObject,
|
|
@@ -7,16 +14,14 @@ import React, {
|
|
|
7
14
|
useLayoutEffect,
|
|
8
15
|
useRef,
|
|
9
16
|
useState,
|
|
17
|
+
useMemo,
|
|
10
18
|
} from 'react';
|
|
11
|
-
import {
|
|
12
|
-
formRefStore,
|
|
13
|
-
getGlobalStore,
|
|
14
|
-
ValidationError,
|
|
15
|
-
ValidationSeverity,
|
|
16
|
-
} from './store';
|
|
19
|
+
import { getGlobalStore, ValidationError, ValidationSeverity } from './store';
|
|
17
20
|
import { useInView } from 'react-intersection-observer';
|
|
18
21
|
import { v4 as uuidv4 } from 'uuid';
|
|
19
22
|
import { isDeepEqual } from './utility';
|
|
23
|
+
import { runValidation } from './validation';
|
|
24
|
+
|
|
20
25
|
const {
|
|
21
26
|
getInitialOptions,
|
|
22
27
|
|
|
@@ -30,6 +35,8 @@ const {
|
|
|
30
35
|
notifyPathSubscribers,
|
|
31
36
|
subscribeToPath,
|
|
32
37
|
} = getGlobalStore.getState();
|
|
38
|
+
const { stateHandlers, notifyFormUpdate } = pluginStore.getState();
|
|
39
|
+
|
|
33
40
|
export type ValidationWrapperProps = {
|
|
34
41
|
formOpts?: FormOptsType;
|
|
35
42
|
path: string[];
|
|
@@ -222,16 +229,32 @@ export function FormElementWrapper({
|
|
|
222
229
|
formOpts?: FormOptsType;
|
|
223
230
|
setState: any;
|
|
224
231
|
}) {
|
|
225
|
-
const
|
|
226
|
-
const [, forceUpdate] = useState({});
|
|
232
|
+
const componentId = useRef(uuidv4()).current;
|
|
227
233
|
|
|
234
|
+
const [, forceUpdate] = useState({});
|
|
235
|
+
const formElementRef = useRef<any>(null);
|
|
228
236
|
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
229
237
|
useRegisterComponent(stateKey, componentId, forceUpdate);
|
|
238
|
+
// Get the shadow node to access typeInfo and schema
|
|
239
|
+
const shadowNode = getGlobalStore.getState().getShadowNode(stateKey, path);
|
|
240
|
+
const typeInfo = shadowNode?._meta?.typeInfo;
|
|
241
|
+
|
|
230
242
|
const globalStateValue = getShadowValue(stateKey, path);
|
|
231
243
|
const [localValue, setLocalValue] = useState<any>(globalStateValue);
|
|
232
244
|
const isCurrentlyDebouncing = useRef(false);
|
|
233
245
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
234
246
|
|
|
247
|
+
// 2. Memoize the list of active form wrappers to avoid re-calculating on every render.
|
|
248
|
+
const activeFormWrappers = useMemo(() => {
|
|
249
|
+
return (
|
|
250
|
+
pluginStore
|
|
251
|
+
.getState()
|
|
252
|
+
.getPluginConfigsForState(stateKey)
|
|
253
|
+
// We only care about plugins that have defined a formWrapper
|
|
254
|
+
.filter((config) => typeof config.plugin.formWrapper === 'function')
|
|
255
|
+
);
|
|
256
|
+
}, [stateKey]);
|
|
257
|
+
|
|
235
258
|
useEffect(() => {
|
|
236
259
|
if (
|
|
237
260
|
!isCurrentlyDebouncing.current &&
|
|
@@ -242,6 +265,42 @@ export function FormElementWrapper({
|
|
|
242
265
|
}, [globalStateValue]);
|
|
243
266
|
|
|
244
267
|
useEffect(() => {
|
|
268
|
+
const { getShadowMetadata, setShadowMetadata } = getGlobalStore.getState();
|
|
269
|
+
|
|
270
|
+
// Initialize clientActivityState if needed
|
|
271
|
+
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
272
|
+
if (!currentMeta.clientActivityState) {
|
|
273
|
+
currentMeta.clientActivityState = { elements: new Map() };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Detect element type from the ref
|
|
277
|
+
const detectElementType = () => {
|
|
278
|
+
const el = formElementRef.current;
|
|
279
|
+
if (!el) return 'input';
|
|
280
|
+
const tagName = el.tagName.toLowerCase();
|
|
281
|
+
if (tagName === 'textarea') return 'textarea';
|
|
282
|
+
if (tagName === 'select') return 'select';
|
|
283
|
+
if (tagName === 'input') {
|
|
284
|
+
const type = (el as HTMLInputElement).type;
|
|
285
|
+
if (type === 'checkbox') return 'checkbox';
|
|
286
|
+
if (type === 'radio') return 'radio';
|
|
287
|
+
if (type === 'range') return 'range';
|
|
288
|
+
if (type === 'file') return 'file';
|
|
289
|
+
}
|
|
290
|
+
return 'input';
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Add this element to the Map
|
|
294
|
+
currentMeta.clientActivityState.elements.set(componentId, {
|
|
295
|
+
domRef: formElementRef,
|
|
296
|
+
elementType: detectElementType(),
|
|
297
|
+
inputType: formElementRef.current?.type,
|
|
298
|
+
mountedAt: Date.now(),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
setShadowMetadata(stateKey, path, currentMeta);
|
|
302
|
+
|
|
303
|
+
// Subscribe to path updates
|
|
245
304
|
const unsubscribe = getGlobalStore
|
|
246
305
|
.getState()
|
|
247
306
|
.subscribeToPath(stateKeyPathKey, (newValue) => {
|
|
@@ -249,208 +308,192 @@ export function FormElementWrapper({
|
|
|
249
308
|
forceUpdate({});
|
|
250
309
|
}
|
|
251
310
|
});
|
|
311
|
+
|
|
312
|
+
// Cleanup
|
|
252
313
|
return () => {
|
|
253
314
|
unsubscribe();
|
|
315
|
+
|
|
254
316
|
if (debounceTimeoutRef.current) {
|
|
255
317
|
clearTimeout(debounceTimeoutRef.current);
|
|
256
318
|
isCurrentlyDebouncing.current = false;
|
|
257
319
|
}
|
|
320
|
+
|
|
321
|
+
// Remove element from Map
|
|
322
|
+
const meta = getGlobalStore.getState().getShadowMetadata(stateKey, path);
|
|
323
|
+
if (meta?.clientActivityState?.elements) {
|
|
324
|
+
meta.clientActivityState.elements.delete(componentId);
|
|
325
|
+
setShadowMetadata(stateKey, path, meta);
|
|
326
|
+
}
|
|
258
327
|
};
|
|
259
328
|
}, []);
|
|
260
329
|
|
|
261
330
|
const debouncedUpdate = useCallback(
|
|
262
331
|
(newValue: any) => {
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
|
|
332
|
+
// Type conversion logic (keep existing)
|
|
333
|
+
if (typeInfo) {
|
|
334
|
+
if (typeInfo.type === 'number' && typeof newValue === 'string') {
|
|
335
|
+
newValue =
|
|
336
|
+
newValue === ''
|
|
337
|
+
? typeInfo.nullable
|
|
338
|
+
? null
|
|
339
|
+
: (typeInfo.default ?? 0)
|
|
340
|
+
: Number(newValue);
|
|
341
|
+
} else if (
|
|
342
|
+
typeInfo.type === 'boolean' &&
|
|
343
|
+
typeof newValue === 'string'
|
|
344
|
+
) {
|
|
345
|
+
newValue = newValue === 'true' || newValue === '1';
|
|
346
|
+
} else if (typeInfo.type === 'date' && typeof newValue === 'string') {
|
|
347
|
+
newValue = new Date(newValue);
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
const currentType = typeof globalStateValue;
|
|
351
|
+
if (currentType === 'number' && typeof newValue === 'string') {
|
|
352
|
+
newValue = newValue === '' ? 0 : Number(newValue);
|
|
353
|
+
}
|
|
266
354
|
}
|
|
355
|
+
|
|
267
356
|
setLocalValue(newValue);
|
|
268
|
-
isCurrentlyDebouncing.current = true;
|
|
269
357
|
|
|
358
|
+
// Update input activity details
|
|
359
|
+
const { getShadowMetadata, setShadowMetadata } =
|
|
360
|
+
getGlobalStore.getState();
|
|
361
|
+
const meta = getShadowMetadata(stateKey, path);
|
|
362
|
+
if (meta?.clientActivityState?.elements?.has(componentId)) {
|
|
363
|
+
const element = meta.clientActivityState.elements.get(componentId);
|
|
364
|
+
if (element && element.currentActivity?.type === 'focus') {
|
|
365
|
+
element!.currentActivity.details = {
|
|
366
|
+
...element!.currentActivity.details,
|
|
367
|
+
value: newValue,
|
|
368
|
+
previousValue:
|
|
369
|
+
element!.currentActivity.details?.value || globalStateValue,
|
|
370
|
+
inputLength:
|
|
371
|
+
typeof newValue === 'string' ? newValue.length : undefined,
|
|
372
|
+
keystrokeCount:
|
|
373
|
+
(element!.currentActivity.details?.keystrokeCount || 0) + 1,
|
|
374
|
+
};
|
|
375
|
+
setShadowMetadata(stateKey, path, meta);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Notify plugins
|
|
380
|
+
notifyFormUpdate({
|
|
381
|
+
stateKey,
|
|
382
|
+
type: 'input',
|
|
383
|
+
path,
|
|
384
|
+
value: newValue,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Validation (keep existing)
|
|
388
|
+
const virtualOperation: UpdateTypeDetail = {
|
|
389
|
+
stateKey,
|
|
390
|
+
path,
|
|
391
|
+
newValue: newValue,
|
|
392
|
+
updateType: 'update',
|
|
393
|
+
timeStamp: Date.now(),
|
|
394
|
+
status: 'new',
|
|
395
|
+
oldValue: globalStateValue,
|
|
396
|
+
};
|
|
397
|
+
runValidation(virtualOperation, 'onChange');
|
|
398
|
+
|
|
399
|
+
// Debounce state update (keep existing)
|
|
400
|
+
isCurrentlyDebouncing.current = true;
|
|
270
401
|
if (debounceTimeoutRef.current) {
|
|
271
402
|
clearTimeout(debounceTimeoutRef.current);
|
|
272
403
|
}
|
|
273
404
|
|
|
274
405
|
const debounceTime = formOpts?.debounceTime ?? 200;
|
|
275
|
-
|
|
276
406
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
277
407
|
isCurrentlyDebouncing.current = false;
|
|
278
|
-
setState(newValue, path, {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
.getState()
|
|
283
|
-
.getShadowMetadata(stateKey, []);
|
|
284
|
-
if (!rootMeta?.features?.validationEnabled) return;
|
|
285
|
-
|
|
286
|
-
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
287
|
-
const zodSchema =
|
|
288
|
-
validationOptions?.zodSchemaV4 || validationOptions?.zodSchemaV3;
|
|
289
|
-
|
|
290
|
-
if (zodSchema) {
|
|
291
|
-
const fullState = getShadowValue(stateKey, []);
|
|
292
|
-
const result = zodSchema.safeParse(fullState);
|
|
293
|
-
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
294
|
-
|
|
295
|
-
if (!result.success) {
|
|
296
|
-
const errors =
|
|
297
|
-
'issues' in result.error
|
|
298
|
-
? result.error.issues
|
|
299
|
-
: (result.error as any).errors;
|
|
300
|
-
|
|
301
|
-
const pathErrors = errors.filter(
|
|
302
|
-
(error: any) =>
|
|
303
|
-
JSON.stringify(error.path) === JSON.stringify(path)
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
if (pathErrors.length > 0) {
|
|
307
|
-
setShadowMetadata(stateKey, path, {
|
|
308
|
-
...currentMeta,
|
|
309
|
-
validation: {
|
|
310
|
-
status: 'INVALID',
|
|
311
|
-
errors: [
|
|
312
|
-
{
|
|
313
|
-
source: 'client',
|
|
314
|
-
message: pathErrors[0]?.message,
|
|
315
|
-
severity: 'warning', // Gentle error during typing
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
lastValidated: Date.now(),
|
|
319
|
-
validatedValue: newValue,
|
|
320
|
-
},
|
|
321
|
-
});
|
|
322
|
-
} else {
|
|
323
|
-
setShadowMetadata(stateKey, path, {
|
|
324
|
-
...currentMeta,
|
|
325
|
-
validation: {
|
|
326
|
-
status: 'VALID',
|
|
327
|
-
errors: [],
|
|
328
|
-
lastValidated: Date.now(),
|
|
329
|
-
validatedValue: newValue,
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
334
|
-
setShadowMetadata(stateKey, path, {
|
|
335
|
-
...currentMeta,
|
|
336
|
-
validation: {
|
|
337
|
-
status: 'VALID',
|
|
338
|
-
errors: [],
|
|
339
|
-
lastValidated: Date.now(),
|
|
340
|
-
validatedValue: newValue,
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
408
|
+
setState(newValue, path, {
|
|
409
|
+
updateType: 'update',
|
|
410
|
+
validationTrigger: 'onChange',
|
|
411
|
+
});
|
|
345
412
|
}, debounceTime);
|
|
346
|
-
forceUpdate({});
|
|
347
413
|
},
|
|
348
|
-
[
|
|
414
|
+
[
|
|
415
|
+
setState,
|
|
416
|
+
path,
|
|
417
|
+
formOpts?.debounceTime,
|
|
418
|
+
typeInfo,
|
|
419
|
+
globalStateValue,
|
|
420
|
+
stateKey,
|
|
421
|
+
componentId,
|
|
422
|
+
]
|
|
349
423
|
);
|
|
350
424
|
|
|
351
|
-
const
|
|
352
|
-
|
|
425
|
+
const handleFocus = useCallback(() => {
|
|
426
|
+
const { getShadowMetadata, setShadowMetadata } = getGlobalStore.getState();
|
|
427
|
+
|
|
428
|
+
// Update element's current activity
|
|
429
|
+
const meta = getShadowMetadata(stateKey, path);
|
|
430
|
+
if (meta?.clientActivityState?.elements?.has(componentId)) {
|
|
431
|
+
const element = meta.clientActivityState.elements.get(componentId)!;
|
|
432
|
+
element.currentActivity = {
|
|
433
|
+
type: 'focus',
|
|
434
|
+
startTime: Date.now(),
|
|
435
|
+
details: {
|
|
436
|
+
value: localValue,
|
|
437
|
+
inputLength:
|
|
438
|
+
typeof localValue === 'string' ? localValue.length : undefined,
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
setShadowMetadata(stateKey, path, meta);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Notify plugins
|
|
445
|
+
notifyFormUpdate({
|
|
446
|
+
stateKey,
|
|
447
|
+
type: 'focus',
|
|
448
|
+
path,
|
|
449
|
+
value: localValue,
|
|
450
|
+
});
|
|
451
|
+
}, [stateKey, path, componentId, localValue]);
|
|
452
|
+
const handleBlur = useCallback(() => {
|
|
453
|
+
const { getShadowMetadata, setShadowMetadata } = getGlobalStore.getState();
|
|
353
454
|
|
|
354
|
-
//
|
|
455
|
+
// Clear debounce if active
|
|
355
456
|
if (debounceTimeoutRef.current) {
|
|
356
457
|
clearTimeout(debounceTimeoutRef.current);
|
|
357
458
|
debounceTimeoutRef.current = null;
|
|
358
459
|
isCurrentlyDebouncing.current = false;
|
|
359
|
-
setState(localValue, path, {
|
|
460
|
+
setState(localValue, path, {
|
|
461
|
+
updateType: 'update',
|
|
462
|
+
validationTrigger: 'onBlur',
|
|
463
|
+
});
|
|
360
464
|
}
|
|
361
|
-
const rootMeta = getShadowMetadata(stateKey, []);
|
|
362
|
-
if (!rootMeta?.features?.validationEnabled) return;
|
|
363
|
-
const { getInitialOptions } = getGlobalStore.getState();
|
|
364
|
-
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
365
|
-
const zodSchema =
|
|
366
|
-
validationOptions?.zodSchemaV4 || validationOptions?.zodSchemaV3;
|
|
367
|
-
|
|
368
|
-
if (!zodSchema) return;
|
|
369
465
|
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
466
|
+
// Clear element's current activity
|
|
467
|
+
const meta = getShadowMetadata(stateKey, path);
|
|
468
|
+
if (meta?.clientActivityState?.elements?.has(componentId)) {
|
|
469
|
+
const element = meta.clientActivityState.elements.get(componentId)!;
|
|
470
|
+
element.currentActivity = undefined;
|
|
471
|
+
setShadowMetadata(stateKey, path, meta);
|
|
472
|
+
}
|
|
374
473
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
validatedValue: localValue,
|
|
382
|
-
},
|
|
474
|
+
// Notify plugins
|
|
475
|
+
notifyFormUpdate({
|
|
476
|
+
stateKey,
|
|
477
|
+
type: 'blur',
|
|
478
|
+
path,
|
|
479
|
+
value: localValue,
|
|
383
480
|
});
|
|
384
481
|
|
|
385
|
-
//
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (path.some((p) => p.startsWith('id:'))) {
|
|
399
|
-
// This is an array item path like ["id:xyz", "name"]
|
|
400
|
-
const parentPath = path[0]!.startsWith('id:')
|
|
401
|
-
? []
|
|
402
|
-
: path.slice(0, -1);
|
|
403
|
-
|
|
404
|
-
const arrayMeta = getGlobalStore
|
|
405
|
-
.getState()
|
|
406
|
-
.getShadowMetadata(stateKey, parentPath);
|
|
407
|
-
|
|
408
|
-
if (arrayMeta?.arrayKeys) {
|
|
409
|
-
const itemKey = [stateKey, ...path.slice(0, -1)].join('.');
|
|
410
|
-
const itemIndex = arrayMeta.arrayKeys.indexOf(itemKey);
|
|
411
|
-
|
|
412
|
-
// Compare with Zod path
|
|
413
|
-
const zodPath = [...parentPath, itemIndex, ...path.slice(-1)];
|
|
414
|
-
const match =
|
|
415
|
-
JSON.stringify(error.path) === JSON.stringify(zodPath);
|
|
416
|
-
|
|
417
|
-
return match;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const directMatch = JSON.stringify(error.path) === JSON.stringify(path);
|
|
422
|
-
|
|
423
|
-
return directMatch;
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
// Update shadow metadata with validation result
|
|
427
|
-
setShadowMetadata(stateKey, path, {
|
|
428
|
-
...currentMeta,
|
|
429
|
-
validation: {
|
|
430
|
-
status: 'INVALID',
|
|
431
|
-
errors: pathErrors.map((err: any) => ({
|
|
432
|
-
source: 'client' as const,
|
|
433
|
-
message: err.message,
|
|
434
|
-
severity: 'error' as const, // Hard error on blur
|
|
435
|
-
})),
|
|
436
|
-
lastValidated: Date.now(),
|
|
437
|
-
validatedValue: localValue,
|
|
438
|
-
},
|
|
439
|
-
});
|
|
440
|
-
} else {
|
|
441
|
-
// Validation passed
|
|
442
|
-
setShadowMetadata(stateKey, path, {
|
|
443
|
-
...currentMeta,
|
|
444
|
-
validation: {
|
|
445
|
-
status: 'VALID',
|
|
446
|
-
errors: [],
|
|
447
|
-
lastValidated: Date.now(),
|
|
448
|
-
validatedValue: localValue,
|
|
449
|
-
},
|
|
450
|
-
});
|
|
482
|
+
// Run validation if configured
|
|
483
|
+
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
484
|
+
if (validationOptions?.onBlur) {
|
|
485
|
+
const virtualOperation: UpdateTypeDetail = {
|
|
486
|
+
stateKey,
|
|
487
|
+
path,
|
|
488
|
+
newValue: localValue,
|
|
489
|
+
updateType: 'update',
|
|
490
|
+
timeStamp: Date.now(),
|
|
491
|
+
status: 'new',
|
|
492
|
+
oldValue: globalStateValue,
|
|
493
|
+
};
|
|
494
|
+
runValidation(virtualOperation, 'onBlur');
|
|
451
495
|
}
|
|
452
|
-
|
|
453
|
-
}, [stateKey, path, localValue, setState]);
|
|
496
|
+
}, [localValue, setState, path, stateKey, componentId, globalStateValue]);
|
|
454
497
|
|
|
455
498
|
const baseState = rebuildStateShape({
|
|
456
499
|
path: path,
|
|
@@ -466,11 +509,9 @@ export function FormElementWrapper({
|
|
|
466
509
|
onChange: (e: any) => {
|
|
467
510
|
debouncedUpdate(e.target.value);
|
|
468
511
|
},
|
|
469
|
-
|
|
512
|
+
onFocus: handleFocus,
|
|
470
513
|
onBlur: handleBlur,
|
|
471
|
-
ref:
|
|
472
|
-
.getState()
|
|
473
|
-
.getFormRef(stateKey + '.' + path.join('.')),
|
|
514
|
+
ref: formElementRef,
|
|
474
515
|
};
|
|
475
516
|
}
|
|
476
517
|
|
|
@@ -478,9 +519,25 @@ export function FormElementWrapper({
|
|
|
478
519
|
},
|
|
479
520
|
});
|
|
480
521
|
|
|
522
|
+
const initialElement = renderFn(stateWithInputProps);
|
|
523
|
+
|
|
524
|
+
const wrappedElement = activeFormWrappers.reduceRight(
|
|
525
|
+
(currentElement, config, index) => (
|
|
526
|
+
<PluginWrapper
|
|
527
|
+
stateKey={stateKey}
|
|
528
|
+
path={path}
|
|
529
|
+
pluginName={config.plugin.name}
|
|
530
|
+
wrapperDepth={activeFormWrappers.length - 1 - index}
|
|
531
|
+
>
|
|
532
|
+
{currentElement}
|
|
533
|
+
</PluginWrapper>
|
|
534
|
+
),
|
|
535
|
+
initialElement
|
|
536
|
+
);
|
|
537
|
+
|
|
481
538
|
return (
|
|
482
539
|
<ValidationWrapper formOpts={formOpts} path={path} stateKey={stateKey}>
|
|
483
|
-
{
|
|
540
|
+
{wrappedElement}
|
|
484
541
|
</ValidationWrapper>
|
|
485
542
|
);
|
|
486
543
|
}
|
|
@@ -591,3 +648,70 @@ export function IsolatedComponentWrapper({
|
|
|
591
648
|
|
|
592
649
|
return <>{renderFn(baseState)}</>;
|
|
593
650
|
}
|
|
651
|
+
|
|
652
|
+
// 1. Define the MINIMAL props needed.
|
|
653
|
+
type PluginWrapperProps = {
|
|
654
|
+
children: React.ReactNode;
|
|
655
|
+
stateKey: string;
|
|
656
|
+
path: string[];
|
|
657
|
+
pluginName: string;
|
|
658
|
+
wrapperDepth: number;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const PluginWrapper = memo(function PluginWrapper({
|
|
662
|
+
children,
|
|
663
|
+
stateKey,
|
|
664
|
+
path,
|
|
665
|
+
pluginName,
|
|
666
|
+
wrapperDepth,
|
|
667
|
+
}: PluginWrapperProps) {
|
|
668
|
+
const [, forceUpdate] = useState({});
|
|
669
|
+
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
const fullPathKey = [stateKey, ...path].join('.');
|
|
672
|
+
const unsubscribe = getGlobalStore
|
|
673
|
+
.getState()
|
|
674
|
+
.subscribeToPath(fullPathKey, () => {
|
|
675
|
+
forceUpdate({});
|
|
676
|
+
});
|
|
677
|
+
return unsubscribe;
|
|
678
|
+
}, [stateKey, path]);
|
|
679
|
+
|
|
680
|
+
const plugin = pluginStore
|
|
681
|
+
.getState()
|
|
682
|
+
.registeredPlugins.find((p) => p.name === pluginName);
|
|
683
|
+
|
|
684
|
+
const stateHandler: StateObject<any> | undefined = pluginStore
|
|
685
|
+
.getState()
|
|
686
|
+
.stateHandlers.get(stateKey);
|
|
687
|
+
|
|
688
|
+
const typeInfo = getGlobalStore.getState().getShadowNode(stateKey, path)
|
|
689
|
+
?._meta?.typeInfo;
|
|
690
|
+
|
|
691
|
+
const options = pluginStore
|
|
692
|
+
.getState()
|
|
693
|
+
.pluginOptions.get(stateKey)
|
|
694
|
+
?.get(pluginName);
|
|
695
|
+
|
|
696
|
+
const hookData = pluginStore.getState().getHookResult(stateKey, pluginName);
|
|
697
|
+
|
|
698
|
+
if (!plugin?.formWrapper || !stateHandler) {
|
|
699
|
+
return <>{children}</>;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const metadataContext = createMetadataContext(stateKey, plugin.name);
|
|
703
|
+
const deconstructed = toDeconstructedMethods(stateHandler);
|
|
704
|
+
|
|
705
|
+
return plugin.formWrapper({
|
|
706
|
+
element: children,
|
|
707
|
+
path,
|
|
708
|
+
stateKey,
|
|
709
|
+
pluginName: plugin.name,
|
|
710
|
+
...deconstructed,
|
|
711
|
+
...metadataContext,
|
|
712
|
+
options,
|
|
713
|
+
hookData,
|
|
714
|
+
fieldType: typeInfo?.type,
|
|
715
|
+
wrapperDepth,
|
|
716
|
+
});
|
|
717
|
+
});
|