cogsbox-state 0.5.473 → 0.5.475-canary.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/README.md +59 -29
- package/dist/CogsState.d.ts +26 -17
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +455 -452
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +250 -223
- package/dist/Components.jsx.map +1 -1
- package/dist/PluginRunner.d.ts.map +1 -1
- package/dist/PluginRunner.jsx +73 -64
- package/dist/PluginRunner.jsx.map +1 -1
- package/dist/index.js +17 -16
- package/dist/pluginStore.d.ts +56 -18
- package/dist/pluginStore.d.ts.map +1 -1
- package/dist/pluginStore.js.map +1 -1
- package/dist/plugins.d.ts +95 -980
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +48 -36
- package/dist/plugins.js.map +1 -1
- package/dist/store.js +168 -169
- package/dist/store.js.map +1 -1
- package/package.json +4 -3
- package/src/CogsState.tsx +233 -551
- package/src/Components.tsx +69 -41
- package/src/PluginRunner.tsx +28 -15
- package/src/pluginStore.ts +37 -20
- package/src/plugins.ts +93 -131
- package/src/store.ts +1 -1
package/src/Components.tsx
CHANGED
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
type FormOptsType,
|
|
6
6
|
} from './CogsState';
|
|
7
7
|
import { pluginStore } from './pluginStore';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createMetadataContext,
|
|
10
|
+
createScopedMetadataContext,
|
|
11
|
+
toDeconstructedMethods,
|
|
12
|
+
} from './plugins';
|
|
9
13
|
import React, {
|
|
10
14
|
memo,
|
|
11
15
|
RefObject,
|
|
@@ -78,6 +82,34 @@ export function ValidationWrapper({
|
|
|
78
82
|
: warningMessages.length > 0
|
|
79
83
|
? 'warning'
|
|
80
84
|
: undefined;
|
|
85
|
+
const { registeredPlugins } = pluginStore.getState();
|
|
86
|
+
const pluginsApi: any = {};
|
|
87
|
+
|
|
88
|
+
// We iterate over ALL registered plugins in the app.
|
|
89
|
+
registeredPlugins.forEach((plugin) => {
|
|
90
|
+
// A plugin is considered "active" for this state key if its name
|
|
91
|
+
// exists as a key in the options (e.g., options.syncPlugin exists).
|
|
92
|
+
if (thisStateOpts && thisStateOpts.hasOwnProperty(plugin.name)) {
|
|
93
|
+
const pluginName = plugin.name;
|
|
94
|
+
|
|
95
|
+
// Now we can safely build the API for this active plugin.
|
|
96
|
+
const hookData = pluginStore
|
|
97
|
+
.getState()
|
|
98
|
+
.getHookResult(stateKey, pluginName);
|
|
99
|
+
|
|
100
|
+
const scopedMetadata = createScopedMetadataContext(
|
|
101
|
+
stateKey,
|
|
102
|
+
pluginName,
|
|
103
|
+
path
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
pluginsApi[pluginName] = {
|
|
107
|
+
hookData,
|
|
108
|
+
getFieldMetaData: scopedMetadata.getFieldMetaData,
|
|
109
|
+
setFieldMetaData: scopedMetadata.setFieldMetaData,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
81
113
|
return (
|
|
82
114
|
<>
|
|
83
115
|
{thisStateOpts?.formElements?.validation &&
|
|
@@ -96,6 +128,7 @@ export function ValidationWrapper({
|
|
|
96
128
|
allErrors: errors,
|
|
97
129
|
path: path,
|
|
98
130
|
getData: () => getShadowValue(stateKey!, path),
|
|
131
|
+
plugins: pluginsApi,
|
|
99
132
|
})
|
|
100
133
|
) : (
|
|
101
134
|
<React.Fragment key={path.toString()}>{children}</React.Fragment>
|
|
@@ -244,17 +277,6 @@ export function FormElementWrapper({
|
|
|
244
277
|
const isCurrentlyDebouncing = useRef(false);
|
|
245
278
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
246
279
|
|
|
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
|
-
|
|
258
280
|
useEffect(() => {
|
|
259
281
|
if (
|
|
260
282
|
!isCurrentlyDebouncing.current &&
|
|
@@ -375,15 +397,24 @@ export function FormElementWrapper({
|
|
|
375
397
|
setShadowMetadata(stateKey, path, meta);
|
|
376
398
|
}
|
|
377
399
|
}
|
|
400
|
+
const element = meta?.clientActivityState?.elements?.get(componentId);
|
|
378
401
|
|
|
379
402
|
// Notify plugins
|
|
380
403
|
notifyFormUpdate({
|
|
381
404
|
stateKey,
|
|
382
|
-
|
|
405
|
+
activityType: 'input', // Changed from 'type'
|
|
383
406
|
path,
|
|
384
|
-
|
|
407
|
+
timestamp: Date.now(),
|
|
408
|
+
details: {
|
|
409
|
+
value: newValue,
|
|
410
|
+
inputLength:
|
|
411
|
+
typeof newValue === 'string' ? newValue.length : undefined,
|
|
412
|
+
isComposing: false, // You'd need to track this from the actual input event
|
|
413
|
+
isPasting: false, // You'd need to track this from paste events
|
|
414
|
+
keystrokeCount:
|
|
415
|
+
(element?.currentActivity?.details?.keystrokeCount || 0) + 1,
|
|
416
|
+
},
|
|
385
417
|
});
|
|
386
|
-
|
|
387
418
|
// Validation (keep existing)
|
|
388
419
|
const virtualOperation: UpdateTypeDetail = {
|
|
389
420
|
stateKey,
|
|
@@ -444,9 +475,12 @@ export function FormElementWrapper({
|
|
|
444
475
|
// Notify plugins
|
|
445
476
|
notifyFormUpdate({
|
|
446
477
|
stateKey,
|
|
447
|
-
|
|
478
|
+
activityType: 'focus', // Changed from 'type'
|
|
448
479
|
path,
|
|
449
|
-
|
|
480
|
+
timestamp: Date.now(),
|
|
481
|
+
details: {
|
|
482
|
+
cursorPosition: formElementRef.current?.selectionStart,
|
|
483
|
+
},
|
|
450
484
|
});
|
|
451
485
|
}, [stateKey, path, componentId, localValue]);
|
|
452
486
|
const handleBlur = useCallback(() => {
|
|
@@ -470,13 +504,20 @@ export function FormElementWrapper({
|
|
|
470
504
|
element.currentActivity = undefined;
|
|
471
505
|
setShadowMetadata(stateKey, path, meta);
|
|
472
506
|
}
|
|
507
|
+
const focusStartTime =
|
|
508
|
+
meta?.clientActivityState?.elements?.get(componentId)?.currentActivity
|
|
509
|
+
?.startTime;
|
|
473
510
|
|
|
474
511
|
// Notify plugins
|
|
475
512
|
notifyFormUpdate({
|
|
476
513
|
stateKey,
|
|
477
|
-
|
|
514
|
+
activityType: 'blur', // Changed from 'type'
|
|
478
515
|
path,
|
|
479
|
-
|
|
516
|
+
timestamp: Date.now(),
|
|
517
|
+
duration: focusStartTime ? Date.now() - focusStartTime : undefined,
|
|
518
|
+
details: {
|
|
519
|
+
duration: focusStartTime ? Date.now() - focusStartTime : 0,
|
|
520
|
+
},
|
|
480
521
|
});
|
|
481
522
|
|
|
482
523
|
// Run validation if configured
|
|
@@ -521,23 +562,9 @@ export function FormElementWrapper({
|
|
|
521
562
|
|
|
522
563
|
const initialElement = renderFn(stateWithInputProps);
|
|
523
564
|
|
|
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
|
-
|
|
538
565
|
return (
|
|
539
566
|
<ValidationWrapper formOpts={formOpts} path={path} stateKey={stateKey}>
|
|
540
|
-
{
|
|
567
|
+
{initialElement}
|
|
541
568
|
</ValidationWrapper>
|
|
542
569
|
);
|
|
543
570
|
}
|
|
@@ -549,18 +576,15 @@ export function useRegisterComponent(
|
|
|
549
576
|
const fullComponentId = `${stateKey}////${componentId}`;
|
|
550
577
|
|
|
551
578
|
useLayoutEffect(() => {
|
|
552
|
-
// Call the safe, centralized function to register
|
|
553
579
|
registerComponent(stateKey, fullComponentId, {
|
|
554
580
|
forceUpdate: () => forceUpdate({}),
|
|
555
581
|
paths: new Set(),
|
|
556
582
|
reactiveType: ['component'],
|
|
557
583
|
});
|
|
558
|
-
|
|
559
|
-
// The cleanup now calls the safe, centralized unregister function
|
|
560
584
|
return () => {
|
|
561
585
|
unregisterComponent(stateKey, fullComponentId);
|
|
562
586
|
};
|
|
563
|
-
}, [stateKey, fullComponentId]);
|
|
587
|
+
}, [stateKey, fullComponentId]);
|
|
564
588
|
}
|
|
565
589
|
|
|
566
590
|
const useImageLoaded = (ref: RefObject<HTMLElement>): boolean => {
|
|
@@ -699,16 +723,20 @@ const PluginWrapper = memo(function PluginWrapper({
|
|
|
699
723
|
return <>{children}</>;
|
|
700
724
|
}
|
|
701
725
|
|
|
702
|
-
const metadataContext = createMetadataContext(stateKey, plugin.name);
|
|
703
726
|
const deconstructed = toDeconstructedMethods(stateHandler);
|
|
704
|
-
|
|
727
|
+
const scopedMetadataContext = createScopedMetadataContext(
|
|
728
|
+
stateKey,
|
|
729
|
+
plugin.name,
|
|
730
|
+
path
|
|
731
|
+
);
|
|
705
732
|
return plugin.formWrapper({
|
|
706
733
|
element: children,
|
|
707
734
|
path,
|
|
708
735
|
stateKey,
|
|
709
736
|
pluginName: plugin.name,
|
|
710
737
|
...deconstructed,
|
|
711
|
-
|
|
738
|
+
|
|
739
|
+
...scopedMetadataContext,
|
|
712
740
|
options,
|
|
713
741
|
hookData,
|
|
714
742
|
fieldType: typeInfo?.type,
|
package/src/PluginRunner.tsx
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState, useRef, useReducer } from 'react';
|
|
2
|
-
import { pluginStore } from './pluginStore';
|
|
2
|
+
import { ClientActivityEvent, pluginStore } from './pluginStore';
|
|
3
3
|
import { isDeepEqual } from './utility';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createMetadataContext,
|
|
6
|
+
createScopedMetadataContext,
|
|
7
|
+
toDeconstructedMethods,
|
|
8
|
+
} from './plugins';
|
|
5
9
|
import type { CogsPlugin } from './plugins';
|
|
6
10
|
import type { StateObject, UpdateTypeDetail } from './CogsState';
|
|
7
|
-
import { FormEventType } from './store';
|
|
11
|
+
import { ClientActivityState, FormEventType } from './store';
|
|
8
12
|
|
|
9
13
|
const { setHookResult, removeHookResult } = pluginStore.getState();
|
|
10
14
|
|
|
@@ -99,6 +103,13 @@ const PluginInstance = React.memo(
|
|
|
99
103
|
|
|
100
104
|
const handleUpdate = (update: UpdateTypeDetail) => {
|
|
101
105
|
if (update.stateKey === stateKey) {
|
|
106
|
+
// Create a new, SCOPED context for this specific path
|
|
107
|
+
const scopedMetadata = createScopedMetadataContext(
|
|
108
|
+
stateKey,
|
|
109
|
+
plugin.name,
|
|
110
|
+
update.path
|
|
111
|
+
);
|
|
112
|
+
|
|
102
113
|
plugin.onUpdate!({
|
|
103
114
|
stateKey,
|
|
104
115
|
pluginName: plugin.name,
|
|
@@ -107,7 +118,7 @@ const PluginInstance = React.memo(
|
|
|
107
118
|
options,
|
|
108
119
|
hookData: hookDataRef.current,
|
|
109
120
|
...deconstructed,
|
|
110
|
-
...
|
|
121
|
+
...scopedMetadata, // <-- Use the new scoped context
|
|
111
122
|
});
|
|
112
123
|
}
|
|
113
124
|
};
|
|
@@ -116,29 +127,31 @@ const PluginInstance = React.memo(
|
|
|
116
127
|
.getState()
|
|
117
128
|
.subscribeToUpdates(handleUpdate);
|
|
118
129
|
return unsubscribe;
|
|
119
|
-
}, [stateKey, plugin, options, deconstructed
|
|
130
|
+
}, [stateKey, plugin, options, deconstructed]);
|
|
120
131
|
|
|
121
132
|
useEffect(() => {
|
|
122
133
|
if (!plugin.onFormUpdate) return;
|
|
123
134
|
|
|
124
135
|
const handleFormUpdate = (
|
|
125
|
-
event:
|
|
136
|
+
event: ClientActivityEvent // Use the proper type
|
|
126
137
|
) => {
|
|
127
138
|
if (event.stateKey === stateKey) {
|
|
128
|
-
|
|
139
|
+
// Create a new, SCOPED context for this specific path
|
|
140
|
+
const scopedMetadata = createScopedMetadataContext(
|
|
141
|
+
stateKey,
|
|
142
|
+
plugin.name,
|
|
143
|
+
event.path
|
|
144
|
+
);
|
|
145
|
+
|
|
129
146
|
plugin.onFormUpdate!({
|
|
130
147
|
stateKey,
|
|
131
148
|
pluginName: plugin.name,
|
|
132
|
-
path,
|
|
133
|
-
event:
|
|
134
|
-
type: event.type,
|
|
135
|
-
value: event.value,
|
|
136
|
-
path,
|
|
137
|
-
},
|
|
149
|
+
path: event.path,
|
|
150
|
+
event: event,
|
|
138
151
|
options,
|
|
139
152
|
hookData: hookDataRef.current,
|
|
140
153
|
...deconstructed,
|
|
141
|
-
...
|
|
154
|
+
...scopedMetadata, // <-- Use the new scoped context
|
|
142
155
|
});
|
|
143
156
|
}
|
|
144
157
|
};
|
|
@@ -147,7 +160,7 @@ const PluginInstance = React.memo(
|
|
|
147
160
|
.getState()
|
|
148
161
|
.subscribeToFormUpdates(handleFormUpdate);
|
|
149
162
|
return unsubscribe;
|
|
150
|
-
}, [stateKey, plugin, options, deconstructed
|
|
163
|
+
}, [stateKey, plugin, options, deconstructed]);
|
|
151
164
|
|
|
152
165
|
return null;
|
|
153
166
|
}
|
package/src/pluginStore.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import type { PluginData, StateObject, UpdateTypeDetail } from './CogsState';
|
|
3
3
|
import type { CogsPlugin } from './plugins';
|
|
4
|
+
export type ClientActivityEvent = {
|
|
5
|
+
stateKey: string;
|
|
6
|
+
path: string[];
|
|
7
|
+
timestamp: number;
|
|
8
|
+
duration?: number;
|
|
9
|
+
} & (
|
|
10
|
+
| { activityType: 'focus'; details: { cursorPosition?: number } }
|
|
11
|
+
| { activityType: 'blur'; details: { duration: number } }
|
|
12
|
+
| {
|
|
13
|
+
activityType: 'input';
|
|
14
|
+
details: {
|
|
15
|
+
value: any;
|
|
16
|
+
inputLength?: number;
|
|
17
|
+
isComposing?: boolean;
|
|
18
|
+
isPasting?: boolean;
|
|
19
|
+
keystrokeCount?: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
activityType: 'select';
|
|
24
|
+
details: {
|
|
25
|
+
selectionStart: number;
|
|
26
|
+
selectionEnd: number;
|
|
27
|
+
selectedText?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
| { activityType: 'hover_enter'; details: { cursorPosition?: number } }
|
|
31
|
+
| { activityType: 'hover_exit'; details: { duration: number } }
|
|
32
|
+
| {
|
|
33
|
+
activityType: 'scroll';
|
|
34
|
+
details: { scrollTop: number; scrollLeft: number };
|
|
35
|
+
}
|
|
36
|
+
| { activityType: 'cursor_move'; details: { cursorPosition: number } }
|
|
37
|
+
);
|
|
4
38
|
|
|
5
39
|
type PluginRegistryStore = {
|
|
6
40
|
stateHandlers: Map<string, StateObject<any>>; // stateKey -> handler
|
|
@@ -27,28 +61,11 @@ type PluginRegistryStore = {
|
|
|
27
61
|
callback: (update: UpdateTypeDetail) => void
|
|
28
62
|
) => () => void;
|
|
29
63
|
notifyUpdate: (update: UpdateTypeDetail) => void;
|
|
30
|
-
formUpdateSubscribers: Set<
|
|
31
|
-
(event: {
|
|
32
|
-
stateKey: string;
|
|
33
|
-
type: 'focus' | 'blur' | 'input';
|
|
34
|
-
path: string[];
|
|
35
|
-
value?: any;
|
|
36
|
-
}) => void
|
|
37
|
-
>;
|
|
64
|
+
formUpdateSubscribers: Set<(event: ClientActivityEvent) => void>;
|
|
38
65
|
subscribeToFormUpdates: (
|
|
39
|
-
callback: (event:
|
|
40
|
-
stateKey: string;
|
|
41
|
-
type: 'focus' | 'blur' | 'input';
|
|
42
|
-
path: string[];
|
|
43
|
-
value?: any;
|
|
44
|
-
}) => void
|
|
66
|
+
callback: (event: ClientActivityEvent) => void
|
|
45
67
|
) => () => void;
|
|
46
|
-
notifyFormUpdate: (event:
|
|
47
|
-
stateKey: string;
|
|
48
|
-
type: 'focus' | 'blur' | 'input';
|
|
49
|
-
path: string[];
|
|
50
|
-
value?: any;
|
|
51
|
-
}) => void;
|
|
68
|
+
notifyFormUpdate: (event: ClientActivityEvent) => void;
|
|
52
69
|
hookResults: Map<string, Map<string, any>>; // stateKey -> pluginName -> hook
|
|
53
70
|
setHookResult: (stateKey: string, pluginName: string, data: any) => void;
|
|
54
71
|
getHookResult: (stateKey: string, pluginName: string) => any | undefined;
|