cogsbox-state 0.5.279 → 0.5.280

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-state",
3
- "version": "0.5.279",
3
+ "version": "0.5.280",
4
4
  "description": "React state management library with form controls and server sync",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/CogsState.tsx CHANGED
@@ -37,6 +37,7 @@ import { z } from "zod";
37
37
  import { formRefStore, getGlobalStore, type ComponentsType } from "./store.js";
38
38
  import { useCogsConfig } from "./CogsStateClient.js";
39
39
  import { applyPatch } from "fast-json-patch";
40
+ import useMeasure from "react-use-measure";
40
41
 
41
42
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
42
43
 
@@ -2132,7 +2133,53 @@ function createProxyHandler<T>(
2132
2133
  });
2133
2134
  };
2134
2135
  }
2136
+ if (prop === "stateList") {
2137
+ return (
2138
+ callbackfn: (
2139
+ value: InferArrayElement<T>,
2140
+ setter: StateObject<InferArrayElement<T>>,
2141
+ index: number,
2142
+ array: T,
2143
+ arraySetter: StateObject<T>
2144
+ ) => any
2145
+ ) => {
2146
+ const arrayToMap = getGlobalStore
2147
+ .getState()
2148
+ .getNestedState(stateKey, path) as any[];
2149
+
2150
+ if (!Array.isArray(arrayToMap)) {
2151
+ console.warn(
2152
+ `stateList called on a non-array value at path: ${path.join(".")}.`
2153
+ );
2154
+ return null;
2155
+ }
2135
2156
 
2157
+ const indicesToMap =
2158
+ meta?.validIndices ||
2159
+ Array.from({ length: arrayToMap.length }, (_, i) => i);
2160
+
2161
+ return indicesToMap.map((originalIndex, localIndex) => {
2162
+ const item = arrayToMap[originalIndex];
2163
+ const finalPath = [...path, originalIndex.toString()];
2164
+ const setter = rebuildStateShape(item, finalPath, meta);
2165
+ const itemComponentId = `${componentId}-${path.join(".")}-${originalIndex}`;
2166
+
2167
+ return createElement(CogsItemWrapper, {
2168
+ key: originalIndex,
2169
+ stateKey,
2170
+ itemComponentId,
2171
+ itemPath: finalPath,
2172
+ children: callbackfn(
2173
+ item,
2174
+ setter,
2175
+ localIndex,
2176
+ arrayToMap as any,
2177
+ rebuildStateShape(arrayToMap as any, path, meta)
2178
+ ),
2179
+ });
2180
+ });
2181
+ };
2182
+ }
2136
2183
  if (prop === "stateFlattenOn") {
2137
2184
  return (fieldName: string) => {
2138
2185
  const arrayToMap = currentState as any[];
@@ -2851,7 +2898,7 @@ export function $cogsSignalStore(proxy: {
2851
2898
  );
2852
2899
  return createElement("text", {}, String(value));
2853
2900
  }
2854
- // This is an internal component. It should NOT be exported.
2901
+
2855
2902
  function CogsItemWrapper({
2856
2903
  stateKey,
2857
2904
  itemComponentId,
@@ -2863,10 +2910,17 @@ function CogsItemWrapper({
2863
2910
  itemPath: string[];
2864
2911
  children: React.ReactNode;
2865
2912
  }) {
2866
- // This is a real component, so we can safely call hooks.
2867
2913
  const [, forceUpdate] = useState({});
2914
+ const [ref, bounds] = useMeasure();
2915
+
2916
+ useEffect(() => {
2917
+ if (bounds.height > 0) {
2918
+ getGlobalStore
2919
+ .getState()
2920
+ .setShadowMetadata(stateKey, itemPath, { itemHeight: bounds.height });
2921
+ }
2922
+ }, [bounds.height]);
2868
2923
 
2869
- // We use useLayoutEffect to register the component and clean up when it unmounts.
2870
2924
  useLayoutEffect(() => {
2871
2925
  const fullComponentId = `${stateKey}////${itemComponentId}`;
2872
2926
  const stateEntry = getGlobalStore
@@ -2875,15 +2929,13 @@ function CogsItemWrapper({
2875
2929
  components: new Map(),
2876
2930
  };
2877
2931
 
2878
- // Register the component with its unique ID and its specific, atomic path.
2879
2932
  stateEntry.components.set(fullComponentId, {
2880
2933
  forceUpdate: () => forceUpdate({}),
2881
- paths: new Set([itemPath.join(".")]), // ATOMIC: Subscribes only to this item's path.
2934
+ paths: new Set([itemPath.join(".")]),
2882
2935
  });
2883
2936
 
2884
2937
  getGlobalStore.getState().stateComponents.set(stateKey, stateEntry);
2885
2938
 
2886
- // Return a cleanup function to unregister on unmount.
2887
2939
  return () => {
2888
2940
  const currentEntry = getGlobalStore
2889
2941
  .getState()
@@ -2892,8 +2944,7 @@ function CogsItemWrapper({
2892
2944
  currentEntry.components.delete(fullComponentId);
2893
2945
  }
2894
2946
  };
2895
- }, [stateKey, itemComponentId, itemPath.join(".")]); // Effect dependency array is stable.
2947
+ }, [stateKey, itemComponentId, itemPath.join(".")]);
2896
2948
 
2897
- // Render the actual component the user provided.
2898
- return <>{children}</>;
2949
+ return <div ref={ref}>{children}</div>;
2899
2950
  }