@cripty2001/utils 0.0.158 → 0.0.160

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.
@@ -5,5 +5,6 @@ export type LoaderProps<T> = {
5
5
  children: React.ComponentType<{
6
6
  data: T;
7
7
  }>;
8
+ loader?: React.ReactNode;
8
9
  };
9
10
  export default function Loader<T>(props: LoaderProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -19,12 +19,12 @@ if (typeof document !== 'undefined') {
19
19
  }
20
20
  function Loader(props) {
21
21
  const data = (0, react_whispr_1.useWhisprValue)(props.data.data);
22
- return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(Content, { data: data, children: props.children }) }));
22
+ return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(Content, { data: data, children: props.children, loader: props.loader }) }));
23
23
  }
24
- function Content({ data, children }) {
24
+ function Content({ data, children, loader }) {
25
25
  const ChildComponent = children;
26
26
  if (data.loading)
27
- return ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }, children: (0, jsx_runtime_1.jsx)("div", { style: {
27
+ return loader ?? ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }, children: (0, jsx_runtime_1.jsx)("div", { style: {
28
28
  animation: 'spin 1s linear infinite',
29
29
  borderRadius: '9999px',
30
30
  height: '3rem',
@@ -1,4 +1,5 @@
1
1
  import { Whispr } from "@cripty2001/whispr";
2
+ import { JSONEncodable } from ".";
2
3
  import { Dispatcher } from "./Dispatcher";
3
4
  import { SearcherData } from "./Searcher";
4
5
  /**
@@ -130,3 +131,70 @@ export declare function useSearcher<T>(data: SearcherData<T>[], q: string): Sear
130
131
  * @returns
131
132
  */
132
133
  export declare function useSafeRef<T>(value: (() => T)): T;
134
+ export type AsyncInputValue<C extends Record<string, JSONEncodable>, R extends Record<string, JSONEncodable>> = R & {
135
+ _meta: {
136
+ ts: number;
137
+ config: C;
138
+ };
139
+ };
140
+ /**
141
+ * Creates a bidirectional async input handler that manages synchronous config updates
142
+ * and asynchronous result computation with automatic deduplication and conflict resolution.
143
+ *
144
+ * This hook acts as a "gateway" between controlled input components and expensive async
145
+ * operations (like autocomplete, search, or validation). It maintains two separate concerns:
146
+ *
147
+ * 1. **External state** (`value`/`setValue` params): Contains only valid, complete data.
148
+ * Updates when async operations complete. Parent components see no debouncing/loading states.
149
+ *
150
+ * 2. **Internal state** (returned `value`/`setValue`): Synchronously tracks user input/config.
151
+ * Updates immediately on user interaction without waiting for async results.
152
+ *
153
+ * The hook automatically:
154
+ * - Merges updates from both external (param) and internal (returned) setValue calls
155
+ * - Detects and discards stale async results
156
+ * - Handles concurrent updates gracefully with last-write-wins semantics
157
+ *
158
+ * @template C - Configuration object type (the input/config that triggers async work)
159
+ * @template R - Result object type (the output of the async handler)
160
+ *
161
+ * @param value - Current external value containing both result data and metadata with config/timestamp. Metadata should be considered opaque, and always carried araoud as they are
162
+ * @param setValue - Callback to update external value when async operations complete
163
+ * @param handler - Async function that computes results from config.
164
+ *
165
+ * @returns Array containing:
166
+ * - `value`: Current config (updates synchronously with user input)
167
+ * - `setValue`: Function to update config (triggers new async operation)
168
+ * - `result`: Latest computed result or null if no result yet. Useful for displaying loaders
169
+ *
170
+ * @example
171
+ * ```tsx
172
+ * // Parent component manages complete, valid autocomplete selections
173
+ * const [selectedUser, setSelectedUser] = useState<AsyncInputValue<{query: string}, {id: string, name: string}>>(...)
174
+ *
175
+ * function AutocompleteInput({selectedUser, setSelectedUser}) {
176
+ * const [ value, setValue, result ] = useAsyncInput<{query: string}, {id: string, name: string}>(
177
+ * selectedUser,
178
+ * setSelectedUser,
179
+ * async ({query}) => fetchUsers(query) // Debounced by useAsync
180
+ * );
181
+ *
182
+ * return (
183
+ * <input
184
+ * value={value.query}
185
+ * onChange={e => setValue({query: e.target.value})} // Immediate update
186
+ * />
187
+ * {result === null ? <Spinner /> : <UserList users={result} />}
188
+ * );
189
+ * }
190
+ * ```
191
+ *
192
+ * @remarks
193
+ * The returned `result` will lag behind `value` during async processing. Consider showing a loader or some other similar indication
194
+ * Handler is NOT reactive. Conceptually it is a pure function that derives an async status from the value input, so there is no reason for it to be reactive, and this saves a lot heachaches with react reactivity loops.
195
+ */
196
+ export declare function useAsyncInput<C extends Record<string, JSONEncodable>, R extends Record<string, JSONEncodable>>(value: AsyncInputValue<C, R>, setValue: (value: AsyncInputValue<C, R>) => void, handler: (config: C) => Promise<R>): [
197
+ value: C,
198
+ setValue: (value: C) => void,
199
+ result: R | null
200
+ ];
@@ -11,6 +11,7 @@ exports.useAsyncEffect = useAsyncEffect;
11
11
  exports.useRelTime = useRelTime;
12
12
  exports.useSearcher = useSearcher;
13
13
  exports.useSafeRef = useSafeRef;
14
+ exports.useAsyncInput = useAsyncInput;
14
15
  const whispr_1 = require("@cripty2001/whispr");
15
16
  const react_1 = require("react");
16
17
  const lodash_1 = require("lodash");
@@ -108,7 +109,7 @@ function useSynced(def, value, setValue) {
108
109
  const [v, setV] = (0, react_1.useState)(def);
109
110
  if ((value !== undefined && setValue === undefined) ||
110
111
  (value === undefined && setValue !== undefined))
111
- throw new Error('Either value and setValue must be provided, or both must be undefined');
112
+ throw new Error('Either both value and setValue must be provided, or both must be undefined');
112
113
  const setValueRef = (0, react_1.useRef)(setValue);
113
114
  (0, react_1.useEffect)(() => {
114
115
  setValueRef.current = setValue;
@@ -294,3 +295,111 @@ function useSafeRef(value) {
294
295
  }
295
296
  return ref.current;
296
297
  }
298
+ /**
299
+ * Creates a bidirectional async input handler that manages synchronous config updates
300
+ * and asynchronous result computation with automatic deduplication and conflict resolution.
301
+ *
302
+ * This hook acts as a "gateway" between controlled input components and expensive async
303
+ * operations (like autocomplete, search, or validation). It maintains two separate concerns:
304
+ *
305
+ * 1. **External state** (`value`/`setValue` params): Contains only valid, complete data.
306
+ * Updates when async operations complete. Parent components see no debouncing/loading states.
307
+ *
308
+ * 2. **Internal state** (returned `value`/`setValue`): Synchronously tracks user input/config.
309
+ * Updates immediately on user interaction without waiting for async results.
310
+ *
311
+ * The hook automatically:
312
+ * - Merges updates from both external (param) and internal (returned) setValue calls
313
+ * - Detects and discards stale async results
314
+ * - Handles concurrent updates gracefully with last-write-wins semantics
315
+ *
316
+ * @template C - Configuration object type (the input/config that triggers async work)
317
+ * @template R - Result object type (the output of the async handler)
318
+ *
319
+ * @param value - Current external value containing both result data and metadata with config/timestamp. Metadata should be considered opaque, and always carried araoud as they are
320
+ * @param setValue - Callback to update external value when async operations complete
321
+ * @param handler - Async function that computes results from config.
322
+ *
323
+ * @returns Array containing:
324
+ * - `value`: Current config (updates synchronously with user input)
325
+ * - `setValue`: Function to update config (triggers new async operation)
326
+ * - `result`: Latest computed result or null if no result yet. Useful for displaying loaders
327
+ *
328
+ * @example
329
+ * ```tsx
330
+ * // Parent component manages complete, valid autocomplete selections
331
+ * const [selectedUser, setSelectedUser] = useState<AsyncInputValue<{query: string}, {id: string, name: string}>>(...)
332
+ *
333
+ * function AutocompleteInput({selectedUser, setSelectedUser}) {
334
+ * const [ value, setValue, result ] = useAsyncInput<{query: string}, {id: string, name: string}>(
335
+ * selectedUser,
336
+ * setSelectedUser,
337
+ * async ({query}) => fetchUsers(query) // Debounced by useAsync
338
+ * );
339
+ *
340
+ * return (
341
+ * <input
342
+ * value={value.query}
343
+ * onChange={e => setValue({query: e.target.value})} // Immediate update
344
+ * />
345
+ * {result === null ? <Spinner /> : <UserList users={result} />}
346
+ * );
347
+ * }
348
+ * ```
349
+ *
350
+ * @remarks
351
+ * The returned `result` will lag behind `value` during async processing. Consider showing a loader or some other similar indication
352
+ * Handler is NOT reactive. Conceptually it is a pure function that derives an async status from the value input, so there is no reason for it to be reactive, and this saves a lot heachaches with react reactivity loops.
353
+ */
354
+ function useAsyncInput(value, setValue, handler) {
355
+ const [meta, setMeta] = (0, react_1.useState)({
356
+ config: value._meta.config,
357
+ ts: value._meta.ts
358
+ });
359
+ (0, react_1.useEffect)(() => {
360
+ if (value._meta.ts > meta.ts) {
361
+ setMeta(value._meta);
362
+ }
363
+ }, [value, meta, setMeta]);
364
+ // // React has always random problems with stale closures, expecially in async environment.
365
+ // // On the other side, the watched param of useAsync is always perfect
366
+ // // We aren't taking any risks, here
367
+ // type AsyncPack = {
368
+ // c: C,
369
+ // ts: number,
370
+ // }
371
+ // const asyncPack: AsyncPack = useMemo(() => {
372
+ // return {
373
+ // c: meta.config,
374
+ // ts: meta.ts,
375
+ // }
376
+ // }, [meta]);
377
+ const result_d = useAsync(async ({ config, ts }) => {
378
+ console.log("useAsyncInput", { config, ts });
379
+ const r = await handler(config);
380
+ return {
381
+ ...r,
382
+ _meta: {
383
+ ts: ts,
384
+ config: config,
385
+ },
386
+ };
387
+ }, meta, 0);
388
+ const result = useWhisprValue(result_d.filtered);
389
+ (0, react_1.useEffect)(() => {
390
+ if (result === null)
391
+ return;
392
+ setValue(result);
393
+ }, [result, setValue]);
394
+ const returnedSetValue = (0, react_1.useCallback)((v) => {
395
+ setMeta({
396
+ config: v,
397
+ ts: Date.now(),
398
+ });
399
+ }, [setMeta]);
400
+ return [
401
+ meta.config,
402
+ returnedSetValue,
403
+ result,
404
+ ];
405
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cripty2001/utils",
3
- "version": "0.0.158",
3
+ "version": "0.0.160",
4
4
  "description": "Internal Set of utils. If you need them use them, otherwise go to the next package ;)",
5
5
  "homepage": "https://github.com/cripty2001/utils#readme",
6
6
  "bugs": {