cogsbox-state 0.5.472 → 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/dist/CogsState.d.ts +98 -82
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1020 -950
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +276 -217
- 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 +50 -15
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +509 -470
- 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 +13 -3
- package/src/CogsState.tsx +658 -457
- package/src/Components.tsx +272 -194
- 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 +748 -493
- 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[];
|
|
@@ -223,21 +230,31 @@ export function FormElementWrapper({
|
|
|
223
230
|
setState: any;
|
|
224
231
|
}) {
|
|
225
232
|
const componentId = useRef(uuidv4()).current;
|
|
226
|
-
const [, forceUpdate] = useState({});
|
|
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);
|
|
230
|
-
|
|
231
238
|
// Get the shadow node to access typeInfo and schema
|
|
232
239
|
const shadowNode = getGlobalStore.getState().getShadowNode(stateKey, path);
|
|
233
240
|
const typeInfo = shadowNode?._meta?.typeInfo;
|
|
234
|
-
const fieldSchema = typeInfo?.schema; // The actual Zod schema for this field
|
|
235
241
|
|
|
236
242
|
const globalStateValue = getShadowValue(stateKey, path);
|
|
237
243
|
const [localValue, setLocalValue] = useState<any>(globalStateValue);
|
|
238
244
|
const isCurrentlyDebouncing = useRef(false);
|
|
239
245
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
240
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
|
+
|
|
241
258
|
useEffect(() => {
|
|
242
259
|
if (
|
|
243
260
|
!isCurrentlyDebouncing.current &&
|
|
@@ -248,6 +265,42 @@ export function FormElementWrapper({
|
|
|
248
265
|
}, [globalStateValue]);
|
|
249
266
|
|
|
250
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
|
|
251
304
|
const unsubscribe = getGlobalStore
|
|
252
305
|
.getState()
|
|
253
306
|
.subscribeToPath(stateKeyPathKey, (newValue) => {
|
|
@@ -255,182 +308,28 @@ export function FormElementWrapper({
|
|
|
255
308
|
forceUpdate({});
|
|
256
309
|
}
|
|
257
310
|
});
|
|
311
|
+
|
|
312
|
+
// Cleanup
|
|
258
313
|
return () => {
|
|
259
314
|
unsubscribe();
|
|
315
|
+
|
|
260
316
|
if (debounceTimeoutRef.current) {
|
|
261
317
|
clearTimeout(debounceTimeoutRef.current);
|
|
262
318
|
isCurrentlyDebouncing.current = false;
|
|
263
319
|
}
|
|
264
|
-
};
|
|
265
|
-
}, []);
|
|
266
320
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
.getShadowMetadata(stateKey, []);
|
|
273
|
-
if (!rootMeta?.features?.validationEnabled) return;
|
|
274
|
-
|
|
275
|
-
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
276
|
-
|
|
277
|
-
if (!validationOptions) return;
|
|
278
|
-
|
|
279
|
-
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
280
|
-
const currentStatus = currentMeta?.validation?.status;
|
|
281
|
-
|
|
282
|
-
let shouldValidate = false;
|
|
283
|
-
let severity: 'error' | 'warning' | undefined;
|
|
284
|
-
console.log('trigger', trigger, validationOptions);
|
|
285
|
-
if (trigger === 'onBlur' && validationOptions.onBlur) {
|
|
286
|
-
shouldValidate = true;
|
|
287
|
-
severity = validationOptions.onBlur ?? 'error';
|
|
288
|
-
} else if (trigger === 'onChange') {
|
|
289
|
-
if (validationOptions.onChange) {
|
|
290
|
-
shouldValidate = true;
|
|
291
|
-
severity = validationOptions.onChange;
|
|
292
|
-
} else if (currentStatus === 'INVALID') {
|
|
293
|
-
shouldValidate = true;
|
|
294
|
-
severity = 'warning';
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (!shouldValidate) return;
|
|
299
|
-
|
|
300
|
-
let validationResult: { success: boolean; message?: string } | null =
|
|
301
|
-
null;
|
|
302
|
-
console.log(
|
|
303
|
-
'shouldValidate 33',
|
|
304
|
-
path,
|
|
305
|
-
fieldSchema,
|
|
306
|
-
shouldValidate,
|
|
307
|
-
value,
|
|
308
|
-
typeof value
|
|
309
|
-
);
|
|
310
|
-
if (fieldSchema && shouldValidate) {
|
|
311
|
-
// Direct field validation using its own schema
|
|
312
|
-
const result = fieldSchema.safeParse(value);
|
|
313
|
-
|
|
314
|
-
if (!result.success) {
|
|
315
|
-
const errors =
|
|
316
|
-
'issues' in result.error
|
|
317
|
-
? result.error.issues
|
|
318
|
-
: (result.error as any).errors;
|
|
319
|
-
|
|
320
|
-
validationResult = {
|
|
321
|
-
success: false,
|
|
322
|
-
message: errors[0]?.message || 'Invalid value',
|
|
323
|
-
};
|
|
324
|
-
} else {
|
|
325
|
-
validationResult = { success: true };
|
|
326
|
-
}
|
|
327
|
-
} else {
|
|
328
|
-
// Fallback: validate using the entire schema
|
|
329
|
-
const zodSchema =
|
|
330
|
-
validationOptions.zodSchemaV4 || validationOptions.zodSchemaV3;
|
|
331
|
-
if (!zodSchema) return;
|
|
332
|
-
|
|
333
|
-
// Create a test state with the new value at the correct path
|
|
334
|
-
const fullState = getShadowValue(stateKey, []);
|
|
335
|
-
const testState = JSON.parse(JSON.stringify(fullState)); // Deep clone
|
|
336
|
-
|
|
337
|
-
// Set the value at the correct path
|
|
338
|
-
let current = testState;
|
|
339
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
340
|
-
if (!current[path[i]!]) current[path[i]!] = {};
|
|
341
|
-
current = current[path[i]!];
|
|
342
|
-
}
|
|
343
|
-
if (path.length > 0) {
|
|
344
|
-
current[path[path.length - 1]!] = value;
|
|
345
|
-
} else {
|
|
346
|
-
// Root level update
|
|
347
|
-
Object.assign(testState, value);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const result = zodSchema.safeParse(testState);
|
|
351
|
-
|
|
352
|
-
if (!result.success) {
|
|
353
|
-
const errors =
|
|
354
|
-
'issues' in result.error
|
|
355
|
-
? result.error.issues
|
|
356
|
-
: (result.error as any).errors;
|
|
357
|
-
|
|
358
|
-
// Find errors for this specific path
|
|
359
|
-
const pathErrors = errors.filter((error: any) => {
|
|
360
|
-
// Handle array paths with id: prefixes
|
|
361
|
-
if (path.some((p) => p.startsWith('id:'))) {
|
|
362
|
-
const parentPath = path[0]!.startsWith('id:')
|
|
363
|
-
? []
|
|
364
|
-
: path.slice(0, -1);
|
|
365
|
-
const arrayMeta = getGlobalStore
|
|
366
|
-
.getState()
|
|
367
|
-
.getShadowMetadata(stateKey, parentPath);
|
|
368
|
-
|
|
369
|
-
if (arrayMeta?.arrayKeys) {
|
|
370
|
-
const itemKey = path.slice(0, -1).join('.');
|
|
371
|
-
const itemIndex = arrayMeta.arrayKeys.findIndex(
|
|
372
|
-
(k) => k === path[path.length - 2]
|
|
373
|
-
);
|
|
374
|
-
const zodPath = [...parentPath, itemIndex, ...path.slice(-1)];
|
|
375
|
-
return JSON.stringify(error.path) === JSON.stringify(zodPath);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return JSON.stringify(error.path) === JSON.stringify(path);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
if (pathErrors.length > 0) {
|
|
383
|
-
validationResult = {
|
|
384
|
-
success: false,
|
|
385
|
-
message: pathErrors[0]?.message,
|
|
386
|
-
};
|
|
387
|
-
} else {
|
|
388
|
-
validationResult = { success: true };
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
validationResult = { success: true };
|
|
392
|
-
}
|
|
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);
|
|
393
326
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (validationResult) {
|
|
397
|
-
if (!validationResult.success) {
|
|
398
|
-
setShadowMetadata(stateKey, path, {
|
|
399
|
-
...currentMeta,
|
|
400
|
-
validation: {
|
|
401
|
-
status: 'INVALID',
|
|
402
|
-
errors: [
|
|
403
|
-
{
|
|
404
|
-
source: 'client' as const,
|
|
405
|
-
message: validationResult.message!,
|
|
406
|
-
severity: severity!,
|
|
407
|
-
},
|
|
408
|
-
],
|
|
409
|
-
lastValidated: Date.now(),
|
|
410
|
-
validatedValue: value,
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
} else {
|
|
414
|
-
setShadowMetadata(stateKey, path, {
|
|
415
|
-
...currentMeta,
|
|
416
|
-
validation: {
|
|
417
|
-
status: 'VALID',
|
|
418
|
-
errors: [],
|
|
419
|
-
lastValidated: Date.now(),
|
|
420
|
-
validatedValue: value,
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
forceUpdate({});
|
|
426
|
-
},
|
|
427
|
-
[stateKey, path, fieldSchema]
|
|
428
|
-
);
|
|
327
|
+
};
|
|
328
|
+
}, []);
|
|
429
329
|
|
|
430
330
|
const debouncedUpdate = useCallback(
|
|
431
331
|
(newValue: any) => {
|
|
432
|
-
//
|
|
433
|
-
|
|
332
|
+
// Type conversion logic (keep existing)
|
|
434
333
|
if (typeInfo) {
|
|
435
334
|
if (typeInfo.type === 'number' && typeof newValue === 'string') {
|
|
436
335
|
newValue =
|
|
@@ -448,10 +347,7 @@ export function FormElementWrapper({
|
|
|
448
347
|
newValue = new Date(newValue);
|
|
449
348
|
}
|
|
450
349
|
} else {
|
|
451
|
-
// Fallback to old behavior if no typeInfo
|
|
452
|
-
|
|
453
350
|
const currentType = typeof globalStateValue;
|
|
454
|
-
|
|
455
351
|
if (currentType === 'number' && typeof newValue === 'string') {
|
|
456
352
|
newValue = newValue === '' ? 0 : Number(newValue);
|
|
457
353
|
}
|
|
@@ -459,45 +355,145 @@ export function FormElementWrapper({
|
|
|
459
355
|
|
|
460
356
|
setLocalValue(newValue);
|
|
461
357
|
|
|
462
|
-
//
|
|
463
|
-
|
|
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
|
+
});
|
|
464
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)
|
|
465
400
|
isCurrentlyDebouncing.current = true;
|
|
466
|
-
|
|
467
401
|
if (debounceTimeoutRef.current) {
|
|
468
402
|
clearTimeout(debounceTimeoutRef.current);
|
|
469
403
|
}
|
|
470
404
|
|
|
471
405
|
const debounceTime = formOpts?.debounceTime ?? 200;
|
|
472
|
-
|
|
473
|
-
// Debounce only the state update, not the validation
|
|
474
406
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
475
407
|
isCurrentlyDebouncing.current = false;
|
|
476
|
-
setState(newValue, path, {
|
|
408
|
+
setState(newValue, path, {
|
|
409
|
+
updateType: 'update',
|
|
410
|
+
validationTrigger: 'onChange',
|
|
411
|
+
});
|
|
477
412
|
}, debounceTime);
|
|
478
413
|
},
|
|
479
414
|
[
|
|
480
415
|
setState,
|
|
481
416
|
path,
|
|
482
417
|
formOpts?.debounceTime,
|
|
483
|
-
validateField,
|
|
484
418
|
typeInfo,
|
|
485
419
|
globalStateValue,
|
|
420
|
+
stateKey,
|
|
421
|
+
componentId,
|
|
486
422
|
]
|
|
487
423
|
);
|
|
488
424
|
|
|
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]);
|
|
489
452
|
const handleBlur = useCallback(() => {
|
|
490
|
-
|
|
453
|
+
const { getShadowMetadata, setShadowMetadata } = getGlobalStore.getState();
|
|
454
|
+
|
|
455
|
+
// Clear debounce if active
|
|
491
456
|
if (debounceTimeoutRef.current) {
|
|
492
457
|
clearTimeout(debounceTimeoutRef.current);
|
|
493
458
|
debounceTimeoutRef.current = null;
|
|
494
459
|
isCurrentlyDebouncing.current = false;
|
|
495
|
-
setState(localValue, path, {
|
|
460
|
+
setState(localValue, path, {
|
|
461
|
+
updateType: 'update',
|
|
462
|
+
validationTrigger: 'onBlur',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
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);
|
|
496
472
|
}
|
|
497
473
|
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
474
|
+
// Notify plugins
|
|
475
|
+
notifyFormUpdate({
|
|
476
|
+
stateKey,
|
|
477
|
+
type: 'blur',
|
|
478
|
+
path,
|
|
479
|
+
value: localValue,
|
|
480
|
+
});
|
|
481
|
+
|
|
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');
|
|
495
|
+
}
|
|
496
|
+
}, [localValue, setState, path, stateKey, componentId, globalStateValue]);
|
|
501
497
|
|
|
502
498
|
const baseState = rebuildStateShape({
|
|
503
499
|
path: path,
|
|
@@ -513,10 +509,9 @@ export function FormElementWrapper({
|
|
|
513
509
|
onChange: (e: any) => {
|
|
514
510
|
debouncedUpdate(e.target.value);
|
|
515
511
|
},
|
|
512
|
+
onFocus: handleFocus,
|
|
516
513
|
onBlur: handleBlur,
|
|
517
|
-
ref:
|
|
518
|
-
.getState()
|
|
519
|
-
.getFormRef(stateKey + '.' + path.join('.')),
|
|
514
|
+
ref: formElementRef,
|
|
520
515
|
};
|
|
521
516
|
}
|
|
522
517
|
|
|
@@ -524,9 +519,25 @@ export function FormElementWrapper({
|
|
|
524
519
|
},
|
|
525
520
|
});
|
|
526
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
|
+
|
|
527
538
|
return (
|
|
528
539
|
<ValidationWrapper formOpts={formOpts} path={path} stateKey={stateKey}>
|
|
529
|
-
{
|
|
540
|
+
{wrappedElement}
|
|
530
541
|
</ValidationWrapper>
|
|
531
542
|
);
|
|
532
543
|
}
|
|
@@ -637,3 +648,70 @@ export function IsolatedComponentWrapper({
|
|
|
637
648
|
|
|
638
649
|
return <>{renderFn(baseState)}</>;
|
|
639
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
|
+
});
|