@clicktap/ui 0.42.4 → 0.43.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.
|
@@ -4,6 +4,10 @@ import { ResendCodeProps } from './ResendCode.types';
|
|
|
4
4
|
* request and cooldown state (e.g. via a resend hook composing `useCooldown`). Renders one of three
|
|
5
5
|
* labels: the cooldown countdown, the in-flight label, or the idle action label; the button is
|
|
6
6
|
* disabled unless `canResend`.
|
|
7
|
+
*
|
|
8
|
+
* A visually-hidden polite live region announces when resend becomes available (CTAP-1167). The
|
|
9
|
+
* announcement is a separate region — not `aria-live` on the button — so screen-reader users hear
|
|
10
|
+
* the cooldown END without having every one-second countdown label change read out.
|
|
7
11
|
*/
|
|
8
|
-
export declare function ResendCode({ cooldownSeconds, isResending, canResend, onResend, prompt, label, resendingLabel, className, buttonClassName, }: ResendCodeProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function ResendCode({ cooldownSeconds, isResending, canResend, onResend, prompt, label, resendingLabel, availableAnnouncement, className, buttonClassName, }: ResendCodeProps): import("react/jsx-runtime").JSX.Element;
|
|
9
13
|
export default ResendCode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsxs as
|
|
1
|
+
"use client";import{jsxs as m,jsx as t}from"react/jsx-runtime";import{cn as a}from"../../utils/cn.js";function x({cooldownSeconds:e,isResending:n,canResend:s,onResend:d,prompt:i="Didn't get a code?",label:l="Resend code",resendingLabel:r="Sending…",availableAnnouncement:o="You can request a new code now.",className:c,buttonClassName:u}){const b=e>0?`Resend in ${e}s`:n?r:l;return m("p",{className:a("text-sm",c),children:[i," ",t("button",{type:"button",onClick:d,disabled:!s,className:a("underline disabled:no-underline disabled:opacity-50 disabled:cursor-not-allowed",u),children:b}),t("span",{"aria-live":"polite",className:"sr-only",children:e===0&&!n?o:""})]})}export{x as ResendCode,x as default};
|
|
@@ -13,6 +13,11 @@ export type ResendCodeProps = {
|
|
|
13
13
|
label?: string;
|
|
14
14
|
/** Label shown while a resend is in flight. Defaults to "Sending…". */
|
|
15
15
|
resendingLabel?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Screen-reader announcement made (via a polite live region) when the cooldown ends and resend
|
|
18
|
+
* becomes available. Defaults to "You can request a new code now.".
|
|
19
|
+
*/
|
|
20
|
+
availableAnnouncement?: string;
|
|
16
21
|
/** className for the wrapping paragraph. */
|
|
17
22
|
className?: string;
|
|
18
23
|
/** className for the action button. */
|
package/hooks/useCooldown.d.ts
CHANGED
|
@@ -16,9 +16,16 @@ export interface UseCooldownResult {
|
|
|
16
16
|
/**
|
|
17
17
|
* A headless one-second countdown. `start(n)` sets the remaining seconds and it ticks down to zero.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* Deadline-based (CTAP-1167): `start(n)` stores an absolute deadline and every tick recomputes the
|
|
20
|
+
* remaining time from the clock instead of decrementing. Browsers and WebViews throttle timers in
|
|
21
|
+
* backgrounded tabs (Capacitor apps aggressively), so a decrementing counter falls behind real
|
|
22
|
+
* time — a 60s cooldown resumed after backgrounding would still show tens of seconds long after
|
|
23
|
+
* the real deadline passed. Recomputing makes the first tick after a throttled gap snap to the
|
|
24
|
+
* true remaining time, and `isActiveNow()` compares against the deadline itself so re-entry
|
|
25
|
+
* guards are immune to a stale count.
|
|
26
|
+
*
|
|
27
|
+
* StrictMode-safe: the tick lives in an effect (not a state updater), and the deadline is a ref so
|
|
28
|
+
* the timer chain reads the latest value without re-subscribing each second.
|
|
22
29
|
*/
|
|
23
30
|
export declare function useCooldown(): UseCooldownResult;
|
|
24
31
|
export default useCooldown;
|
package/hooks/useCooldown.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{useState as
|
|
1
|
+
"use client";import{useState as f,useRef as d,useCallback as r,useEffect as w}from"react";function M(){const[u,o]=f(0),e=d(0),i=r(n=>{const t=Math.max(0,Math.ceil(n));e.current=t>0?Date.now()+t*1e3:0,o(t)},[]),l=r(()=>{e.current=0,o(0)},[]),m=r(()=>e.current>Date.now(),[]),c=u>0;return w(()=>{if(!c)return;let n;const t=()=>{const s=e.current-Date.now();return s>0?s%1e3||1e3:0},a=()=>{const s=Math.max(0,Math.ceil((e.current-Date.now())/1e3));o(s),s>0&&(n=setTimeout(a,t()))};return n=setTimeout(a,t()),()=>clearTimeout(n)},[c]),{seconds:u,isActive:c,start:i,reset:l,isActiveNow:m}}export{M as default,M as useCooldown};
|