tempest-react-sdk 0.60.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +9 -7
  2. package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -1
  3. package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -1
  4. package/dist/components/PasswordInput/PasswordInput.module.cjs.map +1 -1
  5. package/dist/components/PasswordInput/PasswordInput.module.js.map +1 -1
  6. package/dist/components/PinInput/PinInput.module.cjs.map +1 -1
  7. package/dist/components/PinInput/PinInput.module.js.map +1 -1
  8. package/dist/sse/create-event-stream.cjs +1 -1
  9. package/dist/sse/create-event-stream.cjs.map +1 -1
  10. package/dist/sse/create-event-stream.js +32 -27
  11. package/dist/sse/create-event-stream.js.map +1 -1
  12. package/dist/sse/use-event-stream.cjs.map +1 -1
  13. package/dist/sse/use-event-stream.js.map +1 -1
  14. package/dist/styles/ImageCropper.css +3 -3
  15. package/dist/styles/PasswordInput.css +1 -1
  16. package/dist/styles/PinInput.css +1 -1
  17. package/dist/styles/base.css +1 -1
  18. package/dist/styles/core.css +3 -3
  19. package/dist/styles/forms.css +5 -5
  20. package/dist/styles/scoped.css +1 -1
  21. package/dist/styles/tokens.css +2 -2
  22. package/dist/styles.css +1 -1
  23. package/dist/tempest-react-sdk.d.ts +180 -21
  24. package/dist/utils/dev-mode.cjs.map +1 -1
  25. package/dist/utils/dev-mode.js.map +1 -1
  26. package/dist/utils/json-frame.cjs +1 -1
  27. package/dist/utils/json-frame.cjs.map +1 -1
  28. package/dist/utils/json-frame.js +33 -14
  29. package/dist/utils/json-frame.js.map +1 -1
  30. package/dist/utils/schema-like.cjs +2 -0
  31. package/dist/utils/schema-like.cjs.map +1 -0
  32. package/dist/utils/schema-like.js +43 -0
  33. package/dist/utils/schema-like.js.map +1 -0
  34. package/dist/ws/create-web-socket.cjs +1 -1
  35. package/dist/ws/create-web-socket.cjs.map +1 -1
  36. package/dist/ws/create-web-socket.js +54 -38
  37. package/dist/ws/create-web-socket.js.map +1 -1
  38. package/dist/ws/use-web-socket.cjs +1 -1
  39. package/dist/ws/use-web-socket.cjs.map +1 -1
  40. package/dist/ws/use-web-socket.js +5 -3
  41. package/dist/ws/use-web-socket.js.map +1 -1
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -273,8 +273,8 @@ Every module is re-exported from the package root — `import { Button, useDebou
273
273
  | `app` | `AppProviders` (composes `ErrorBoundary` → `QueryProvider` → `ThemeProvider` → `I18nProvider`), type: `AppProvidersProps` |
274
274
  | `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev`), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `AppleSplashSpec`, `TempestVitePlugin` |
275
275
  | `forms` _(peer: `zod`, `react-hook-form`)_ | `validateForm`, `zodResolver`, `useZodForm`, `validateCPF`, `validateCNPJ`, `formatCEP`, `formatCNPJ`, `unmask`, `CPFInput`, `CNPJInput`, `PhoneInput`, `CEPInput`, `MoneyInput`, `useViaCEP` |
276
- | `sse` | `createEventStream`, `useEventStream` (`onParseError` para frame que não é JSON) |
277
- | `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
276
+ | `sse` | `createEventStream`, `useEventStream` (`schema` valida cada frame — zod/valibot/arktype via Standard Schema — e `onParseError` para frame que não é JSON) |
277
+ | `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `schema` + `onValidationError` (frame fora do contrato não é entregue; o `pong` do heartbeat continua saindo), `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
278
278
  | `webrtc` | `tuneOpus` (Opus `fmtp` per audio m-line: per-key merge, `stereo` + `sprop-stereo`, inserts a missing `fmtp`), `setTunedLocalDescription` (falls back to the untouched SDP when the browser refuses the edit), `setSenderBitrate`, `createLinkStatsSampler` / `useLinkStats` / `readRoundTripMs` (throughput as a delta, round trip from the **selected** candidate pair) |
279
279
  | `geo` _(opt. peer `leaflet`)_ | `haversineKm`, `pathLengthKm`, `bearingDeg`, `estimateTravel`, `createOSRMBackend`, `boundingBox`, `projectMercator`, `fitProjection`, `createPositionTracker`, `usePositionTracker`, `TrajectoryMap` — tile-free SVG map + trajectory tracking (no external/paid API; Leaflet is an opt-in, lazy-loaded tile layer for self-hosted tiles), types: `Coordinate`, `TrackPoint`, `TravelEstimate`, `TravelMode`, `GeoBounds`, `RoutingBackend` |
280
280
  | `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
@@ -308,8 +308,8 @@ Every module is re-exported from the package root — `import { Button, useDebou
308
308
  | `app` | `AppProviders` (composes `ErrorBoundary` → `QueryProvider` → `ThemeProvider` → `I18nProvider`), type: `AppProvidersProps` |
309
309
  | `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev`), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `AppleSplashSpec`, `TempestVitePlugin` |
310
310
  | `forms` _(peer: `zod`, `react-hook-form`)_ | `validateForm`, `zodResolver`, `useZodForm`, `validateCPF`, `validateCNPJ`, `formatCEP`, `formatCNPJ`, `unmask`, `CPFInput`, `CNPJInput`, `PhoneInput`, `CEPInput`, `MoneyInput`, `useViaCEP` |
311
- | `sse` | `createEventStream`, `useEventStream` (`onParseError` para frame que não é JSON) |
312
- | `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
311
+ | `sse` | `createEventStream`, `useEventStream` (`schema` valida cada frame — zod/valibot/arktype via Standard Schema — e `onParseError` para frame que não é JSON) |
312
+ | `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `schema` + `onValidationError` (frame fora do contrato não é entregue; o `pong` do heartbeat continua saindo), `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
313
313
  | `webrtc` | `tuneOpus` (Opus `fmtp` per audio m-line: per-key merge, `stereo` + `sprop-stereo`, inserts a missing `fmtp`), `setTunedLocalDescription` (falls back to the untouched SDP when the browser refuses the edit), `setSenderBitrate`, `createLinkStatsSampler` / `useLinkStats` / `readRoundTripMs` (throughput as a delta, round trip from the **selected** candidate pair) |
314
314
  | `geo` _(opt. peer `leaflet`)_ | `haversineKm`, `pathLengthKm`, `bearingDeg`, `estimateTravel`, `createOSRMBackend`, `boundingBox`, `projectMercator`, `fitProjection`, `createPositionTracker`, `usePositionTracker`, `TrajectoryMap` — tile-free SVG map + trajectory tracking (no external/paid API; Leaflet is an opt-in, lazy-loaded tile layer for self-hosted tiles), types: `Coordinate`, `TrackPoint`, `TravelEstimate`, `TravelMode`, `GeoBounds`, `RoutingBackend` |
315
315
  | `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
@@ -654,15 +654,17 @@ export async function getUser(id: string) {
654
654
 
655
655
  On validation failure `parseResponse` throws an `Error` whose `message` includes the request label and the zod issues — exactly the diagnostic you want during a wire-protocol drift.
656
656
 
657
- **Under Vite that report needs one line to turn on.** The SDK detects a dev build by reading `process.env.NODE_ENV`, which webpack, Rspack and Parcel substitute while building your app; Vite substitutes neither half, and a browser bundle has no `process` at all, so the read fails and the SDK stays on the safe side the generic sentence, `vite dev` included. Say so once at bootstrap:
657
+ **Under Vite the report turns itself on.** The SDK detects a dev build by reading `process.env.NODE_ENV`, and every supported bundler substitutes that expression while building your app Vite included, measured on 5.4.21, 6.4.3, 7.3.6 and 8.2.2, in `vite dev` and in `vite build`, with the SDK installed both as a packed tarball and as a `file:` link. (This paragraph said the opposite until 0.62.0; the belief survived because the expression is not substituted in a browser console, which is where it is natural to check.)
658
+
659
+ `setDevBuild` remains for what nothing compiles or nothing configures — a raw service-worker script, a plain `<script type="module">`, or a staging build that never sets `NODE_ENV=production` and would otherwise embed the raw response body in an error a real user sees:
658
660
 
659
661
  ```ts
660
662
  import { setDevBuild } from "tempest-react-sdk";
661
663
 
662
- setDevBuild(import.meta.env.DEV);
664
+ setDevBuild(false); // staging talking to real data
663
665
  ```
664
666
 
665
- The SDK cannot read `import.meta.env.DEV` for you: Vite would replace it while building _this package_ and the published artifact would carry the constant. The same line un-mutes every other development-only diagnostic in the SDK.
667
+ The SDK cannot read `import.meta.env.DEV` for you: Vite would replace it while building _this package_ and the published artifact would carry the constant.
666
668
 
667
669
  ### Upload with progress recipe
668
670
 
@@ -1 +1 @@
1
- {"version":3,"file":"ImageCropper.module.cjs","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3);\n max-width: 100%;\n font-family: var(--tempest-font-sans);\n}\n\n.frame {\n position: relative;\n width: 100%;\n overflow: hidden;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n /*\n * `grab` rather than `move`: nothing is being moved to a destination, the\n * image is being dragged under a fixed frame.\n */\n cursor: grab;\n /*\n * The frame owns dragging on touch, so the browser must not also scroll the\n * page from the same gesture.\n */\n touch-action: none;\n user-select: none;\n}\n\n.frame:active {\n cursor: grabbing;\n}\n\n.frame:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.circle {\n border-radius: var(--tempest-radius-full);\n}\n\n.image {\n position: absolute;\n top: 50%;\n left: 50%;\n max-width: none;\n pointer-events: none;\n /*\n * Centring lives in `translate` and the pan in the inline `transform`, which\n * are separate CSS properties applied in that order. Keeping them apart means\n * a pan is just `transform: translate(x, y)` — it never has to re-derive the\n * `-50% -50%` centring, and the two cannot overwrite each other.\n */\n translate: -50% -50%;\n}\n\n.controls {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.zoom {\n flex: 1;\n min-width: 0;\n accent-color: var(--tempest-primary);\n}\n\n.zoom:disabled {\n opacity: 0.5;\n}\n\n.reset {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font: inherit;\n font-size: var(--tempest-text-sm);\n cursor: pointer;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .reset:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.reset:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.reset:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.hint {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n}\n\n.hint kbd {\n padding: 0 var(--tempest-space-1);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface);\n font-family: var(--tempest-font-mono, monospace);\n font-size: inherit;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"ImageCropper.module.cjs","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3);\n max-width: 100%;\n font-family: var(--tempest-font-sans);\n}\n\n.frame {\n position: relative;\n width: 100%;\n overflow: hidden;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n /*\n * `grab` rather than `move`: nothing is being moved to a destination, the\n * image is being dragged under a fixed frame.\n */\n cursor: grab;\n /*\n * The frame owns dragging on touch, so the browser must not also scroll the\n * page from the same gesture.\n */\n touch-action: none;\n user-select: none;\n}\n\n.frame:active {\n cursor: grabbing;\n}\n\n.frame:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.circle {\n border-radius: var(--tempest-radius-full);\n}\n\n.image {\n position: absolute;\n top: 50%;\n left: 50%;\n /*\n * Both `max-*`, not just the width. The size is set inline from the zoom, so\n * either cap silently overrides it — and an app reset carrying\n * `img { max-height: 100% }` is enough, since a 0-0-1 element rule meets no\n * competition here. The failure is worse than it looks: `crop()` builds its\n * rectangle from `computeCropRect({ image: natural, ... })` and never reads the\n * rendered element, so the export stays correct while the preview is squashed\n * on one axis. The user drags against a distorted image and gets a framing they\n * never saw. A component that sizes itself inline owns neutralising both caps.\n */\n max-width: none;\n max-height: none;\n pointer-events: none;\n /*\n * Centring lives in `translate` and the pan in the inline `transform`, which\n * are separate CSS properties applied in that order. Keeping them apart means\n * a pan is just `transform: translate(x, y)` — it never has to re-derive the\n * `-50% -50%` centring, and the two cannot overwrite each other.\n */\n translate: -50% -50%;\n}\n\n.controls {\n display: flex;\n /*\n * Wraps rather than squeezing. The zoom slider is the flexible item, so on a\n * narrow row it is the one that gives — and a range input has no useful floor\n * of its own, so it gave all the way to `width: 0`, leaving the thumb alone and\n * no visible focus ring while the button beside it kept both. Measured at 390px\n * with a crowded row. Wrapping moves the slider to its own line instead.\n */\n flex-wrap: wrap;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.zoom {\n /*\n * `min-width: 0` is what let it collapse to nothing: it exists to let a flex\n * item shrink past its min-content size, which for a range input is the whole\n * control. A real floor keeps the slider draggable and its focus ring visible;\n * the wrap on `.controls` is what absorbs the width this refuses to give up.\n */\n flex: 1 1 6rem;\n min-width: 6rem;\n accent-color: var(--tempest-primary);\n}\n\n.zoom:disabled {\n opacity: 0.5;\n}\n\n.reset {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font: inherit;\n font-size: var(--tempest-text-sm);\n cursor: pointer;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .reset:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.reset:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.reset:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.hint {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n}\n\n.hint kbd {\n padding: 0 var(--tempest-space-1);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface);\n font-family: var(--tempest-font-mono, monospace);\n font-size: inherit;\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"ImageCropper.module.js","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3);\n max-width: 100%;\n font-family: var(--tempest-font-sans);\n}\n\n.frame {\n position: relative;\n width: 100%;\n overflow: hidden;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n /*\n * `grab` rather than `move`: nothing is being moved to a destination, the\n * image is being dragged under a fixed frame.\n */\n cursor: grab;\n /*\n * The frame owns dragging on touch, so the browser must not also scroll the\n * page from the same gesture.\n */\n touch-action: none;\n user-select: none;\n}\n\n.frame:active {\n cursor: grabbing;\n}\n\n.frame:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.circle {\n border-radius: var(--tempest-radius-full);\n}\n\n.image {\n position: absolute;\n top: 50%;\n left: 50%;\n max-width: none;\n pointer-events: none;\n /*\n * Centring lives in `translate` and the pan in the inline `transform`, which\n * are separate CSS properties applied in that order. Keeping them apart means\n * a pan is just `transform: translate(x, y)` — it never has to re-derive the\n * `-50% -50%` centring, and the two cannot overwrite each other.\n */\n translate: -50% -50%;\n}\n\n.controls {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.zoom {\n flex: 1;\n min-width: 0;\n accent-color: var(--tempest-primary);\n}\n\n.zoom:disabled {\n opacity: 0.5;\n}\n\n.reset {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font: inherit;\n font-size: var(--tempest-text-sm);\n cursor: pointer;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .reset:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.reset:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.reset:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.hint {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n}\n\n.hint kbd {\n padding: 0 var(--tempest-space-1);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface);\n font-family: var(--tempest-font-mono, monospace);\n font-size: inherit;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"ImageCropper.module.js","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3);\n max-width: 100%;\n font-family: var(--tempest-font-sans);\n}\n\n.frame {\n position: relative;\n width: 100%;\n overflow: hidden;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n /*\n * `grab` rather than `move`: nothing is being moved to a destination, the\n * image is being dragged under a fixed frame.\n */\n cursor: grab;\n /*\n * The frame owns dragging on touch, so the browser must not also scroll the\n * page from the same gesture.\n */\n touch-action: none;\n user-select: none;\n}\n\n.frame:active {\n cursor: grabbing;\n}\n\n.frame:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.circle {\n border-radius: var(--tempest-radius-full);\n}\n\n.image {\n position: absolute;\n top: 50%;\n left: 50%;\n /*\n * Both `max-*`, not just the width. The size is set inline from the zoom, so\n * either cap silently overrides it — and an app reset carrying\n * `img { max-height: 100% }` is enough, since a 0-0-1 element rule meets no\n * competition here. The failure is worse than it looks: `crop()` builds its\n * rectangle from `computeCropRect({ image: natural, ... })` and never reads the\n * rendered element, so the export stays correct while the preview is squashed\n * on one axis. The user drags against a distorted image and gets a framing they\n * never saw. A component that sizes itself inline owns neutralising both caps.\n */\n max-width: none;\n max-height: none;\n pointer-events: none;\n /*\n * Centring lives in `translate` and the pan in the inline `transform`, which\n * are separate CSS properties applied in that order. Keeping them apart means\n * a pan is just `transform: translate(x, y)` — it never has to re-derive the\n * `-50% -50%` centring, and the two cannot overwrite each other.\n */\n translate: -50% -50%;\n}\n\n.controls {\n display: flex;\n /*\n * Wraps rather than squeezing. The zoom slider is the flexible item, so on a\n * narrow row it is the one that gives — and a range input has no useful floor\n * of its own, so it gave all the way to `width: 0`, leaving the thumb alone and\n * no visible focus ring while the button beside it kept both. Measured at 390px\n * with a crowded row. Wrapping moves the slider to its own line instead.\n */\n flex-wrap: wrap;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.zoom {\n /*\n * `min-width: 0` is what let it collapse to nothing: it exists to let a flex\n * item shrink past its min-content size, which for a range input is the whole\n * control. A real floor keeps the slider draggable and its focus ring visible;\n * the wrap on `.controls` is what absorbs the width this refuses to give up.\n */\n flex: 1 1 6rem;\n min-width: 6rem;\n accent-color: var(--tempest-primary);\n}\n\n.zoom:disabled {\n opacity: 0.5;\n}\n\n.reset {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font: inherit;\n font-size: var(--tempest-text-sm);\n cursor: pointer;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .reset:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.reset:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.reset:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.hint {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n}\n\n.hint kbd {\n padding: 0 var(--tempest-space-1);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface);\n font-family: var(--tempest-font-mono, monospace);\n font-size: inherit;\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordInput.module.cjs","names":[],"sources":["../../../src/components/PasswordInput/PasswordInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.field {\n position: relative;\n display: flex;\n align-items: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.field:focus-within {\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary-100));\n}\n\n.input {\n flex: 1 1 auto;\n border: 0;\n background: transparent;\n outline: 0;\n color: var(--tempest-text);\n font: inherit;\n font-family: var(--tempest-font-sans);\n}\n\n.sm .input {\n padding: 6px 8px;\n font-size: var(--tempest-text-sm);\n}\n\n.md .input {\n padding: 8px 12px;\n font-size: var(--tempest-text-md);\n}\n\n.lg .input {\n padding: 10px 14px;\n font-size: var(--tempest-text-lg);\n}\n\n.toggle {\n flex: 0 0 auto;\n background: none;\n border: 0;\n cursor: pointer;\n padding: 0 12px;\n font-size: 16px;\n color: var(--tempest-text-muted);\n}\n\n.toggle:hover {\n color: var(--tempest-text);\n}\n\n.error .field {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n\n.strength {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n font-size: var(--tempest-text-xs);\n}\n\n.strengthBar {\n flex: 1 1 auto;\n height: 4px;\n background: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n overflow: hidden;\n}\n\n.strengthBar > span {\n display: block;\n height: 100%;\n transition: width var(--tempest-duration-base, 200ms);\n}\n\n.strengthLabel {\n color: var(--tempest-text-muted);\n flex: 0 0 auto;\n}\n\n.level0 .strengthBar > span,\n.level1 .strengthBar > span {\n background: var(--tempest-danger-solid, var(--tempest-danger));\n}\n\n.level2 .strengthBar > span {\n background: var(--tempest-warning-solid, var(--tempest-warning));\n}\n\n.level3 .strengthBar > span,\n.level4 .strengthBar > span {\n background: var(--tempest-success-solid, var(--tempest-success));\n}\n"],"mappings":""}
1
+ {"version":3,"file":"PasswordInput.module.cjs","names":[],"sources":["../../../src/components/PasswordInput/PasswordInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.field {\n position: relative;\n display: flex;\n align-items: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.field:focus-within {\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary));\n}\n\n.input {\n flex: 1 1 auto;\n border: 0;\n background: transparent;\n outline: 0;\n color: var(--tempest-text);\n font: inherit;\n font-family: var(--tempest-font-sans);\n}\n\n.sm .input {\n padding: 6px 8px;\n font-size: var(--tempest-text-sm);\n}\n\n.md .input {\n padding: 8px 12px;\n font-size: var(--tempest-text-md);\n}\n\n.lg .input {\n padding: 10px 14px;\n font-size: var(--tempest-text-lg);\n}\n\n.toggle {\n flex: 0 0 auto;\n background: none;\n border: 0;\n cursor: pointer;\n padding: 0 12px;\n font-size: 16px;\n color: var(--tempest-text-muted);\n}\n\n.toggle:hover {\n color: var(--tempest-text);\n}\n\n.error .field {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n\n.strength {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n font-size: var(--tempest-text-xs);\n}\n\n.strengthBar {\n flex: 1 1 auto;\n height: 4px;\n background: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n overflow: hidden;\n}\n\n.strengthBar > span {\n display: block;\n height: 100%;\n transition: width var(--tempest-duration-base, 200ms);\n}\n\n.strengthLabel {\n color: var(--tempest-text-muted);\n flex: 0 0 auto;\n}\n\n.level0 .strengthBar > span,\n.level1 .strengthBar > span {\n background: var(--tempest-danger-solid, var(--tempest-danger));\n}\n\n.level2 .strengthBar > span {\n background: var(--tempest-warning-solid, var(--tempest-warning));\n}\n\n.level3 .strengthBar > span,\n.level4 .strengthBar > span {\n background: var(--tempest-success-solid, var(--tempest-success));\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordInput.module.js","names":[],"sources":["../../../src/components/PasswordInput/PasswordInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.field {\n position: relative;\n display: flex;\n align-items: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.field:focus-within {\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary-100));\n}\n\n.input {\n flex: 1 1 auto;\n border: 0;\n background: transparent;\n outline: 0;\n color: var(--tempest-text);\n font: inherit;\n font-family: var(--tempest-font-sans);\n}\n\n.sm .input {\n padding: 6px 8px;\n font-size: var(--tempest-text-sm);\n}\n\n.md .input {\n padding: 8px 12px;\n font-size: var(--tempest-text-md);\n}\n\n.lg .input {\n padding: 10px 14px;\n font-size: var(--tempest-text-lg);\n}\n\n.toggle {\n flex: 0 0 auto;\n background: none;\n border: 0;\n cursor: pointer;\n padding: 0 12px;\n font-size: 16px;\n color: var(--tempest-text-muted);\n}\n\n.toggle:hover {\n color: var(--tempest-text);\n}\n\n.error .field {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n\n.strength {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n font-size: var(--tempest-text-xs);\n}\n\n.strengthBar {\n flex: 1 1 auto;\n height: 4px;\n background: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n overflow: hidden;\n}\n\n.strengthBar > span {\n display: block;\n height: 100%;\n transition: width var(--tempest-duration-base, 200ms);\n}\n\n.strengthLabel {\n color: var(--tempest-text-muted);\n flex: 0 0 auto;\n}\n\n.level0 .strengthBar > span,\n.level1 .strengthBar > span {\n background: var(--tempest-danger-solid, var(--tempest-danger));\n}\n\n.level2 .strengthBar > span {\n background: var(--tempest-warning-solid, var(--tempest-warning));\n}\n\n.level3 .strengthBar > span,\n.level4 .strengthBar > span {\n background: var(--tempest-success-solid, var(--tempest-success));\n}\n"],"mappings":""}
1
+ {"version":3,"file":"PasswordInput.module.js","names":[],"sources":["../../../src/components/PasswordInput/PasswordInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.field {\n position: relative;\n display: flex;\n align-items: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.field:focus-within {\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary));\n}\n\n.input {\n flex: 1 1 auto;\n border: 0;\n background: transparent;\n outline: 0;\n color: var(--tempest-text);\n font: inherit;\n font-family: var(--tempest-font-sans);\n}\n\n.sm .input {\n padding: 6px 8px;\n font-size: var(--tempest-text-sm);\n}\n\n.md .input {\n padding: 8px 12px;\n font-size: var(--tempest-text-md);\n}\n\n.lg .input {\n padding: 10px 14px;\n font-size: var(--tempest-text-lg);\n}\n\n.toggle {\n flex: 0 0 auto;\n background: none;\n border: 0;\n cursor: pointer;\n padding: 0 12px;\n font-size: 16px;\n color: var(--tempest-text-muted);\n}\n\n.toggle:hover {\n color: var(--tempest-text);\n}\n\n.error .field {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n\n.strength {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n font-size: var(--tempest-text-xs);\n}\n\n.strengthBar {\n flex: 1 1 auto;\n height: 4px;\n background: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n overflow: hidden;\n}\n\n.strengthBar > span {\n display: block;\n height: 100%;\n transition: width var(--tempest-duration-base, 200ms);\n}\n\n.strengthLabel {\n color: var(--tempest-text-muted);\n flex: 0 0 auto;\n}\n\n.level0 .strengthBar > span,\n.level1 .strengthBar > span {\n background: var(--tempest-danger-solid, var(--tempest-danger));\n}\n\n.level2 .strengthBar > span {\n background: var(--tempest-warning-solid, var(--tempest-warning));\n}\n\n.level3 .strengthBar > span,\n.level4 .strengthBar > span {\n background: var(--tempest-success-solid, var(--tempest-success));\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PinInput.module.cjs","names":[],"sources":["../../../src/components/PinInput/PinInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.cells {\n display: flex;\n gap: var(--tempest-space-2);\n}\n\n.cell {\n flex: 0 0 auto;\n text-align: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n font-family: var(--tempest-font-mono, var(--tempest-font-sans));\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.cells.sm .cell {\n width: 32px;\n height: 36px;\n font-size: var(--tempest-text-md);\n}\n\n.cells.md .cell {\n width: 44px;\n height: 52px;\n font-size: var(--tempest-text-lg);\n}\n\n.cells.lg .cell {\n width: 56px;\n height: 64px;\n font-size: var(--tempest-text-xl);\n}\n\n.cell:focus {\n outline: none;\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary-100));\n}\n\n.cell:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.error .cell {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n"],"mappings":""}
1
+ {"version":3,"file":"PinInput.module.cjs","names":[],"sources":["../../../src/components/PinInput/PinInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.cells {\n display: flex;\n gap: var(--tempest-space-2);\n}\n\n.cell {\n flex: 0 0 auto;\n text-align: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n font-family: var(--tempest-font-mono, var(--tempest-font-sans));\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.cells.sm .cell {\n width: 32px;\n height: 36px;\n font-size: var(--tempest-text-md);\n}\n\n.cells.md .cell {\n width: 44px;\n height: 52px;\n font-size: var(--tempest-text-lg);\n}\n\n.cells.lg .cell {\n width: 56px;\n height: 64px;\n font-size: var(--tempest-text-xl);\n}\n\n.cell:focus {\n outline: none;\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary));\n}\n\n.cell:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.error .cell {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PinInput.module.js","names":[],"sources":["../../../src/components/PinInput/PinInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.cells {\n display: flex;\n gap: var(--tempest-space-2);\n}\n\n.cell {\n flex: 0 0 auto;\n text-align: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n font-family: var(--tempest-font-mono, var(--tempest-font-sans));\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.cells.sm .cell {\n width: 32px;\n height: 36px;\n font-size: var(--tempest-text-md);\n}\n\n.cells.md .cell {\n width: 44px;\n height: 52px;\n font-size: var(--tempest-text-lg);\n}\n\n.cells.lg .cell {\n width: 56px;\n height: 64px;\n font-size: var(--tempest-text-xl);\n}\n\n.cell:focus {\n outline: none;\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary-100));\n}\n\n.cell:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.error .cell {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n"],"mappings":""}
1
+ {"version":3,"file":"PinInput.module.js","names":[],"sources":["../../../src/components/PinInput/PinInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n color: var(--tempest-text);\n}\n\n.cells {\n display: flex;\n gap: var(--tempest-space-2);\n}\n\n.cell {\n flex: 0 0 auto;\n text-align: center;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n font-family: var(--tempest-font-mono, var(--tempest-font-sans));\n transition: border-color var(--tempest-duration-fast, 120ms);\n}\n\n.cells.sm .cell {\n width: 32px;\n height: 36px;\n font-size: var(--tempest-text-md);\n}\n\n.cells.md .cell {\n width: 44px;\n height: 52px;\n font-size: var(--tempest-text-lg);\n}\n\n.cells.lg .cell {\n width: 56px;\n height: 64px;\n font-size: var(--tempest-text-xl);\n}\n\n.cell:focus {\n outline: none;\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width, 2px)\n var(--tempest-focus-ring-color, var(--tempest-primary));\n}\n\n.cell:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.error .cell {\n border-color: var(--tempest-danger-border, var(--tempest-danger));\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger-fg, var(--tempest-danger));\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- const e=require("../utils/json-frame.cjs");function t(t,n={}){let{withCredentials:r=!1,namedEvents:i=[],heartbeatEvents:a=[`ping`],maxRetries:o=10,initialBackoff:s=1e3,maxBackoff:c=3e4,parser:l,onOpen:u,onMessage:d,onError:f,onParseError:p,onStatusChange:m}=n,h=null,g=null,_=0,v=`idle`,y=!1;function b(e){v!==e&&(v=e,m?.(e))}function x(t,n){if(a.includes(t))return;let r=e.decodeFrame(typeof n.data==`string`?n.data:``,l,p,`createEventStream`);r.delivered&&d?.({event:t,data:r.data,id:n.lastEventId||void 0,raw:n})}function S(){if(y)return;if(_>=o){b(`error`);return}let e=Math.min(s*2**_,c);_+=1,g=setTimeout(C,e)}function C(){if(y)return;h&&h.close(),b(`connecting`);let e=new EventSource(t,{withCredentials:r});h=e,e.onopen=()=>{_=0,b(`open`),u?.()},e.onmessage=e=>x(`message`,e);for(let t of i)e.addEventListener(t,e=>x(t,e));for(let t of a)e.addEventListener(t,()=>{});e.onerror=t=>{f?.(t),e.close(),h=null,b(`closed`),S()}}function w(){y=!0,g&&=(clearTimeout(g),null),_=0,h&&=(h.close(),null),b(`closed`)}function T(){g&&=(clearTimeout(g),null),_=0,y=!1,C()}return C(),{close:w,reconnect:T,get status(){return v}}}exports.createEventStream=t;
1
+ const e=require("../utils/json-frame.cjs");function t(t,n={}){let{withCredentials:r=!1,namedEvents:i=[],heartbeatEvents:a=[`ping`],maxRetries:o=10,initialBackoff:s=1e3,maxBackoff:c=3e4,parser:l,onOpen:u,onMessage:d,onError:f,onParseError:p,schema:m,onValidationError:h,onStatusChange:g}=n,_=null,v=null,y=0,b=`idle`,x=!1;function S(e){b!==e&&(b=e,g?.(e))}function C(t,n){if(a.includes(t))return;let r=e.decodeFrame(typeof n.data==`string`?n.data:``,`createEventStream`,{parser:l,onParseError:p,schema:m,onValidationError:h});r.delivered&&d?.({event:t,data:r.data,id:n.lastEventId||void 0,raw:n})}function w(){if(x)return;if(y>=o){S(`error`);return}let e=Math.min(s*2**y,c);y+=1,v=setTimeout(T,e)}function T(){if(x)return;_&&_.close(),S(`connecting`);let e=new EventSource(t,{withCredentials:r});_=e,e.onopen=()=>{y=0,S(`open`),u?.()},e.onmessage=e=>C(`message`,e);for(let t of i)e.addEventListener(t,e=>C(t,e));for(let t of a)e.addEventListener(t,()=>{});e.onerror=t=>{f?.(t),e.close(),_=null,S(`closed`),w()}}function E(){x=!0,v&&=(clearTimeout(v),null),y=0,_&&=(_.close(),null),S(`closed`)}function D(){v&&=(clearTimeout(v),null),y=0,x=!1,T()}return T(),{close:E,reconnect:D,get status(){return b}}}exports.createEventStream=t;
2
2
  //# sourceMappingURL=create-event-stream.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-event-stream.cjs","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n parser,\n onParseError,\n \"createEventStream\",\n );\n if (!decoded.delivered) return;\n onMessage?.({\n event: eventName,\n data: decoded.data,\n id: event.lastEventId || undefined,\n raw: event,\n });\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (source) source.close();\n setStatus(\"connecting\");\n\n const es = new EventSource(url, { withCredentials });\n source = es;\n\n es.onopen = () => {\n retries = 0;\n setStatus(\"open\");\n onOpen?.();\n };\n\n es.onmessage = (event) => emit(\"message\", event);\n for (const name of namedEvents) {\n es.addEventListener(name, (event) => emit(name, event as MessageEvent));\n }\n for (const name of heartbeatEvents) {\n es.addEventListener(name, () => {\n /* heartbeat — keep socket alive */\n });\n }\n\n es.onerror = (event) => {\n onError?.(event);\n es.close();\n source = null;\n setStatus(\"closed\");\n scheduleReconnect();\n };\n }\n\n function close(): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n if (source) {\n source.close();\n source = null;\n }\n setStatus(\"closed\");\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":"2CAqEA,SAAgB,EACZ,EACA,EAAuC,CAAC,EACnB,CACrB,GAAM,CACF,kBAAkB,GAClB,cAAc,CAAC,EACf,kBAAkB,CAAC,MAAM,EACzB,aAAa,GACb,iBAAiB,IACjB,aAAa,IACb,SACA,SACA,YACA,UACA,eACA,kBACA,EAEA,EAA6B,KAC7B,EAAmD,KACnD,EAAU,EACV,EAA4B,OAC5B,EAAS,GAEb,SAAS,EAAU,EAA+B,CAC1C,IAAW,IACf,EAAS,EACT,IAAiB,CAAI,EACzB,CAEA,SAAS,EAAK,EAAmB,EAA2B,CACxD,GAAI,EAAgB,SAAS,CAAS,EAAG,OACzC,IAAM,EAAU,EAAA,YACZ,OAAO,EAAM,MAAS,SAAW,EAAM,KAAO,GAC9C,EACA,EACA,mBACJ,EACK,EAAQ,WACb,IAAY,CACR,MAAO,EACP,KAAM,EAAQ,KACd,GAAI,EAAM,aAAe,IAAA,GACzB,IAAK,CACT,CAAC,CACL,CAEA,SAAS,GAA0B,CAC/B,GAAI,EAAQ,OACZ,GAAI,GAAW,EAAY,CACvB,EAAU,OAAO,EACjB,MACJ,CACA,IAAM,EAAQ,KAAK,IAAI,EAAiB,GAAK,EAAS,CAAU,EAChE,GAAW,EACX,EAAa,WAAW,EAAS,CAAK,CAC1C,CAEA,SAAS,GAAgB,CACrB,GAAI,EAAQ,OACR,GAAQ,EAAO,MAAM,EACzB,EAAU,YAAY,EAEtB,IAAM,EAAK,IAAI,YAAY,EAAK,CAAE,iBAAgB,CAAC,EACnD,EAAS,EAET,EAAG,WAAe,CACd,EAAU,EACV,EAAU,MAAM,EAChB,IAAS,CACb,EAEA,EAAG,UAAa,GAAU,EAAK,UAAW,CAAK,EAC/C,IAAK,IAAM,KAAQ,EACf,EAAG,iBAAiB,EAAO,GAAU,EAAK,EAAM,CAAqB,CAAC,EAE1E,IAAK,IAAM,KAAQ,EACf,EAAG,iBAAiB,MAAY,CAEhC,CAAC,EAGL,EAAG,QAAW,GAAU,CACpB,IAAU,CAAK,EACf,EAAG,MAAM,EACT,EAAS,KACT,EAAU,QAAQ,EAClB,EAAkB,CACtB,CACJ,CAEA,SAAS,GAAc,CACnB,EAAS,GACT,AAEI,KADA,aAAa,CAAU,EACV,MAEjB,EAAU,EACV,AAEI,KADA,EAAO,MAAM,EACJ,MAEb,EAAU,QAAQ,CACtB,CAEA,SAAS,GAAkB,CACvB,AAEI,KADA,aAAa,CAAU,EACV,MAEjB,EAAU,EACV,EAAS,GACT,EAAQ,CACZ,CAIA,OAFA,EAAQ,EAED,CACH,QACA,YACA,IAAI,QAAS,CACT,OAAO,CACX,CACJ,CACJ"}
1
+ {"version":3,"file":"create-event-stream.cjs","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nimport type { SchemaIssue, SchemaLike } from \"../utils/schema-like\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — validated when `schema` is set, JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n /**\n * Schema every decoded frame must satisfy, from zod, valibot, arktype or\n * anything else exposing `~standard` or `.safeParse`.\n *\n * Without it nothing changes: the payload reaches `onMessage` announced as\n * `T` on the strength of the type argument alone, which is a promise about\n * the server that TypeScript cannot keep. With it, a frame that does not\n * match is **not** delivered — the same rule `onParseError` already follows —\n * and `onValidationError` hears why. The value delivered is the schema's\n * output, so coercions and defaults are honoured.\n *\n * When `parser` is also supplied, it decodes first and the schema validates\n * what it returned.\n *\n * The validation must be synchronous. A frame is decoded inside the\n * `message` handler and delivered from it, so an async schema would deliver\n * frames in whatever order their validations settled; that case is reported\n * through `onValidationError` instead of awaited.\n */\n schema?: SchemaLike<T>;\n /**\n * A frame was decoded but the `schema` refused it, so it was dropped.\n *\n * The one signal that does not depend on how the app's bundler resolves\n * `process`: the one-time development warning behind `onParseError` needs\n * `isDevBuild()` to be able to answer, and this callback is the app's own.\n */\n onValidationError?: (issues: SchemaIssue[], raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n schema,\n onValidationError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n \"createEventStream\",\n { parser, onParseError, schema, onValidationError },\n );\n if (!decoded.delivered) return;\n onMessage?.({\n event: eventName,\n data: decoded.data,\n id: event.lastEventId || undefined,\n raw: event,\n });\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (source) source.close();\n setStatus(\"connecting\");\n\n const es = new EventSource(url, { withCredentials });\n source = es;\n\n es.onopen = () => {\n retries = 0;\n setStatus(\"open\");\n onOpen?.();\n };\n\n es.onmessage = (event) => emit(\"message\", event);\n for (const name of namedEvents) {\n es.addEventListener(name, (event) => emit(name, event as MessageEvent));\n }\n for (const name of heartbeatEvents) {\n es.addEventListener(name, () => {\n /* heartbeat — keep socket alive */\n });\n }\n\n es.onerror = (event) => {\n onError?.(event);\n es.close();\n source = null;\n setStatus(\"closed\");\n scheduleReconnect();\n };\n }\n\n function close(): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n if (source) {\n source.close();\n source = null;\n }\n setStatus(\"closed\");\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":"2CAkGA,SAAgB,EACZ,EACA,EAAuC,CAAC,EACnB,CACrB,GAAM,CACF,kBAAkB,GAClB,cAAc,CAAC,EACf,kBAAkB,CAAC,MAAM,EACzB,aAAa,GACb,iBAAiB,IACjB,aAAa,IACb,SACA,SACA,YACA,UACA,eACA,SACA,oBACA,kBACA,EAEA,EAA6B,KAC7B,EAAmD,KACnD,EAAU,EACV,EAA4B,OAC5B,EAAS,GAEb,SAAS,EAAU,EAA+B,CAC1C,IAAW,IACf,EAAS,EACT,IAAiB,CAAI,EACzB,CAEA,SAAS,EAAK,EAAmB,EAA2B,CACxD,GAAI,EAAgB,SAAS,CAAS,EAAG,OACzC,IAAM,EAAU,EAAA,YACZ,OAAO,EAAM,MAAS,SAAW,EAAM,KAAO,GAC9C,oBACA,CAAE,SAAQ,eAAc,SAAQ,mBAAkB,CACtD,EACK,EAAQ,WACb,IAAY,CACR,MAAO,EACP,KAAM,EAAQ,KACd,GAAI,EAAM,aAAe,IAAA,GACzB,IAAK,CACT,CAAC,CACL,CAEA,SAAS,GAA0B,CAC/B,GAAI,EAAQ,OACZ,GAAI,GAAW,EAAY,CACvB,EAAU,OAAO,EACjB,MACJ,CACA,IAAM,EAAQ,KAAK,IAAI,EAAiB,GAAK,EAAS,CAAU,EAChE,GAAW,EACX,EAAa,WAAW,EAAS,CAAK,CAC1C,CAEA,SAAS,GAAgB,CACrB,GAAI,EAAQ,OACR,GAAQ,EAAO,MAAM,EACzB,EAAU,YAAY,EAEtB,IAAM,EAAK,IAAI,YAAY,EAAK,CAAE,iBAAgB,CAAC,EACnD,EAAS,EAET,EAAG,WAAe,CACd,EAAU,EACV,EAAU,MAAM,EAChB,IAAS,CACb,EAEA,EAAG,UAAa,GAAU,EAAK,UAAW,CAAK,EAC/C,IAAK,IAAM,KAAQ,EACf,EAAG,iBAAiB,EAAO,GAAU,EAAK,EAAM,CAAqB,CAAC,EAE1E,IAAK,IAAM,KAAQ,EACf,EAAG,iBAAiB,MAAY,CAEhC,CAAC,EAGL,EAAG,QAAW,GAAU,CACpB,IAAU,CAAK,EACf,EAAG,MAAM,EACT,EAAS,KACT,EAAU,QAAQ,EAClB,EAAkB,CACtB,CACJ,CAEA,SAAS,GAAc,CACnB,EAAS,GACT,AAEI,KADA,aAAa,CAAU,EACV,MAEjB,EAAU,EACV,AAEI,KADA,EAAO,MAAM,EACJ,MAEb,EAAU,QAAQ,CACtB,CAEA,SAAS,GAAkB,CACvB,AAEI,KADA,aAAa,CAAU,EACV,MAEjB,EAAU,EACV,EAAS,GACT,EAAQ,CACZ,CAIA,OAFA,EAAQ,EAED,CACH,QACA,YACA,IAAI,QAAS,CACT,OAAO,CACX,CACJ,CACJ"}
@@ -1,13 +1,18 @@
1
1
  import { decodeFrame as e } from "../utils/json-frame.js";
2
2
  //#region src/sse/create-event-stream.ts
3
3
  function t(t, n = {}) {
4
- let { withCredentials: r = !1, namedEvents: i = [], heartbeatEvents: a = ["ping"], maxRetries: o = 10, initialBackoff: s = 1e3, maxBackoff: c = 3e4, parser: l, onOpen: u, onMessage: d, onError: f, onParseError: p, onStatusChange: m } = n, h = null, g = null, _ = 0, v = "idle", y = !1;
5
- function b(e) {
6
- v !== e && (v = e, m?.(e));
4
+ let { withCredentials: r = !1, namedEvents: i = [], heartbeatEvents: a = ["ping"], maxRetries: o = 10, initialBackoff: s = 1e3, maxBackoff: c = 3e4, parser: l, onOpen: u, onMessage: d, onError: f, onParseError: p, schema: m, onValidationError: h, onStatusChange: g } = n, _ = null, v = null, y = 0, b = "idle", x = !1;
5
+ function S(e) {
6
+ b !== e && (b = e, g?.(e));
7
7
  }
8
- function x(t, n) {
8
+ function C(t, n) {
9
9
  if (a.includes(t)) return;
10
- let r = e(typeof n.data == "string" ? n.data : "", l, p, "createEventStream");
10
+ let r = e(typeof n.data == "string" ? n.data : "", "createEventStream", {
11
+ parser: l,
12
+ onParseError: p,
13
+ schema: m,
14
+ onValidationError: h
15
+ });
11
16
  r.delivered && d?.({
12
17
  event: t,
13
18
  data: r.data,
@@ -15,39 +20,39 @@ function t(t, n = {}) {
15
20
  raw: n
16
21
  });
17
22
  }
18
- function S() {
19
- if (y) return;
20
- if (_ >= o) {
21
- b("error");
23
+ function w() {
24
+ if (x) return;
25
+ if (y >= o) {
26
+ S("error");
22
27
  return;
23
28
  }
24
- let e = Math.min(s * 2 ** _, c);
25
- _ += 1, g = setTimeout(C, e);
29
+ let e = Math.min(s * 2 ** y, c);
30
+ y += 1, v = setTimeout(T, e);
26
31
  }
27
- function C() {
28
- if (y) return;
29
- h && h.close(), b("connecting");
32
+ function T() {
33
+ if (x) return;
34
+ _ && _.close(), S("connecting");
30
35
  let e = new EventSource(t, { withCredentials: r });
31
- h = e, e.onopen = () => {
32
- _ = 0, b("open"), u?.();
33
- }, e.onmessage = (e) => x("message", e);
34
- for (let t of i) e.addEventListener(t, (e) => x(t, e));
36
+ _ = e, e.onopen = () => {
37
+ y = 0, S("open"), u?.();
38
+ }, e.onmessage = (e) => C("message", e);
39
+ for (let t of i) e.addEventListener(t, (e) => C(t, e));
35
40
  for (let t of a) e.addEventListener(t, () => {});
36
41
  e.onerror = (t) => {
37
- f?.(t), e.close(), h = null, b("closed"), S();
42
+ f?.(t), e.close(), _ = null, S("closed"), w();
38
43
  };
39
44
  }
40
- function w() {
41
- y = !0, g &&= (clearTimeout(g), null), _ = 0, h &&= (h.close(), null), b("closed");
45
+ function E() {
46
+ x = !0, v &&= (clearTimeout(v), null), y = 0, _ &&= (_.close(), null), S("closed");
42
47
  }
43
- function T() {
44
- g &&= (clearTimeout(g), null), _ = 0, y = !1, C();
48
+ function D() {
49
+ v &&= (clearTimeout(v), null), y = 0, x = !1, T();
45
50
  }
46
- return C(), {
47
- close: w,
48
- reconnect: T,
51
+ return T(), {
52
+ close: E,
53
+ reconnect: D,
49
54
  get status() {
50
- return v;
55
+ return b;
51
56
  }
52
57
  };
53
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-event-stream.js","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n parser,\n onParseError,\n \"createEventStream\",\n );\n if (!decoded.delivered) return;\n onMessage?.({\n event: eventName,\n data: decoded.data,\n id: event.lastEventId || undefined,\n raw: event,\n });\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (source) source.close();\n setStatus(\"connecting\");\n\n const es = new EventSource(url, { withCredentials });\n source = es;\n\n es.onopen = () => {\n retries = 0;\n setStatus(\"open\");\n onOpen?.();\n };\n\n es.onmessage = (event) => emit(\"message\", event);\n for (const name of namedEvents) {\n es.addEventListener(name, (event) => emit(name, event as MessageEvent));\n }\n for (const name of heartbeatEvents) {\n es.addEventListener(name, () => {\n /* heartbeat — keep socket alive */\n });\n }\n\n es.onerror = (event) => {\n onError?.(event);\n es.close();\n source = null;\n setStatus(\"closed\");\n scheduleReconnect();\n };\n }\n\n function close(): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n if (source) {\n source.close();\n source = null;\n }\n setStatus(\"closed\");\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":";;AAqEA,SAAgB,EACZ,GACA,IAAuC,CAAC,GACnB;CACrB,IAAM,EACF,qBAAkB,IAClB,iBAAc,CAAC,GACf,qBAAkB,CAAC,MAAM,GACzB,gBAAa,IACb,oBAAiB,KACjB,gBAAa,KACb,WACA,WACA,cACA,YACA,iBACA,sBACA,GAEA,IAA6B,MAC7B,IAAmD,MACnD,IAAU,GACV,IAA4B,QAC5B,IAAS;CAEb,SAAS,EAAU,GAA+B;EAC1C,MAAW,MACf,IAAS,GACT,IAAiB,CAAI;CACzB;CAEA,SAAS,EAAK,GAAmB,GAA2B;EACxD,IAAI,EAAgB,SAAS,CAAS,GAAG;EACzC,IAAM,IAAU,EACZ,OAAO,EAAM,QAAS,WAAW,EAAM,OAAO,IAC9C,GACA,GACA,mBACJ;EACK,EAAQ,aACb,IAAY;GACR,OAAO;GACP,MAAM,EAAQ;GACd,IAAI,EAAM,eAAe,KAAA;GACzB,KAAK;EACT,CAAC;CACL;CAEA,SAAS,IAA0B;EAC/B,IAAI,GAAQ;EACZ,IAAI,KAAW,GAAY;GACvB,EAAU,OAAO;GACjB;EACJ;EACA,IAAM,IAAQ,KAAK,IAAI,IAAiB,KAAK,GAAS,CAAU;EAEhE,AADA,KAAW,GACX,IAAa,WAAW,GAAS,CAAK;CAC1C;CAEA,SAAS,IAAgB;EACrB,IAAI,GAAQ;EAEZ,AADI,KAAQ,EAAO,MAAM,GACzB,EAAU,YAAY;EAEtB,IAAM,IAAK,IAAI,YAAY,GAAK,EAAE,mBAAgB,CAAC;EASnD,AARA,IAAS,GAET,EAAG,eAAe;GAGd,AAFA,IAAU,GACV,EAAU,MAAM,GAChB,IAAS;EACb,GAEA,EAAG,aAAa,MAAU,EAAK,WAAW,CAAK;EAC/C,KAAK,IAAM,KAAQ,GACf,EAAG,iBAAiB,IAAO,MAAU,EAAK,GAAM,CAAqB,CAAC;EAE1E,KAAK,IAAM,KAAQ,GACf,EAAG,iBAAiB,SAAY,CAEhC,CAAC;EAGL,EAAG,WAAW,MAAU;GAKpB,AAJA,IAAU,CAAK,GACf,EAAG,MAAM,GACT,IAAS,MACT,EAAU,QAAQ,GAClB,EAAkB;EACtB;CACJ;CAEA,SAAS,IAAc;EAWnB,AAVA,IAAS,IACT,AAEI,OADA,aAAa,CAAU,GACV,OAEjB,IAAU,GACV,AAEI,OADA,EAAO,MAAM,GACJ,OAEb,EAAU,QAAQ;CACtB;CAEA,SAAS,IAAkB;EAOvB,AANA,AAEI,OADA,aAAa,CAAU,GACV,OAEjB,IAAU,GACV,IAAS,IACT,EAAQ;CACZ;CAIA,OAFA,EAAQ,GAED;EACH;EACA;EACA,IAAI,SAAS;GACT,OAAO;EACX;CACJ;AACJ"}
1
+ {"version":3,"file":"create-event-stream.js","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nimport type { SchemaIssue, SchemaLike } from \"../utils/schema-like\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — validated when `schema` is set, JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n /**\n * Schema every decoded frame must satisfy, from zod, valibot, arktype or\n * anything else exposing `~standard` or `.safeParse`.\n *\n * Without it nothing changes: the payload reaches `onMessage` announced as\n * `T` on the strength of the type argument alone, which is a promise about\n * the server that TypeScript cannot keep. With it, a frame that does not\n * match is **not** delivered — the same rule `onParseError` already follows —\n * and `onValidationError` hears why. The value delivered is the schema's\n * output, so coercions and defaults are honoured.\n *\n * When `parser` is also supplied, it decodes first and the schema validates\n * what it returned.\n *\n * The validation must be synchronous. A frame is decoded inside the\n * `message` handler and delivered from it, so an async schema would deliver\n * frames in whatever order their validations settled; that case is reported\n * through `onValidationError` instead of awaited.\n */\n schema?: SchemaLike<T>;\n /**\n * A frame was decoded but the `schema` refused it, so it was dropped.\n *\n * The one signal that does not depend on how the app's bundler resolves\n * `process`: the one-time development warning behind `onParseError` needs\n * `isDevBuild()` to be able to answer, and this callback is the app's own.\n */\n onValidationError?: (issues: SchemaIssue[], raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n schema,\n onValidationError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n \"createEventStream\",\n { parser, onParseError, schema, onValidationError },\n );\n if (!decoded.delivered) return;\n onMessage?.({\n event: eventName,\n data: decoded.data,\n id: event.lastEventId || undefined,\n raw: event,\n });\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (source) source.close();\n setStatus(\"connecting\");\n\n const es = new EventSource(url, { withCredentials });\n source = es;\n\n es.onopen = () => {\n retries = 0;\n setStatus(\"open\");\n onOpen?.();\n };\n\n es.onmessage = (event) => emit(\"message\", event);\n for (const name of namedEvents) {\n es.addEventListener(name, (event) => emit(name, event as MessageEvent));\n }\n for (const name of heartbeatEvents) {\n es.addEventListener(name, () => {\n /* heartbeat — keep socket alive */\n });\n }\n\n es.onerror = (event) => {\n onError?.(event);\n es.close();\n source = null;\n setStatus(\"closed\");\n scheduleReconnect();\n };\n }\n\n function close(): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n if (source) {\n source.close();\n source = null;\n }\n setStatus(\"closed\");\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":";;AAkGA,SAAgB,EACZ,GACA,IAAuC,CAAC,GACnB;CACrB,IAAM,EACF,qBAAkB,IAClB,iBAAc,CAAC,GACf,qBAAkB,CAAC,MAAM,GACzB,gBAAa,IACb,oBAAiB,KACjB,gBAAa,KACb,WACA,WACA,cACA,YACA,iBACA,WACA,sBACA,sBACA,GAEA,IAA6B,MAC7B,IAAmD,MACnD,IAAU,GACV,IAA4B,QAC5B,IAAS;CAEb,SAAS,EAAU,GAA+B;EAC1C,MAAW,MACf,IAAS,GACT,IAAiB,CAAI;CACzB;CAEA,SAAS,EAAK,GAAmB,GAA2B;EACxD,IAAI,EAAgB,SAAS,CAAS,GAAG;EACzC,IAAM,IAAU,EACZ,OAAO,EAAM,QAAS,WAAW,EAAM,OAAO,IAC9C,qBACA;GAAE;GAAQ;GAAc;GAAQ;EAAkB,CACtD;EACK,EAAQ,aACb,IAAY;GACR,OAAO;GACP,MAAM,EAAQ;GACd,IAAI,EAAM,eAAe,KAAA;GACzB,KAAK;EACT,CAAC;CACL;CAEA,SAAS,IAA0B;EAC/B,IAAI,GAAQ;EACZ,IAAI,KAAW,GAAY;GACvB,EAAU,OAAO;GACjB;EACJ;EACA,IAAM,IAAQ,KAAK,IAAI,IAAiB,KAAK,GAAS,CAAU;EAEhE,AADA,KAAW,GACX,IAAa,WAAW,GAAS,CAAK;CAC1C;CAEA,SAAS,IAAgB;EACrB,IAAI,GAAQ;EAEZ,AADI,KAAQ,EAAO,MAAM,GACzB,EAAU,YAAY;EAEtB,IAAM,IAAK,IAAI,YAAY,GAAK,EAAE,mBAAgB,CAAC;EASnD,AARA,IAAS,GAET,EAAG,eAAe;GAGd,AAFA,IAAU,GACV,EAAU,MAAM,GAChB,IAAS;EACb,GAEA,EAAG,aAAa,MAAU,EAAK,WAAW,CAAK;EAC/C,KAAK,IAAM,KAAQ,GACf,EAAG,iBAAiB,IAAO,MAAU,EAAK,GAAM,CAAqB,CAAC;EAE1E,KAAK,IAAM,KAAQ,GACf,EAAG,iBAAiB,SAAY,CAEhC,CAAC;EAGL,EAAG,WAAW,MAAU;GAKpB,AAJA,IAAU,CAAK,GACf,EAAG,MAAM,GACT,IAAS,MACT,EAAU,QAAQ,GAClB,EAAkB;EACtB;CACJ;CAEA,SAAS,IAAc;EAWnB,AAVA,IAAS,IACT,AAEI,OADA,aAAa,CAAU,GACV,OAEjB,IAAU,GACV,AAEI,OADA,EAAO,MAAM,GACJ,OAEb,EAAU,QAAQ;CACtB;CAEA,SAAS,IAAkB;EAOvB,AANA,AAEI,OADA,aAAa,CAAU,GACV,OAEjB,IAAU,GACV,IAAS,IACT,EAAQ;CACZ;CAIA,OAFA,EAAQ,GAED;EACH;EACA;EACA,IAAI,SAAS;GACT,OAAO;EACX;CACJ;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-event-stream.cjs","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":"6GAqCA,SAAgB,EACZ,EACA,EAAoC,CAAC,EACd,CACvB,GAAM,CAAE,UAAU,GAAM,YAAW,GAAG,GAAS,EACzC,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA4B,MAAM,EACxD,CAAC,EAAa,IAAA,EAAkB,EAAA,SAAA,CAAuC,IAAI,EAC3E,GAAA,EAAe,EAAA,OAAA,CAA4B,IAAI,EAE/C,EAAe,EAAA,aAAa,CAAS,EAyB3C,OAvBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,GAAW,CAAC,EAAK,CAClB,EAAU,MAAM,EAChB,MACJ,CAEA,IAAM,EAAa,EAAA,kBAAqB,EAAK,CACzC,GAAG,EACH,eAAgB,EAChB,UAAY,GAAY,CACpB,EAAe,CAAO,EACtB,EAAa,UAAU,CAAO,CAClC,CACJ,CAAC,EAGD,MAFA,GAAa,QAAU,EAAW,cAErB,CACT,EAAW,MAAM,EACjB,EAAa,QAAU,IAC3B,CAEJ,EAAG,CAAC,EAAK,CAAO,CAAC,EAEV,CACH,SACA,cACA,cAAiB,EAAa,UAAU,CAC5C,CACJ"}
1
+ {"version":3,"file":"use-event-stream.cjs","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * Pass `schema` to have every frame validated before it reaches `onMessage`,\n * instead of trusting the type argument: without it `data` is announced as `T`\n * on the strength of the generic alone, which is a promise about the server\n * that TypeScript cannot keep. It is read when the stream opens, so declare it\n * outside the component rather than building one inline per render.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n *\n * @example\n * // Validated: a frame that does not match never reaches `onMessage`\n * const notificationSchema = z.object({ id: z.string(), message: z.string() });\n *\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * schema: notificationSchema,\n * onValidationError: (issues) => logger.warn(\"stream drift\", { issues }),\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":"6GAqDA,SAAgB,EACZ,EACA,EAAoC,CAAC,EACd,CACvB,GAAM,CAAE,UAAU,GAAM,YAAW,GAAG,GAAS,EACzC,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA4B,MAAM,EACxD,CAAC,EAAa,IAAA,EAAkB,EAAA,SAAA,CAAuC,IAAI,EAC3E,GAAA,EAAe,EAAA,OAAA,CAA4B,IAAI,EAE/C,EAAe,EAAA,aAAa,CAAS,EAyB3C,OAvBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,GAAW,CAAC,EAAK,CAClB,EAAU,MAAM,EAChB,MACJ,CAEA,IAAM,EAAa,EAAA,kBAAqB,EAAK,CACzC,GAAG,EACH,eAAgB,EAChB,UAAY,GAAY,CACpB,EAAe,CAAO,EACtB,EAAa,UAAU,CAAO,CAClC,CACJ,CAAC,EAGD,MAFA,GAAa,QAAU,EAAW,cAErB,CACT,EAAW,MAAM,EACjB,EAAa,QAAU,IAC3B,CAEJ,EAAG,CAAC,EAAK,CAAO,CAAC,EAEV,CACH,SACA,cACA,cAAiB,EAAa,UAAU,CAC5C,CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-event-stream.js","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":";;;;AAqCA,SAAgB,EACZ,GACA,IAAoC,CAAC,GACd;CACvB,IAAM,EAAE,aAAU,IAAM,cAAW,GAAG,MAAS,GACzC,CAAC,GAAQ,KAAa,EAA4B,MAAM,GACxD,CAAC,GAAa,KAAkB,EAAuC,IAAI,GAC3E,IAAe,EAA4B,IAAI,GAE/C,IAAe,EAAa,CAAS;CAyB3C,OAvBA,QAAgB;EACZ,IAAI,CAAC,KAAW,CAAC,GAAK;GAClB,EAAU,MAAM;GAChB;EACJ;EAEA,IAAM,IAAa,EAAqB,GAAK;GACzC,GAAG;GACH,gBAAgB;GAChB,YAAY,MAAY;IAEpB,AADA,EAAe,CAAO,GACtB,EAAa,UAAU,CAAO;GAClC;EACJ,CAAC;EAGD,OAFA,EAAa,UAAU,EAAW,iBAErB;GAET,AADA,EAAW,MAAM,GACjB,EAAa,UAAU;EAC3B;CAEJ,GAAG,CAAC,GAAK,CAAO,CAAC,GAEV;EACH;EACA;EACA,iBAAiB,EAAa,UAAU;CAC5C;AACJ"}
1
+ {"version":3,"file":"use-event-stream.js","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * Pass `schema` to have every frame validated before it reaches `onMessage`,\n * instead of trusting the type argument: without it `data` is announced as `T`\n * on the strength of the generic alone, which is a promise about the server\n * that TypeScript cannot keep. It is read when the stream opens, so declare it\n * outside the component rather than building one inline per render.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n *\n * @example\n * // Validated: a frame that does not match never reaches `onMessage`\n * const notificationSchema = z.object({ id: z.string(), message: z.string() });\n *\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * schema: notificationSchema,\n * onValidationError: (issues) => logger.warn(\"stream drift\", { issues }),\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":";;;;AAqDA,SAAgB,EACZ,GACA,IAAoC,CAAC,GACd;CACvB,IAAM,EAAE,aAAU,IAAM,cAAW,GAAG,MAAS,GACzC,CAAC,GAAQ,KAAa,EAA4B,MAAM,GACxD,CAAC,GAAa,KAAkB,EAAuC,IAAI,GAC3E,IAAe,EAA4B,IAAI,GAE/C,IAAe,EAAa,CAAS;CAyB3C,OAvBA,QAAgB;EACZ,IAAI,CAAC,KAAW,CAAC,GAAK;GAClB,EAAU,MAAM;GAChB;EACJ;EAEA,IAAM,IAAa,EAAqB,GAAK;GACzC,GAAG;GACH,gBAAgB;GAChB,YAAY,MAAY;IAEpB,AADA,EAAe,CAAO,GACtB,EAAa,UAAU,CAAO;GAClC;EACJ,CAAC;EAGD,OAFA,EAAa,UAAU,EAAW,iBAErB;GAET,AADA,EAAW,MAAM,GACjB,EAAa,UAAU;EAC3B;CAEJ,GAAG,CAAC,GAAK,CAAO,CAAC,GAEV;EACH;EACA;EACA,iBAAiB,EAAa,UAAU;CAC5C;AACJ"}
@@ -4,9 +4,9 @@
4
4
  .tempest_frame_AFLKa:active{cursor:grabbing}
5
5
  .tempest_frame_AFLKa:focus-visible{outline:2px solid var(--tempest-primary);outline-offset:2px}
6
6
  .tempest_circle_qI5b8{border-radius:var(--tempest-radius-full)}
7
- .tempest_image_8RAkb{pointer-events:none;max-width:none;position:absolute;top:50%;left:50%;translate:-50% -50%}
8
- .tempest_controls_Tbg70{align-items:center;gap:var(--tempest-space-3);display:flex}
9
- .tempest_zoom_hMOSD{min-width:0;accent-color:var(--tempest-primary);flex:1}
7
+ .tempest_image_8RAkb{pointer-events:none;max-width:none;max-height:none;position:absolute;top:50%;left:50%;translate:-50% -50%}
8
+ .tempest_controls_Tbg70{align-items:center;gap:var(--tempest-space-3);flex-wrap:wrap;display:flex}
9
+ .tempest_zoom_hMOSD{min-width:6rem;accent-color:var(--tempest-primary);flex:6rem}
10
10
  .tempest_zoom_hMOSD:disabled{opacity:.5}
11
11
  .tempest_reset_TK3vP{padding:var(--tempest-space-1) var(--tempest-space-3);border:1px solid var(--tempest-border);border-radius:var(--tempest-radius-md);background-color:var(--tempest-bg);color:var(--tempest-text);font:inherit;font-size:var(--tempest-text-sm);cursor:pointer}
12
12
  @media (hover:hover) and (pointer:fine){.tempest_reset_TK3vP:hover:not(:disabled){background-color:var(--tempest-surface)}}
@@ -2,7 +2,7 @@
2
2
  .tempest_wrapper_zEU-L{gap:var(--tempest-space-1);font-family:var(--tempest-font-sans);flex-direction:column;width:100%;display:flex}
3
3
  .tempest_label_ps3gD{font-size:var(--tempest-text-sm);font-weight:var(--tempest-weight-medium);color:var(--tempest-text)}
4
4
  .tempest_field_7taZG{border:1px solid var(--tempest-border);border-radius:var(--tempest-radius-md);background:var(--tempest-surface);transition:border-color var(--tempest-duration-fast,.12s);align-items:center;display:flex;position:relative}
5
- .tempest_field_7taZG:focus-within{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary-100))}
5
+ .tempest_field_7taZG:focus-within{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary))}
6
6
  .tempest_input_FWEvb{color:var(--tempest-text);font:inherit;font-family:var(--tempest-font-sans);background:0 0;border:0;outline:0;flex:auto}
7
7
  .tempest_sm_3-CZz .tempest_input_FWEvb{font-size:var(--tempest-text-sm);padding:6px 8px}
8
8
  .tempest_md_ReV0O .tempest_input_FWEvb{font-size:var(--tempest-text-md);padding:8px 12px}
@@ -6,7 +6,7 @@
6
6
  .tempest_cells_URO3v.tempest_sm_SQM-s .tempest_cell_fxLzs{width:32px;height:36px;font-size:var(--tempest-text-md)}
7
7
  .tempest_cells_URO3v.tempest_md_aHCGl .tempest_cell_fxLzs{width:44px;height:52px;font-size:var(--tempest-text-lg)}
8
8
  .tempest_cells_URO3v.tempest_lg_JpI4A .tempest_cell_fxLzs{width:56px;height:64px;font-size:var(--tempest-text-xl)}
9
- .tempest_cell_fxLzs:focus{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary-100));outline:none}
9
+ .tempest_cell_fxLzs:focus{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary));outline:none}
10
10
  .tempest_cell_fxLzs:disabled{opacity:.4;cursor:not-allowed}
11
11
  .tempest_error_Kou0o .tempest_cell_fxLzs{border-color:var(--tempest-danger-border,var(--tempest-danger))}
12
12
  .tempest_helper_W04iL{font-size:var(--tempest-text-xs);color:var(--tempest-text-muted)}
@@ -17,7 +17,7 @@ input[type=search]::-webkit-search-results-button{-webkit-appearance:none}
17
17
  input[type=search]::-webkit-search-results-decoration{-webkit-appearance:none}
18
18
  table{border-collapse:collapse;border-spacing:0}
19
19
  :where(ul,ol)[class]{margin:0;padding:0;list-style:none}
20
- :focus-visible{outline:var(--tempest-focus-ring-width,3px) solid var(--tempest-focus-ring-color,#0066ff59);outline-offset:var(--tempest-focus-ring-offset,2px)}
20
+ :focus-visible{outline:var(--tempest-focus-ring-width,3px) solid var(--tempest-focus-ring-color,currentColor);outline-offset:var(--tempest-focus-ring-offset,2px)}
21
21
  @media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}
22
22
  @media (width<=767.98px){.tempest-hide-mobile{display:none!important}}
23
23
  @media (width>=768px) and (width<=1023.98px){.tempest-hide-tablet{display:none!important}}
@@ -1,6 +1,6 @@
1
1
  /* Generated by scripts/split-css.mjs — do not edit. */
2
- :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--tempest-primary-50:#eef4ff;--tempest-primary-100:#d9e6ff;--tempest-primary-200:#b3ccff;--tempest-primary-300:#80aaff;--tempest-primary-400:#4d85ff;--tempest-primary-500:#06f;--tempest-primary-600:#0052cc;--tempest-primary-700:#003d99;--tempest-primary-800:#002e75;--tempest-primary-900:#001f4d;--tempest-primary:var(--tempest-primary-500);--tempest-primary-hover:var(--tempest-primary-600);--tempest-primary-active:var(--tempest-primary-700);--tempest-primary-soft:var(--tempest-primary-50);--tempest-primary-soft-hover:var(--tempest-primary-100);--tempest-primary-foreground:#fff;--tempest-danger-on-solid:#fff;--tempest-info-on-solid:#fff;--tempest-success-on-solid:#1f0606;--tempest-warning-on-solid:#1f0606;--tempest-neutral-on-solid:#fff;--tempest-primary-on-soft:var(--tempest-primary-600);--tempest-gray-50:#f8f9fb;--tempest-gray-100:#f1f3f6;--tempest-gray-200:#e4e7ec;--tempest-gray-300:#d0d5dd;--tempest-gray-400:#98a2b3;--tempest-gray-500:#667085;--tempest-gray-600:#475467;--tempest-gray-700:#344054;--tempest-gray-800:#1d2939;--tempest-gray-900:#101828;--tempest-bg:#fff;--tempest-surface:var(--tempest-gray-50);--tempest-surface-2:var(--tempest-gray-100);--tempest-surface-3:var(--tempest-gray-200);--tempest-border:var(--tempest-gray-200);--tempest-border-strong:var(--tempest-gray-300);--tempest-text:var(--tempest-gray-900);--tempest-text-muted:var(--tempest-gray-600);--tempest-text-subtle:var(--tempest-gray-500);--tempest-text-on-primary:#fff;--tempest-success:#15803d;--tempest-success-fg:#14532d;--tempest-success-bg:#dcfce7;--tempest-success-border:#86efac;--tempest-success-solid:#16a34a;--tempest-warning:#b45309;--tempest-warning-fg:#78350f;--tempest-warning-bg:#fef3c7;--tempest-warning-border:#fcd34d;--tempest-warning-solid:#d97706;--tempest-danger:#b91c1c;--tempest-danger-fg:#7f1d1d;--tempest-danger-bg:#fee2e2;--tempest-danger-border:#fca5a5;--tempest-danger-solid:#dc2626;--tempest-danger-hover:#991b1b;--tempest-info:#1d4ed8;--tempest-info-fg:#1e3a8a;--tempest-info-bg:#dbeafe;--tempest-info-border:#93c5fd;--tempest-info-solid:#2563eb;--tempest-focus-ring-color:#0066ff59;--tempest-focus-ring-width:3px;--tempest-focus-ring-offset:2px;--tempest-radius-xs:2px;--tempest-radius-sm:4px;--tempest-radius-md:8px;--tempest-radius-lg:12px;--tempest-radius-xl:16px;--tempest-radius-2xl:24px;--tempest-radius-full:9999px;--tempest-shadow-xs:0 1px 2px #0f172a0a;--tempest-shadow-sm:0 1px 3px #0f172a14, 0 1px 2px #0f172a0a;--tempest-shadow-md:0 4px 12px #0f172a14, 0 2px 4px #0f172a0a;--tempest-shadow-lg:0 12px 32px #0f172a1f, 0 4px 8px #0f172a0f;--tempest-shadow-xl:0 24px 48px #0f172a2e, 0 8px 16px #0f172a14;--tempest-shadow-inner:inset 0 2px 4px #0f172a0f;--tempest-space-0:0px;--tempest-space-1:4px;--tempest-space-2:8px;--tempest-space-3:12px;--tempest-space-4:16px;--tempest-space-5:20px;--tempest-space-6:24px;--tempest-space-7:28px;--tempest-space-8:32px;--tempest-space-10:40px;--tempest-space-12:48px;--tempest-space-16:64px;--tempest-space-20:80px;--tempest-space-24:96px;--tempest-font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;--tempest-font-mono:ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Code", Menlo, Consolas, "Liberation Mono", monospace;--tempest-font-display:var(--tempest-font-sans);--tempest-z-base:0;--tempest-z-raised:10;--tempest-z-dropdown:1000;--tempest-z-sticky:1020;--tempest-z-overlay:1050;--tempest-z-modal:1100;--tempest-z-popover:1150;--tempest-z-toast:1200;--tempest-z-tooltip:1300;--tempest-bp-xs:480px;--tempest-bp-sm:640px;--tempest-bp-md:768px;--tempest-bp-lg:1024px;--tempest-bp-xl:1280px;--tempest-bp-2xl:1536px;--tempest-chart-1:#2563eb;--tempest-chart-2:#16a34a;--tempest-chart-3:#f59e0b;--tempest-chart-4:#7c3aed;--tempest-chart-5:#ec4899;--tempest-chart-6:#06b6d4;--tempest-chart-7:#ea580c;--tempest-chart-8:#0f766e;--tempest-chart-sequential-1:#dde8fe;--tempest-chart-sequential-2:#acc8fe;--tempest-chart-sequential-3:#7aa7ff;--tempest-chart-sequential-4:#4983fc;--tempest-chart-sequential-5:#3667ce;--tempest-chart-sequential-6:#2b4e96;--tempest-chart-sequential-7:#25375a;--tempest-chart-diverging-1:#0f3ca1;--tempest-chart-diverging-2:#416eca;--tempest-chart-diverging-3:#7ba0e7;--tempest-chart-diverging-4:#bdd2f9;--tempest-chart-diverging-5:#e4e7ec;--tempest-chart-diverging-6:#f6c3bc;--tempest-chart-diverging-7:#de847a;--tempest-chart-diverging-8:#ba473f;--tempest-chart-diverging-9:#870f11;--tempest-chart-grid:var(--tempest-gray-200);--tempest-chart-axis:var(--tempest-gray-500);--tempest-code-comment:#5f666e;--tempest-code-punctuation:#5d6671;--tempest-code-string:#327243;--tempest-code-number:#9d5035;--tempest-code-keyword:#625baf;--tempest-code-literal:#9f4c4f;--tempest-code-function:#1a6c94;--tempest-code-tag:#974d72;--tempest-code-attribute:#127171;--tempest-code-property:#127171;--tempest-safe-area-top:env(safe-area-inset-top,0px);--tempest-safe-area-right:env(safe-area-inset-right,0px);--tempest-safe-area-bottom:env(safe-area-inset-bottom,0px);--tempest-safe-area-left:env(safe-area-inset-left,0px)}
3
- [data-tempest-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--tempest-primary-50:#0a1730;--tempest-primary-100:#0f2245;--tempest-primary-200:#133066;--tempest-primary-300:#1a4399;--tempest-primary-400:#2563eb;--tempest-primary-500:#3b82f6;--tempest-primary-600:#60a5fa;--tempest-primary-700:#93c5fd;--tempest-primary-800:#bfdbfe;--tempest-primary-900:#dbeafe;--tempest-primary:var(--tempest-primary-500);--tempest-primary-hover:var(--tempest-primary-600);--tempest-primary-active:var(--tempest-primary-700);--tempest-primary-soft:var(--tempest-primary-100);--tempest-primary-soft-hover:var(--tempest-primary-200);--tempest-primary-on-soft:var(--tempest-primary-700);--tempest-bg:#0b0d12;--tempest-surface:#14171f;--tempest-surface-2:#1d2230;--tempest-surface-3:#262d3f;--tempest-border:#2a3142;--tempest-border-strong:#3a4258;--tempest-text:#f1f3f8;--tempest-text-muted:#aab2c0;--tempest-text-subtle:#7b849a;--tempest-primary-foreground:#1f0606;--tempest-text-on-primary:var(--tempest-primary-foreground);--tempest-danger-on-solid:#1f0606;--tempest-info-on-solid:#1f0606;--tempest-success-on-solid:#1f0606;--tempest-warning-on-solid:#1f0606;--tempest-neutral-on-solid:#fff;--tempest-success-fg:#4ade80;--tempest-success-bg:#052e16;--tempest-success-border:#166534;--tempest-success-solid:#22c55e;--tempest-success:#4ade80;--tempest-warning-fg:#fbbf24;--tempest-warning-bg:#2c1c05;--tempest-warning-border:#78350f;--tempest-warning-solid:#f59e0b;--tempest-warning:#fbbf24;--tempest-danger-fg:#f87171;--tempest-danger-bg:#2c0a0a;--tempest-danger-border:#7f1d1d;--tempest-danger-solid:#ef4444;--tempest-danger:#f87171;--tempest-danger-hover:#f87171;--tempest-info-fg:#60a5fa;--tempest-info-bg:#0a1730;--tempest-info-border:#1e3a8a;--tempest-info-solid:#3b82f6;--tempest-info:#60a5fa;--tempest-focus-ring-color:#60a5fa73;--tempest-shadow-xs:0 1px 2px #0000004d;--tempest-shadow-sm:0 1px 3px #0006, 0 1px 2px #0003;--tempest-shadow-md:0 4px 12px #0006, 0 2px 4px #0003;--tempest-shadow-lg:0 12px 32px #00000080, 0 4px 8px #0000004d;--tempest-shadow-xl:0 24px 48px #0009, 0 8px 16px #0000004d;--tempest-chart-1:#60a5fa;--tempest-chart-2:#4ade80;--tempest-chart-3:#fbbf24;--tempest-chart-4:#a78bfa;--tempest-chart-5:#f472b6;--tempest-chart-6:#22d3ee;--tempest-chart-7:#fb923c;--tempest-chart-8:#2dd4bf;--tempest-chart-sequential-1:#172849;--tempest-chart-sequential-2:#1b3c83;--tempest-chart-sequential-3:#2453b9;--tempest-chart-sequential-4:#376fe5;--tempest-chart-sequential-5:#5b90fb;--tempest-chart-sequential-6:#8cb2fa;--tempest-chart-sequential-7:#bad2fe;--tempest-chart-diverging-1:#1543a8;--tempest-chart-diverging-2:#416eca;--tempest-chart-diverging-3:#7599e0;--tempest-chart-diverging-4:#b0c5ec;--tempest-chart-diverging-5:#262d3f;--tempest-chart-diverging-6:#e8b6af;--tempest-chart-diverging-7:#d77e74;--tempest-chart-diverging-8:#ba473f;--tempest-chart-diverging-9:#94020c;--tempest-chart-grid:var(--tempest-gray-300);--tempest-chart-axis:var(--tempest-gray-600);--tempest-code-comment:#838b93;--tempest-code-punctuation:#818b95;--tempest-code-string:#589866;--tempest-code-number:#c57558;--tempest-code-keyword:#8580d8;--tempest-code-literal:#c87172;--tempest-code-function:#2893c6;--tempest-code-tag:#c07197;--tempest-code-attribute:#349897;--tempest-code-property:#349897}
2
+ :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--tempest-primary-50:#eef4ff;--tempest-primary-100:#d9e6ff;--tempest-primary-200:#b3ccff;--tempest-primary-300:#80aaff;--tempest-primary-400:#4d85ff;--tempest-primary-500:#06f;--tempest-primary-600:#0052cc;--tempest-primary-700:#003d99;--tempest-primary-800:#002e75;--tempest-primary-900:#001f4d;--tempest-primary:var(--tempest-primary-500);--tempest-primary-hover:var(--tempest-primary-600);--tempest-primary-active:var(--tempest-primary-700);--tempest-primary-soft:var(--tempest-primary-50);--tempest-primary-soft-hover:var(--tempest-primary-100);--tempest-primary-foreground:#fff;--tempest-danger-on-solid:#fff;--tempest-info-on-solid:#fff;--tempest-success-on-solid:#1f0606;--tempest-warning-on-solid:#1f0606;--tempest-neutral-on-solid:#fff;--tempest-primary-on-soft:var(--tempest-primary-600);--tempest-gray-50:#f8f9fb;--tempest-gray-100:#f1f3f6;--tempest-gray-200:#e4e7ec;--tempest-gray-300:#d0d5dd;--tempest-gray-400:#98a2b3;--tempest-gray-500:#667085;--tempest-gray-600:#475467;--tempest-gray-700:#344054;--tempest-gray-800:#1d2939;--tempest-gray-900:#101828;--tempest-bg:#fff;--tempest-surface:var(--tempest-gray-50);--tempest-surface-2:var(--tempest-gray-100);--tempest-surface-3:var(--tempest-gray-200);--tempest-border:var(--tempest-gray-200);--tempest-border-strong:var(--tempest-gray-300);--tempest-text:var(--tempest-gray-900);--tempest-text-muted:var(--tempest-gray-600);--tempest-text-subtle:var(--tempest-gray-500);--tempest-text-on-primary:#fff;--tempest-success:#15803d;--tempest-success-fg:#14532d;--tempest-success-bg:#dcfce7;--tempest-success-border:#86efac;--tempest-success-solid:#16a34a;--tempest-warning:#b45309;--tempest-warning-fg:#78350f;--tempest-warning-bg:#fef3c7;--tempest-warning-border:#fcd34d;--tempest-warning-solid:#d97706;--tempest-danger:#b91c1c;--tempest-danger-fg:#7f1d1d;--tempest-danger-bg:#fee2e2;--tempest-danger-border:#fca5a5;--tempest-danger-solid:#dc2626;--tempest-danger-hover:#991b1b;--tempest-info:#1d4ed8;--tempest-info-fg:#1e3a8a;--tempest-info-bg:#dbeafe;--tempest-info-border:#93c5fd;--tempest-info-solid:#2563eb;--tempest-focus-ring-color:#0052cc;--tempest-focus-ring-width:3px;--tempest-focus-ring-offset:2px;--tempest-radius-xs:2px;--tempest-radius-sm:4px;--tempest-radius-md:8px;--tempest-radius-lg:12px;--tempest-radius-xl:16px;--tempest-radius-2xl:24px;--tempest-radius-full:9999px;--tempest-shadow-xs:0 1px 2px #0f172a0a;--tempest-shadow-sm:0 1px 3px #0f172a14, 0 1px 2px #0f172a0a;--tempest-shadow-md:0 4px 12px #0f172a14, 0 2px 4px #0f172a0a;--tempest-shadow-lg:0 12px 32px #0f172a1f, 0 4px 8px #0f172a0f;--tempest-shadow-xl:0 24px 48px #0f172a2e, 0 8px 16px #0f172a14;--tempest-shadow-inner:inset 0 2px 4px #0f172a0f;--tempest-space-0:0px;--tempest-space-1:4px;--tempest-space-2:8px;--tempest-space-3:12px;--tempest-space-4:16px;--tempest-space-5:20px;--tempest-space-6:24px;--tempest-space-7:28px;--tempest-space-8:32px;--tempest-space-10:40px;--tempest-space-12:48px;--tempest-space-16:64px;--tempest-space-20:80px;--tempest-space-24:96px;--tempest-font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;--tempest-font-mono:ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Code", Menlo, Consolas, "Liberation Mono", monospace;--tempest-font-display:var(--tempest-font-sans);--tempest-z-base:0;--tempest-z-raised:10;--tempest-z-dropdown:1000;--tempest-z-sticky:1020;--tempest-z-overlay:1050;--tempest-z-modal:1100;--tempest-z-popover:1150;--tempest-z-toast:1200;--tempest-z-tooltip:1300;--tempest-bp-xs:480px;--tempest-bp-sm:640px;--tempest-bp-md:768px;--tempest-bp-lg:1024px;--tempest-bp-xl:1280px;--tempest-bp-2xl:1536px;--tempest-chart-1:#2563eb;--tempest-chart-2:#16a34a;--tempest-chart-3:#f59e0b;--tempest-chart-4:#7c3aed;--tempest-chart-5:#ec4899;--tempest-chart-6:#06b6d4;--tempest-chart-7:#ea580c;--tempest-chart-8:#0f766e;--tempest-chart-sequential-1:#dde8fe;--tempest-chart-sequential-2:#acc8fe;--tempest-chart-sequential-3:#7aa7ff;--tempest-chart-sequential-4:#4983fc;--tempest-chart-sequential-5:#3667ce;--tempest-chart-sequential-6:#2b4e96;--tempest-chart-sequential-7:#25375a;--tempest-chart-diverging-1:#0f3ca1;--tempest-chart-diverging-2:#416eca;--tempest-chart-diverging-3:#7ba0e7;--tempest-chart-diverging-4:#bdd2f9;--tempest-chart-diverging-5:#e4e7ec;--tempest-chart-diverging-6:#f6c3bc;--tempest-chart-diverging-7:#de847a;--tempest-chart-diverging-8:#ba473f;--tempest-chart-diverging-9:#870f11;--tempest-chart-grid:var(--tempest-gray-200);--tempest-chart-axis:var(--tempest-gray-500);--tempest-code-comment:#5f666e;--tempest-code-punctuation:#5d6671;--tempest-code-string:#327243;--tempest-code-number:#9d5035;--tempest-code-keyword:#625baf;--tempest-code-literal:#9f4c4f;--tempest-code-function:#1a6c94;--tempest-code-tag:#974d72;--tempest-code-attribute:#127171;--tempest-code-property:#127171;--tempest-safe-area-top:env(safe-area-inset-top,0px);--tempest-safe-area-right:env(safe-area-inset-right,0px);--tempest-safe-area-bottom:env(safe-area-inset-bottom,0px);--tempest-safe-area-left:env(safe-area-inset-left,0px)}
3
+ [data-tempest-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--tempest-primary-50:#0a1730;--tempest-primary-100:#0f2245;--tempest-primary-200:#133066;--tempest-primary-300:#1a4399;--tempest-primary-400:#2563eb;--tempest-primary-500:#3b82f6;--tempest-primary-600:#60a5fa;--tempest-primary-700:#93c5fd;--tempest-primary-800:#bfdbfe;--tempest-primary-900:#dbeafe;--tempest-primary:var(--tempest-primary-500);--tempest-primary-hover:var(--tempest-primary-600);--tempest-primary-active:var(--tempest-primary-700);--tempest-primary-soft:var(--tempest-primary-100);--tempest-primary-soft-hover:var(--tempest-primary-200);--tempest-primary-on-soft:var(--tempest-primary-700);--tempest-bg:#0b0d12;--tempest-surface:#14171f;--tempest-surface-2:#1d2230;--tempest-surface-3:#262d3f;--tempest-border:#2a3142;--tempest-border-strong:#3a4258;--tempest-text:#f1f3f8;--tempest-text-muted:#aab2c0;--tempest-text-subtle:#7b849a;--tempest-primary-foreground:#1f0606;--tempest-text-on-primary:var(--tempest-primary-foreground);--tempest-danger-on-solid:#1f0606;--tempest-info-on-solid:#1f0606;--tempest-success-on-solid:#1f0606;--tempest-warning-on-solid:#1f0606;--tempest-neutral-on-solid:#fff;--tempest-success-fg:#4ade80;--tempest-success-bg:#052e16;--tempest-success-border:#166534;--tempest-success-solid:#22c55e;--tempest-success:#4ade80;--tempest-warning-fg:#fbbf24;--tempest-warning-bg:#2c1c05;--tempest-warning-border:#78350f;--tempest-warning-solid:#f59e0b;--tempest-warning:#fbbf24;--tempest-danger-fg:#f87171;--tempest-danger-bg:#2c0a0a;--tempest-danger-border:#7f1d1d;--tempest-danger-solid:#ef4444;--tempest-danger:#f87171;--tempest-danger-hover:#f87171;--tempest-info-fg:#60a5fa;--tempest-info-bg:#0a1730;--tempest-info-border:#1e3a8a;--tempest-info-solid:#3b82f6;--tempest-info:#60a5fa;--tempest-focus-ring-color:#60a5fa;--tempest-shadow-xs:0 1px 2px #0000004d;--tempest-shadow-sm:0 1px 3px #0006, 0 1px 2px #0003;--tempest-shadow-md:0 4px 12px #0006, 0 2px 4px #0003;--tempest-shadow-lg:0 12px 32px #00000080, 0 4px 8px #0000004d;--tempest-shadow-xl:0 24px 48px #0009, 0 8px 16px #0000004d;--tempest-chart-1:#60a5fa;--tempest-chart-2:#4ade80;--tempest-chart-3:#fbbf24;--tempest-chart-4:#a78bfa;--tempest-chart-5:#f472b6;--tempest-chart-6:#22d3ee;--tempest-chart-7:#fb923c;--tempest-chart-8:#2dd4bf;--tempest-chart-sequential-1:#172849;--tempest-chart-sequential-2:#1b3c83;--tempest-chart-sequential-3:#2453b9;--tempest-chart-sequential-4:#376fe5;--tempest-chart-sequential-5:#5b90fb;--tempest-chart-sequential-6:#8cb2fa;--tempest-chart-sequential-7:#bad2fe;--tempest-chart-diverging-1:#1543a8;--tempest-chart-diverging-2:#416eca;--tempest-chart-diverging-3:#7599e0;--tempest-chart-diverging-4:#b0c5ec;--tempest-chart-diverging-5:#262d3f;--tempest-chart-diverging-6:#e8b6af;--tempest-chart-diverging-7:#d77e74;--tempest-chart-diverging-8:#ba473f;--tempest-chart-diverging-9:#94020c;--tempest-chart-grid:var(--tempest-gray-300);--tempest-chart-axis:var(--tempest-gray-600);--tempest-code-comment:#838b93;--tempest-code-punctuation:#818b95;--tempest-code-string:#589866;--tempest-code-number:#c57558;--tempest-code-keyword:#8580d8;--tempest-code-literal:#c87172;--tempest-code-function:#2893c6;--tempest-code-tag:#c07197;--tempest-code-attribute:#349897;--tempest-code-property:#349897}
4
4
  :root{--tempest-text-2xs:10px;--tempest-text-xs:12px;--tempest-text-sm:13px;--tempest-text-base:14px;--tempest-text-md:15px;--tempest-text-lg:16px;--tempest-text-xl:18px;--tempest-text-2xl:20px;--tempest-text-3xl:24px;--tempest-text-4xl:30px;--tempest-text-5xl:36px;--tempest-text-6xl:48px;--tempest-leading-none:1;--tempest-leading-tight:1.2;--tempest-leading-snug:1.35;--tempest-leading-normal:1.5;--tempest-leading-relaxed:1.65;--tempest-leading-loose:1.9;--tempest-weight-regular:400;--tempest-weight-medium:500;--tempest-weight-semibold:600;--tempest-weight-bold:700;--tempest-weight-extrabold:800;--tempest-tracking-tight:-.02em;--tempest-tracking-normal:0;--tempest-tracking-wide:.025em;--tempest-tracking-wider:.05em;--tempest-tracking-widest:.1em;--tempest-text-fluid-sm:clamp(13px, 12px + .2vw, 15px);--tempest-text-fluid-base:clamp(14px, 13px + .3vw, 16px);--tempest-text-fluid-lg:clamp(16px, 14px + .5vw, 20px);--tempest-text-fluid-xl:clamp(18px, 15px + .8vw, 24px);--tempest-text-fluid-2xl:clamp(20px, 16px + 1.2vw, 30px);--tempest-text-fluid-3xl:clamp(24px, 18px + 2vw, 40px);--tempest-text-fluid-4xl:clamp(28px, 20px + 3vw, 56px);--tempest-text-fluid-5xl:clamp(32px, 24px + 4vw, 72px);--tempest-text-fluid-6xl:clamp(40px, 28px + 5vw, 96px);--tempest-duration-instant:0s;--tempest-duration-fast:.12s;--tempest-duration-base:.18s;--tempest-duration-slow:.28s;--tempest-duration-slower:.42s;--tempest-ease-linear:linear;--tempest-ease-in:cubic-bezier(.4, 0, 1, 1);--tempest-ease-out:cubic-bezier(0, 0, .2, 1);--tempest-ease-in-out:cubic-bezier(.4, 0, .2, 1);--tempest-ease-emphasized:cubic-bezier(.2, 0, 0, 1);--tempest-ease-bounce:cubic-bezier(.34, 1.56, .64, 1);--tempest-transition-color:color var(--tempest-duration-fast) var(--tempest-ease-out), background-color var(--tempest-duration-fast) var(--tempest-ease-out), border-color var(--tempest-duration-fast) var(--tempest-ease-out);--tempest-transition-shadow:box-shadow var(--tempest-duration-base) var(--tempest-ease-out);--tempest-transition-transform:transform var(--tempest-duration-fast) var(--tempest-ease-out);--tempest-transition-base:color var(--tempest-duration-fast) var(--tempest-ease-out), background-color var(--tempest-duration-fast) var(--tempest-ease-out), border-color var(--tempest-duration-fast) var(--tempest-ease-out), box-shadow var(--tempest-duration-base) var(--tempest-ease-out), transform var(--tempest-duration-fast) var(--tempest-ease-out), opacity var(--tempest-duration-fast) var(--tempest-ease-out)}
5
5
  @media (prefers-reduced-motion:reduce){:root{--tempest-duration-fast:.01ms;--tempest-duration-base:.01ms;--tempest-duration-slow:.01ms;--tempest-duration-slower:.01ms}}
6
6
  :root,[data-tempest-density=comfortable]{--tempest-control-height-xs:24px;--tempest-control-height-sm:32px;--tempest-control-height-md:40px;--tempest-control-height-lg:48px;--tempest-control-height-xl:56px;--tempest-control-padding-xs:var(--tempest-space-2);--tempest-control-padding-sm:var(--tempest-space-3);--tempest-control-padding-md:var(--tempest-space-4);--tempest-control-padding-lg:var(--tempest-space-5);--tempest-control-padding-xl:var(--tempest-space-6);--tempest-control-font-xs:var(--tempest-text-xs);--tempest-control-font-sm:var(--tempest-text-sm);--tempest-control-font-md:var(--tempest-text-base);--tempest-control-font-lg:var(--tempest-text-lg);--tempest-control-font-xl:var(--tempest-text-xl);--tempest-control-radius:var(--tempest-radius-md);--tempest-control-gap:var(--tempest-space-2)}
@@ -26,7 +26,7 @@ input[type=search]::-webkit-search-results-button{-webkit-appearance:none}
26
26
  input[type=search]::-webkit-search-results-decoration{-webkit-appearance:none}
27
27
  table{border-collapse:collapse;border-spacing:0}
28
28
  :where(ul,ol)[class]{margin:0;padding:0;list-style:none}
29
- :focus-visible{outline:var(--tempest-focus-ring-width,3px) solid var(--tempest-focus-ring-color,#0066ff59);outline-offset:var(--tempest-focus-ring-offset,2px)}
29
+ :focus-visible{outline:var(--tempest-focus-ring-width,3px) solid var(--tempest-focus-ring-color,currentColor);outline-offset:var(--tempest-focus-ring-offset,2px)}
30
30
  @media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}
31
31
  @media (width<=767.98px){.tempest-hide-mobile{display:none!important}}
32
32
  @media (width>=768px) and (width<=1023.98px){.tempest-hide-tablet{display:none!important}}
@@ -106,9 +106,9 @@
106
106
  .tempest_frame_AFLKa:active{cursor:grabbing}
107
107
  .tempest_frame_AFLKa:focus-visible{outline:2px solid var(--tempest-primary);outline-offset:2px}
108
108
  .tempest_circle_qI5b8{border-radius:var(--tempest-radius-full)}
109
- .tempest_image_8RAkb{pointer-events:none;max-width:none;position:absolute;top:50%;left:50%;translate:-50% -50%}
110
- .tempest_controls_Tbg70{align-items:center;gap:var(--tempest-space-3);display:flex}
111
- .tempest_zoom_hMOSD{min-width:0;accent-color:var(--tempest-primary);flex:1}
109
+ .tempest_image_8RAkb{pointer-events:none;max-width:none;max-height:none;position:absolute;top:50%;left:50%;translate:-50% -50%}
110
+ .tempest_controls_Tbg70{align-items:center;gap:var(--tempest-space-3);flex-wrap:wrap;display:flex}
111
+ .tempest_zoom_hMOSD{min-width:6rem;accent-color:var(--tempest-primary);flex:6rem}
112
112
  .tempest_zoom_hMOSD:disabled{opacity:.5}
113
113
  .tempest_reset_TK3vP{padding:var(--tempest-space-1) var(--tempest-space-3);border:1px solid var(--tempest-border);border-radius:var(--tempest-radius-md);background-color:var(--tempest-bg);color:var(--tempest-text);font:inherit;font-size:var(--tempest-text-sm);cursor:pointer}
114
114
  @media (hover:hover) and (pointer:fine){.tempest_reset_TK3vP:hover:not(:disabled){background-color:var(--tempest-surface)}}
@@ -162,7 +162,7 @@
162
162
  .tempest_wrapper_zEU-L{gap:var(--tempest-space-1);font-family:var(--tempest-font-sans);flex-direction:column;width:100%;display:flex}
163
163
  .tempest_label_ps3gD{font-size:var(--tempest-text-sm);font-weight:var(--tempest-weight-medium);color:var(--tempest-text)}
164
164
  .tempest_field_7taZG{border:1px solid var(--tempest-border);border-radius:var(--tempest-radius-md);background:var(--tempest-surface);transition:border-color var(--tempest-duration-fast,.12s);align-items:center;display:flex;position:relative}
165
- .tempest_field_7taZG:focus-within{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary-100))}
165
+ .tempest_field_7taZG:focus-within{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary))}
166
166
  .tempest_input_FWEvb{color:var(--tempest-text);font:inherit;font-family:var(--tempest-font-sans);background:0 0;border:0;outline:0;flex:auto}
167
167
  .tempest_sm_3-CZz .tempest_input_FWEvb{font-size:var(--tempest-text-sm);padding:6px 8px}
168
168
  .tempest_md_ReV0O .tempest_input_FWEvb{font-size:var(--tempest-text-md);padding:8px 12px}
@@ -186,7 +186,7 @@
186
186
  .tempest_cells_URO3v.tempest_sm_SQM-s .tempest_cell_fxLzs{width:32px;height:36px;font-size:var(--tempest-text-md)}
187
187
  .tempest_cells_URO3v.tempest_md_aHCGl .tempest_cell_fxLzs{width:44px;height:52px;font-size:var(--tempest-text-lg)}
188
188
  .tempest_cells_URO3v.tempest_lg_JpI4A .tempest_cell_fxLzs{width:56px;height:64px;font-size:var(--tempest-text-xl)}
189
- .tempest_cell_fxLzs:focus{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary-100));outline:none}
189
+ .tempest_cell_fxLzs:focus{border-color:var(--tempest-primary);box-shadow:0 0 0 var(--tempest-focus-ring-width,2px) var(--tempest-focus-ring-color,var(--tempest-primary));outline:none}
190
190
  .tempest_cell_fxLzs:disabled{opacity:.4;cursor:not-allowed}
191
191
  .tempest_error_Kou0o .tempest_cell_fxLzs{border-color:var(--tempest-danger-border,var(--tempest-danger))}
192
192
  .tempest_helper_W04iL{font-size:var(--tempest-text-xs);color:var(--tempest-text-muted)}
@@ -115,7 +115,7 @@ table:where([class*="tempest_"]) {
115
115
  :where([class*="tempest_"]) :focus-visible,
116
116
  :focus-visible:where([class*="tempest_"]) {
117
117
  outline: var(--tempest-focus-ring-width, 3px) solid
118
- var(--tempest-focus-ring-color, rgba(0, 102, 255, 0.35));
118
+ var(--tempest-focus-ring-color, currentColor);
119
119
  outline-offset: var(--tempest-focus-ring-offset, 2px);
120
120
  }
121
121