tempest-react-sdk 0.21.0 → 0.22.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.
@@ -38,6 +38,7 @@ import { LabelHTMLAttributes } from 'react';
38
38
  import { lazy } from 'react';
39
39
  import { Link } from 'react-router-dom';
40
40
  import { MemoryRouter } from 'react-router-dom';
41
+ import { MouseEvent as MouseEvent_2 } from 'react';
41
42
  import { MouseEventHandler } from 'react';
42
43
  import { Navigate } from 'react-router-dom';
43
44
  import { NavigateOptions } from 'react-router-dom';
@@ -67,6 +68,7 @@ import { Table as Table_2 } from 'dexie';
67
68
  import { TextareaHTMLAttributes } from 'react';
68
69
  import { To } from 'react-router-dom';
69
70
  import { ToggleEventHandler } from 'react';
71
+ import { TouchEvent as TouchEvent_2 } from 'react';
70
72
  import { TouchEventHandler } from 'react';
71
73
  import { TransitionEventHandler } from 'react';
72
74
  import { UIEventHandler } from 'react';
@@ -580,6 +582,24 @@ export declare type BannerVariant = "info" | "success" | "warning" | "danger";
580
582
  */
581
583
  export declare function bearingDeg(origin: Coordinate, destination: Coordinate): number;
582
584
 
585
+ /**
586
+ * The Chromium-only `beforeinstallprompt` event. iOS Safari never fires it —
587
+ * for those users the app falls back to manual "Add to Home Screen"
588
+ * instructions. Ported into the SDK so consumers get the type without a global
589
+ * augmentation.
590
+ */
591
+ export declare interface BeforeInstallPromptEvent extends Event {
592
+ /** Platforms the prompt can target (e.g. `["web", "android"]`). */
593
+ readonly platforms: readonly string[];
594
+ /** Resolves once the user accepts or dismisses the prompt. */
595
+ readonly userChoice: Promise<{
596
+ outcome: "accepted" | "dismissed";
597
+ platform: string;
598
+ }>;
599
+ /** Shows the native install prompt. */
600
+ prompt: () => Promise<void>;
601
+ }
602
+
583
603
  /**
584
604
  * Fixed-bottom mobile tab bar. 3–5 items recommended. Pair with
585
605
  * `<Show below="md">` to render only on mobile.
@@ -723,6 +743,16 @@ export declare function buildApiError(status: number, body: unknown, headers?: H
723
743
  get(name: string): string | null;
724
744
  }, sentRequestId?: string): ApiError;
725
745
 
746
+ /**
747
+ * Builds an `intent://` URL that re-opens the current page inside Chrome on
748
+ * Android. When Chrome is missing, the fallback URL points to the Play Store so
749
+ * the user can still recover. Returns `null` outside Android.
750
+ *
751
+ * @returns The `intent://` URL, or `null` when not on Android or without a DOM.
752
+ * @see https://developer.chrome.com/docs/multidevice/android/intents
753
+ */
754
+ export declare function buildOpenInChromeIntent(): string | null;
755
+
726
756
  /**
727
757
  * Primary action button with variants, sizes and a loading state that
728
758
  * preserves layout via an absolutely-positioned spinner.
@@ -3406,6 +3436,17 @@ export declare interface InstallButtonProps extends Omit<ButtonProps, "onClick"
3406
3436
  onResult?: (outcome: InstallOutcome) => void;
3407
3437
  }
3408
3438
 
3439
+ /**
3440
+ * Strategy the UI should use to install the app:
3441
+ *
3442
+ * - `"native"`: trigger `BeforeInstallPromptEvent.prompt()`.
3443
+ * - `"ios"`: show iOS Safari Share → Add to Home Screen instructions.
3444
+ * - `"manual"`: show generic browser-menu instructions (Chromium forks that
3445
+ * strip the prompt API, plus the timeout fallback when no event arrives).
3446
+ * - `"none"`: nothing to offer (already installed or unsupported runtime).
3447
+ */
3448
+ export declare type InstallMethod = "native" | "ios" | "manual" | "none";
3449
+
3409
3450
  /**
3410
3451
  * Install a `notificationclick` handler that focuses an existing client when
3411
3452
  * possible and falls back to opening a new window.
@@ -3485,6 +3526,22 @@ export declare function installSkipWaitingListener(): void;
3485
3526
 
3486
3527
  export declare type InterpolationValues = Record<string, string | number>;
3487
3528
 
3529
+ /**
3530
+ * Detects any Android browser.
3531
+ *
3532
+ * @returns `true` when the user agent reports Android.
3533
+ */
3534
+ export declare function isAndroid(): boolean;
3535
+
3536
+ /**
3537
+ * Detects Android browsers that ship a Chromium fork with the
3538
+ * `beforeinstallprompt` event stripped or unreliable. Those users must install
3539
+ * via the browser menu manually, identical to iOS Safari.
3540
+ *
3541
+ * @returns `true` when running on such an Android browser.
3542
+ */
3543
+ export declare function isAndroidWithoutPromptApi(): boolean;
3544
+
3488
3545
  /**
3489
3546
  * Type guard for the {@link ApiError} shape. Matches both {@link TempestApiError}
3490
3547
  * instances and plain objects carrying `status` + `detail`.
@@ -3539,6 +3596,14 @@ export declare function isDefined<T>(value: T | null | undefined): value is T;
3539
3596
  */
3540
3597
  export declare function isEmpty(value: unknown): boolean;
3541
3598
 
3599
+ /**
3600
+ * Detects iOS / iPadOS Safari, including modern iPads that report `MacIntel`
3601
+ * plus multi-touch instead of an `iPad` user agent.
3602
+ *
3603
+ * @returns `true` when running on iOS/iPadOS.
3604
+ */
3605
+ export declare function isIOS(): boolean;
3606
+
3542
3607
  /**
3543
3608
  * Return true when the JWT's `exp` claim is in the past (or missing).
3544
3609
  *
@@ -3588,6 +3653,15 @@ export declare function isPushSupported(): boolean;
3588
3653
  /** True when the Web Share API is available in this environment. */
3589
3654
  export declare function isShareSupported(): boolean;
3590
3655
 
3656
+ /**
3657
+ * Reports whether the app is running as an installed PWA (Android Chrome, iOS,
3658
+ * or desktop). Use it to suppress install CTAs once the user is already in the
3659
+ * standalone shell.
3660
+ *
3661
+ * @returns `true` when the display mode is standalone or iOS reports standalone.
3662
+ */
3663
+ export declare function isStandalone(): boolean;
3664
+
3591
3665
  /**
3592
3666
  * Type guard asserting a value is a string primitive.
3593
3667
  *
@@ -3799,6 +3873,27 @@ export declare interface LoggerSink {
3799
3873
 
3800
3874
  export declare type LogLevel = "debug" | "info" | "warn" | "error";
3801
3875
 
3876
+ /**
3877
+ * DOM event handlers produced by {@link useLongPressHandlers}, ready to spread
3878
+ * onto a JSX element (`<div {...handlers}>`).
3879
+ */
3880
+ export declare interface LongPressHandlers {
3881
+ /** Starts the hold timer on mouse press. */
3882
+ onMouseDown: (event: MouseEvent_2) => void;
3883
+ /** Cancels the pending hold when the mouse is released. */
3884
+ onMouseUp: () => void;
3885
+ /** Cancels the pending hold when the pointer leaves the element. */
3886
+ onMouseLeave: () => void;
3887
+ /** Starts the hold timer on touch start. */
3888
+ onTouchStart: (event: TouchEvent_2) => void;
3889
+ /** Cancels the pending hold when the touch ends. */
3890
+ onTouchEnd: () => void;
3891
+ /** Cancels the pending hold when the finger moves. */
3892
+ onTouchMove: () => void;
3893
+ /** Fires the long-press immediately (desktop right-click parity). */
3894
+ onContextMenu: (event: MouseEvent_2) => void;
3895
+ }
3896
+
3802
3897
  /**
3803
3898
  * Memoize only the most recent call of `fn`, keyed by shallow-equal arguments.
3804
3899
  *
@@ -5014,6 +5109,11 @@ export declare interface RefreshIndicatorProps {
5014
5109
  * app keeps full control over the SW file — this helper only handles the
5015
5110
  * boilerplate around `register()` and `updatefound`.
5016
5111
  *
5112
+ * With `autoUpdate` enabled it additionally polls `registration.update()` on
5113
+ * an interval and (by default) reloads the page when a freshly activated worker
5114
+ * takes control — a framework-agnostic equivalent of `vite-plugin-pwa`'s
5115
+ * auto-update client, implemented directly on `navigator.serviceWorker`.
5116
+ *
5017
5117
  * @returns The registration when it succeeds, or `null` when unsupported.
5018
5118
  */
5019
5119
  export declare function registerServiceWorker(options: RegisterServiceWorkerOptions): Promise<ServiceWorkerRegistration | null>;
@@ -5033,6 +5133,27 @@ export declare interface RegisterServiceWorkerOptions {
5033
5133
  onUpdate?: (waiting: ServiceWorker, registration: ServiceWorkerRegistration) => void;
5034
5134
  /** Called on registration failure. */
5035
5135
  onError?: (error: unknown) => void;
5136
+ /**
5137
+ * When `true`, poll the server for a fresh service worker on an interval
5138
+ * (see {@link RegisterServiceWorkerOptions.updateIntervalMs}) and, unless
5139
+ * {@link RegisterServiceWorkerOptions.reloadOnActivate} is disabled, reload
5140
+ * the page as soon as a new worker takes control. Mirrors the auto-update
5141
+ * behaviour of `vite-plugin-pwa` without depending on it. Default `false`.
5142
+ */
5143
+ autoUpdate?: boolean;
5144
+ /**
5145
+ * How often, in ms, to call `registration.update()` while `autoUpdate` is
5146
+ * on. Browsers already re-check roughly every 24h; an hourly poll keeps
5147
+ * long-lived sessions current without flooding the network. Default
5148
+ * `3600000` (1 hour).
5149
+ */
5150
+ updateIntervalMs?: number;
5151
+ /**
5152
+ * When `autoUpdate` is on, reload the page once a new worker takes control
5153
+ * (`controllerchange`), guarded against reload loops. Set to `false` to
5154
+ * keep polling but leave the reload to the host app. Default `true`.
5155
+ */
5156
+ reloadOnActivate?: boolean;
5036
5157
  }
5037
5158
 
5038
5159
  /**
@@ -5410,6 +5531,33 @@ export declare interface SentryLike {
5410
5531
  */
5411
5532
  export declare function share(payload: SharePayload): Promise<ShareResult>;
5412
5533
 
5534
+ /**
5535
+ * Try the Web Share API with the given `Blob` as a file; fall back to a
5536
+ * download anchor when the browser cannot share files (desktop Firefox, older
5537
+ * iOS Safari, etc.).
5538
+ *
5539
+ * A companion to {@link share} for the common "export a generated artifact"
5540
+ * flow: on mobile it opens the native share sheet, and everywhere else it
5541
+ * triggers a plain download. Returns early when the user shares or cancels the
5542
+ * dialog; otherwise it downloads.
5543
+ *
5544
+ * @param blob - The binary payload to share or download.
5545
+ * @param fileName - The file name presented to the user.
5546
+ * @param options - Optional `title` for the share dialog.
5547
+ * @returns A promise that resolves once the share or download completes.
5548
+ *
5549
+ * @example
5550
+ * const zip = new Blob([bytes], { type: "application/zip" });
5551
+ * await shareOrDownloadBlob(zip, "export.zip");
5552
+ */
5553
+ export declare function shareOrDownloadBlob(blob: Blob, fileName: string, options?: ShareOrDownloadOptions): Promise<void>;
5554
+
5555
+ /** Options for {@link shareOrDownloadBlob}. */
5556
+ export declare interface ShareOrDownloadOptions {
5557
+ /** Title passed to the Web Share dialog. Defaults to the file name. */
5558
+ title?: string;
5559
+ }
5560
+
5413
5561
  export declare interface SharePayload {
5414
5562
  title?: string;
5415
5563
  text?: string;
@@ -6912,6 +7060,75 @@ export declare function useI18n(): I18nContextValue;
6912
7060
  */
6913
7061
  export declare function useIdle(timeout?: number): boolean;
6914
7062
 
7063
+ /**
7064
+ * React hook that resolves how (and whether) to offer PWA installation.
7065
+ *
7066
+ * It caches the `beforeinstallprompt` event, detects iOS/iPadOS, detects
7067
+ * Android Chromium forks that lack the prompt API, detects standalone display
7068
+ * mode, and applies a decline cooldown persisted in `localStorage`. The
7069
+ * resulting `method` tells the UI which install affordance to render.
7070
+ *
7071
+ * The decline persistence is pluggable through
7072
+ * {@link UseInstallPromptOptions.declineStorageKey} and
7073
+ * {@link UseInstallPromptOptions.declineCooldownMs} — no app-specific storage
7074
+ * layer is required, and it is SSR-guarded.
7075
+ *
7076
+ * @param options - Optional storage key, cooldown, and fallback-delay tuning.
7077
+ * @returns The install state plus `install()` and `recordDecline()` actions.
7078
+ *
7079
+ * @example
7080
+ * const { method, install } = useInstallPrompt();
7081
+ * if (method === "native") return <button onClick={install}>Install</button>;
7082
+ */
7083
+ export declare function useInstallPrompt(options?: UseInstallPromptOptions): UseInstallPromptResult;
7084
+
7085
+ /** Options for {@link useInstallPrompt}. */
7086
+ export declare interface UseInstallPromptOptions {
7087
+ /**
7088
+ * `localStorage` key used to persist the decline timestamp. Defaults to
7089
+ * `"tempest:install-declined-at"`.
7090
+ */
7091
+ declineStorageKey?: string;
7092
+ /**
7093
+ * How long, in ms, the install CTA stays hidden after the user declines.
7094
+ * Defaults to 7 days.
7095
+ */
7096
+ declineCooldownMs?: number;
7097
+ /**
7098
+ * How long, in ms, to wait for a `beforeinstallprompt` event before
7099
+ * resolving to the `"manual"` method. Defaults to `3000`.
7100
+ */
7101
+ manualFallbackDelayMs?: number;
7102
+ }
7103
+
7104
+ /** State returned by {@link useInstallPrompt}. */
7105
+ export declare interface UseInstallPromptResult {
7106
+ /** The cached `beforeinstallprompt` event, or `null` when unavailable. */
7107
+ deferredPrompt: BeforeInstallPromptEvent | null;
7108
+ /** True when the app can be installed through any supported method. */
7109
+ canInstall: boolean;
7110
+ /** True when running on iOS/iPadOS Safari. */
7111
+ isIOS: boolean;
7112
+ /** True when already running as an installed PWA. */
7113
+ isStandalone: boolean;
7114
+ /** True when the browser is an Android Chromium fork with no prompt API. */
7115
+ isManualAndroid: boolean;
7116
+ /** True when no `beforeinstallprompt` arrived within the timeout window. */
7117
+ promptTimedOut: boolean;
7118
+ /** The resolved install strategy the UI should follow. */
7119
+ method: InstallMethod;
7120
+ /** An `intent://` URL to re-open the page in Chrome on Android, else `null`. */
7121
+ openInChromeIntent: string | null;
7122
+ /**
7123
+ * Triggers the native prompt.
7124
+ *
7125
+ * @returns `true` when the user accepted the install, `false` otherwise.
7126
+ */
7127
+ install: () => Promise<boolean>;
7128
+ /** Records that the user declined the CTA, starting the cooldown window. */
7129
+ recordDecline: () => void;
7130
+ }
7131
+
6915
7132
  /**
6916
7133
  * Track whether the referenced element intersects the viewport. Useful for
6917
7134
  * lazy-loading images, "load more" sentinels, and animation triggers.
@@ -7006,6 +7223,52 @@ export { useLocation }
7006
7223
  */
7007
7224
  export declare function useLongPress<T extends HTMLElement>(ref: RefObject<T | null>, fn: () => void, options?: UseLongPressOptions): void;
7008
7225
 
7226
+ /**
7227
+ * Returns DOM event handlers that detect a long press (touch or mouse hold)
7228
+ * and invoke `onLongPress` once after `delayMs`.
7229
+ *
7230
+ * Cancels on touch movement or pointer release. Also wires `contextmenu` to
7231
+ * fire `onLongPress` immediately so a right-click on desktop opens selection
7232
+ * mode the same way an Android long-press does on mobile.
7233
+ *
7234
+ * The hook does not `preventDefault` on the underlying click — the consumer
7235
+ * decides whether the subsequent click should still fire. Track that with the
7236
+ * returned `wasLongPress()` guard to tell "long-press fired" apart from a
7237
+ * "regular click".
7238
+ *
7239
+ * Unlike {@link useLongPress}, which attaches pointer listeners to a `ref`,
7240
+ * this variant returns handler props you spread directly onto an element and
7241
+ * exposes a `wasLongPress()` guard.
7242
+ *
7243
+ * @param onLongPress - Callback invoked once the hold threshold is reached.
7244
+ * @param options - Optional `delayMs` and `disabled` flags.
7245
+ * @returns The spreadable handlers plus a `wasLongPress()` guard.
7246
+ *
7247
+ * @example
7248
+ * const longPress = useLongPressHandlers(() => enterSelectionMode(), { delayMs: 600 });
7249
+ * <button {...longPress} onClick={() => { if (!longPress.wasLongPress()) navigate(); }}>
7250
+ * Item
7251
+ * </button>
7252
+ */
7253
+ export declare function useLongPressHandlers(onLongPress: () => void, options?: UseLongPressHandlersOptions): UseLongPressHandlersResult;
7254
+
7255
+ /** Options accepted by {@link useLongPressHandlers}. */
7256
+ export declare interface UseLongPressHandlersOptions {
7257
+ /** Hold duration in ms before `onLongPress` fires. Defaults to `500`. */
7258
+ delayMs?: number;
7259
+ /** When `true`, the hook is inert and every handler is a no-op. */
7260
+ disabled?: boolean;
7261
+ }
7262
+
7263
+ /** Return shape of {@link useLongPressHandlers}. */
7264
+ export declare type UseLongPressHandlersResult = LongPressHandlers & {
7265
+ /**
7266
+ * Reads whether the most recent interaction fired a long-press. Use it in
7267
+ * the element's `onClick` to suppress the click that follows a long-press.
7268
+ */
7269
+ wasLongPress: () => boolean;
7270
+ };
7271
+
7009
7272
  export declare interface UseLongPressOptions {
7010
7273
  /** Press duration in ms. Default `500`. */
7011
7274
  delay?: number;
@@ -8043,6 +8306,32 @@ export declare type WithSelectors<S> = S extends {
8043
8306
  */
8044
8307
  export declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
8045
8308
 
8309
+ /**
8310
+ * Write a minimal single-sheet Office Open XML (`.xlsx`) workbook and return
8311
+ * its bytes. No extra dependency beyond `fflate` — the archive is assembled and
8312
+ * deflated in-process.
8313
+ *
8314
+ * The output is UTF-8 throughout, so accents round-trip in
8315
+ * Excel/LibreOffice/Google Sheets without the BOM-detection fragility that
8316
+ * plagues CSV exports. The XML stays compact: inline strings (no shared-string
8317
+ * table), no styles, no merged cells. Numeric cells use the native `"n"` type
8318
+ * so spreadsheets recognise them as numbers; `null` renders as an empty cell.
8319
+ *
8320
+ * @param headers - Column headers written as the first row.
8321
+ * @param rows - Data rows; each value is a string, a number, or `null` (empty).
8322
+ * @returns The `.xlsx` file contents as a `Uint8Array`.
8323
+ *
8324
+ * @example
8325
+ * const bytes = writeXlsx(
8326
+ * ["Name", "Score"],
8327
+ * [["Ada", 99], ["Alan", null]],
8328
+ * );
8329
+ * const blob = new Blob([bytes], {
8330
+ * type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
8331
+ * });
8332
+ */
8333
+ export declare function writeXlsx(headers: readonly string[], rows: readonly (readonly (string | number | null)[])[]): Uint8Array;
8334
+
8046
8335
  /**
8047
8336
  * Minimal `react-hook-form` resolver built on top of zod. Mirrors the shape
8048
8337
  * produced by `@hookform/resolvers/zod` so it can be passed straight to