cogsbox-state 0.5.296 → 0.5.298
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.jsx +520 -511
- package/dist/CogsState.jsx.map +1 -1
- package/dist/store.d.ts +26 -24
- package/dist/store.js +109 -95
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +51 -30
- package/src/store.ts +96 -57
package/src/CogsState.tsx
CHANGED
|
@@ -1814,16 +1814,30 @@ function createProxyHandler<T>(
|
|
|
1814
1814
|
endIndex: 10,
|
|
1815
1815
|
});
|
|
1816
1816
|
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
.getShadowMetadata(stateKey, [...path, index.toString()]);
|
|
1822
|
-
return metadata?.virtualizer?.itemHeight || itemHeight;
|
|
1823
|
-
},
|
|
1824
|
-
[itemHeight, stateKey, path]
|
|
1817
|
+
const [heightsVersion, setHeightsVersion] = useState(0);
|
|
1818
|
+
const forceRecalculate = useCallback(
|
|
1819
|
+
() => setHeightsVersion((v) => v + 1),
|
|
1820
|
+
[]
|
|
1825
1821
|
);
|
|
1826
1822
|
|
|
1823
|
+
// --- This useEffect now cleanly subscribes to height changes ---
|
|
1824
|
+
useEffect(() => {
|
|
1825
|
+
// Subscribe to shadow state changes for this specific key.
|
|
1826
|
+
const unsubscribe = getGlobalStore
|
|
1827
|
+
.getState()
|
|
1828
|
+
.subscribeToShadowState(stateKey, forceRecalculate);
|
|
1829
|
+
|
|
1830
|
+
// On initial mount, we still need to trigger one recalculation
|
|
1831
|
+
// to capture heights from the very first render.
|
|
1832
|
+
const timer = setTimeout(forceRecalculate, 50);
|
|
1833
|
+
|
|
1834
|
+
// Cleanup function to unsubscribe when the component unmounts.
|
|
1835
|
+
return () => {
|
|
1836
|
+
unsubscribe();
|
|
1837
|
+
clearTimeout(timer);
|
|
1838
|
+
};
|
|
1839
|
+
}, [stateKey, forceRecalculate]); // Runs only once on mount.
|
|
1840
|
+
|
|
1827
1841
|
const isAtBottomRef = useRef(stickToBottom);
|
|
1828
1842
|
const previousTotalCountRef = useRef(0);
|
|
1829
1843
|
const isInitialMountRef = useRef(true);
|
|
@@ -1835,17 +1849,20 @@ function createProxyHandler<T>(
|
|
|
1835
1849
|
const totalCount = sourceArray.length;
|
|
1836
1850
|
|
|
1837
1851
|
const { totalHeight, positions } = useMemo(() => {
|
|
1852
|
+
const shadowArray =
|
|
1853
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
1854
|
+
[];
|
|
1838
1855
|
let height = 0;
|
|
1839
1856
|
const pos: number[] = [];
|
|
1840
1857
|
for (let i = 0; i < totalCount; i++) {
|
|
1841
1858
|
pos[i] = height;
|
|
1842
|
-
|
|
1843
|
-
|
|
1859
|
+
const measuredHeight =
|
|
1860
|
+
shadowArray[i]?.virtualizer?.itemHeight;
|
|
1861
|
+
height += measuredHeight || itemHeight;
|
|
1844
1862
|
}
|
|
1845
1863
|
return { totalHeight: height, positions: pos };
|
|
1846
|
-
}, [totalCount,
|
|
1864
|
+
}, [totalCount, stateKey, path, itemHeight, heightsVersion]);
|
|
1847
1865
|
|
|
1848
|
-
// This logic is IDENTICAL to your original code.
|
|
1849
1866
|
const virtualState = useMemo(() => {
|
|
1850
1867
|
const start = Math.max(0, range.startIndex);
|
|
1851
1868
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1858,9 +1875,8 @@ function createProxyHandler<T>(
|
|
|
1858
1875
|
...meta,
|
|
1859
1876
|
validIndices,
|
|
1860
1877
|
});
|
|
1861
|
-
}, [range.startIndex, range.endIndex, sourceArray
|
|
1878
|
+
}, [range.startIndex, range.endIndex, sourceArray]);
|
|
1862
1879
|
|
|
1863
|
-
// This useLayoutEffect is from your original code.
|
|
1864
1880
|
useLayoutEffect(() => {
|
|
1865
1881
|
const container = containerRef.current;
|
|
1866
1882
|
if (!container) return;
|
|
@@ -1874,15 +1890,12 @@ function createProxyHandler<T>(
|
|
|
1874
1890
|
isAtBottomRef.current =
|
|
1875
1891
|
scrollHeight - scrollTop - clientHeight < 10;
|
|
1876
1892
|
|
|
1877
|
-
// --- THE ROBUST FIX: Binary search to find the start index ---
|
|
1878
|
-
// This is extremely fast and correctly handles all scroll positions.
|
|
1879
1893
|
let search = (list: number[], value: number) => {
|
|
1880
1894
|
let low = 0;
|
|
1881
1895
|
let high = list.length - 1;
|
|
1882
1896
|
while (low <= high) {
|
|
1883
1897
|
const mid = Math.floor((low + high) / 2);
|
|
1884
|
-
|
|
1885
|
-
if (midValue < value) {
|
|
1898
|
+
if (list[mid]! < value) {
|
|
1886
1899
|
low = mid + 1;
|
|
1887
1900
|
} else {
|
|
1888
1901
|
high = mid - 1;
|
|
@@ -1892,7 +1905,6 @@ function createProxyHandler<T>(
|
|
|
1892
1905
|
};
|
|
1893
1906
|
|
|
1894
1907
|
let startIndex = search(positions, scrollTop);
|
|
1895
|
-
|
|
1896
1908
|
let endIndex = startIndex;
|
|
1897
1909
|
while (
|
|
1898
1910
|
endIndex < totalCount &&
|
|
@@ -1903,7 +1915,7 @@ function createProxyHandler<T>(
|
|
|
1903
1915
|
|
|
1904
1916
|
startIndex = Math.max(0, startIndex - overscan);
|
|
1905
1917
|
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1906
|
-
|
|
1918
|
+
|
|
1907
1919
|
setRange((prevRange) => {
|
|
1908
1920
|
if (
|
|
1909
1921
|
prevRange.startIndex !== startIndex ||
|
|
@@ -1919,7 +1931,6 @@ function createProxyHandler<T>(
|
|
|
1919
1931
|
passive: true,
|
|
1920
1932
|
});
|
|
1921
1933
|
|
|
1922
|
-
// This stickToBottom logic is IDENTICAL to your original.
|
|
1923
1934
|
if (stickToBottom) {
|
|
1924
1935
|
if (isInitialMountRef.current) {
|
|
1925
1936
|
container.scrollTo({
|
|
@@ -1927,15 +1938,13 @@ function createProxyHandler<T>(
|
|
|
1927
1938
|
behavior: "auto",
|
|
1928
1939
|
});
|
|
1929
1940
|
} else if (wasAtBottom && listGrew) {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
});
|
|
1941
|
+
// Use 'auto' for an instant jump to the bottom to prevent visual glitches.
|
|
1942
|
+
container.scrollTo({
|
|
1943
|
+
top: container.scrollHeight,
|
|
1944
|
+
behavior: "auto",
|
|
1935
1945
|
});
|
|
1936
1946
|
}
|
|
1937
1947
|
}
|
|
1938
|
-
|
|
1939
1948
|
isInitialMountRef.current = false;
|
|
1940
1949
|
handleScroll();
|
|
1941
1950
|
|
|
@@ -2933,7 +2942,6 @@ export function $cogsSignalStore(proxy: {
|
|
|
2933
2942
|
);
|
|
2934
2943
|
return createElement("text", {}, String(value));
|
|
2935
2944
|
}
|
|
2936
|
-
|
|
2937
2945
|
function CogsItemWrapper({
|
|
2938
2946
|
stateKey,
|
|
2939
2947
|
itemComponentId,
|
|
@@ -2945,19 +2953,31 @@ function CogsItemWrapper({
|
|
|
2945
2953
|
itemPath: string[];
|
|
2946
2954
|
children: React.ReactNode;
|
|
2947
2955
|
}) {
|
|
2956
|
+
// This hook handles the re-rendering when the item's own data changes.
|
|
2948
2957
|
const [, forceUpdate] = useState({});
|
|
2958
|
+
// This hook measures the element.
|
|
2949
2959
|
const [ref, bounds] = useMeasure();
|
|
2960
|
+
// This ref prevents sending the same height update repeatedly.
|
|
2961
|
+
const lastReportedHeight = useRef<number | null>(null);
|
|
2950
2962
|
|
|
2963
|
+
// This is the primary effect for this component.
|
|
2951
2964
|
useEffect(() => {
|
|
2952
|
-
|
|
2965
|
+
// We only report a height if it's a valid number AND it's different
|
|
2966
|
+
// from the last height we reported. This prevents infinite loops.
|
|
2967
|
+
if (bounds.height > 0 && bounds.height !== lastReportedHeight.current) {
|
|
2968
|
+
// Store the new height so we don't report it again.
|
|
2969
|
+
lastReportedHeight.current = bounds.height;
|
|
2970
|
+
|
|
2971
|
+
// Call the store function to save the height and notify listeners.
|
|
2953
2972
|
getGlobalStore.getState().setShadowMetadata(stateKey, itemPath, {
|
|
2954
2973
|
virtualizer: {
|
|
2955
2974
|
itemHeight: bounds.height,
|
|
2956
2975
|
},
|
|
2957
2976
|
});
|
|
2958
2977
|
}
|
|
2959
|
-
}, [bounds.height]);
|
|
2978
|
+
}, [bounds.height, stateKey, itemPath]); // Reruns whenever the measured height changes.
|
|
2960
2979
|
|
|
2980
|
+
// This effect handles subscribing the item to its own data path for updates.
|
|
2961
2981
|
useLayoutEffect(() => {
|
|
2962
2982
|
const fullComponentId = `${stateKey}////${itemComponentId}`;
|
|
2963
2983
|
const stateEntry = getGlobalStore
|
|
@@ -2983,5 +3003,6 @@ function CogsItemWrapper({
|
|
|
2983
3003
|
};
|
|
2984
3004
|
}, [stateKey, itemComponentId, itemPath.join(".")]);
|
|
2985
3005
|
|
|
3006
|
+
// The rendered output is a simple div that gets measured.
|
|
2986
3007
|
return <div ref={ref}>{children}</div>;
|
|
2987
3008
|
}
|
package/src/store.ts
CHANGED
|
@@ -98,8 +98,12 @@ type ShadowState<T> =
|
|
|
98
98
|
: T extends object
|
|
99
99
|
? { [K in keyof T]: ShadowState<T[K]> } & ShadowMetadata
|
|
100
100
|
: ShadowMetadata;
|
|
101
|
+
|
|
101
102
|
export type CogsGlobalState = {
|
|
103
|
+
// --- Shadow State and Subscription System ---
|
|
102
104
|
shadowStateStore: { [key: string]: any };
|
|
105
|
+
shadowStateSubscribers: Map<string, Set<() => void>>; // Stores subscribers for shadow state updates
|
|
106
|
+
subscribeToShadowState: (key: string, callback: () => void) => () => void; // Subscribes a listener, returns an unsubscribe function
|
|
103
107
|
initializeShadowState: (key: string, initialState: any) => void;
|
|
104
108
|
updateShadowAtPath: (key: string, path: string[], newValue: any) => void;
|
|
105
109
|
insertShadowArrayElement: (
|
|
@@ -115,9 +119,8 @@ export type CogsGlobalState = {
|
|
|
115
119
|
getShadowMetadata: (key: string, path: string[]) => any;
|
|
116
120
|
setShadowMetadata: (key: string, path: string[], metadata: any) => void;
|
|
117
121
|
|
|
122
|
+
// --- Selected Item State ---
|
|
118
123
|
selectedIndicesMap: Map<string, Map<string, number>>; // stateKey -> (parentPath -> selectedIndex)
|
|
119
|
-
|
|
120
|
-
// Add these new methods
|
|
121
124
|
getSelectedIndex: (
|
|
122
125
|
stateKey: string,
|
|
123
126
|
parentPath: string
|
|
@@ -135,36 +138,16 @@ export type CogsGlobalState = {
|
|
|
135
138
|
path: string[];
|
|
136
139
|
}) => void;
|
|
137
140
|
clearSelectedIndexesForState: (stateKey: string) => void;
|
|
141
|
+
|
|
142
|
+
// --- Core State and Updaters ---
|
|
138
143
|
updaterState: { [key: string]: any };
|
|
139
144
|
initialStateOptions: { [key: string]: OptionsType };
|
|
140
145
|
cogsStateStore: { [key: string]: StateValue };
|
|
141
146
|
isLoadingGlobal: { [key: string]: boolean };
|
|
142
|
-
|
|
143
147
|
initialStateGlobal: { [key: string]: StateValue };
|
|
144
148
|
iniitialCreatedState: { [key: string]: StateValue };
|
|
145
|
-
validationErrors: Map<string, string[]>;
|
|
146
|
-
|
|
147
149
|
serverState: { [key: string]: StateValue };
|
|
148
|
-
serverSyncActions: { [key: string]: SyncActionsType<any> };
|
|
149
|
-
|
|
150
|
-
serverSyncLog: { [key: string]: SyncLogType[] };
|
|
151
|
-
serverSideOrNot: { [key: string]: boolean };
|
|
152
|
-
setServerSyncLog: (key: string, newValue: SyncLogType) => void;
|
|
153
|
-
|
|
154
|
-
setServerSideOrNot: (key: string, value: boolean) => void;
|
|
155
|
-
getServerSideOrNot: (key: string) => boolean | undefined;
|
|
156
|
-
setServerState: <StateKey extends StateKeys>(
|
|
157
|
-
key: StateKey,
|
|
158
|
-
value: StateValue
|
|
159
|
-
) => void;
|
|
160
150
|
|
|
161
|
-
getThisLocalUpdate: (key: string) => UpdateTypeDetail[] | undefined;
|
|
162
|
-
setServerSyncActions: (key: string, value: SyncActionsType<any>) => void;
|
|
163
|
-
addValidationError: (path: string, message: string) => void;
|
|
164
|
-
getValidationErrors: (path: string) => string[];
|
|
165
|
-
updateInitialStateGlobal: (key: string, newState: StateValue) => void;
|
|
166
|
-
updateInitialCreatedState: (key: string, newState: StateValue) => void;
|
|
167
|
-
getInitialOptions: (key: string) => OptionsType | undefined;
|
|
168
151
|
getUpdaterState: (key: string) => StateUpdater<StateValue>;
|
|
169
152
|
setUpdaterState: (key: string, newUpdater: any) => void;
|
|
170
153
|
getKeyState: <StateKey extends StateKeys>(key: StateKey) => StateValue;
|
|
@@ -178,15 +161,41 @@ export type CogsGlobalState = {
|
|
|
178
161
|
) => void;
|
|
179
162
|
setInitialStates: (initialState: StateValue) => void;
|
|
180
163
|
setCreatedState: (initialState: StateValue) => void;
|
|
164
|
+
updateInitialStateGlobal: (key: string, newState: StateValue) => void;
|
|
165
|
+
updateInitialCreatedState: (key: string, newState: StateValue) => void;
|
|
166
|
+
setIsLoadingGlobal: (key: string, value: boolean) => void;
|
|
167
|
+
setServerState: <StateKey extends StateKeys>(
|
|
168
|
+
key: StateKey,
|
|
169
|
+
value: StateValue
|
|
170
|
+
) => void;
|
|
171
|
+
getInitialOptions: (key: string) => OptionsType | undefined;
|
|
172
|
+
setInitialStateOptions: (key: string, value: OptionsType) => void;
|
|
173
|
+
|
|
174
|
+
// --- Validation ---
|
|
175
|
+
validationErrors: Map<string, string[]>;
|
|
176
|
+
addValidationError: (path: string, message: string) => void;
|
|
177
|
+
getValidationErrors: (path: string) => string[];
|
|
178
|
+
removeValidationError: (path: string) => void;
|
|
179
|
+
|
|
180
|
+
// --- Server Sync and Logging ---
|
|
181
|
+
serverSyncActions: { [key: string]: SyncActionsType<any> };
|
|
182
|
+
serverSyncLog: { [key: string]: SyncLogType[] };
|
|
181
183
|
stateLog: { [key: string]: UpdateTypeDetail[] };
|
|
184
|
+
syncInfoStore: Map<string, SyncInfo>;
|
|
185
|
+
serverSideOrNot: { [key: string]: boolean };
|
|
186
|
+
setServerSyncLog: (key: string, newValue: SyncLogType) => void;
|
|
187
|
+
setServerSideOrNot: (key: string, value: boolean) => void;
|
|
188
|
+
getServerSideOrNot: (key: string) => boolean | undefined;
|
|
189
|
+
getThisLocalUpdate: (key: string) => UpdateTypeDetail[] | undefined;
|
|
190
|
+
setServerSyncActions: (key: string, value: SyncActionsType<any>) => void;
|
|
182
191
|
setStateLog: (
|
|
183
192
|
key: string,
|
|
184
193
|
updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
|
|
185
194
|
) => void;
|
|
186
|
-
|
|
195
|
+
setSyncInfo: (key: string, syncInfo: SyncInfo) => void;
|
|
196
|
+
getSyncInfo: (key: string) => SyncInfo | null;
|
|
187
197
|
|
|
188
|
-
|
|
189
|
-
removeValidationError: (path: string) => void;
|
|
198
|
+
// --- Component and DOM Integration ---
|
|
190
199
|
signalDomElements: Map<
|
|
191
200
|
string,
|
|
192
201
|
Set<{
|
|
@@ -208,8 +217,10 @@ export type CogsGlobalState = {
|
|
|
208
217
|
}
|
|
209
218
|
) => void;
|
|
210
219
|
removeSignalElement: (signalId: string, instanceId: string) => void;
|
|
211
|
-
|
|
220
|
+
stateComponents: Map<string, ComponentsType>;
|
|
212
221
|
|
|
222
|
+
// --- Deprecated/Legacy (Review for removal) ---
|
|
223
|
+
reRenderTriggerPrevValue: Record<string, any>;
|
|
213
224
|
reactiveDeps: Record<
|
|
214
225
|
string,
|
|
215
226
|
{
|
|
@@ -228,11 +239,6 @@ export type CogsGlobalState = {
|
|
|
228
239
|
) => void;
|
|
229
240
|
deleteReactiveDeps: (key: string) => void;
|
|
230
241
|
subscribe: (listener: () => void) => () => void;
|
|
231
|
-
|
|
232
|
-
stateComponents: Map<string, ComponentsType>;
|
|
233
|
-
syncInfoStore: Map<string, SyncInfo>;
|
|
234
|
-
setSyncInfo: (key: string, syncInfo: SyncInfo) => void;
|
|
235
|
-
getSyncInfo: (key: string) => SyncInfo | null;
|
|
236
242
|
};
|
|
237
243
|
|
|
238
244
|
export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
@@ -250,30 +256,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
250
256
|
return current;
|
|
251
257
|
},
|
|
252
258
|
|
|
253
|
-
setShadowMetadata: (key: string, path: string[], metadata: any) => {
|
|
254
|
-
set((state) => {
|
|
255
|
-
const newShadow = { ...state.shadowStateStore };
|
|
256
|
-
if (!newShadow[key]) return state;
|
|
257
|
-
|
|
258
|
-
newShadow[key] = JSON.parse(JSON.stringify(newShadow[key]));
|
|
259
|
-
|
|
260
|
-
let current: any = newShadow[key];
|
|
261
|
-
for (const segment of path) {
|
|
262
|
-
if (!current[segment]) current[segment] = {};
|
|
263
|
-
current = current[segment];
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Merge the metadata into the existing structure
|
|
267
|
-
Object.keys(metadata).forEach((category) => {
|
|
268
|
-
if (!current[category]) {
|
|
269
|
-
current[category] = {};
|
|
270
|
-
}
|
|
271
|
-
Object.assign(current[category], metadata[category]);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
return { shadowStateStore: newShadow };
|
|
275
|
-
});
|
|
276
|
-
},
|
|
277
259
|
initializeShadowState: (key: string, initialState: any) => {
|
|
278
260
|
const createShadowStructure = (obj: any): any => {
|
|
279
261
|
if (Array.isArray(obj)) {
|
|
@@ -389,6 +371,63 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
389
371
|
return { shadowStateStore: newShadow };
|
|
390
372
|
});
|
|
391
373
|
},
|
|
374
|
+
shadowStateSubscribers: new Map<string, Set<() => void>>(), // key -> Set of callbacks
|
|
375
|
+
|
|
376
|
+
subscribeToShadowState: (key: string, callback: () => void) => {
|
|
377
|
+
set((state) => {
|
|
378
|
+
const newSubs = new Map(state.shadowStateSubscribers);
|
|
379
|
+
const subsForKey = newSubs.get(key) || new Set();
|
|
380
|
+
subsForKey.add(callback);
|
|
381
|
+
newSubs.set(key, subsForKey);
|
|
382
|
+
return { shadowStateSubscribers: newSubs };
|
|
383
|
+
});
|
|
384
|
+
// Return an unsubscribe function
|
|
385
|
+
return () => {
|
|
386
|
+
set((state) => {
|
|
387
|
+
const newSubs = new Map(state.shadowStateSubscribers);
|
|
388
|
+
const subsForKey = newSubs.get(key);
|
|
389
|
+
if (subsForKey) {
|
|
390
|
+
subsForKey.delete(callback);
|
|
391
|
+
}
|
|
392
|
+
return { shadowStateSubscribers: newSubs };
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
setShadowMetadata: (key: string, path: string[], metadata: any) => {
|
|
398
|
+
let hasChanged = false;
|
|
399
|
+
set((state) => {
|
|
400
|
+
const newShadow = { ...state.shadowStateStore };
|
|
401
|
+
if (!newShadow[key]) return state;
|
|
402
|
+
|
|
403
|
+
newShadow[key] = JSON.parse(JSON.stringify(newShadow[key]));
|
|
404
|
+
|
|
405
|
+
let current: any = newShadow[key];
|
|
406
|
+
for (const segment of path) {
|
|
407
|
+
if (!current[segment]) current[segment] = {};
|
|
408
|
+
current = current[segment];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const oldHeight = current.virtualizer?.itemHeight;
|
|
412
|
+
const newHeight = metadata.virtualizer?.itemHeight;
|
|
413
|
+
|
|
414
|
+
if (newHeight && oldHeight !== newHeight) {
|
|
415
|
+
hasChanged = true;
|
|
416
|
+
if (!current.virtualizer) current.virtualizer = {};
|
|
417
|
+
current.virtualizer.itemHeight = newHeight;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { shadowStateStore: newShadow };
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// If a height value was actually changed, notify the specific subscribers.
|
|
424
|
+
if (hasChanged) {
|
|
425
|
+
const subscribers = get().shadowStateSubscribers.get(key);
|
|
426
|
+
if (subscribers) {
|
|
427
|
+
subscribers.forEach((callback) => callback());
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
},
|
|
392
431
|
selectedIndicesMap: new Map<string, Map<string, number>>(),
|
|
393
432
|
|
|
394
433
|
// Add the new methods
|