xantiagoma 0.1.0 → 0.1.2
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 +115 -52
- package/dist/entry-react.cjs +160 -0
- package/dist/entry-react.cjs.map +1 -1
- package/dist/entry-react.d.cts +162 -2
- package/dist/entry-react.d.mts +162 -2
- package/dist/entry-react.mjs +158 -2
- package/dist/entry-react.mjs.map +1 -1
- package/dist/entry-web.cjs +256 -0
- package/dist/entry-web.cjs.map +1 -1
- package/dist/entry-web.d.cts +139 -1
- package/dist/entry-web.d.mts +139 -1
- package/dist/entry-web.mjs +256 -1
- package/dist/entry-web.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ npm install xantiagoma
|
|
|
34
34
|
| `xantiagoma/unstorage` | Cache helpers with unstorage | `unstorage`, `ohash` |
|
|
35
35
|
| `xantiagoma/valibot` | TimeZone validation schema | `valibot` |
|
|
36
36
|
| `xantiagoma/sonner` | Toast streaming for iterables | `sonner`, `react` |
|
|
37
|
-
| `xantiagoma/react` |
|
|
37
|
+
| `xantiagoma/react` | React hooks + components | `react`, `@tanstack/react-query` |
|
|
38
38
|
|
|
39
39
|
Sub-entry dependencies are **optional peer deps** — only install what you use.
|
|
40
40
|
|
|
@@ -42,75 +42,84 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
|
|
|
42
42
|
|
|
43
43
|
### Error Handling
|
|
44
44
|
|
|
45
|
-
| Export
|
|
46
|
-
|
|
|
47
|
-
| `tryCatch
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `AssertError` | Custom error class for assertions |
|
|
45
|
+
| Export | Description | Source | Tests |
|
|
46
|
+
| --------------------------- | -------------------------------------- | ------------------------------- | --------------------------------------- |
|
|
47
|
+
| `tryCatch` / `tryCatchSync` | `[data, error]` tuples — no try/catch | [src](./src/try-catch.ts) | [tests](./test/try-catch.test.ts) |
|
|
48
|
+
| `assertNotNull` | Throws if null/undefined, narrows type | [src](./src/assert-not-null.ts) | [tests](./test/assert-not-null.test.ts) |
|
|
49
|
+
| `valueOrThrow` | Returns value or throws | [src](./src/error.ts) | [tests](./test/error.test.ts) |
|
|
50
|
+
| `AssertError` | Custom error class | [src](./src/errors.ts) | [tests](./test/errors.test.ts) |
|
|
52
51
|
|
|
53
52
|
### Async
|
|
54
53
|
|
|
55
|
-
| Export
|
|
56
|
-
|
|
|
57
|
-
| `wait
|
|
58
|
-
| `Completer`
|
|
59
|
-
| `collect
|
|
60
|
-
| `asyncOf
|
|
61
|
-
| `AsyncChannel`
|
|
62
|
-
| `resolveMaybePromise
|
|
54
|
+
| Export | Description | Source | Tests |
|
|
55
|
+
| --------------------- | ---------------------------------------- | ------------------------------------- | --------------------------------------------- |
|
|
56
|
+
| `wait` | Typed setTimeout delay | [src](./src/wait.ts) | [tests](./test/wait.test.ts) |
|
|
57
|
+
| `Completer` | Externally resolvable Promise | [src](./src/completer.ts) | [tests](./test/completer.test.ts) |
|
|
58
|
+
| `collect` | Drain `AsyncIterable<T>` into `T[]` | [src](./src/collect.ts) | [tests](./test/collect.test.ts) |
|
|
59
|
+
| `asyncOf` | Create `AsyncGenerator` from values | [src](./src/async-of.ts) | [tests](./test/async-of.test.ts) |
|
|
60
|
+
| `AsyncChannel` | Push-based `AsyncIterable` with modes | [src](./src/async-channel.ts) | [tests](./test/async-channel.test.ts) |
|
|
61
|
+
| `resolveMaybePromise` | Resolve `T \| Promise<T>` → `Promise<T>` | [src](./src/resolve-maybe-promise.ts) | [tests](./test/resolve-maybe-promise.test.ts) |
|
|
63
62
|
|
|
64
63
|
### Iterables & Generators
|
|
65
64
|
|
|
66
|
-
| Export | Description
|
|
67
|
-
| ------------------------------ |
|
|
68
|
-
| `range
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `toIterator(iterable)` | Normalize to `Iterator` |
|
|
73
|
-
| `toAsyncIterable(source)` | Normalize any iterable to `AsyncGenerator` |
|
|
65
|
+
| Export | Description | Source | Tests |
|
|
66
|
+
| ------------------------------ | --------------------------------- | --------------------------------- | ----------------------------------------- |
|
|
67
|
+
| `range` / `rangeLazy` | Numeric range (array / generator) | [src](./src/range.ts) | [tests](./test/range.test.ts) |
|
|
68
|
+
| `enumerate` / `enumerateAsync` | `[index, value]` tuples | [src](./src/enumerate.ts) | [tests](./test/enumerate.test.ts) |
|
|
69
|
+
| `toIterator` | Normalize to `Iterator` | [src](./src/to-iterator.ts) | [tests](./test/to-iterator.test.ts) |
|
|
70
|
+
| `toAsyncIterable` | Normalize to `AsyncGenerator` | [src](./src/to-async-iterable.ts) | [tests](./test/to-async-iterable.test.ts) |
|
|
74
71
|
|
|
75
72
|
### Type Guards
|
|
76
73
|
|
|
77
|
-
| Export
|
|
78
|
-
|
|
|
79
|
-
| `isPromise
|
|
80
|
-
| `isIterable
|
|
81
|
-
| `isAsyncIterable
|
|
82
|
-
| `isIterator
|
|
83
|
-
| `isGenerator
|
|
84
|
-
| `isAsyncGenerator
|
|
85
|
-
| `isDisposable
|
|
86
|
-
| `isAsyncDisposable(value)` | Check for `AsyncDisposable` |
|
|
87
|
-
| `isSyncDisposable(value)` | Check for `Disposable` |
|
|
74
|
+
| Export | Description | Source | Tests |
|
|
75
|
+
| --------------------------------------------------------- | ---------------------- | ---------------------------------- | ------------------------------------------ |
|
|
76
|
+
| `isPromise` | Promise-like check | [src](./src/is-promise.ts) | [tests](./test/is-promise.test.ts) |
|
|
77
|
+
| `isIterable` | `Iterable` check | [src](./src/is-iterable.ts) | [tests](./test/is-iterable.test.ts) |
|
|
78
|
+
| `isAsyncIterable` | `AsyncIterable` check | [src](./src/is-async-iterable.ts) | [tests](./test/is-async-iterable.test.ts) |
|
|
79
|
+
| `isIterator` | `Iterator` check | [src](./src/is-iterator.ts) | [tests](./test/is-iterator.test.ts) |
|
|
80
|
+
| `isGenerator` | `Generator` check | [src](./src/is-generator.ts) | [tests](./test/is-generator.test.ts) |
|
|
81
|
+
| `isAsyncGenerator` | `AsyncGenerator` check | [src](./src/is-async-generator.ts) | [tests](./test/is-async-generator.test.ts) |
|
|
82
|
+
| `isDisposable` / `isAsyncDisposable` / `isSyncDisposable` | Disposable checks | [src](./src/is-disposable.ts) | [tests](./test/is-disposable.test.ts) |
|
|
88
83
|
|
|
89
84
|
### Disposable Utilities
|
|
90
85
|
|
|
91
|
-
| Export | Description |
|
|
92
|
-
| --------------------- | --------------------------------------- |
|
|
93
|
-
| `defer
|
|
94
|
-
| `
|
|
95
|
-
| `makeDisposable(obj)` | Add `Symbol.asyncDispose` to any object |
|
|
86
|
+
| Export | Description | Source | Tests |
|
|
87
|
+
| --------------------- | --------------------------------------- | ------------------------------- | --------------------------------------- |
|
|
88
|
+
| `defer` / `deferSync` | Cancellable `using`/`await using` | [src](./src/defer.ts) | [tests](./test/defer.test.ts) |
|
|
89
|
+
| `makeDisposable` | Add `Symbol.asyncDispose` to any object | [src](./src/make-disposable.ts) | [tests](./test/make-disposable.test.ts) |
|
|
96
90
|
|
|
97
91
|
### Strings
|
|
98
92
|
|
|
99
|
-
| Export
|
|
100
|
-
|
|
|
101
|
-
| `ensureString
|
|
102
|
-
| `naturalSortCompare(a, b)` | Natural sort comparator |
|
|
103
|
-
| `jaroWinklerDistance(a, b)` | Fuzzy string similarity (0-1) |
|
|
93
|
+
| Export | Description | Source | Tests |
|
|
94
|
+
| ------------------------------------------------------------- | ---------------- | ---------------------- | ------------------------------ |
|
|
95
|
+
| `ensureString` / `naturalSortCompare` / `jaroWinklerDistance` | String utilities | [src](./src/string.ts) | [tests](./test/string.test.ts) |
|
|
104
96
|
|
|
105
97
|
### Misc
|
|
106
98
|
|
|
107
|
-
| Export | Description |
|
|
108
|
-
| ------------------------------------------- | --------------------------------------- |
|
|
109
|
-
| `cast<T
|
|
110
|
-
| `log
|
|
111
|
-
| `prepareLoaderResult
|
|
112
|
-
| `resolveStreamSource
|
|
113
|
-
| `secondsToMs` / `minutesToMs` / `hoursToMs` | Time unit converters |
|
|
99
|
+
| Export | Description | Source | Tests |
|
|
100
|
+
| ------------------------------------------- | --------------------------------------- | ------------------------------------- | --------------------------------------------- |
|
|
101
|
+
| `cast<T>` | Unsafe `as T` type cast | [src](./src/cast.ts) | [tests](./test/cast.test.ts) |
|
|
102
|
+
| `log` | `console.log` that returns its argument | [src](./src/log.ts) | [tests](./test/log.test.ts) |
|
|
103
|
+
| `prepareLoaderResult` | Map DB rows to DataLoader key order | [src](./src/prepare-loader-result.ts) | [tests](./test/prepare-loader-result.test.ts) |
|
|
104
|
+
| `resolveStreamSource` | Resolve `StreamSource<T>` | [src](./src/stream-source.ts) | [tests](./test/stream-source.test.ts) |
|
|
105
|
+
| `secondsToMs` / `minutesToMs` / `hoursToMs` | Time unit converters | [src](./src/time-convert.ts) | [tests](./test/time-convert.test.ts) |
|
|
106
|
+
|
|
107
|
+
## Web Utilities (`xantiagoma/web`)
|
|
108
|
+
|
|
109
|
+
| Export | Description | Source | Tests |
|
|
110
|
+
| ----------------------- | ----------------------------------- | ----------------------------------------- | ------------------------------------------- |
|
|
111
|
+
| `formDataToObject` | `FormData` → plain object | [src](./src/form-data-to-object-utils.ts) | [tests](./test/form-data-to-object.test.ts) |
|
|
112
|
+
| `fetchWithProgress` | Fetch with upload/download progress | [src](./src/fetch-with-progress.ts) | [tests](./test/fetch-with-progress.test.ts) |
|
|
113
|
+
| `createHttpInterceptor` | Intercept fetch + XHR with rules | [src](./src/intercept-http.ts) | [tests](./test/intercept-http.test.tsx) |
|
|
114
|
+
|
|
115
|
+
## React Utilities (`xantiagoma/react`)
|
|
116
|
+
|
|
117
|
+
| Export | Description | Source | Tests |
|
|
118
|
+
| ------------------------------ | ----------------------------------- | -------------------------------------- | ----------------------------------------------- |
|
|
119
|
+
| `Providers` / `provider` | Compose providers without nesting | [src](./src/providers.tsx) | [tests](./test/providers.test.tsx) |
|
|
120
|
+
| `usePreventAutoFocus` | Prevent auto-focus in modals | [src](./src/use-prevent-auto-focus.ts) | [tests](./test/use-prevent-auto-focus.test.tsx) |
|
|
121
|
+
| `useDynamicRefs` | Dynamic ref registry by key | [src](./src/use-dynamic-refs.ts) | [tests](./test/use-dynamic-refs.test.tsx) |
|
|
122
|
+
| `useStream` / `StreamRenderer` | Stream consumption hook + component | [src](./src/stream-renderer.tsx) | [tests](./test/stream-renderer.test.tsx) |
|
|
114
123
|
|
|
115
124
|
## Recommended Libraries
|
|
116
125
|
|
|
@@ -149,6 +158,7 @@ These are libraries we use and recommend. They're not re-exported — install th
|
|
|
149
158
|
| [hono](https://hono.dev/) | Lightweight web framework |
|
|
150
159
|
| [inngest](https://www.inngest.com/) | Background jobs + durable functions |
|
|
151
160
|
| [stripe](https://stripe.com/) | Payment processing |
|
|
161
|
+
| [paykit](https://github.com/getpaykit/paykit) | Payment toolkit for Stripe |
|
|
152
162
|
| [@tanstack/react-query](https://tanstack.com/query) | Async state management (React) |
|
|
153
163
|
| [@tanstack/react-table](https://tanstack.com/table) | Headless table (React) |
|
|
154
164
|
| [@tanstack/react-form](https://tanstack.com/form) | Form management (React) |
|
|
@@ -166,6 +176,59 @@ These are libraries we use and recommend. They're not re-exported — install th
|
|
|
166
176
|
| [citty](https://github.com/unjs/citty) | CLI framework |
|
|
167
177
|
| [evlog](https://www.npmjs.com/package/evlog) | Event logging |
|
|
168
178
|
| [@electric-sql/pglite](https://pglite.dev/) | In-memory PostgreSQL |
|
|
179
|
+
| [neverthrow](https://github.com/supermacro/neverthrow) | Type-safe Result type |
|
|
180
|
+
| [ts-pattern](https://github.com/gvergnaud/ts-pattern) | Exhaustive pattern matching |
|
|
181
|
+
| [nanoid](https://github.com/ai/nanoid) | Tiny unique ID generator |
|
|
182
|
+
| [superjson](https://github.com/flightcontrolhq/superjson) | Serialize Date, Map, Set via JSON |
|
|
183
|
+
| [destr](https://github.com/unjs/destr) | Safe, fast JSON.parse alternative |
|
|
184
|
+
| [dequal](https://github.com/lukeed/dequal) | Tiny deep equality check |
|
|
185
|
+
| [klona](https://github.com/lukeed/klona) | Tiny deep clone |
|
|
186
|
+
| [mutative](https://github.com/unadlib/mutative) | Fast immutable updates (like Immer) |
|
|
187
|
+
| [ofetch](https://github.com/unjs/ofetch) | Better fetch with retries + parsing |
|
|
188
|
+
| [ky](https://github.com/sindresorhus/ky) | Tiny fetch-based HTTP client |
|
|
189
|
+
| [consola](https://github.com/unjs/consola) | Elegant structured logger |
|
|
190
|
+
| [p-queue](https://github.com/sindresorhus/p-queue) | Promise queue with concurrency |
|
|
191
|
+
| [croner](https://github.com/Hexagon/croner) | Cron scheduler (Node + browser) |
|
|
192
|
+
| [oslo](https://github.com/pilcrowonpaper/oslo) | Auth utilities (TOTP, JWT, hashing) |
|
|
193
|
+
| [arctic](https://github.com/pilcrowonpaper/arctic) | OAuth 2.0 provider integrations |
|
|
194
|
+
| [casl](https://github.com/stalniy/casl) | Isomorphic authorization |
|
|
195
|
+
| [unctx](https://github.com/unjs/unctx) | Composables via AsyncLocalStorage |
|
|
196
|
+
| [execa](https://github.com/sindresorhus/execa) | Better child_process |
|
|
197
|
+
| [knip](https://github.com/webpro-nl/knip) | Find unused code + dependencies |
|
|
198
|
+
| [clack](https://github.com/bombshell-dev/clack) | Beautiful CLI prompts + spinners |
|
|
199
|
+
| [sonner](https://github.com/emilkowalski/sonner) | Toast notifications (React) |
|
|
200
|
+
| [vaul](https://github.com/emilkowalski/vaul) | Drawer component (React) |
|
|
201
|
+
| [cmdk](https://github.com/pacocoursey/cmdk) | Command menu (React) |
|
|
202
|
+
| [embla-carousel](https://github.com/davidjerleke/embla-carousel) | Lightweight carousel |
|
|
203
|
+
| [dnd-kit](https://github.com/clauderic/dnd-kit) | Drag and drop toolkit (React) |
|
|
204
|
+
| [nuqs](https://github.com/47ng/nuqs) | Type-safe URL search params (React) |
|
|
205
|
+
| [react-error-boundary](https://github.com/bvaughn/react-error-boundary) | Error boundary component (React) |
|
|
206
|
+
| [react-hotkeys-hook](https://github.com/JohannesKlauss/react-hotkeys-hook) | Keyboard shortcuts hook (React) |
|
|
207
|
+
| [auto-animate](https://github.com/formkit/auto-animate) | Zero-config DOM animations |
|
|
208
|
+
| [satori](https://github.com/vercel/satori) | JSX/HTML to SVG (OG images) |
|
|
209
|
+
| [orama](https://github.com/oramasearch/orama) | In-memory full-text + vector search |
|
|
210
|
+
| [gql.tada](https://github.com/0no-co/gql.tada) | Typed GraphQL documents at compile |
|
|
211
|
+
| [kysely](https://github.com/kysely-org/kysely) | Type-safe SQL query builder |
|
|
212
|
+
| [currency.js](https://github.com/scurker/currency.js) | Safe currency arithmetic |
|
|
213
|
+
| [thumbhash](https://github.com/evanw/thumbhash) | Image placeholder algorithm |
|
|
214
|
+
| [noble-hashes](https://github.com/paulmillr/noble-hashes) | Audited crypto hashes (pure TS) |
|
|
215
|
+
| [better-all](https://github.com/shuding/better-all) | Better Promise.all with named keys |
|
|
216
|
+
| [better-result](https://github.com/dmmulroy/better-result) | Rust-like Result type for TS |
|
|
217
|
+
| [fuse.js](https://www.fusejs.io/) | Lightweight fuzzy search |
|
|
218
|
+
| [files-sdk](https://github.com/haydenbleasel/files-sdk) | Unified file storage SDK |
|
|
219
|
+
| [streamdown](https://github.com/vercel/streamdown) | Stream Markdown rendering |
|
|
220
|
+
| [ai-elements](https://github.com/vercel/ai-elements) | AI-powered UI components |
|
|
221
|
+
| [tiptap](https://tiptap.dev/) | Headless rich text editor |
|
|
222
|
+
| [better-notify](https://github.com/better-notify/better-notify) | Notification management |
|
|
223
|
+
| [@vercel/chat](https://github.com/vercel/chat) | Chat UI components |
|
|
224
|
+
| [@vercel/workflow](https://github.com/vercel/workflow) | Durable workflow engine |
|
|
225
|
+
| [trigger.dev](https://trigger.dev/) | Background jobs platform |
|
|
226
|
+
| [temporal](https://temporal.io/) | Durable workflow orchestration |
|
|
227
|
+
| [@vercel/sandbox](https://github.com/vercel/sandbox) | Code sandbox execution |
|
|
228
|
+
| [reactflow](https://reactflow.dev/) | Node-based graph editor (React) |
|
|
229
|
+
| [date-fns](https://date-fns.org/) | Modern date utility library |
|
|
230
|
+
| [cheerio](https://cheerio.js.org/) | Fast HTML parsing + manipulation |
|
|
231
|
+
| [dexie](https://dexie.org/) | IndexedDB wrapper with clean API |
|
|
169
232
|
|
|
170
233
|
## See Also
|
|
171
234
|
|
package/dist/entry-react.cjs
CHANGED
|
@@ -3,6 +3,7 @@ require("./chunk-CKQMccvm.cjs");
|
|
|
3
3
|
const require_src = require("./src-BaG3gXLj.cjs");
|
|
4
4
|
let _tanstack_react_query = require("@tanstack/react-query");
|
|
5
5
|
let react = require("react");
|
|
6
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
7
|
//#region src/stream-renderer.tsx
|
|
7
8
|
function normalizeDelay(delay) {
|
|
8
9
|
if (delay === void 0) return {};
|
|
@@ -391,7 +392,166 @@ function StreamRenderer({ source, queryKey, delay, onItem, onSuccess, onError, l
|
|
|
391
392
|
return resolveRenderPropNoData(loading, fallback);
|
|
392
393
|
}
|
|
393
394
|
//#endregion
|
|
395
|
+
//#region src/providers.tsx
|
|
396
|
+
/**
|
|
397
|
+
* Create a typed `ProviderSpec` tuple with prop inference and required-prop enforcement.
|
|
398
|
+
*
|
|
399
|
+
* - If a provider has required props (excluding `children`), the props argument is required.
|
|
400
|
+
* - If a provider has no props (excluding `children`), the props argument is optional and
|
|
401
|
+
* rejects extra keys.
|
|
402
|
+
*
|
|
403
|
+
* @param Provider - The React component to use as a provider.
|
|
404
|
+
* @param args - Props to pass to the provider (excluding `children`).
|
|
405
|
+
* @returns A `ProviderSpec` tuple.
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```tsx
|
|
409
|
+
* import { provider, Providers } from "xantiagoma/react";
|
|
410
|
+
*
|
|
411
|
+
* // Provider with required props
|
|
412
|
+
* provider(ThemeProvider, { theme: "dark" })
|
|
413
|
+
*
|
|
414
|
+
* // Provider with no props (optional)
|
|
415
|
+
* provider(PermissionsProvider)
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
const provider = (Provider, ...args) => {
|
|
419
|
+
return [Provider, args[0] ?? {}];
|
|
420
|
+
};
|
|
421
|
+
const getProviderKey = (providerComponent, index) => {
|
|
422
|
+
return `${providerComponent.displayName ?? providerComponent.name ?? "Provider"}:${index}`;
|
|
423
|
+
};
|
|
424
|
+
function ProviderComponent({ Provider, props, children }) {
|
|
425
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Provider, {
|
|
426
|
+
...props,
|
|
427
|
+
children
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Compose multiple React providers without deeply nesting JSX.
|
|
432
|
+
* Eliminates "provider hell" by flattening the tree into an array.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```tsx
|
|
436
|
+
* import { Providers, provider } from "xantiagoma/react";
|
|
437
|
+
*
|
|
438
|
+
* <Providers
|
|
439
|
+
* providers={[
|
|
440
|
+
* provider(QueryClientProvider, { client: queryClient }),
|
|
441
|
+
* provider(ThemeProvider, { theme: "dark" }),
|
|
442
|
+
* provider(AuthProvider),
|
|
443
|
+
* ]}
|
|
444
|
+
* >
|
|
445
|
+
* <App />
|
|
446
|
+
* </Providers>
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
const Providers = ({ providers, children }) => providers.reduceRight((acc, [Provider, props], index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderComponent, {
|
|
450
|
+
Provider,
|
|
451
|
+
props,
|
|
452
|
+
children: acc
|
|
453
|
+
}, getProviderKey(Provider, index)), children ?? null);
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/use-prevent-auto-focus.ts
|
|
456
|
+
/**
|
|
457
|
+
* Hook that prevents auto-focus behavior while still maintaining focus management.
|
|
458
|
+
* Useful for modal dialogs (e.g. Radix Dialog) where you want to control the initial focus.
|
|
459
|
+
*
|
|
460
|
+
* @template E - Element type, defaults to HTMLDivElement
|
|
461
|
+
* @returns Object containing:
|
|
462
|
+
* - `ref` — React ref to attach to the element
|
|
463
|
+
* - `onOpenAutoFocus` — Event handler to prevent default focus behavior
|
|
464
|
+
* - `tabIndex` — `-1` to make the element programmatically focusable
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```tsx
|
|
468
|
+
* import { usePreventAutoFocus } from "xantiagoma/react";
|
|
469
|
+
*
|
|
470
|
+
* function MyDialog() {
|
|
471
|
+
* const preventAutoFocus = usePreventAutoFocus();
|
|
472
|
+
*
|
|
473
|
+
* return (
|
|
474
|
+
* <DialogContent {...preventAutoFocus}>
|
|
475
|
+
* <p>Dialog content here</p>
|
|
476
|
+
* </DialogContent>
|
|
477
|
+
* );
|
|
478
|
+
* }
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
function usePreventAutoFocus() {
|
|
482
|
+
const ref = (0, react.useRef)(null);
|
|
483
|
+
return {
|
|
484
|
+
ref,
|
|
485
|
+
onOpenAutoFocus: (0, react.useCallback)((event) => {
|
|
486
|
+
event.preventDefault();
|
|
487
|
+
ref.current?.focus({ preventScroll: true });
|
|
488
|
+
}, []),
|
|
489
|
+
tabIndex: -1
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/use-dynamic-refs.ts
|
|
494
|
+
function createRefGetter(refMap) {
|
|
495
|
+
function getRef(key) {
|
|
496
|
+
if (!key) return;
|
|
497
|
+
const existing = refMap.get(key);
|
|
498
|
+
if (existing) return existing;
|
|
499
|
+
const ref = (0, react.createRef)();
|
|
500
|
+
refMap.set(key, ref);
|
|
501
|
+
return ref;
|
|
502
|
+
}
|
|
503
|
+
return getRef;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* React hook that returns a ref "getter" function.
|
|
507
|
+
* Call the getter with a string key to get (or create) a `RefObject` for that key.
|
|
508
|
+
* Useful for dynamic lists, tabs, or any UI where the number of refs isn't known at compile time.
|
|
509
|
+
*
|
|
510
|
+
* @template T - The element type for the refs (e.g. `HTMLDivElement`)
|
|
511
|
+
* @param options - Optional configuration
|
|
512
|
+
* @returns A {@link DynamicRefGetter} function
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```tsx
|
|
516
|
+
* import { useDynamicRefs } from "xantiagoma/react";
|
|
517
|
+
*
|
|
518
|
+
* function TabList({ tabs }: { tabs: string[] }) {
|
|
519
|
+
* const getRef = useDynamicRefs<HTMLButtonElement>();
|
|
520
|
+
*
|
|
521
|
+
* return (
|
|
522
|
+
* <div>
|
|
523
|
+
* {tabs.map((tab) => (
|
|
524
|
+
* <button key={tab} ref={getRef(tab)}>
|
|
525
|
+
* {tab}
|
|
526
|
+
* </button>
|
|
527
|
+
* ))}
|
|
528
|
+
* </div>
|
|
529
|
+
* );
|
|
530
|
+
* }
|
|
531
|
+
* ```
|
|
532
|
+
*
|
|
533
|
+
* @example
|
|
534
|
+
* ```tsx
|
|
535
|
+
* // Scroll to a specific item
|
|
536
|
+
* const getRef = useDynamicRefs<HTMLDivElement>();
|
|
537
|
+
*
|
|
538
|
+
* function scrollTo(id: string) {
|
|
539
|
+
* getRef(id)?.current?.scrollIntoView({ behavior: "smooth" });
|
|
540
|
+
* }
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
543
|
+
function useDynamicRefs(options) {
|
|
544
|
+
const prefix = options?.prefix;
|
|
545
|
+
return (0, react.useMemo)(() => {
|
|
546
|
+
return createRefGetter(/* @__PURE__ */ new Map());
|
|
547
|
+
}, [prefix]);
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
550
|
+
exports.Providers = Providers;
|
|
394
551
|
exports.StreamRenderer = StreamRenderer;
|
|
552
|
+
exports.provider = provider;
|
|
553
|
+
exports.useDynamicRefs = useDynamicRefs;
|
|
554
|
+
exports.usePreventAutoFocus = usePreventAutoFocus;
|
|
395
555
|
exports.useStream = useStream;
|
|
396
556
|
|
|
397
557
|
//# sourceMappingURL=entry-react.cjs.map
|
package/dist/entry-react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-react.cjs","names":["resolveStreamSource","wait","isGenerator","isAsyncGenerator","AsyncChannel","isAsyncIterable","isIterable","toAsyncIterable"],"sources":["../src/stream-renderer.tsx"],"sourcesContent":["import {\n AsyncChannel,\n isAsyncGenerator,\n isAsyncIterable,\n isGenerator,\n isIterable,\n resolveStreamSource,\n type StreamSource,\n toAsyncIterable,\n wait,\n} from \"./index.ts\";\nimport {\n experimental_streamedQuery as streamedQuery,\n type UseQueryResult,\n useQuery,\n} from \"@tanstack/react-query\";\nimport { type ReactNode, useEffect, useMemo, useRef } from \"react\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Render prop: can be ReactNode or function returning ReactNode */\ntype RenderProp<TData> = ReactNode | ((data: TData) => ReactNode);\n\n/** Render prop without data (for loading) */\ntype RenderPropNoData = ReactNode | (() => ReactNode);\n\n/** Stream state with accumulated items */\nexport type StreamState<TYield, TReturn = void> = {\n items: TYield[];\n count: number;\n latest: TYield | null;\n returnValue: TReturn | undefined;\n status: \"streaming\" | \"complete\";\n};\n\n/** Granular delay configuration */\nexport type DelayConfig = {\n items?: number;\n first?: number;\n result?: number;\n};\n\n/** Streaming data passed to streaming render prop */\nexport type StreamingData<TYield> = {\n count: number;\n latest: TYield;\n items: TYield[];\n};\n\n/** Success data passed to success render prop */\nexport type SuccessData<TYield, TReturn> = {\n count: number;\n items: TYield[];\n returnValue: TReturn | undefined;\n};\n\n/** Error data passed to error render prop */\nexport type ErrorData<TYield> = {\n error: Error;\n count: number;\n items: TYield[];\n};\n\n/** Data passed to onItem callback */\nexport type OnItemData<TYield> = {\n item: TYield;\n count: number;\n items: TYield[];\n};\n\n/** Data passed to onSuccess callback */\nexport type OnSuccessData<TYield, TReturn = void> = {\n count: number;\n items: TYield[];\n returnValue: TReturn | undefined;\n};\n\n/** Data passed to onError callback */\nexport type OnErrorData<TYield> = {\n error: Error;\n count: number;\n items: TYield[];\n};\n\n/** Children render prop data - includes status and all relevant data */\nexport type ChildrenData<TYield, TReturn = void> =\n | {\n status: \"loading\";\n count: 0;\n items: [];\n latest: null;\n returnValue: undefined;\n error: null;\n }\n | {\n status: \"streaming\";\n count: number;\n items: TYield[];\n latest: TYield;\n returnValue: undefined;\n error: null;\n }\n | {\n status: \"success\";\n count: number;\n items: TYield[];\n latest: TYield | null;\n returnValue: TReturn | undefined;\n error: null;\n }\n | {\n status: \"error\";\n count: number;\n items: TYield[];\n latest: TYield | null;\n returnValue: undefined;\n error: Error;\n };\n\n/** Base props for StreamRenderer */\nexport type StreamRendererBaseProps<TYield, TReturn = void> = {\n /** The stream source - can be instance or factory */\n source: StreamSource<TYield> | null | undefined;\n\n /** Unique key for the query cache */\n queryKey?: unknown[];\n /** Delay configuration for visualizing sync streams */\n delay?: number | DelayConfig;\n /** Called for each item received */\n onItem?: (data: OnItemData<TYield>) => void;\n /** Called when stream completes successfully */\n onSuccess?: (data: OnSuccessData<TYield, TReturn>) => void;\n /** Called when stream errors */\n onError?: (data: OnErrorData<TYield>) => void;\n /** Whether to start streaming immediately (default: true) */\n enabled?: boolean;\n /**\n * Retry configuration on error (default: false = no retries)\n * - `false`: no retries\n * - `true`: use TanStack Query default (3 retries)\n * - `number`: retry that many times\n * - `(data) => boolean`: custom retry logic with access to partial data\n */\n retry?: boolean | number | ((data: RetryData<TYield>) => boolean);\n};\n\n/** Props for StreamRenderer - supports all accumulation modes */\nexport type StreamRendererProps<TYield, TReturn = void> = StreamRendererBaseProps<\n TYield,\n TReturn\n> & {\n /**\n * Accumulation mode (mutually exclusive with maxItems)\n * - `\"accumulate\"` (default): keep all items\n * - `\"latest\"`: only keep the most recent item\n */\n mode?: StreamMode;\n /** Keep only the last N items (mutually exclusive with mode) */\n maxItems?: number;\n /** Render while loading - ReactNode or () => ReactNode */\n loading?: RenderPropNoData;\n /** Render while streaming - ReactNode or (data) => ReactNode */\n streaming?: RenderProp<StreamingData<TYield>>;\n /** Render on success - ReactNode or (data) => ReactNode */\n success?: RenderProp<SuccessData<TYield, TReturn>>;\n /** Render on error - ReactNode or (data) => ReactNode */\n error?: RenderProp<ErrorData<TYield>>;\n /** Fallback/universal render prop - ReactNode or (data) => ReactNode */\n children?: RenderProp<ChildrenData<TYield, TReturn>>;\n};\n\n/** Data passed to retry callback */\nexport type RetryData<TYield> = {\n /** Number of times the query has failed (starts at 0) */\n failureCount: number;\n /** The error that caused the failure */\n error: Error;\n /** Items collected before the error */\n items: TYield[];\n /** Number of items collected before the error */\n count: number;\n /** Last item received before the error (null if none) */\n latest: TYield | null;\n};\n\n/**\n * Accumulation mode for stream items.\n * - `\"accumulate\"` (default): keep all items in memory\n * - `\"latest\"`: only keep the most recent item\n */\nexport type StreamMode = \"accumulate\" | \"latest\";\n\n/**\n * Custom reducer for stream state.\n * Receives current state and new item, returns new state.\n */\nexport type StreamReducer<TYield, TState> = (state: TState, item: TYield, count: number) => TState;\n\n/** Base options shared by all useStream configurations */\ntype UseStreamBaseOptions<TYield, TReturn = void> = {\n /** The stream source - can be instance or factory */\n source: StreamSource<TYield> | null | undefined;\n /** Unique key for the query cache */\n queryKey?: unknown[];\n /** Delay configuration for visualizing sync streams */\n delay?: number | DelayConfig;\n /** Called for each item received */\n onItem?: (data: OnItemData<TYield>) => void;\n /** Called when stream completes successfully */\n onSuccess?: (data: OnSuccessData<TYield, TReturn>) => void;\n /** Called when stream errors */\n onError?: (data: OnErrorData<TYield>) => void;\n /** Whether to start streaming immediately (default: true) */\n enabled?: boolean;\n /**\n * Retry configuration on error (default: false = no retries)\n * - `false`: no retries\n * - `true`: use TanStack Query default (3 retries)\n * - `number`: retry that many times\n * - `(data) => boolean`: custom retry logic with access to partial data\n */\n retry?: boolean | number | ((data: RetryData<TYield>) => boolean);\n};\n\n/** Options with default accumulation (all items) */\ntype UseStreamAccumulateOptions<TYield, TReturn = void> = UseStreamBaseOptions<TYield, TReturn> & {\n mode?: \"accumulate\";\n maxItems?: never;\n reducer?: never;\n};\n\n/** Options with \"latest\" mode (only keep last item) */\ntype UseStreamLatestOptions<TYield, TReturn = void> = UseStreamBaseOptions<TYield, TReturn> & {\n mode: \"latest\";\n maxItems?: never;\n reducer?: never;\n};\n\n/** Options with maxItems (sliding window) */\ntype UseStreamMaxItemsOptions<TYield, TReturn = void> = UseStreamBaseOptions<TYield, TReturn> & {\n mode?: never;\n /** Keep only the last N items (sliding window) */\n maxItems: number;\n reducer?: never;\n};\n\n/** Options with custom reducer */\ntype UseStreamReducerOptions<TYield, TState, TReturn = void> = UseStreamBaseOptions<\n TYield,\n TReturn\n> & {\n mode?: never;\n maxItems?: never;\n /** Custom reducer function for complete control over state */\n reducer: StreamReducer<TYield, TState>;\n /** Initial state for custom reducer */\n initialState: TState;\n};\n\n/** Props for useStream hook - mutually exclusive accumulation options */\nexport type UseStreamOptions<TYield, TReturn = void, TState = unknown> =\n | UseStreamAccumulateOptions<TYield, TReturn>\n | UseStreamLatestOptions<TYield, TReturn>\n | UseStreamMaxItemsOptions<TYield, TReturn>\n | UseStreamReducerOptions<TYield, TState, TReturn>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction normalizeDelay(delay: number | DelayConfig | undefined): DelayConfig {\n if (delay === undefined) {\n return {};\n }\n if (typeof delay === \"number\") {\n return { first: delay, items: delay, result: delay };\n }\n return delay;\n}\n\n/** Resolve a render prop (ReactNode or function) */\nfunction resolveRenderProp<TData>(\n prop: RenderProp<TData> | undefined,\n data: TData,\n fallback: ReactNode,\n): ReactNode {\n if (prop === undefined) {\n return fallback;\n }\n if (typeof prop === \"function\") {\n return prop(data);\n }\n return prop;\n}\n\n/** Resolve a render prop without data (for loading) */\nfunction resolveRenderPropNoData(\n prop: RenderPropNoData | undefined,\n fallback: ReactNode,\n): ReactNode {\n if (prop === undefined) {\n return fallback;\n }\n if (typeof prop === \"function\") {\n return prop();\n }\n return prop;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// useStream Hook\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Result type for standard useStream (accumulate, latest, maxItems modes) */\nexport type UseStreamResult<TYield, TReturn = void> = {\n query: UseQueryResult<StreamState<TYield, TReturn>, Error>;\n /** Items received (all, last N, or just latest depending on mode) */\n items: TYield[];\n /** Total number of items received (even if not all kept in memory) */\n count: number;\n /** Most recent item */\n latest: TYield | null;\n /** Return value from generator (if complete) */\n returnValue: TReturn | undefined;\n /** Restart the stream */\n refetch: () => void;\n // Status flags\n isLoading: boolean;\n isStreaming: boolean;\n isSuccess: boolean;\n isError: boolean;\n};\n\n/** Result type for useStream with custom reducer */\nexport type UseStreamReducerResult<_TYield, TState> = {\n query: UseQueryResult<{ state: TState; count: number; status: \"streaming\" | \"complete\" }, Error>;\n /** Custom state from reducer */\n state: TState;\n /** Total number of items received */\n count: number;\n /** Restart the stream */\n refetch: () => void;\n // Status flags\n isLoading: boolean;\n isStreaming: boolean;\n isSuccess: boolean;\n isError: boolean;\n};\n\n/**\n * Hook for consuming streams using TanStack Query's streamedQuery.\n *\n * @example\n * ```tsx\n * // Default: accumulate all items\n * const { items, isStreaming, count } = useStream({\n * source: fetchItems,\n * queryKey: ['my-stream'],\n * });\n *\n * // Latest only: keep just the most recent item\n * const { items, latest } = useStream({\n * source: sseEvents,\n * mode: \"latest\",\n * });\n *\n * // Sliding window: keep last N items\n * const { items } = useStream({\n * source: logStream,\n * maxItems: 100,\n * });\n *\n * // Custom reducer: full control over state\n * const { state } = useStream({\n * source: events,\n * reducer: (state, event) => ({ ...state, [event.id]: event }),\n * initialState: {},\n * });\n * ```\n */\n// Overload: custom reducer\nexport function useStream<TYield, TState, TReturn = void>(\n options: UseStreamReducerOptions<TYield, TState, TReturn>,\n): UseStreamReducerResult<TYield, TState>;\n\n// Overload: standard modes (accumulate, latest, maxItems)\nexport function useStream<TYield, TReturn = void>(\n options:\n | UseStreamAccumulateOptions<TYield, TReturn>\n | UseStreamLatestOptions<TYield, TReturn>\n | UseStreamMaxItemsOptions<TYield, TReturn>,\n): UseStreamResult<TYield, TReturn>;\n\n// Implementation\nexport function useStream<TYield, TReturn = void, TState = unknown>(\n options: UseStreamOptions<TYield, TReturn, TState>,\n): UseStreamResult<TYield, TReturn> | UseStreamReducerResult<TYield, TState> {\n const {\n source,\n queryKey,\n delay,\n onItem,\n onSuccess,\n onError,\n enabled = true,\n retry = false,\n } = options;\n\n // Extract mode options\n const mode = \"mode\" in options ? options.mode : undefined;\n const maxItems = \"maxItems\" in options ? options.maxItems : undefined;\n const customReducer = \"reducer\" in options ? options.reducer : undefined;\n const initialState = \"initialState\" in options ? options.initialState : undefined;\n\n const delayConfig = useMemo(() => normalizeDelay(delay), [delay]);\n\n // Track partial state for retry callback\n const partialStateRef = useRef<{\n items: TYield[];\n count: number;\n latest: TYield | null;\n }>({ items: [], count: 0, latest: null });\n\n // Create a stable query key\n const stableQueryKey = useMemo(() => queryKey ?? [\"stream\", source], [queryKey, source]);\n\n const query = useQuery({\n queryKey: stableQueryKey,\n enabled: enabled && source != null,\n queryFn: streamedQuery({\n async *streamFn() {\n // Reset partial state on new fetch\n partialStateRef.current = { items: [], count: 0, latest: null };\n\n const resolved = resolveStreamSource(source as StreamSource<TYield>);\n let isFirst = true;\n let itemCount = 0;\n\n const processDelay = async () => {\n if (isFirst && delayConfig.first) {\n await wait(delayConfig.first);\n isFirst = false;\n } else if (!isFirst && delayConfig.items) {\n await wait(delayConfig.items);\n } else {\n isFirst = false;\n }\n };\n\n const trackItem = (item: TYield) => {\n partialStateRef.current = {\n items: [...partialStateRef.current.items, item],\n count: partialStateRef.current.count + 1,\n latest: item,\n };\n };\n\n const emitItem = (item: TYield) => {\n onItem?.({\n item,\n count: partialStateRef.current.count,\n items: partialStateRef.current.items,\n });\n };\n\n // Handle sync Generator (captures return value)\n if (isGenerator<TYield, TReturn>(resolved)) {\n let result = resolved.next();\n while (!result.done) {\n await processDelay();\n itemCount += 1;\n trackItem(result.value);\n emitItem(result.value);\n yield {\n type: \"item\" as const,\n value: result.value,\n count: itemCount,\n };\n result = resolved.next();\n }\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"return\" as const, value: result.value };\n return;\n }\n\n // Handle AsyncGenerator (captures return value)\n if (isAsyncGenerator<TYield, TReturn>(resolved)) {\n let result = await resolved.next();\n while (!result.done) {\n await processDelay();\n itemCount += 1;\n trackItem(result.value);\n emitItem(result.value);\n yield {\n type: \"item\" as const,\n value: result.value,\n count: itemCount,\n };\n result = await resolved.next();\n }\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"return\" as const, value: result.value };\n return;\n }\n\n // Handle AsyncChannel (captures return value)\n if (resolved instanceof AsyncChannel) {\n const iterator = resolved[Symbol.asyncIterator]();\n let result = await iterator.next();\n while (!result.done) {\n await processDelay();\n itemCount += 1;\n trackItem(result.value);\n emitItem(result.value);\n yield {\n type: \"item\" as const,\n value: result.value,\n count: itemCount,\n };\n result = await iterator.next();\n }\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"return\" as const, value: result.value };\n return;\n }\n\n // Handle plain iterables/iterators (no return value)\n const iterable =\n isAsyncIterable(resolved) || isIterable(resolved)\n ? toAsyncIterable(resolved)\n : toAsyncIterable(resolved as AsyncIterator<TYield>);\n\n for await (const item of iterable) {\n await processDelay();\n itemCount += 1;\n trackItem(item);\n emitItem(item);\n yield { type: \"item\" as const, value: item, count: itemCount };\n }\n\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"complete\" as const };\n },\n\n reducer: (\n state:\n | StreamState<TYield, TReturn>\n | { state: TState; count: number; status: \"streaming\" | \"complete\" },\n chunk:\n | { type: \"item\"; value: TYield; count: number }\n | { type: \"return\"; value: TReturn }\n | { type: \"complete\" },\n ) => {\n // Custom reducer mode\n if (customReducer) {\n const s = state as {\n state: TState;\n count: number;\n status: \"streaming\" | \"complete\";\n };\n if (chunk.type === \"item\") {\n return {\n state: customReducer(s.state, chunk.value, chunk.count),\n count: chunk.count,\n status: \"streaming\" as const,\n };\n }\n return { ...s, status: \"complete\" as const };\n }\n\n // Standard modes\n const s = state as StreamState<TYield, TReturn>;\n if (chunk.type === \"item\") {\n let newItems: TYield[];\n if (mode === \"latest\") {\n // Only keep the latest item\n newItems = [chunk.value];\n } else if (maxItems !== undefined) {\n // Sliding window: keep last N items\n newItems = [...s.items, chunk.value].slice(-maxItems);\n } else {\n // Default: accumulate all\n newItems = [...s.items, chunk.value];\n }\n return {\n ...s,\n items: newItems,\n count: chunk.count,\n latest: chunk.value,\n status: \"streaming\" as const,\n };\n }\n if (chunk.type === \"return\") {\n return {\n ...s,\n returnValue: chunk.value,\n status: \"complete\" as const,\n };\n }\n // complete\n return { ...s, status: \"complete\" as const };\n },\n\n initialValue: customReducer\n ? {\n state: initialState as TState,\n count: 0,\n status: \"streaming\" as const,\n }\n : ({\n items: [] as TYield[],\n count: 0,\n latest: null,\n returnValue: undefined,\n status: \"streaming\" as const,\n } as StreamState<TYield, TReturn>),\n\n refetchMode: \"reset\",\n }),\n // Wrap our callback to include partial state data\n retry:\n typeof retry === \"function\"\n ? (failureCount: number, error: Error) =>\n retry({\n failureCount,\n error,\n ...partialStateRef.current,\n })\n : retry,\n staleTime: Number.POSITIVE_INFINITY,\n refetchOnWindowFocus: false,\n });\n\n // Track callback invocations to prevent duplicates\n const callbackFiredRef = useRef<\"success\" | \"error\" | null>(null);\n\n // Compute derived state for both paths\n const reducerData = customReducer\n ? ((query.data as\n | { state: TState; count: number; status: \"streaming\" | \"complete\" }\n | undefined) ?? {\n state: initialState as TState,\n count: 0,\n status: \"streaming\" as const,\n })\n : null;\n\n const standardData = customReducer\n ? null\n : ((query.data as StreamState<TYield, TReturn> | undefined) ?? {\n items: [] as TYield[],\n count: 0,\n latest: null,\n returnValue: undefined,\n status: \"streaming\" as const,\n });\n\n const isComplete = customReducer\n ? query.isSuccess && reducerData?.status === \"complete\"\n : query.isSuccess && standardData?.status === \"complete\";\n\n const isErrorState = query.isError;\n\n // Fire onSuccess/onError callbacks\n useEffect(() => {\n if (isComplete && callbackFiredRef.current !== \"success\") {\n callbackFiredRef.current = \"success\";\n if (standardData) {\n onSuccess?.({\n count: standardData.count,\n items: standardData.items,\n returnValue: standardData.returnValue,\n });\n } else if (reducerData) {\n // For reducer mode, we don't have items/returnValue in the same way\n // but we can still call onSuccess with partial data from ref\n onSuccess?.({\n count: reducerData.count,\n items: partialStateRef.current.items,\n returnValue: undefined as TReturn | undefined,\n });\n }\n }\n if (isErrorState && callbackFiredRef.current !== \"error\" && query.error) {\n callbackFiredRef.current = \"error\";\n onError?.({\n error: query.error,\n count: partialStateRef.current.count,\n items: partialStateRef.current.items,\n });\n }\n }, [isComplete, isErrorState, onSuccess, onError, standardData, reducerData, query.error]);\n\n // Reset callback tracking on refetch\n useEffect(() => {\n if (query.fetchStatus === \"fetching\" && callbackFiredRef.current !== null) {\n callbackFiredRef.current = null;\n }\n }, [query.fetchStatus]);\n\n // Handle custom reducer result\n if (customReducer && reducerData) {\n const isReducerLoading =\n query.fetchStatus === \"fetching\" && reducerData.count === 0 && !query.isError;\n const isReducerStreaming =\n query.fetchStatus === \"fetching\" &&\n reducerData.count > 0 &&\n reducerData.status === \"streaming\";\n\n return {\n query: query as UseQueryResult<\n { state: TState; count: number; status: \"streaming\" | \"complete\" },\n Error\n >,\n state: reducerData.state,\n count: reducerData.count,\n refetch: query.refetch,\n isLoading: isReducerLoading,\n isStreaming: isReducerStreaming,\n isSuccess: isComplete,\n isError: query.isError,\n } as UseStreamReducerResult<TYield, TState>;\n }\n\n // Standard result (standardData is guaranteed non-null after the reducer early return)\n const data = standardData ?? {\n items: [] as TYield[],\n count: 0,\n latest: null,\n returnValue: undefined as TReturn | undefined,\n status: \"streaming\" as const,\n };\n\n // isLoading: fetching but no data yet (not just isPending which is true when disabled)\n const isLoading = query.fetchStatus === \"fetching\" && data.count === 0 && !query.isError;\n const isStreaming =\n query.fetchStatus === \"fetching\" && data.count > 0 && data.status === \"streaming\";\n\n return {\n query: query as UseQueryResult<StreamState<TYield, TReturn>, Error>,\n items: data.items,\n count: data.count,\n latest: data.latest,\n returnValue: data.returnValue,\n refetch: query.refetch,\n isLoading,\n isStreaming,\n isSuccess: isComplete,\n isError: query.isError,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// StreamRenderer Component\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Renders different content based on stream state using TanStack Query.\n *\n * @example\n * ```tsx\n * <StreamRenderer\n * source={fetchItems}\n * queryKey={['items']}\n * loading={() => <Spinner />}\n * streaming={({ count, latest }) => <p>Received {count}: {latest.name}</p>}\n * success={({ items }) => <List items={items} />}\n * error={({ error }) => <Error message={error.message} />}\n * />\n * ```\n */\nexport function StreamRenderer<TYield, TReturn = void>({\n source,\n queryKey,\n delay,\n onItem,\n onSuccess,\n onError,\n loading,\n streaming,\n success,\n error,\n children,\n mode,\n maxItems,\n}: StreamRendererProps<TYield, TReturn>) {\n // Build useStream options based on mode/maxItems\n const streamOptions = useMemo(() => {\n const base = { source, queryKey, delay, onItem, onSuccess, onError };\n if (maxItems !== undefined) {\n return { ...base, maxItems };\n }\n if (mode === \"latest\") {\n return { ...base, mode: \"latest\" as const };\n }\n return base;\n }, [source, queryKey, delay, onItem, onSuccess, onError, mode, maxItems]);\n\n const stream = useStream<TYield, TReturn>(streamOptions);\n\n // Build children data based on current status\n const getChildrenData = (): ChildrenData<TYield, TReturn> => {\n if (stream.isError && stream.query.error) {\n return {\n status: \"error\",\n count: stream.count,\n items: stream.items,\n latest: stream.latest,\n returnValue: undefined,\n error: stream.query.error,\n };\n }\n if (stream.isSuccess) {\n return {\n status: \"success\",\n count: stream.count,\n items: stream.items,\n latest: stream.latest,\n returnValue: stream.returnValue,\n error: null,\n };\n }\n if (stream.isStreaming && stream.latest !== null) {\n return {\n status: \"streaming\",\n count: stream.count,\n items: stream.items,\n latest: stream.latest,\n returnValue: undefined,\n error: null,\n };\n }\n // Loading state\n return {\n status: \"loading\",\n count: 0,\n items: [] as TYield[],\n latest: null,\n returnValue: undefined,\n error: null,\n } as ChildrenData<TYield, TReturn>;\n };\n\n const childrenData = getChildrenData();\n const fallback = resolveRenderProp(children, childrenData, null);\n\n // Loading (before any data)\n if (stream.isLoading) {\n return resolveRenderPropNoData(loading, fallback);\n }\n\n // Error\n if (stream.isError && stream.query.error) {\n return resolveRenderProp(\n error,\n {\n error: stream.query.error,\n count: stream.count,\n items: stream.items,\n },\n fallback,\n );\n }\n\n // Success (stream complete)\n if (stream.isSuccess) {\n return resolveRenderProp(\n success,\n {\n count: stream.count,\n items: stream.items,\n returnValue: stream.returnValue,\n },\n fallback,\n );\n }\n\n // Streaming\n if (stream.isStreaming && stream.latest !== null) {\n return resolveRenderProp(\n streaming,\n {\n count: stream.count,\n latest: stream.latest,\n items: stream.items,\n },\n fallback,\n );\n }\n\n // Fallback (initial state before streaming starts)\n return resolveRenderPropNoData(loading, fallback);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entry-react.cjs","names":["resolveStreamSource","wait","isGenerator","isAsyncGenerator","AsyncChannel","isAsyncIterable","isIterable","toAsyncIterable","Component"],"sources":["../src/stream-renderer.tsx","../src/providers.tsx","../src/use-prevent-auto-focus.ts","../src/use-dynamic-refs.ts"],"sourcesContent":["import {\n AsyncChannel,\n isAsyncGenerator,\n isAsyncIterable,\n isGenerator,\n isIterable,\n resolveStreamSource,\n type StreamSource,\n toAsyncIterable,\n wait,\n} from \"./index.ts\";\nimport {\n experimental_streamedQuery as streamedQuery,\n type UseQueryResult,\n useQuery,\n} from \"@tanstack/react-query\";\nimport { type ReactNode, useEffect, useMemo, useRef } from \"react\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Render prop: can be ReactNode or function returning ReactNode */\ntype RenderProp<TData> = ReactNode | ((data: TData) => ReactNode);\n\n/** Render prop without data (for loading) */\ntype RenderPropNoData = ReactNode | (() => ReactNode);\n\n/** Stream state with accumulated items */\nexport type StreamState<TYield, TReturn = void> = {\n items: TYield[];\n count: number;\n latest: TYield | null;\n returnValue: TReturn | undefined;\n status: \"streaming\" | \"complete\";\n};\n\n/** Granular delay configuration */\nexport type DelayConfig = {\n items?: number;\n first?: number;\n result?: number;\n};\n\n/** Streaming data passed to streaming render prop */\nexport type StreamingData<TYield> = {\n count: number;\n latest: TYield;\n items: TYield[];\n};\n\n/** Success data passed to success render prop */\nexport type SuccessData<TYield, TReturn> = {\n count: number;\n items: TYield[];\n returnValue: TReturn | undefined;\n};\n\n/** Error data passed to error render prop */\nexport type ErrorData<TYield> = {\n error: Error;\n count: number;\n items: TYield[];\n};\n\n/** Data passed to onItem callback */\nexport type OnItemData<TYield> = {\n item: TYield;\n count: number;\n items: TYield[];\n};\n\n/** Data passed to onSuccess callback */\nexport type OnSuccessData<TYield, TReturn = void> = {\n count: number;\n items: TYield[];\n returnValue: TReturn | undefined;\n};\n\n/** Data passed to onError callback */\nexport type OnErrorData<TYield> = {\n error: Error;\n count: number;\n items: TYield[];\n};\n\n/** Children render prop data - includes status and all relevant data */\nexport type ChildrenData<TYield, TReturn = void> =\n | {\n status: \"loading\";\n count: 0;\n items: [];\n latest: null;\n returnValue: undefined;\n error: null;\n }\n | {\n status: \"streaming\";\n count: number;\n items: TYield[];\n latest: TYield;\n returnValue: undefined;\n error: null;\n }\n | {\n status: \"success\";\n count: number;\n items: TYield[];\n latest: TYield | null;\n returnValue: TReturn | undefined;\n error: null;\n }\n | {\n status: \"error\";\n count: number;\n items: TYield[];\n latest: TYield | null;\n returnValue: undefined;\n error: Error;\n };\n\n/** Base props for StreamRenderer */\nexport type StreamRendererBaseProps<TYield, TReturn = void> = {\n /** The stream source - can be instance or factory */\n source: StreamSource<TYield> | null | undefined;\n\n /** Unique key for the query cache */\n queryKey?: unknown[];\n /** Delay configuration for visualizing sync streams */\n delay?: number | DelayConfig;\n /** Called for each item received */\n onItem?: (data: OnItemData<TYield>) => void;\n /** Called when stream completes successfully */\n onSuccess?: (data: OnSuccessData<TYield, TReturn>) => void;\n /** Called when stream errors */\n onError?: (data: OnErrorData<TYield>) => void;\n /** Whether to start streaming immediately (default: true) */\n enabled?: boolean;\n /**\n * Retry configuration on error (default: false = no retries)\n * - `false`: no retries\n * - `true`: use TanStack Query default (3 retries)\n * - `number`: retry that many times\n * - `(data) => boolean`: custom retry logic with access to partial data\n */\n retry?: boolean | number | ((data: RetryData<TYield>) => boolean);\n};\n\n/** Props for StreamRenderer - supports all accumulation modes */\nexport type StreamRendererProps<TYield, TReturn = void> = StreamRendererBaseProps<\n TYield,\n TReturn\n> & {\n /**\n * Accumulation mode (mutually exclusive with maxItems)\n * - `\"accumulate\"` (default): keep all items\n * - `\"latest\"`: only keep the most recent item\n */\n mode?: StreamMode;\n /** Keep only the last N items (mutually exclusive with mode) */\n maxItems?: number;\n /** Render while loading - ReactNode or () => ReactNode */\n loading?: RenderPropNoData;\n /** Render while streaming - ReactNode or (data) => ReactNode */\n streaming?: RenderProp<StreamingData<TYield>>;\n /** Render on success - ReactNode or (data) => ReactNode */\n success?: RenderProp<SuccessData<TYield, TReturn>>;\n /** Render on error - ReactNode or (data) => ReactNode */\n error?: RenderProp<ErrorData<TYield>>;\n /** Fallback/universal render prop - ReactNode or (data) => ReactNode */\n children?: RenderProp<ChildrenData<TYield, TReturn>>;\n};\n\n/** Data passed to retry callback */\nexport type RetryData<TYield> = {\n /** Number of times the query has failed (starts at 0) */\n failureCount: number;\n /** The error that caused the failure */\n error: Error;\n /** Items collected before the error */\n items: TYield[];\n /** Number of items collected before the error */\n count: number;\n /** Last item received before the error (null if none) */\n latest: TYield | null;\n};\n\n/**\n * Accumulation mode for stream items.\n * - `\"accumulate\"` (default): keep all items in memory\n * - `\"latest\"`: only keep the most recent item\n */\nexport type StreamMode = \"accumulate\" | \"latest\";\n\n/**\n * Custom reducer for stream state.\n * Receives current state and new item, returns new state.\n */\nexport type StreamReducer<TYield, TState> = (state: TState, item: TYield, count: number) => TState;\n\n/** Base options shared by all useStream configurations */\ntype UseStreamBaseOptions<TYield, TReturn = void> = {\n /** The stream source - can be instance or factory */\n source: StreamSource<TYield> | null | undefined;\n /** Unique key for the query cache */\n queryKey?: unknown[];\n /** Delay configuration for visualizing sync streams */\n delay?: number | DelayConfig;\n /** Called for each item received */\n onItem?: (data: OnItemData<TYield>) => void;\n /** Called when stream completes successfully */\n onSuccess?: (data: OnSuccessData<TYield, TReturn>) => void;\n /** Called when stream errors */\n onError?: (data: OnErrorData<TYield>) => void;\n /** Whether to start streaming immediately (default: true) */\n enabled?: boolean;\n /**\n * Retry configuration on error (default: false = no retries)\n * - `false`: no retries\n * - `true`: use TanStack Query default (3 retries)\n * - `number`: retry that many times\n * - `(data) => boolean`: custom retry logic with access to partial data\n */\n retry?: boolean | number | ((data: RetryData<TYield>) => boolean);\n};\n\n/** Options with default accumulation (all items) */\ntype UseStreamAccumulateOptions<TYield, TReturn = void> = UseStreamBaseOptions<TYield, TReturn> & {\n mode?: \"accumulate\";\n maxItems?: never;\n reducer?: never;\n};\n\n/** Options with \"latest\" mode (only keep last item) */\ntype UseStreamLatestOptions<TYield, TReturn = void> = UseStreamBaseOptions<TYield, TReturn> & {\n mode: \"latest\";\n maxItems?: never;\n reducer?: never;\n};\n\n/** Options with maxItems (sliding window) */\ntype UseStreamMaxItemsOptions<TYield, TReturn = void> = UseStreamBaseOptions<TYield, TReturn> & {\n mode?: never;\n /** Keep only the last N items (sliding window) */\n maxItems: number;\n reducer?: never;\n};\n\n/** Options with custom reducer */\ntype UseStreamReducerOptions<TYield, TState, TReturn = void> = UseStreamBaseOptions<\n TYield,\n TReturn\n> & {\n mode?: never;\n maxItems?: never;\n /** Custom reducer function for complete control over state */\n reducer: StreamReducer<TYield, TState>;\n /** Initial state for custom reducer */\n initialState: TState;\n};\n\n/** Props for useStream hook - mutually exclusive accumulation options */\nexport type UseStreamOptions<TYield, TReturn = void, TState = unknown> =\n | UseStreamAccumulateOptions<TYield, TReturn>\n | UseStreamLatestOptions<TYield, TReturn>\n | UseStreamMaxItemsOptions<TYield, TReturn>\n | UseStreamReducerOptions<TYield, TState, TReturn>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction normalizeDelay(delay: number | DelayConfig | undefined): DelayConfig {\n if (delay === undefined) {\n return {};\n }\n if (typeof delay === \"number\") {\n return { first: delay, items: delay, result: delay };\n }\n return delay;\n}\n\n/** Resolve a render prop (ReactNode or function) */\nfunction resolveRenderProp<TData>(\n prop: RenderProp<TData> | undefined,\n data: TData,\n fallback: ReactNode,\n): ReactNode {\n if (prop === undefined) {\n return fallback;\n }\n if (typeof prop === \"function\") {\n return prop(data);\n }\n return prop;\n}\n\n/** Resolve a render prop without data (for loading) */\nfunction resolveRenderPropNoData(\n prop: RenderPropNoData | undefined,\n fallback: ReactNode,\n): ReactNode {\n if (prop === undefined) {\n return fallback;\n }\n if (typeof prop === \"function\") {\n return prop();\n }\n return prop;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// useStream Hook\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Result type for standard useStream (accumulate, latest, maxItems modes) */\nexport type UseStreamResult<TYield, TReturn = void> = {\n query: UseQueryResult<StreamState<TYield, TReturn>, Error>;\n /** Items received (all, last N, or just latest depending on mode) */\n items: TYield[];\n /** Total number of items received (even if not all kept in memory) */\n count: number;\n /** Most recent item */\n latest: TYield | null;\n /** Return value from generator (if complete) */\n returnValue: TReturn | undefined;\n /** Restart the stream */\n refetch: () => void;\n // Status flags\n isLoading: boolean;\n isStreaming: boolean;\n isSuccess: boolean;\n isError: boolean;\n};\n\n/** Result type for useStream with custom reducer */\nexport type UseStreamReducerResult<_TYield, TState> = {\n query: UseQueryResult<{ state: TState; count: number; status: \"streaming\" | \"complete\" }, Error>;\n /** Custom state from reducer */\n state: TState;\n /** Total number of items received */\n count: number;\n /** Restart the stream */\n refetch: () => void;\n // Status flags\n isLoading: boolean;\n isStreaming: boolean;\n isSuccess: boolean;\n isError: boolean;\n};\n\n/**\n * Hook for consuming streams using TanStack Query's streamedQuery.\n *\n * @example\n * ```tsx\n * // Default: accumulate all items\n * const { items, isStreaming, count } = useStream({\n * source: fetchItems,\n * queryKey: ['my-stream'],\n * });\n *\n * // Latest only: keep just the most recent item\n * const { items, latest } = useStream({\n * source: sseEvents,\n * mode: \"latest\",\n * });\n *\n * // Sliding window: keep last N items\n * const { items } = useStream({\n * source: logStream,\n * maxItems: 100,\n * });\n *\n * // Custom reducer: full control over state\n * const { state } = useStream({\n * source: events,\n * reducer: (state, event) => ({ ...state, [event.id]: event }),\n * initialState: {},\n * });\n * ```\n */\n// Overload: custom reducer\nexport function useStream<TYield, TState, TReturn = void>(\n options: UseStreamReducerOptions<TYield, TState, TReturn>,\n): UseStreamReducerResult<TYield, TState>;\n\n// Overload: standard modes (accumulate, latest, maxItems)\nexport function useStream<TYield, TReturn = void>(\n options:\n | UseStreamAccumulateOptions<TYield, TReturn>\n | UseStreamLatestOptions<TYield, TReturn>\n | UseStreamMaxItemsOptions<TYield, TReturn>,\n): UseStreamResult<TYield, TReturn>;\n\n// Implementation\nexport function useStream<TYield, TReturn = void, TState = unknown>(\n options: UseStreamOptions<TYield, TReturn, TState>,\n): UseStreamResult<TYield, TReturn> | UseStreamReducerResult<TYield, TState> {\n const {\n source,\n queryKey,\n delay,\n onItem,\n onSuccess,\n onError,\n enabled = true,\n retry = false,\n } = options;\n\n // Extract mode options\n const mode = \"mode\" in options ? options.mode : undefined;\n const maxItems = \"maxItems\" in options ? options.maxItems : undefined;\n const customReducer = \"reducer\" in options ? options.reducer : undefined;\n const initialState = \"initialState\" in options ? options.initialState : undefined;\n\n const delayConfig = useMemo(() => normalizeDelay(delay), [delay]);\n\n // Track partial state for retry callback\n const partialStateRef = useRef<{\n items: TYield[];\n count: number;\n latest: TYield | null;\n }>({ items: [], count: 0, latest: null });\n\n // Create a stable query key\n const stableQueryKey = useMemo(() => queryKey ?? [\"stream\", source], [queryKey, source]);\n\n const query = useQuery({\n queryKey: stableQueryKey,\n enabled: enabled && source != null,\n queryFn: streamedQuery({\n async *streamFn() {\n // Reset partial state on new fetch\n partialStateRef.current = { items: [], count: 0, latest: null };\n\n const resolved = resolveStreamSource(source as StreamSource<TYield>);\n let isFirst = true;\n let itemCount = 0;\n\n const processDelay = async () => {\n if (isFirst && delayConfig.first) {\n await wait(delayConfig.first);\n isFirst = false;\n } else if (!isFirst && delayConfig.items) {\n await wait(delayConfig.items);\n } else {\n isFirst = false;\n }\n };\n\n const trackItem = (item: TYield) => {\n partialStateRef.current = {\n items: [...partialStateRef.current.items, item],\n count: partialStateRef.current.count + 1,\n latest: item,\n };\n };\n\n const emitItem = (item: TYield) => {\n onItem?.({\n item,\n count: partialStateRef.current.count,\n items: partialStateRef.current.items,\n });\n };\n\n // Handle sync Generator (captures return value)\n if (isGenerator<TYield, TReturn>(resolved)) {\n let result = resolved.next();\n while (!result.done) {\n await processDelay();\n itemCount += 1;\n trackItem(result.value);\n emitItem(result.value);\n yield {\n type: \"item\" as const,\n value: result.value,\n count: itemCount,\n };\n result = resolved.next();\n }\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"return\" as const, value: result.value };\n return;\n }\n\n // Handle AsyncGenerator (captures return value)\n if (isAsyncGenerator<TYield, TReturn>(resolved)) {\n let result = await resolved.next();\n while (!result.done) {\n await processDelay();\n itemCount += 1;\n trackItem(result.value);\n emitItem(result.value);\n yield {\n type: \"item\" as const,\n value: result.value,\n count: itemCount,\n };\n result = await resolved.next();\n }\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"return\" as const, value: result.value };\n return;\n }\n\n // Handle AsyncChannel (captures return value)\n if (resolved instanceof AsyncChannel) {\n const iterator = resolved[Symbol.asyncIterator]();\n let result = await iterator.next();\n while (!result.done) {\n await processDelay();\n itemCount += 1;\n trackItem(result.value);\n emitItem(result.value);\n yield {\n type: \"item\" as const,\n value: result.value,\n count: itemCount,\n };\n result = await iterator.next();\n }\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"return\" as const, value: result.value };\n return;\n }\n\n // Handle plain iterables/iterators (no return value)\n const iterable =\n isAsyncIterable(resolved) || isIterable(resolved)\n ? toAsyncIterable(resolved)\n : toAsyncIterable(resolved as AsyncIterator<TYield>);\n\n for await (const item of iterable) {\n await processDelay();\n itemCount += 1;\n trackItem(item);\n emitItem(item);\n yield { type: \"item\" as const, value: item, count: itemCount };\n }\n\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n yield { type: \"complete\" as const };\n },\n\n reducer: (\n state:\n | StreamState<TYield, TReturn>\n | { state: TState; count: number; status: \"streaming\" | \"complete\" },\n chunk:\n | { type: \"item\"; value: TYield; count: number }\n | { type: \"return\"; value: TReturn }\n | { type: \"complete\" },\n ) => {\n // Custom reducer mode\n if (customReducer) {\n const s = state as {\n state: TState;\n count: number;\n status: \"streaming\" | \"complete\";\n };\n if (chunk.type === \"item\") {\n return {\n state: customReducer(s.state, chunk.value, chunk.count),\n count: chunk.count,\n status: \"streaming\" as const,\n };\n }\n return { ...s, status: \"complete\" as const };\n }\n\n // Standard modes\n const s = state as StreamState<TYield, TReturn>;\n if (chunk.type === \"item\") {\n let newItems: TYield[];\n if (mode === \"latest\") {\n // Only keep the latest item\n newItems = [chunk.value];\n } else if (maxItems !== undefined) {\n // Sliding window: keep last N items\n newItems = [...s.items, chunk.value].slice(-maxItems);\n } else {\n // Default: accumulate all\n newItems = [...s.items, chunk.value];\n }\n return {\n ...s,\n items: newItems,\n count: chunk.count,\n latest: chunk.value,\n status: \"streaming\" as const,\n };\n }\n if (chunk.type === \"return\") {\n return {\n ...s,\n returnValue: chunk.value,\n status: \"complete\" as const,\n };\n }\n // complete\n return { ...s, status: \"complete\" as const };\n },\n\n initialValue: customReducer\n ? {\n state: initialState as TState,\n count: 0,\n status: \"streaming\" as const,\n }\n : ({\n items: [] as TYield[],\n count: 0,\n latest: null,\n returnValue: undefined,\n status: \"streaming\" as const,\n } as StreamState<TYield, TReturn>),\n\n refetchMode: \"reset\",\n }),\n // Wrap our callback to include partial state data\n retry:\n typeof retry === \"function\"\n ? (failureCount: number, error: Error) =>\n retry({\n failureCount,\n error,\n ...partialStateRef.current,\n })\n : retry,\n staleTime: Number.POSITIVE_INFINITY,\n refetchOnWindowFocus: false,\n });\n\n // Track callback invocations to prevent duplicates\n const callbackFiredRef = useRef<\"success\" | \"error\" | null>(null);\n\n // Compute derived state for both paths\n const reducerData = customReducer\n ? ((query.data as\n | { state: TState; count: number; status: \"streaming\" | \"complete\" }\n | undefined) ?? {\n state: initialState as TState,\n count: 0,\n status: \"streaming\" as const,\n })\n : null;\n\n const standardData = customReducer\n ? null\n : ((query.data as StreamState<TYield, TReturn> | undefined) ?? {\n items: [] as TYield[],\n count: 0,\n latest: null,\n returnValue: undefined,\n status: \"streaming\" as const,\n });\n\n const isComplete = customReducer\n ? query.isSuccess && reducerData?.status === \"complete\"\n : query.isSuccess && standardData?.status === \"complete\";\n\n const isErrorState = query.isError;\n\n // Fire onSuccess/onError callbacks\n useEffect(() => {\n if (isComplete && callbackFiredRef.current !== \"success\") {\n callbackFiredRef.current = \"success\";\n if (standardData) {\n onSuccess?.({\n count: standardData.count,\n items: standardData.items,\n returnValue: standardData.returnValue,\n });\n } else if (reducerData) {\n // For reducer mode, we don't have items/returnValue in the same way\n // but we can still call onSuccess with partial data from ref\n onSuccess?.({\n count: reducerData.count,\n items: partialStateRef.current.items,\n returnValue: undefined as TReturn | undefined,\n });\n }\n }\n if (isErrorState && callbackFiredRef.current !== \"error\" && query.error) {\n callbackFiredRef.current = \"error\";\n onError?.({\n error: query.error,\n count: partialStateRef.current.count,\n items: partialStateRef.current.items,\n });\n }\n }, [isComplete, isErrorState, onSuccess, onError, standardData, reducerData, query.error]);\n\n // Reset callback tracking on refetch\n useEffect(() => {\n if (query.fetchStatus === \"fetching\" && callbackFiredRef.current !== null) {\n callbackFiredRef.current = null;\n }\n }, [query.fetchStatus]);\n\n // Handle custom reducer result\n if (customReducer && reducerData) {\n const isReducerLoading =\n query.fetchStatus === \"fetching\" && reducerData.count === 0 && !query.isError;\n const isReducerStreaming =\n query.fetchStatus === \"fetching\" &&\n reducerData.count > 0 &&\n reducerData.status === \"streaming\";\n\n return {\n query: query as UseQueryResult<\n { state: TState; count: number; status: \"streaming\" | \"complete\" },\n Error\n >,\n state: reducerData.state,\n count: reducerData.count,\n refetch: query.refetch,\n isLoading: isReducerLoading,\n isStreaming: isReducerStreaming,\n isSuccess: isComplete,\n isError: query.isError,\n } as UseStreamReducerResult<TYield, TState>;\n }\n\n // Standard result (standardData is guaranteed non-null after the reducer early return)\n const data = standardData ?? {\n items: [] as TYield[],\n count: 0,\n latest: null,\n returnValue: undefined as TReturn | undefined,\n status: \"streaming\" as const,\n };\n\n // isLoading: fetching but no data yet (not just isPending which is true when disabled)\n const isLoading = query.fetchStatus === \"fetching\" && data.count === 0 && !query.isError;\n const isStreaming =\n query.fetchStatus === \"fetching\" && data.count > 0 && data.status === \"streaming\";\n\n return {\n query: query as UseQueryResult<StreamState<TYield, TReturn>, Error>,\n items: data.items,\n count: data.count,\n latest: data.latest,\n returnValue: data.returnValue,\n refetch: query.refetch,\n isLoading,\n isStreaming,\n isSuccess: isComplete,\n isError: query.isError,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// StreamRenderer Component\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Renders different content based on stream state using TanStack Query.\n *\n * @example\n * ```tsx\n * <StreamRenderer\n * source={fetchItems}\n * queryKey={['items']}\n * loading={() => <Spinner />}\n * streaming={({ count, latest }) => <p>Received {count}: {latest.name}</p>}\n * success={({ items }) => <List items={items} />}\n * error={({ error }) => <Error message={error.message} />}\n * />\n * ```\n */\nexport function StreamRenderer<TYield, TReturn = void>({\n source,\n queryKey,\n delay,\n onItem,\n onSuccess,\n onError,\n loading,\n streaming,\n success,\n error,\n children,\n mode,\n maxItems,\n}: StreamRendererProps<TYield, TReturn>) {\n // Build useStream options based on mode/maxItems\n const streamOptions = useMemo(() => {\n const base = { source, queryKey, delay, onItem, onSuccess, onError };\n if (maxItems !== undefined) {\n return { ...base, maxItems };\n }\n if (mode === \"latest\") {\n return { ...base, mode: \"latest\" as const };\n }\n return base;\n }, [source, queryKey, delay, onItem, onSuccess, onError, mode, maxItems]);\n\n const stream = useStream<TYield, TReturn>(streamOptions);\n\n // Build children data based on current status\n const getChildrenData = (): ChildrenData<TYield, TReturn> => {\n if (stream.isError && stream.query.error) {\n return {\n status: \"error\",\n count: stream.count,\n items: stream.items,\n latest: stream.latest,\n returnValue: undefined,\n error: stream.query.error,\n };\n }\n if (stream.isSuccess) {\n return {\n status: \"success\",\n count: stream.count,\n items: stream.items,\n latest: stream.latest,\n returnValue: stream.returnValue,\n error: null,\n };\n }\n if (stream.isStreaming && stream.latest !== null) {\n return {\n status: \"streaming\",\n count: stream.count,\n items: stream.items,\n latest: stream.latest,\n returnValue: undefined,\n error: null,\n };\n }\n // Loading state\n return {\n status: \"loading\",\n count: 0,\n items: [] as TYield[],\n latest: null,\n returnValue: undefined,\n error: null,\n } as ChildrenData<TYield, TReturn>;\n };\n\n const childrenData = getChildrenData();\n const fallback = resolveRenderProp(children, childrenData, null);\n\n // Loading (before any data)\n if (stream.isLoading) {\n return resolveRenderPropNoData(loading, fallback);\n }\n\n // Error\n if (stream.isError && stream.query.error) {\n return resolveRenderProp(\n error,\n {\n error: stream.query.error,\n count: stream.count,\n items: stream.items,\n },\n fallback,\n );\n }\n\n // Success (stream complete)\n if (stream.isSuccess) {\n return resolveRenderProp(\n success,\n {\n count: stream.count,\n items: stream.items,\n returnValue: stream.returnValue,\n },\n fallback,\n );\n }\n\n // Streaming\n if (stream.isStreaming && stream.latest !== null) {\n return resolveRenderProp(\n streaming,\n {\n count: stream.count,\n latest: stream.latest,\n items: stream.items,\n },\n fallback,\n );\n }\n\n // Fallback (initial state before streaming starts)\n return resolveRenderPropNoData(loading, fallback);\n}\n","import type { ComponentType, ReactNode } from \"react\";\n\ntype AnyProps = Record<string, unknown>;\n\n/**\n * A provider entry for {@link Providers}.\n *\n * This is intentionally \"erased\" (non-generic) so you can compose a heterogeneous list of\n * providers (each with different props) in one array.\n *\n * Use {@link provider} to create a `ProviderSpec` with correct prop inference.\n */\nexport type ProviderSpec = readonly [Provider: ComponentType<unknown>, props: AnyProps];\n\ntype RequiredKeys<T> = {\n // biome-ignore lint/complexity/noBannedTypes: Intentional optional-key detection pattern.\n [K in keyof T]-?: {} extends Pick<T, K> ? never : K;\n}[keyof T];\n\ntype ProviderInputProps<TProps extends AnyProps> = Omit<TProps, \"children\">;\n\ntype ProviderArgProps<TProps extends AnyProps> = keyof ProviderInputProps<TProps> extends never\n ? Record<string, never>\n : ProviderInputProps<TProps>;\n\ntype ProviderPropsArg<TProps extends AnyProps> =\n RequiredKeys<ProviderInputProps<TProps>> extends never\n ? [props?: ProviderArgProps<TProps>]\n : [props: ProviderInputProps<TProps>];\n\n/**\n * Create a typed `ProviderSpec` tuple with prop inference and required-prop enforcement.\n *\n * - If a provider has required props (excluding `children`), the props argument is required.\n * - If a provider has no props (excluding `children`), the props argument is optional and\n * rejects extra keys.\n *\n * @param Provider - The React component to use as a provider.\n * @param args - Props to pass to the provider (excluding `children`).\n * @returns A `ProviderSpec` tuple.\n *\n * @example\n * ```tsx\n * import { provider, Providers } from \"xantiagoma/react\";\n *\n * // Provider with required props\n * provider(ThemeProvider, { theme: \"dark\" })\n *\n * // Provider with no props (optional)\n * provider(PermissionsProvider)\n * ```\n */\nexport const provider = <TProps extends AnyProps>(\n Provider: ComponentType<TProps>,\n ...args: ProviderPropsArg<TProps>\n): ProviderSpec => {\n const props = (args[0] ?? {}) as AnyProps;\n return [Provider as ComponentType<unknown>, props] as const;\n};\n\n/**\n * Props for the {@link Providers} component.\n */\nexport type ProvidersProps = {\n /** List of providers (outer → inner), created with {@link provider}. */\n providers: readonly ProviderSpec[];\n children?: ReactNode;\n};\n\nconst getProviderKey = (providerComponent: unknown, index: number): string => {\n const providerName =\n (providerComponent as { displayName?: string }).displayName ??\n (providerComponent as { name?: string }).name ??\n \"Provider\";\n return `${providerName}:${index}`;\n};\n\nfunction ProviderComponent({\n Provider,\n props,\n children,\n}: {\n Provider: ComponentType<unknown>;\n props: AnyProps;\n children: ReactNode;\n}): ReactNode {\n const Component = Provider as ComponentType<AnyProps & { children?: ReactNode }>;\n return <Component {...props}>{children}</Component>;\n}\n\n/**\n * Compose multiple React providers without deeply nesting JSX.\n * Eliminates \"provider hell\" by flattening the tree into an array.\n *\n * @example\n * ```tsx\n * import { Providers, provider } from \"xantiagoma/react\";\n *\n * <Providers\n * providers={[\n * provider(QueryClientProvider, { client: queryClient }),\n * provider(ThemeProvider, { theme: \"dark\" }),\n * provider(AuthProvider),\n * ]}\n * >\n * <App />\n * </Providers>\n * ```\n */\nexport const Providers = ({ providers, children }: ProvidersProps): ReactNode =>\n providers.reduceRight<ReactNode>(\n (acc, [Provider, props], index) => (\n <ProviderComponent key={getProviderKey(Provider, index)} Provider={Provider} props={props}>\n {acc}\n </ProviderComponent>\n ),\n children ?? null,\n );\n","import { useCallback, useRef } from \"react\";\n\n/**\n * Hook that prevents auto-focus behavior while still maintaining focus management.\n * Useful for modal dialogs (e.g. Radix Dialog) where you want to control the initial focus.\n *\n * @template E - Element type, defaults to HTMLDivElement\n * @returns Object containing:\n * - `ref` — React ref to attach to the element\n * - `onOpenAutoFocus` — Event handler to prevent default focus behavior\n * - `tabIndex` — `-1` to make the element programmatically focusable\n *\n * @example\n * ```tsx\n * import { usePreventAutoFocus } from \"xantiagoma/react\";\n *\n * function MyDialog() {\n * const preventAutoFocus = usePreventAutoFocus();\n *\n * return (\n * <DialogContent {...preventAutoFocus}>\n * <p>Dialog content here</p>\n * </DialogContent>\n * );\n * }\n * ```\n */\nexport function usePreventAutoFocus<E extends HTMLElement = HTMLDivElement>() {\n const ref = useRef<E>(null);\n\n const onOpenAutoFocus = useCallback((event: Event) => {\n event.preventDefault();\n ref.current?.focus({ preventScroll: true });\n }, []);\n\n return { ref, onOpenAutoFocus, tabIndex: -1 as const };\n}\n","import { createRef, useMemo, type RefObject } from \"react\";\n\n/**\n * Options for {@link useDynamicRefs}.\n */\nexport interface UseDynamicRefsOptions {\n /** Optional namespace prefix. Changing the prefix creates a new ref map. */\n prefix?: string;\n}\n\n/** The internal Map type for a given ref element type. */\nexport type RefMap<T> = Map<string, RefObject<T | null>>;\n\n/**\n * Returns a ref for the given key, creating one if it doesn't exist yet.\n * Overloads ensure that calling without a key returns `undefined`.\n */\nexport interface DynamicRefGetter<T> {\n (): undefined;\n (key: string): RefObject<T | null>;\n}\n\nfunction createRefGetter<T>(refMap: RefMap<T>): DynamicRefGetter<T> {\n function getRef(): undefined;\n function getRef(key: string): RefObject<T | null>;\n function getRef(key?: string): RefObject<T | null> | undefined {\n if (!key) {\n return undefined;\n }\n const existing = refMap.get(key);\n if (existing) return existing;\n\n const ref = createRef<T>();\n refMap.set(key, ref);\n return ref;\n }\n return getRef as DynamicRefGetter<T>;\n}\n\n/**\n * React hook that returns a ref \"getter\" function.\n * Call the getter with a string key to get (or create) a `RefObject` for that key.\n * Useful for dynamic lists, tabs, or any UI where the number of refs isn't known at compile time.\n *\n * @template T - The element type for the refs (e.g. `HTMLDivElement`)\n * @param options - Optional configuration\n * @returns A {@link DynamicRefGetter} function\n *\n * @example\n * ```tsx\n * import { useDynamicRefs } from \"xantiagoma/react\";\n *\n * function TabList({ tabs }: { tabs: string[] }) {\n * const getRef = useDynamicRefs<HTMLButtonElement>();\n *\n * return (\n * <div>\n * {tabs.map((tab) => (\n * <button key={tab} ref={getRef(tab)}>\n * {tab}\n * </button>\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Scroll to a specific item\n * const getRef = useDynamicRefs<HTMLDivElement>();\n *\n * function scrollTo(id: string) {\n * getRef(id)?.current?.scrollIntoView({ behavior: \"smooth\" });\n * }\n * ```\n */\nexport function useDynamicRefs<T>(options?: UseDynamicRefsOptions): DynamicRefGetter<T> {\n const prefix = options?.prefix;\n\n const getRef = useMemo(() => {\n const refMap: RefMap<T> = new Map();\n return createRefGetter<T>(refMap);\n }, [prefix]);\n\n return getRef;\n}\n"],"mappings":"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"}
|