tempest-react-sdk 0.43.0 → 0.44.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 (121) hide show
  1. package/README.md +6 -2
  2. package/dist/app/AppProviders.cjs +1 -1
  3. package/dist/app/AppProviders.js +6 -6
  4. package/dist/audio/sfx-pool.cjs +2 -0
  5. package/dist/audio/sfx-pool.cjs.map +1 -0
  6. package/dist/audio/sfx-pool.js +62 -0
  7. package/dist/audio/sfx-pool.js.map +1 -0
  8. package/dist/audio/use-sfx-pool.cjs +2 -0
  9. package/dist/audio/use-sfx-pool.cjs.map +1 -0
  10. package/dist/audio/use-sfx-pool.js +18 -0
  11. package/dist/audio/use-sfx-pool.js.map +1 -0
  12. package/dist/auth/lazy-with-retry.cjs +1 -1
  13. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  14. package/dist/auth/lazy-with-retry.js +8 -1
  15. package/dist/auth/lazy-with-retry.js.map +1 -1
  16. package/dist/components/AppBar/AppBar.cjs +1 -1
  17. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  18. package/dist/components/AppBar/AppBar.js +27 -25
  19. package/dist/components/AppBar/AppBar.js.map +1 -1
  20. package/dist/components/AppBar/use-sticky-body-warning.cjs +2 -0
  21. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -0
  22. package/dist/components/AppBar/use-sticky-body-warning.js +16 -0
  23. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -0
  24. package/dist/components/BarList/BarList.cjs +2 -0
  25. package/dist/components/BarList/BarList.cjs.map +1 -0
  26. package/dist/components/BarList/BarList.js +42 -0
  27. package/dist/components/BarList/BarList.js.map +1 -0
  28. package/dist/components/BarList/BarList.module.cjs +2 -0
  29. package/dist/components/BarList/BarList.module.cjs.map +1 -0
  30. package/dist/components/BarList/BarList.module.js +15 -0
  31. package/dist/components/BarList/BarList.module.js.map +1 -0
  32. package/dist/components/BarList/bar-list-model.cjs +2 -0
  33. package/dist/components/BarList/bar-list-model.cjs.map +1 -0
  34. package/dist/components/BarList/bar-list-model.js +18 -0
  35. package/dist/components/BarList/bar-list-model.js.map +1 -0
  36. package/dist/components/DataTable/DataTable.cjs +1 -1
  37. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  38. package/dist/components/DataTable/DataTable.js +141 -108
  39. package/dist/components/DataTable/DataTable.js.map +1 -1
  40. package/dist/components/DataTable/DataTable.module.cjs +1 -1
  41. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  42. package/dist/components/DataTable/DataTable.module.js +13 -10
  43. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  44. package/dist/components/DataTable/LoadingRows.cjs +2 -0
  45. package/dist/components/DataTable/LoadingRows.cjs.map +1 -0
  46. package/dist/components/DataTable/LoadingRows.js +18 -0
  47. package/dist/components/DataTable/LoadingRows.js.map +1 -0
  48. package/dist/components/DataTable/use-dev-warnings.cjs +2 -0
  49. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -0
  50. package/dist/components/DataTable/use-dev-warnings.js +17 -0
  51. package/dist/components/DataTable/use-dev-warnings.js.map +1 -0
  52. package/dist/components/FilterBar/filter-apply.cjs +2 -0
  53. package/dist/components/FilterBar/filter-apply.cjs.map +1 -0
  54. package/dist/components/FilterBar/filter-apply.js +76 -0
  55. package/dist/components/FilterBar/filter-apply.js.map +1 -0
  56. package/dist/components/FilterBar/filter-query.cjs +2 -0
  57. package/dist/components/FilterBar/filter-query.cjs.map +1 -0
  58. package/dist/components/FilterBar/filter-query.js +38 -0
  59. package/dist/components/FilterBar/filter-query.js.map +1 -0
  60. package/dist/hooks/use-countdown.cjs +2 -0
  61. package/dist/hooks/use-countdown.cjs.map +1 -0
  62. package/dist/hooks/use-countdown.js +18 -0
  63. package/dist/hooks/use-countdown.js.map +1 -0
  64. package/dist/hooks/use-latest-ref.cjs +2 -0
  65. package/dist/hooks/use-latest-ref.cjs.map +1 -0
  66. package/dist/hooks/use-latest-ref.js +10 -0
  67. package/dist/hooks/use-latest-ref.js.map +1 -0
  68. package/dist/hooks/use-typewriter.cjs +2 -0
  69. package/dist/hooks/use-typewriter.cjs.map +1 -0
  70. package/dist/hooks/use-typewriter.js +28 -0
  71. package/dist/hooks/use-typewriter.js.map +1 -0
  72. package/dist/http/describe-api-error.cjs +2 -0
  73. package/dist/http/describe-api-error.cjs.map +1 -0
  74. package/dist/http/describe-api-error.js +22 -0
  75. package/dist/http/describe-api-error.js.map +1 -0
  76. package/dist/http/use-describe-api-error.cjs +2 -0
  77. package/dist/http/use-describe-api-error.cjs.map +1 -0
  78. package/dist/http/use-describe-api-error.js +15 -0
  79. package/dist/http/use-describe-api-error.js.map +1 -0
  80. package/dist/i18n/I18nProvider.cjs +1 -1
  81. package/dist/i18n/I18nProvider.cjs.map +1 -1
  82. package/dist/i18n/I18nProvider.js +4 -1
  83. package/dist/i18n/I18nProvider.js.map +1 -1
  84. package/dist/query/QueryProvider.cjs +1 -1
  85. package/dist/query/QueryProvider.cjs.map +1 -1
  86. package/dist/query/QueryProvider.js +13 -12
  87. package/dist/query/QueryProvider.js.map +1 -1
  88. package/dist/query/retry-policy.cjs +2 -0
  89. package/dist/query/retry-policy.cjs.map +1 -0
  90. package/dist/query/retry-policy.js +10 -0
  91. package/dist/query/retry-policy.js.map +1 -0
  92. package/dist/styles.css +1 -1
  93. package/dist/tempest-react-sdk.cjs +1 -1
  94. package/dist/tempest-react-sdk.d.ts +788 -9
  95. package/dist/tempest-react-sdk.js +275 -261
  96. package/dist/utils/compressed-storage.cjs +2 -0
  97. package/dist/utils/compressed-storage.cjs.map +1 -0
  98. package/dist/utils/compressed-storage.js +52 -0
  99. package/dist/utils/compressed-storage.js.map +1 -0
  100. package/dist/utils/csv.cjs +4 -0
  101. package/dist/utils/csv.cjs.map +1 -0
  102. package/dist/utils/csv.js +25 -0
  103. package/dist/utils/csv.js.map +1 -0
  104. package/dist/utils/format.cjs +1 -1
  105. package/dist/utils/format.cjs.map +1 -1
  106. package/dist/utils/format.js +11 -4
  107. package/dist/utils/format.js.map +1 -1
  108. package/dist/utils/numbers.cjs +1 -1
  109. package/dist/utils/numbers.cjs.map +1 -1
  110. package/dist/utils/numbers.js +5 -2
  111. package/dist/utils/numbers.js.map +1 -1
  112. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  113. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  114. package/dist/vite/tempest-pwa-dev-sw.js +10 -5
  115. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  116. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  117. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  118. package/dist/vite/tempest-pwa-manifest.js +17 -13
  119. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  120. package/dist/vite.d.ts +19 -2
  121. package/package.json +1 -1
@@ -543,6 +543,16 @@ declare type AnyEventTarget = EventTarget | {
543
543
  current: EventTarget | null;
544
544
  } | null | undefined;
545
545
 
546
+ /**
547
+ * Translation key the {@link useDescribeApiError} hook looks up.
548
+ *
549
+ * A catalog that does not define it falls back to
550
+ * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the
551
+ * lookup misses and printing `tempest.error.offline` at the user would be worse
552
+ * than printing pt-BR at them.
553
+ */
554
+ export declare const API_ERROR_OFFLINE_KEY = "tempest.error.offline";
555
+
546
556
  export declare interface ApiClient {
547
557
  request<T>(path: string, options?: RequestOptions): Promise<T>;
548
558
  get<T>(path: string, options?: RequestOptions): Promise<T>;
@@ -627,6 +637,12 @@ export declare interface ApiError {
627
637
  body?: unknown;
628
638
  }
629
639
 
640
+ /** The two fixed sentences {@link describeApiError} may need. */
641
+ export declare interface ApiErrorStrings {
642
+ /** Shown when the request never reached the server. */
643
+ offline: string;
644
+ }
645
+
630
646
  /**
631
647
  * Mobile-first top app bar for PWAs — leading (back / brand) + title +
632
648
  * trailing actions, sticky with safe-area padding out of the box.
@@ -675,7 +691,14 @@ export declare interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, "
675
691
  actions?: ReactNode;
676
692
  /** Center the title (three-column grid). Default `false` (left-aligned). */
677
693
  centered?: boolean;
678
- /** Stick to the top of the scroll container. Default `true`. */
694
+ /**
695
+ * Stick to the top of the scroll container. Default `true`.
696
+ *
697
+ * The page has to leave the body out of the scrolling: `body { overflow-x:
698
+ * hidden }` makes the body a scroll container and the bar scrolls away with
699
+ * the content. Use `overflow-x: clip` on `html` and `body` instead — in
700
+ * development the bar says so in the console when it detects it.
701
+ */
679
702
  sticky?: boolean;
680
703
  /** Visual tone. Default `"surface"`. */
681
704
  tone?: AppBarTone;
@@ -687,6 +710,37 @@ export declare interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, "
687
710
 
688
711
  export declare type AppBarTone = "surface" | "primary" | "transparent";
689
712
 
713
+ /**
714
+ * Run a filter set over an in-memory list.
715
+ *
716
+ * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies
717
+ * them. Filters combine with `AND`, matching the flat model the bar builds, and
718
+ * an incomplete filter is skipped rather than treated as a match of nothing — a
719
+ * half-filled form should not empty the table underneath it.
720
+ *
721
+ * Comparison follows the row's type, not the filter's: numbers compare
722
+ * numerically, dates compare by day, and everything else compares as text with
723
+ * `numeric: true` so `"item 2"` lands before `"item 10"`.
724
+ *
725
+ * A few behaviours differ from the SQL the server-side twin produces, and the
726
+ * difference is deliberate rather than accidental:
727
+ *
728
+ * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`
729
+ * for a `NULL` column and the row drops out. Here "is not paid" shows the rows
730
+ * with no status at all, which is what the chip claims.
731
+ * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server
732
+ * only matches `NULL`. A column that stores `""` instead of `NULL` is where
733
+ * the two disagree.
734
+ *
735
+ * @example
736
+ * const visible = applyFilters(orders, filters);
737
+ *
738
+ * @param items - The full list.
739
+ * @param filters - Applied filters; incomplete ones are ignored.
740
+ * @returns A new array with the rows that satisfy every complete filter.
741
+ */
742
+ export declare function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[];
743
+
690
744
  /**
691
745
  * Apply a {@link KanbanMove} to a column list, returning new arrays.
692
746
  *
@@ -1359,6 +1413,84 @@ export declare interface BarcodeScanResult {
1359
1413
  cornerPoints: readonly BarcodePoint[];
1360
1414
  }
1361
1415
 
1416
+ /**
1417
+ * A ranked distribution: label, proportional bar, value, optional share.
1418
+ *
1419
+ * The most common chart on an admin panel, and the one the SDK kept sending
1420
+ * people to write by hand — `Progress` is a single bar and `Sparkline` is a time
1421
+ * series, so a "users per plan" block ended up reimplemented per screen, each
1422
+ * with its own CSS and its own `.sort()`.
1423
+ *
1424
+ * It is a **list**, not a picture: `<ul>` / `<li>` with the value written as
1425
+ * text, the bar `aria-hidden` behind it. A screen reader reads "Free, 128, 62%"
1426
+ * because that text is there, not because an `aria-label` restates a drawing.
1427
+ * Which is also why the label never sits on top of the bar — text over a tinted
1428
+ * fill has to be re-verified against that fill, and the SDK has been caught by
1429
+ * that twice.
1430
+ *
1431
+ * Bar width is relative to the **largest** row, so the biggest bar fills the
1432
+ * track; the percentage is the row's share of the **total**. They are different
1433
+ * numbers on purpose: a width scaled by the total leaves every bar short in a
1434
+ * long list, which is when the chart is needed most.
1435
+ *
1436
+ * @example
1437
+ * <BarList
1438
+ * items={[{ label: "Free", value: 128 }, { label: "Pro", value: 32 }]}
1439
+ * valueFormatter={(n) => `${n} ativos`}
1440
+ * showPercentage
1441
+ * max={5}
1442
+ * otherLabel="Outros"
1443
+ * />
1444
+ *
1445
+ * @param props - Items plus presentation.
1446
+ * @returns The list.
1447
+ */
1448
+ export declare function BarList({ items, valueFormatter, showPercentage, sort, max, otherLabel, className, ...rest }: BarListProps): JSX.Element;
1449
+
1450
+ /** One entry of a {@link BarList}. */
1451
+ export declare interface BarListItem {
1452
+ /** What the row is called. */
1453
+ label: string;
1454
+ /** The measured amount. */
1455
+ value: number;
1456
+ /** Override the bar colour. Defaults to the chart series token for its position. */
1457
+ color?: string;
1458
+ }
1459
+
1460
+ export declare interface BarListProps extends Omit<HTMLAttributes<HTMLUListElement>, "children"> {
1461
+ /** The rows. Entries whose value is not finite are dropped. */
1462
+ items: readonly BarListItem[];
1463
+ /** Render the number. Defaults to the value as-is. */
1464
+ valueFormatter?: (value: number) => string;
1465
+ /** Show each row's share of the total next to its value. Default `false`. */
1466
+ showPercentage?: boolean;
1467
+ /** Ordering. Default `"desc"`, which is what a ranking means. */
1468
+ sort?: BarListSort;
1469
+ /** Keep at most this many rows. */
1470
+ max?: number;
1471
+ /**
1472
+ * Label for one aggregated row holding everything `max` cut off.
1473
+ *
1474
+ * Without it, `max` simply truncates. The aggregate row only appears when more
1475
+ * than one row was cut — collapsing a single row into "others" hides its name
1476
+ * for nothing.
1477
+ */
1478
+ otherLabel?: string;
1479
+ }
1480
+
1481
+ /** One row, with everything the component needs to draw it. */
1482
+ export declare interface BarListRow extends BarListItem {
1483
+ /** Share of the total, 0–100. */
1484
+ percentage: number;
1485
+ /** Bar width relative to the largest row, 0–100. */
1486
+ width: number;
1487
+ /** Position in the original palette cycle. */
1488
+ index: number;
1489
+ }
1490
+
1491
+ /** How the list orders itself before drawing. */
1492
+ export declare type BarListSort = "desc" | "asc" | "none";
1493
+
1362
1494
  /**
1363
1495
  * Decode a base64url string into bytes.
1364
1496
  *
@@ -1552,6 +1684,31 @@ export declare const BREAKPOINTS: Record<Breakpoint, number>;
1552
1684
 
1553
1685
  export { BrowserRouter }
1554
1686
 
1687
+ /**
1688
+ * Order, truncate and measure the rows.
1689
+ *
1690
+ * Two different numbers come out of this, and conflating them is the classic bug
1691
+ * in a hand-written bar list:
1692
+ *
1693
+ * - `percentage` is the row's share of the **total**, which is what the label
1694
+ * claims when it reads "32%".
1695
+ * - `width` is relative to the **largest** row, so the biggest bar fills the
1696
+ * track. Scaling width by the total instead leaves every bar short in a list of
1697
+ * many small values, and the chart stops being readable exactly when it has the
1698
+ * most rows.
1699
+ *
1700
+ * The total counts positive values only. A negative amount draws no bar (a bar of
1701
+ * negative width does not exist) and reports 0%, but its number is still shown —
1702
+ * hiding the row would be worse than showing an odd one.
1703
+ *
1704
+ * @param items - The rows as given.
1705
+ * @param sort - Ordering to apply. `"none"` keeps the caller's order.
1706
+ * @param max - Keep at most this many rows.
1707
+ * @param otherLabel - Aggregate what `max` cut into one row with this label.
1708
+ * @returns The rows to draw, in order.
1709
+ */
1710
+ export declare function buildBarListRows(items: readonly BarListItem[], sort: BarListSort, max: number | undefined, otherLabel: string | undefined): BarListRow[];
1711
+
1555
1712
  /**
1556
1713
  * Builds an `intent://` URL that re-opens the current page inside Chrome on
1557
1714
  * Android. When Chrome is missing, the fallback URL points to the Play Store so
@@ -2328,6 +2485,66 @@ export declare interface CommandProps {
2328
2485
  */
2329
2486
  export declare function compareValues(a: unknown, b: unknown): number;
2330
2487
 
2488
+ /**
2489
+ * Typed `localStorage` wrapper that gzips what it writes.
2490
+ *
2491
+ * Mirrors {@link storage} so the two are interchangeable at the call site; the
2492
+ * difference is only in how the value is encoded.
2493
+ */
2494
+ export declare const compressedStorage: {
2495
+ /**
2496
+ * Read and decompress a key.
2497
+ *
2498
+ * @typeParam T - The expected value shape.
2499
+ * @param key - Storage key.
2500
+ * @param fallback - Returned when the key is absent, unreadable, or corrupt.
2501
+ * @returns The stored value, or `fallback`.
2502
+ */
2503
+ get<T>(key: string, fallback: T): T;
2504
+ /**
2505
+ * Compress and write a key.
2506
+ *
2507
+ * When compression itself fails the value is written as plain JSON rather
2508
+ * than dropped: a slightly larger record still loads, an absent one does
2509
+ * not. Only a storage-level failure — quota, blocked storage — loses the
2510
+ * write.
2511
+ *
2512
+ * @typeParam T - The value being stored.
2513
+ * @param key - Storage key.
2514
+ * @param value - Any JSON-serializable value.
2515
+ */
2516
+ set<T>(key: string, value: T): void;
2517
+ };
2518
+
2519
+ /**
2520
+ * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's
2521
+ * cross-tab sync and SSR guard for free.
2522
+ *
2523
+ * @example
2524
+ * const [save, setSave] = useLocalStorage("save", EMPTY_SAVE, compressedStorageCodec);
2525
+ */
2526
+ export declare const compressedStorageCodec: {
2527
+ serialize: typeof compressToString;
2528
+ deserialize: typeof decompressFromString;
2529
+ };
2530
+
2531
+ /**
2532
+ * Serialize a value to a gzipped, base64 string carrying the format marker.
2533
+ *
2534
+ * Base64 costs a third more characters than the raw compressed bytes, and
2535
+ * `localStorage` bills two bytes per character on top of that. Packing the
2536
+ * bytes into UTF-16 code units directly would be denser, but lone surrogates
2537
+ * survive neither every storage implementation nor a JSON round-trip, and a
2538
+ * save that decodes to garbage is far worse than one that is bigger. Even with
2539
+ * that overhead a typical JSON document lands well under a third of its
2540
+ * uncompressed size.
2541
+ *
2542
+ * @typeParam T - The value being stored.
2543
+ * @param value - Any JSON-serializable value.
2544
+ * @returns The encoded string, ready for `localStorage`.
2545
+ */
2546
+ export declare function compressToString<T>(value: T): string;
2547
+
2331
2548
  /**
2332
2549
  * Conditionally wraps its children with `wrapper`.
2333
2550
  *
@@ -3287,6 +3504,36 @@ export declare interface CreateSentryTelemetryAdapterOptions {
3287
3504
  breadcrumbCategory?: string;
3288
3505
  }
3289
3506
 
3507
+ /**
3508
+ * A pool of preallocated `<audio>` elements for short sound effects.
3509
+ *
3510
+ * `new Audio(src)` on every play allocates an element and re-enters the network
3511
+ * stack for a file the browser already has, which is the wrong shape for a
3512
+ * sound that fires dozens of times a minute — a UI blip, a hit, a pickup. The
3513
+ * pool allocates once per source and replays.
3514
+ *
3515
+ * This is deliberately not {@link createAudioPlayer}: that handle tracks a
3516
+ * single "current" clip with loop, sink routing and lifecycle callbacks, which
3517
+ * is what background music needs. Effects are the opposite case — many
3518
+ * sources, all short, fire-and-forget, and the only thing that matters is that
3519
+ * firing one is cheap.
3520
+ *
3521
+ * A blocked `play()` is swallowed. Browsers reject playback until the user has
3522
+ * interacted with the page, and a sound effect is by definition not worth
3523
+ * interrupting anything over; call {@link SfxPool.preload} after the first
3524
+ * interaction if you want the pool warm.
3525
+ *
3526
+ * @param options - Master volume, base URL, voices per source and pool size.
3527
+ * @returns The pool handle.
3528
+ *
3529
+ * @example
3530
+ * const sfx = createSfxPool({ volume: 0.6, baseUrl: import.meta.env.BASE_URL });
3531
+ * sfx.preload(["sfx/select.mp3", "sfx/back.mp3"]);
3532
+ *
3533
+ * <button onClick={() => sfx.play("sfx/select.mp3")}>Confirmar</button>
3534
+ */
3535
+ export declare function createSfxPool(options?: SfxPoolOptions): SfxPool;
3536
+
3290
3537
  /**
3291
3538
  * Create a typed Zustand store with optional `persist` middleware, wired with
3292
3539
  * Tempest defaults. This is the generic counterpart to `createAuthStore`: use
@@ -3586,6 +3833,36 @@ export declare interface CredentialsContainerLike {
3586
3833
  }): Promise<Credential | null>;
3587
3834
  }
3588
3835
 
3836
+ /** One column of the exported file. */
3837
+ export declare interface CsvColumn<T> {
3838
+ /** Property of the row this column reads from. Doubles as the column key. */
3839
+ key: keyof T;
3840
+ /** Column heading, written to the first line. */
3841
+ header: string;
3842
+ /**
3843
+ * Value for the file. Defaults to `String(row[key])`, with nullish becoming an
3844
+ * empty field.
3845
+ *
3846
+ * A `DataTableColumn` renders cells to `ReactNode`, which cannot be written to
3847
+ * a text file — a badge or a link would serialize as `[object Object]`. Give
3848
+ * the column this accessor and the export says what the badge said.
3849
+ */
3850
+ csv?: (row: T) => string | number | boolean | null | undefined;
3851
+ }
3852
+
3853
+ /** Options for {@link toCsv}. */
3854
+ export declare interface CsvOptions {
3855
+ /**
3856
+ * Field separator. Default `","`.
3857
+ *
3858
+ * Use `";"` for Excel on a locale whose decimal separator is the comma —
3859
+ * which is every pt-BR install — otherwise it opens the file in one column.
3860
+ */
3861
+ delimiter?: "," | ";";
3862
+ /** Prefix the output with a UTF-8 BOM. Default `true`. */
3863
+ bom?: boolean;
3864
+ }
3865
+
3589
3866
  /** Cursor-paginated response envelope (`CursorPaginationSchema[T]`). */
3590
3867
  export declare interface CursorPage<T> {
3591
3868
  /** The rows for this batch. */
@@ -3744,7 +4021,7 @@ export declare interface DataProviderOptions {
3744
4021
  * render without changing behaviour. That is why `exhaustive-deps` is silenced on
3745
4022
  * that dependency array.
3746
4023
  */
3747
- export declare function DataTable<T>({ data, columns, pageSize, searchable, searchKeys, initialSort, rowKey, emptyMessage, onCellChange, editLabels, className, ...rest }: DataTableProps<T>): JSX.Element;
4024
+ export declare function DataTable<T>({ data, columns, pageSize, searchable, searchKeys, initialSort, rowKey, emptyMessage, onCellChange, editLabels, totalItems, page: controlledPage, onPageChange, manualSort, onSortChange, manualSearch, onSearchChange, loading, className, ...rest }: DataTableProps<T>): JSX.Element;
3748
4025
 
3749
4026
  /** One accepted cell edit, handed to `onCellChange`. */
3750
4027
  export declare interface DataTableCellChange<T> {
@@ -3824,7 +4101,13 @@ export declare interface DataTableEditLabels {
3824
4101
  export declare type DataTableEditorType = "text" | "number" | "date" | "email" | "tel" | "url";
3825
4102
 
3826
4103
  export declare interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {
3827
- /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */
4104
+ /**
4105
+ * The rows to work with.
4106
+ *
4107
+ * By default this is the **full** dataset and sorting, searching and paging
4108
+ * all happen in memory. Pass `totalItems` and it becomes the current page as
4109
+ * the server returned it, with those three delegated to the caller.
4110
+ */
3828
4111
  data: T[];
3829
4112
  /** Column definitions. */
3830
4113
  columns: DataTableColumn<T>[];
@@ -3851,6 +4134,51 @@ export declare interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement
3851
4134
  onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;
3852
4135
  /** Override the PT-BR copy of the editing affordances. */
3853
4136
  editLabels?: Partial<DataTableEditLabels>;
4137
+ /**
4138
+ * Total row count across every page — the `total` of a paginated envelope.
4139
+ *
4140
+ * Passing it switches the table to **server mode**: `data` is read as the
4141
+ * current page, the page count comes from this number instead of
4142
+ * `data.length`, and sorting and searching are delegated to the caller
4143
+ * (see `manualSort` / `manualSearch`, which are implied here). Pair it with
4144
+ * `page` and `onPageChange`.
4145
+ */
4146
+ totalItems?: number;
4147
+ /** Current page, 1-based. Controlled — required in server mode. */
4148
+ page?: number;
4149
+ /** Called with the next page. Required whenever `page` is controlled. */
4150
+ onPageChange?: (page: number) => void;
4151
+ /**
4152
+ * Sorting is the caller's job: clicking a header reports through
4153
+ * `onSortChange` and the rows are left in the order they arrived.
4154
+ *
4155
+ * Implied by `totalItems`, because sorting the page in memory would sort
4156
+ * *that page only* while the header claims the whole table is ordered.
4157
+ */
4158
+ manualSort?: boolean;
4159
+ /** Called with the next sort state — `null` when the header cycles back to unsorted. */
4160
+ onSortChange?: (sort: DataTableSort<T> | null) => void;
4161
+ /**
4162
+ * Searching is the caller's job: typing reports through `onSearchChange` and
4163
+ * the rows are left as they arrived.
4164
+ *
4165
+ * Implied by `totalItems`. Filtering the current page would hide the rows
4166
+ * that do not match *on this page* and show nothing for a term that only
4167
+ * matches on page three — an empty table that looks like "no results".
4168
+ */
4169
+ manualSearch?: boolean;
4170
+ /** Called with the current search term (debouncing, if any, is the caller's). */
4171
+ onSearchChange?: (term: string) => void;
4172
+ /**
4173
+ * A fetch is in flight.
4174
+ *
4175
+ * With rows already on screen they stay put, dimmed and `aria-busy`, so the
4176
+ * page does not jump under the cursor between pages. With no rows yet it
4177
+ * renders placeholder lines at full height, which is a different statement
4178
+ * from `emptyMessage`: "loading" and "there is nothing" are not the same
4179
+ * screen.
4180
+ */
4181
+ loading?: boolean;
3854
4182
  }
3855
4183
 
3856
4184
  export declare interface DataTableSort<T> {
@@ -3953,6 +4281,20 @@ export declare interface DecodedJWT {
3953
4281
  */
3954
4282
  export declare function decodeJWT(token: string): DecodedJWT;
3955
4283
 
4284
+ /**
4285
+ * Decode a string produced by {@link compressToString}.
4286
+ *
4287
+ * A string without the marker is parsed as plain JSON, so values written before
4288
+ * compression was enabled — or by a write that fell back after `gzipSync`
4289
+ * failed — still read back.
4290
+ *
4291
+ * @typeParam T - The expected value shape.
4292
+ * @param raw - The stored string.
4293
+ * @returns The decoded value.
4294
+ * @throws If the payload is neither valid compressed data nor valid JSON.
4295
+ */
4296
+ export declare function decompressFromString<T>(raw: string): T;
4297
+
3956
4298
  /**
3957
4299
  * Recursively merge `source` into `target`, returning a new object.
3958
4300
  *
@@ -3974,6 +4316,15 @@ export declare function decodeJWT(token: string): DecodedJWT;
3974
4316
  */
3975
4317
  export declare function deepMerge<T>(target: T, source: Partial<T>): T;
3976
4318
 
4319
+ /**
4320
+ * PT-BR defaults, used when no strings are supplied and no catalog answers.
4321
+ *
4322
+ * The SDK's copy is pt-BR everywhere else (`FilterBar`, `DataTable`, `Chat`), so
4323
+ * the default here matches rather than introducing an English string that only
4324
+ * shows up on a network failure.
4325
+ */
4326
+ export declare const DEFAULT_API_ERROR_STRINGS: ApiErrorStrings;
4327
+
3977
4328
  /**
3978
4329
  * What a scanner looks for when you do not say.
3979
4330
  *
@@ -4035,6 +4386,36 @@ export declare const DEFAULT_PUB_KEY_CRED_PARAMS: {
4035
4386
  */
4036
4387
  export declare function defineRoutes(routes: TempestRouteObject[]): TempestRouteObject[];
4037
4388
 
4389
+ /**
4390
+ * Turn any caught value into a sentence worth showing.
4391
+ *
4392
+ * The funnel, in order:
4393
+ *
4394
+ * 1. A request that never reached the server — `status === 0`, or a non-API
4395
+ * error thrown while the browser reports itself offline — produces the
4396
+ * offline sentence. This is the step apps skip, and skipping it renders
4397
+ * "erro 0" or a raw `TypeError` at the user.
4398
+ * 2. The backend's own `detail`, which is the most specific thing available and
4399
+ * is already written for a person.
4400
+ * 3. `fallback`, with `(HTTP <status>)` appended when a status is known, so the
4401
+ * screenshot in the support ticket carries the one fact a developer needs.
4402
+ *
4403
+ * Pure on purpose: it works in an interceptor, in a logger and anywhere outside
4404
+ * the React tree. {@link useDescribeApiError} is the same funnel with the
4405
+ * sentences resolved through `I18nProvider`.
4406
+ *
4407
+ * @example
4408
+ * catch (error) {
4409
+ * toast(describeApiError(error, "Não foi possível salvar o pedido"));
4410
+ * }
4411
+ *
4412
+ * @param error - The caught value, of any shape.
4413
+ * @param fallback - What to say when the error carries nothing better.
4414
+ * @param strings - Overrides for the fixed sentences.
4415
+ * @returns A sentence to show the user.
4416
+ */
4417
+ export declare function describeApiError(error: unknown, fallback: string, strings?: Partial<ApiErrorStrings>): string;
4418
+
4038
4419
  /**
4039
4420
  * One filter, in words: `"Status é Pago"`.
4040
4421
  *
@@ -4169,6 +4550,25 @@ export declare type DividerVariant = "solid" | "dashed";
4169
4550
 
4170
4551
  export declare type DocumentVisibility = "visible" | "hidden";
4171
4552
 
4553
+ /**
4554
+ * Build a CSV and hand it to the user.
4555
+ *
4556
+ * Goes through {@link shareOrDownloadBlob}, so on a phone it opens the native
4557
+ * share sheet and everywhere else it downloads — the same path every other
4558
+ * generated artifact in the SDK takes, instead of a fourth hand-rolled `<a
4559
+ * download>`.
4560
+ *
4561
+ * @example
4562
+ * await downloadCsv(users, COLUMNS, "usuarios.csv");
4563
+ *
4564
+ * @param rows - The rows to export.
4565
+ * @param columns - Columns, in the order they should appear.
4566
+ * @param fileName - File name offered to the user. Default `"export.csv"`.
4567
+ * @param options - Delimiter and BOM, forwarded to {@link toCsv}.
4568
+ * @returns A promise that resolves once the share or download completes.
4569
+ */
4570
+ export declare function downloadCsv<T>(rows: readonly T[], columns: readonly CsvColumn<T>[], fileName?: string, options?: CsvOptions): Promise<void>;
4571
+
4172
4572
  /**
4173
4573
  * Sliding side panel. Same building blocks as {@link Modal} but anchored to
4174
4574
  * an edge. Locks body scroll while open.
@@ -4637,6 +5037,51 @@ export declare type FilterPredicate<T> = (item: T, search: string) => boolean;
4637
5037
  */
4638
5038
  export declare function filtersFromSearchParams(params: URLSearchParams, fields: readonly FilterField[]): Filter[];
4639
5039
 
5040
+ /**
5041
+ * Encode filters as query params for a paginated backend.
5042
+ *
5043
+ * The counterpart of {@link applyFilters}: same filter set, evaluated by the
5044
+ * database instead of by the browser, which is the only option once the list is
5045
+ * paginated on the server and the page in memory is not the whole result.
5046
+ *
5047
+ * The encoding is the `<column>__<op>` convention `tempest-fastapi-sdk` already
5048
+ * reads (`BaseRepository._apply_filters` → `build_filter_condition`):
5049
+ *
5050
+ * | Operator | Param |
5051
+ * | --- | --- |
5052
+ * | `eq` | `field` (or `field__iexact` for the `name` column) |
5053
+ * | `ne` | `field__ne` |
5054
+ * | `contains` | `field__icontains` |
5055
+ * | `gt` `gte` `lt` `lte` | `field__gt` … `field__lte` |
5056
+ * | `between` | `field__between` twice, low value first |
5057
+ * | `in` | `field__in` once per value |
5058
+ * | `empty` / `notEmpty` | `field__isnull=true` / `=false` |
5059
+ *
5060
+ * Returns `URLSearchParams` rather than a plain object because `between` carries
5061
+ * a pair and `in` carries a list: an object would keep only the last value of
5062
+ * each, silently narrowing the filter. Repeated params are also how FastAPI
5063
+ * receives a `list[str]` declared with `Query`.
5064
+ *
5065
+ * Two things the backend has to hold up its end of, or the filter fails quietly:
5066
+ *
5067
+ * - **Every key must be declared.** `BasePaginationFilterSchema.get_conditions()`
5068
+ * only forwards fields the subclass declares, so a `status__ne` the schema
5069
+ * never mentions is dropped by FastAPI before the repository sees it — no
5070
+ * error, no filtering.
5071
+ * - **`isnull` matches `NULL` only.** A column that stores `""` for "no value"
5072
+ * will not answer an `empty` filter, while {@link applyFilters} treats blank
5073
+ * text as empty.
5074
+ *
5075
+ * @example
5076
+ * const params = filtersToQueryParams(filters);
5077
+ * params.set("page", String(page));
5078
+ * const data = await api.get(`/orders?${params}`);
5079
+ *
5080
+ * @param filters - Applied filters; incomplete ones are ignored.
5081
+ * @returns Params ready to append to a request URL.
5082
+ */
5083
+ export declare function filtersToQueryParams(filters: readonly Filter[]): URLSearchParams;
5084
+
4640
5085
  /**
4641
5086
  * Serialize filters into URL search params.
4642
5087
  *
@@ -4840,6 +5285,30 @@ export declare function formatCurrency(value: number): string;
4840
5285
  */
4841
5286
  export declare function formatDate(value: string | Date): string;
4842
5287
 
5288
+ /**
5289
+ * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an
5290
+ * `<input type="date">` accepts.
5291
+ *
5292
+ * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,
5293
+ * which is the reflex and which is wrong: `toISOString` converts to UTC first, so
5294
+ * anything after 21:00 in UTC-3 reports the next day and the form opens on the
5295
+ * wrong date. `formatDate` cannot fill this role because a date input rejects
5296
+ * `dd/MM/yyyy` outright.
5297
+ *
5298
+ * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut
5299
+ * is load-bearing rather than an optimisation: `new Date("2026-05-16")` is parsed
5300
+ * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the
5301
+ * exact value a backend sent would move it back a day.
5302
+ *
5303
+ * @example
5304
+ * <input type="date" defaultValue={formatDateForInput(order.createdAt)} />
5305
+ *
5306
+ * @param value - ISO string or Date.
5307
+ * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —
5308
+ * which is what a date input reads as "no value", unlike `"Invalid Date"`.
5309
+ */
5310
+ export declare function formatDateForInput(value: string | Date): string;
5311
+
4843
5312
  /**
4844
5313
  * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.
4845
5314
  *
@@ -6186,17 +6655,22 @@ declare interface LayoutProps {
6186
6655
  }
6187
6656
 
6188
6657
  /**
6189
- * Wrap `React.lazy` with automatic retry. Common cause of failure:
6190
- * deployed-then-cached `index.html` references chunk filenames that no
6191
- * longer exist. Retrying after a short delay typically picks up the new
6192
- * bundle; a final `location.reload()` recovers from stale `index.html`.
6658
+ * Wrap `React.lazy` with automatic retry and a `preload()` method.
6659
+ *
6660
+ * Common cause of failure: deployed-then-cached `index.html` references chunk
6661
+ * filenames that no longer exist. Retrying after a short delay typically picks
6662
+ * up the new bundle; a final `location.reload()` recovers from stale
6663
+ * `index.html`.
6193
6664
  *
6194
6665
  * @example
6195
6666
  * const Settings = lazyWithRetry(() => import("./Settings"));
6667
+ *
6668
+ * // Warm the chunk when the route becomes likely, not when it is needed.
6669
+ * <a href="/settings" onMouseEnter={() => void Settings.preload()}>Settings</a>
6196
6670
  */
6197
6671
  export declare function lazyWithRetry<T extends ComponentType<unknown>>(factory: () => Promise<{
6198
6672
  default: T;
6199
- }>, options?: LazyWithRetryOptions): ReturnType<typeof lazy<T>>;
6673
+ }>, options?: LazyWithRetryOptions): PreloadableLazy<T>;
6200
6674
 
6201
6675
  export declare interface LazyWithRetryOptions {
6202
6676
  /** Max attempts. Default: 3. */
@@ -8005,6 +8479,33 @@ export declare interface PcmAudio {
8005
8479
  sampleRate: number;
8006
8480
  }
8007
8481
 
8482
+ /**
8483
+ * What percentage of `total` is `part`, with an empty base yielding `0`.
8484
+ *
8485
+ * The guard is the whole point: `(part / total) * 100` produces `NaN` when the
8486
+ * base is zero and `Infinity` when only the base is missing, and both reach the
8487
+ * screen as a broken label — `NaN%` on an empty dashboard is the single most
8488
+ * common way a panel announces that it has no data yet.
8489
+ *
8490
+ * Returns a **0–100** number, which is what a bar width and a label want.
8491
+ * `formatPercent` takes a 0–1 fraction instead, so pair them as
8492
+ * `formatPercent(percentOf(a, b) / 100)` — or skip `percentOf` and pass the
8493
+ * fraction straight in.
8494
+ *
8495
+ * The result is not clamped: 120% of a target is a real number somebody wants to
8496
+ * see, and capping it would hide the interesting case.
8497
+ *
8498
+ * @example
8499
+ * percentOf(3, 4); // 75
8500
+ * percentOf(5, 0); // 0
8501
+ * percentOf(12, 8); // 150
8502
+ *
8503
+ * @param part - The measured amount.
8504
+ * @param total - The base it is measured against.
8505
+ * @returns The percentage, or `0` when the base is zero or either input is not finite.
8506
+ */
8507
+ export declare function percentOf(part: number, total: number): number;
8508
+
8008
8509
  /**
8009
8510
  * Extract a permission list from a JWT.
8010
8511
  *
@@ -8181,6 +8682,12 @@ export declare interface PlayAudioOptions {
8181
8682
  onError?: (error: unknown) => void;
8182
8683
  }
8183
8684
 
8685
+ /** Per-play overrides. */
8686
+ export declare interface PlaySfxOptions {
8687
+ /** Volume for this play, `0`–`1`, multiplied by the pool's master. Default `1`. */
8688
+ volume?: number;
8689
+ }
8690
+
8184
8691
  /**
8185
8692
  * Pick the singular or plural form of a word based on `count`.
8186
8693
  *
@@ -8276,6 +8783,29 @@ export declare interface PostHogLike {
8276
8783
  reset?: () => void;
8277
8784
  }
8278
8785
 
8786
+ /** A lazy component that can also be fetched ahead of being rendered. */
8787
+ export declare type PreloadableLazy<T extends ComponentType<unknown>> = ReturnType<typeof lazy<T>> & {
8788
+ /**
8789
+ * Start fetching the chunk now, before anything renders it.
8790
+ *
8791
+ * Call it on the interaction that makes the route likely — hovering the
8792
+ * link, opening the menu that holds it, finishing the step before it — so
8793
+ * the chunk is warm by the time the user commits and the suspense fallback
8794
+ * never appears.
8795
+ *
8796
+ * Shares its work with the render path: whichever fires first performs the
8797
+ * single fetch and the other awaits the same promise. Safe to call
8798
+ * repeatedly.
8799
+ *
8800
+ * @returns The module, once loaded. Rejects when every retry failed; that
8801
+ * rejection is already handled internally, so a fire-and-forget call
8802
+ * never surfaces as an unhandled rejection.
8803
+ */
8804
+ preload: () => Promise<{
8805
+ default: T;
8806
+ }>;
8807
+ };
8808
+
8279
8809
  /** A model whose cached size should appear in the report. */
8280
8810
  export declare interface ProfiledModel {
8281
8811
  /** Label for the report row, e.g. `"detector"`. */
@@ -8475,7 +9005,12 @@ export declare type QRMode = "numeric" | "alphanumeric" | "byte";
8475
9005
 
8476
9006
  /**
8477
9007
  * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with
8478
- * sane SDK defaults (5-minute stale time, 30-minute gc time, 1 retry).
9008
+ * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for
9009
+ * failures worth replaying).
9010
+ *
9011
+ * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other
9012
+ * than 408/429 is the server refusing on purpose, and replaying it only doubles
9013
+ * the network log while the spinner keeps turning.
8479
9014
  */
8480
9015
  export declare function QueryProvider({ children, client, defaultOptions }: QueryProviderProps): JSX.Element;
8481
9016
 
@@ -9441,6 +9976,46 @@ export declare interface SequentialScaleOptions {
9441
9976
  */
9442
9977
  export declare function setAudioOutput(element: HTMLMediaElement | null, sinkId: string): Promise<boolean>;
9443
9978
 
9979
+ /** Imperative handle over a pool of short sound effects. */
9980
+ export declare interface SfxPool {
9981
+ /** Play a clip, allocating and caching its element on first use. */
9982
+ play: (src: string, options?: PlaySfxOptions) => void;
9983
+ /** Fetch clips ahead of the first play, so it is not silent while the file downloads. */
9984
+ preload: (src: string | string[]) => void;
9985
+ /** Set the master volume, applying it to anything already sounding. */
9986
+ setVolume: (volume: number) => void;
9987
+ /** Stop one source, or every source when called with no argument. */
9988
+ stop: (src?: string) => void;
9989
+ /** Release every element. Call on unmount. */
9990
+ dispose: () => void;
9991
+ }
9992
+
9993
+ /** Options for {@link createSfxPool}. */
9994
+ export declare interface SfxPoolOptions {
9995
+ /**
9996
+ * Master volume, `0`–`1`, multiplied into every per-play volume. Default
9997
+ * `1`. Wire it to the app's sound setting so one value governs the lot.
9998
+ */
9999
+ volume?: number;
10000
+ /**
10001
+ * Prefix applied to sources that are not absolute URLs — typically Vite's
10002
+ * `import.meta.env.BASE_URL`, so a build served from a subpath resolves.
10003
+ * Default `""`.
10004
+ */
10005
+ baseUrl?: string;
10006
+ /**
10007
+ * Elements kept per source. `1` (the default) restarts the clip on every
10008
+ * play, which is what a menu blip wants. Raise it to let a sound overlap
10009
+ * itself — a hit landing while the previous one is still ringing.
10010
+ */
10011
+ voices?: number;
10012
+ /**
10013
+ * Maximum number of distinct sources held. When exceeded, the least
10014
+ * recently played source is released. Default `48`.
10015
+ */
10016
+ maxSources?: number;
10017
+ }
10018
+
9444
10019
  /**
9445
10020
  * Wrap the Web Share API with a uniform result object. Falls through to
9446
10021
  * `unsupported: true` when the browser lacks `navigator.share`, leaving the
@@ -9493,6 +10068,25 @@ export declare interface ShareResult {
9493
10068
  error?: unknown;
9494
10069
  }
9495
10070
 
10071
+ /**
10072
+ * Default `retry` for SDK queries: replay transport and server failures, never a
10073
+ * deliberate client-side refusal.
10074
+ *
10075
+ * A flat `retry: 1` replays a 403 on an admin-only listing and a 404 for a record
10076
+ * somebody deleted. The server meant both — the second attempt returns the same
10077
+ * answer, doubles the network log and holds the spinner on screen for the length
10078
+ * of another round trip. A network failure or a 5xx is a different thing: it may
10079
+ * well succeed on the next try, so those keep the previous behaviour.
10080
+ *
10081
+ * @example
10082
+ * new QueryClient({ defaultOptions: { queries: { retry: shouldRetryQuery } } });
10083
+ *
10084
+ * @param failureCount - Attempts already made, as react-query counts them.
10085
+ * @param error - The rejection value from the query function.
10086
+ * @returns Whether react-query should try again.
10087
+ */
10088
+ export declare function shouldRetryQuery(failureCount: number, error: unknown): boolean;
10089
+
9496
10090
  /**
9497
10091
  * Conditionally render children based on the viewport breakpoint.
9498
10092
  *
@@ -10642,6 +11236,28 @@ export declare interface ToastProviderProps {
10642
11236
 
10643
11237
  export declare type ToastVariant = "success" | "warning" | "error" | "info";
10644
11238
 
11239
+ /**
11240
+ * Serialize rows to CSV text, RFC 4180 style.
11241
+ *
11242
+ * Rows are separated by `\r\n` — the RFC's terminator and the one Excel is least
11243
+ * surprised by. A row is emitted for the header even when `rows` is empty, so the
11244
+ * person who opens the file sees which columns they asked for instead of a blank
11245
+ * document.
11246
+ *
11247
+ * @example
11248
+ * const csv = toCsv(users, [
11249
+ * { key: "name", header: "Nome" },
11250
+ * { key: "email", header: "E-mail" },
11251
+ * { key: "plan", header: "Plano", csv: (user) => user.plan.label },
11252
+ * ]);
11253
+ *
11254
+ * @param rows - The rows to export.
11255
+ * @param columns - Columns, in the order they should appear.
11256
+ * @param options - Delimiter and BOM.
11257
+ * @returns The complete file contents.
11258
+ */
11259
+ export declare function toCsv<T>(rows: readonly T[], columns: readonly CsvColumn<T>[], options?: CsvOptions): string;
11260
+
10645
11261
  /**
10646
11262
  * A pressable two-state button — like a checkbox styled as a button.
10647
11263
  *
@@ -11598,6 +12214,41 @@ export declare interface UseClipboardResult {
11598
12214
  reset: () => void;
11599
12215
  }
11600
12216
 
12217
+ /**
12218
+ * Counts down the time left on a window that started at a known instant.
12219
+ *
12220
+ * Written around a timestamp rather than a decrementing counter on purpose: the
12221
+ * remaining time is recomputed from `Date.now()` on every tick, so a throttled
12222
+ * background tab, a slow frame or a `setInterval` that drifts cannot make the
12223
+ * countdown disagree with the clock. Remounting recovers the correct value too,
12224
+ * which a counter held in state cannot do.
12225
+ *
12226
+ * The interval stops once it reaches zero instead of ticking forever behind a
12227
+ * clamp.
12228
+ *
12229
+ * @param durationMs - Length of the window.
12230
+ * @param startedAt - Epoch ms the window opened (`Date.now()` when it started).
12231
+ * @param options - Tick cadence.
12232
+ * @returns Milliseconds remaining, never below `0`.
12233
+ *
12234
+ * @example
12235
+ * const remaining = useCountdown(60_000, lastSentAt);
12236
+ *
12237
+ * <button disabled={remaining > 0}>
12238
+ * {remaining > 0 ? `Reenviar em ${Math.ceil(remaining / 1000)}s` : "Reenviar código"}
12239
+ * </button>
12240
+ */
12241
+ export declare function useCountdown(durationMs: number, startedAt: number, options?: UseCountdownOptions): number;
12242
+
12243
+ /** Options for {@link useCountdown}. */
12244
+ export declare interface UseCountdownOptions {
12245
+ /**
12246
+ * How often the remaining time is recomputed, in ms. Default `1000`, which
12247
+ * suits a "try again in 3s" label; drop it to ~50 to drive a progress bar.
12248
+ */
12249
+ tickMs?: number;
12250
+ }
12251
+
11601
12252
  /**
11602
12253
  * Numeric counter clamped to an optional `[min, max]` range.
11603
12254
  *
@@ -11712,6 +12363,31 @@ export declare function useDelete<T>(resource: string, options?: UseDeleteOption
11712
12363
  /** Options for the delete mutation (mutationFn + onSuccess are provided). */
11713
12364
  export declare type UseDeleteOptions<T> = Omit<UseMutationOptions<T, Error, string | number>, "mutationFn">;
11714
12365
 
12366
+ /**
12367
+ * {@link describeApiError} with its fixed sentences resolved through the active
12368
+ * `I18nProvider`.
12369
+ *
12370
+ * The funnel is not duplicated — this hook only supplies the strings and calls
12371
+ * the pure function. Which is also why both exist: the pure one runs in an
12372
+ * interceptor or a logger, where there is no React tree to read a context from,
12373
+ * and this one runs in a component without every caller passing translations
12374
+ * down by hand.
12375
+ *
12376
+ * Works with no provider at all: i18n is opt-in in this SDK, so a missing
12377
+ * provider — or a catalog that never defined `tempest.error.offline` — falls
12378
+ * back to the pt-BR default rather than crashing or printing the raw key.
12379
+ *
12380
+ * @example
12381
+ * const describe = useDescribeApiError();
12382
+ * const { mutate } = useMutation({
12383
+ * mutationFn: save,
12384
+ * onError: (error) => toast(describe(error, t("orders.saveFailed"))),
12385
+ * });
12386
+ *
12387
+ * @returns A stable `(error, fallback) => string` function.
12388
+ */
12389
+ export declare function useDescribeApiError(): (error: unknown, fallback: string) => string;
12390
+
11715
12391
  /**
11716
12392
  * Manage open/closed boolean state with stable `open`/`close`/`toggle` handlers.
11717
12393
  *
@@ -12011,6 +12687,40 @@ export declare interface UseKeyboardShortcutOptions {
12011
12687
  ignoreInput?: boolean;
12012
12688
  }
12013
12689
 
12690
+ /**
12691
+ * Keeps a ref pointing at the most recent value it was given.
12692
+ *
12693
+ * The escape hatch for reading fresh state inside something that must not be
12694
+ * re-created when that state changes: an interval, a subscription, an event
12695
+ * listener registered once on mount. Listing the value in the effect's
12696
+ * dependencies would tear the effect down and set it back up on every change;
12697
+ * omitting it captures the value from the render that created the closure and
12698
+ * never sees another. The ref is neither — a stable object whose `current` is
12699
+ * always current.
12700
+ *
12701
+ * The assignment happens **during render**, for the same reason
12702
+ * {@link useStableCallback} does it: moving it into an effect opens a
12703
+ * one-commit staleness window, where an effect declared earlier in the same
12704
+ * commit reads the previous render's value.
12705
+ *
12706
+ * Reach for {@link useStableCallback} instead when the value is a function you
12707
+ * want to *call* — it hands back a callable with a stable identity, rather than
12708
+ * making every call site reach through `.current`.
12709
+ *
12710
+ * @typeParam T - The tracked value.
12711
+ * @param value - The value to track. Written on every render.
12712
+ * @returns A stable ref whose `current` holds the latest `value`.
12713
+ *
12714
+ * @example
12715
+ * const optionsRef = useLatestRef(options);
12716
+ *
12717
+ * useEffect(() => {
12718
+ * const id = setInterval(() => poll(optionsRef.current), 5_000);
12719
+ * return () => clearInterval(id);
12720
+ * }, []); // the interval survives every options change, and still reads the latest
12721
+ */
12722
+ export declare function useLatestRef<T>(value: T): RefObject<T>;
12723
+
12014
12724
  /**
12015
12725
  * Query a paginated list of a resource through the active {@link useDataProvider}.
12016
12726
  *
@@ -12609,6 +13319,18 @@ export declare interface UseOnlineOptions {
12609
13319
  timeoutMs?: number;
12610
13320
  }
12611
13321
 
13322
+ /**
13323
+ * Access translation helpers when there may be no {@link I18nProvider} above.
13324
+ *
13325
+ * Returns `null` instead of throwing, which is what a piece of the SDK needs
13326
+ * when it wants to be translated where a catalog exists and still work where one
13327
+ * does not — i18n is opt-in in this SDK, and a helper that demanded a provider
13328
+ * would turn "you did not configure translations" into a crash.
13329
+ *
13330
+ * @returns The context value, or `null` outside a provider.
13331
+ */
13332
+ export declare function useOptionalI18n(): I18nContextValue | null;
13333
+
12612
13334
  /**
12613
13335
  * Offset-pagination hook over TanStack Query for the fastapi-pagination /
12614
13336
  * Tempest envelope (`{ items, total, page, size, pages }`).
@@ -13187,6 +13909,30 @@ export declare interface UseSetResult<T> {
13187
13909
  size: number;
13188
13910
  }
13189
13911
 
13912
+ /**
13913
+ * Component-scoped {@link createSfxPool}, with the lifecycle wired up.
13914
+ *
13915
+ * The pool is created once — through a lazy `useState` initializer, so no
13916
+ * element is allocated on a render that gets discarded — and disposed on
13917
+ * unmount, so its elements do not outlive the screen that owns them.
13918
+ *
13919
+ * `volume` is tracked separately: changing it calls `setVolume` on the existing
13920
+ * pool rather than rebuilding it, which would throw away every clip the user
13921
+ * has already downloaded — exactly the cost the pool exists to avoid.
13922
+ * `baseUrl`, `voices` and `maxSources` are read once, at creation.
13923
+ *
13924
+ * @param options - Passed through to {@link createSfxPool}.
13925
+ * @returns A stable pool handle.
13926
+ *
13927
+ * @example
13928
+ * const sfx = useSfxPool({ volume: settings.sfxVolume / 100, baseUrl: import.meta.env.BASE_URL });
13929
+ *
13930
+ * useEffect(() => { sfx.preload(["sfx/select.mp3", "sfx/back.mp3"]); }, [sfx]);
13931
+ *
13932
+ * <button onClick={() => sfx.play("sfx/select.mp3")}>Confirmar</button>
13933
+ */
13934
+ export declare function useSfxPool(options?: SfxPoolOptions): SfxPool;
13935
+
13190
13936
  /**
13191
13937
  * Drag-to-reorder for a list, with a **keyboard path of equal standing**.
13192
13938
  *
@@ -13537,6 +14283,39 @@ export declare interface UseTorchResult {
13537
14283
  */
13538
14284
  export declare function useTranslate(): I18nContextValue["t"];
13539
14285
 
14286
+ /**
14287
+ * Reveals a string one character at a time.
14288
+ *
14289
+ * Changing `text` restarts the reveal. The reset happens during render rather
14290
+ * than in an effect, so the new string never flashes in full for one frame
14291
+ * before the animation takes over.
14292
+ *
14293
+ * Always give the reader a way out: an animation that cannot be skipped is a
14294
+ * tax on anyone re-reading or moving fast, which is what `skip` is for.
14295
+ *
14296
+ * @param text - The full string to reveal. Nullish is treated as empty.
14297
+ * @param speedMs - Delay between characters. `0` or less renders instantly,
14298
+ * which is the hook-safe way to honour `prefers-reduced-motion`.
14299
+ * @returns The revealed prefix, whether it finished, and a `skip` action.
14300
+ *
14301
+ * @example
14302
+ * const reduced = useMediaQuery("(prefers-reduced-motion: reduce)");
14303
+ * const { displayedText, isComplete, skip } = useTypewriter(line, reduced ? 0 : 30);
14304
+ *
14305
+ * return <p onClick={skip}>{displayedText}{isComplete ? "" : "▌"}</p>;
14306
+ */
14307
+ export declare function useTypewriter(text: string, speedMs: number): UseTypewriterResult;
14308
+
14309
+ /** State returned by {@link useTypewriter}. */
14310
+ export declare interface UseTypewriterResult {
14311
+ /** The prefix of `text` revealed so far. */
14312
+ displayedText: string;
14313
+ /** `true` once the whole string is on screen. */
14314
+ isComplete: boolean;
14315
+ /** Reveal the rest immediately — wire it to a tap or a key press. */
14316
+ skip: () => void;
14317
+ }
14318
+
13540
14319
  /**
13541
14320
  * Update a record and invalidate both the list and the single-record caches.
13542
14321
  *