@alfadocs/ui-kit-debug 0.33.1 → 0.33.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{otp-input-BEg_sn8A.js → otp-input-CDTWT5EK.js} +83 -76
- package/dist/_chunks/otp-input-CDTWT5EK.js.map +1 -0
- package/dist/_chunks/use-web-otp-D_utzp6S.js +33 -0
- package/dist/_chunks/use-web-otp-D_utzp6S.js.map +1 -0
- package/dist/agent-catalog.json +1 -1
- package/dist/components/otp-input/index.js +1 -1
- package/dist/components/otp-input/otp-input.d.ts +16 -0
- package/dist/components/otp-input/otp-input.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +27 -25
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/use-web-otp.d.ts +22 -0
- package/dist/hooks/use-web-otp.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/_chunks/otp-input-BEg_sn8A.js.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { forwardRef as
|
|
1
|
+
import { jsxs as x, jsx as A } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as ee, useId as te, useRef as R, useState as se, useEffect as re, useCallback as L, useMemo as ae } from "react";
|
|
3
3
|
import { c as N } from "./index-D2ZczOXr.js";
|
|
4
|
-
import { useTranslation as
|
|
5
|
-
import { u as
|
|
6
|
-
import { u as
|
|
7
|
-
import { u as
|
|
8
|
-
|
|
4
|
+
import { useTranslation as oe } from "react-i18next";
|
|
5
|
+
import { u as ie } from "./use-controllable-state-BiY4xTzM.js";
|
|
6
|
+
import { u as ne } from "./use-web-otp-D_utzp6S.js";
|
|
7
|
+
import { u as ce } from "./form-field-context-B3APVHKx.js";
|
|
8
|
+
import { u as ue } from "./registry-nPAVE19X.js";
|
|
9
|
+
const le = {
|
|
9
10
|
id: "otp-input",
|
|
10
11
|
capabilities: ["edit_inline", "submit"],
|
|
11
12
|
state: {
|
|
@@ -61,7 +62,7 @@ const ce = {
|
|
|
61
62
|
description: "Sourced from the id prop."
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
|
-
},
|
|
65
|
+
}, de = N("ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]"), pe = N(
|
|
65
66
|
[
|
|
66
67
|
"ds:w-[var(--min-target-size)] ds:h-[var(--min-target-size)]",
|
|
67
68
|
"ds:rounded-[var(--radius-sm)]",
|
|
@@ -85,89 +86,95 @@ const ce = {
|
|
|
85
86
|
function M(e) {
|
|
86
87
|
return e.replace(/[\u0660-\u0669]/g, (a) => String(a.charCodeAt(0) - 1632)).replace(/[\u06F0-\u06F9]/g, (a) => String(a.charCodeAt(0) - 1776));
|
|
87
88
|
}
|
|
88
|
-
function
|
|
89
|
+
function y(e, a) {
|
|
89
90
|
return M(e).replace(/\D/g, "").slice(0, a);
|
|
90
91
|
}
|
|
91
|
-
function
|
|
92
|
-
const m =
|
|
92
|
+
function fe(e, a) {
|
|
93
|
+
const m = y(e, a), l = new Array(a).fill("");
|
|
93
94
|
for (let n = 0; n < m.length; n += 1)
|
|
94
95
|
l[n] = m[n];
|
|
95
96
|
return l;
|
|
96
97
|
}
|
|
97
|
-
const
|
|
98
|
+
const me = ee(
|
|
98
99
|
({
|
|
99
100
|
length: e = 6,
|
|
100
101
|
value: a,
|
|
101
102
|
defaultValue: m,
|
|
102
103
|
onChange: l,
|
|
103
104
|
onComplete: n,
|
|
104
|
-
disabled:
|
|
105
|
-
error:
|
|
106
|
-
label:
|
|
105
|
+
disabled: O,
|
|
106
|
+
error: S,
|
|
107
|
+
label: w,
|
|
107
108
|
id: F,
|
|
108
|
-
className:
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
className: $,
|
|
110
|
+
webOtp: j = !1
|
|
111
|
+
}, z) => {
|
|
112
|
+
const { t: H } = oe(), b = ce(), W = te(), K = w ? W : void 0, [_, C] = ie({
|
|
111
113
|
value: a,
|
|
112
|
-
defaultValue:
|
|
113
|
-
}), p =
|
|
114
|
+
defaultValue: y(m ?? "", e)
|
|
115
|
+
}), p = y(_ ?? "", e), q = fe(p, e), v = b.disabled || !!O, P = b.invalid || !!S, G = P ? "error" : "default", B = R([]), I = R(p.length === e), d = R(p);
|
|
114
116
|
d.current = p;
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
return
|
|
118
|
-
}, [f, h] =
|
|
117
|
+
const J = () => {
|
|
118
|
+
const s = p.length;
|
|
119
|
+
return s >= e ? e - 1 : s;
|
|
120
|
+
}, [f, h] = se(J);
|
|
119
121
|
re(() => {
|
|
120
122
|
f >= e && h(e - 1);
|
|
121
123
|
}, [f, e]);
|
|
122
124
|
const c = L(
|
|
123
|
-
(
|
|
124
|
-
const t = Math.max(0, Math.min(e - 1,
|
|
125
|
+
(s) => {
|
|
126
|
+
const t = Math.max(0, Math.min(e - 1, s));
|
|
125
127
|
h(t);
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
+
const r = B.current[t];
|
|
129
|
+
r && (r.focus(), r.select());
|
|
128
130
|
},
|
|
129
131
|
[e]
|
|
130
132
|
), u = L(
|
|
131
|
-
(
|
|
132
|
-
const t =
|
|
133
|
-
|
|
133
|
+
(s) => {
|
|
134
|
+
const t = y(s, e);
|
|
135
|
+
C(t), d.current = t, l == null || l(t), t.length === e ? I.current || (I.current = !0, n == null || n(t)) : I.current = !1;
|
|
134
136
|
},
|
|
135
|
-
[
|
|
136
|
-
)
|
|
137
|
-
|
|
137
|
+
[C, e, l, n]
|
|
138
|
+
);
|
|
139
|
+
ne({
|
|
140
|
+
enabled: j && !v,
|
|
141
|
+
onCode: u
|
|
142
|
+
});
|
|
143
|
+
const Q = (s) => (t) => {
|
|
144
|
+
const r = t.target.value, o = M(r).replace(/\D/g, ""), i = d.current;
|
|
138
145
|
if (o.length === 0) {
|
|
139
|
-
if (
|
|
140
|
-
const T = i.slice(0,
|
|
146
|
+
if (r === "" && s < i.length) {
|
|
147
|
+
const T = i.slice(0, s) + i.slice(s + 1);
|
|
141
148
|
u(T);
|
|
142
149
|
}
|
|
143
150
|
return;
|
|
144
151
|
}
|
|
145
|
-
const
|
|
152
|
+
const g = i.slice(0, s), k = i.slice(s + o.length), V = (g + o + k).slice(0, e);
|
|
146
153
|
u(V);
|
|
147
|
-
const D = Math.min(
|
|
154
|
+
const D = Math.min(s + o.length, e - 1);
|
|
148
155
|
c(D);
|
|
149
|
-
},
|
|
150
|
-
const
|
|
156
|
+
}, U = (s) => (t) => {
|
|
157
|
+
const r = d.current;
|
|
151
158
|
if (t.key === "Backspace") {
|
|
152
|
-
if (s
|
|
159
|
+
if (r[s]) {
|
|
153
160
|
t.preventDefault();
|
|
154
|
-
const o =
|
|
161
|
+
const o = r.slice(0, s) + r.slice(s + 1);
|
|
155
162
|
u(o);
|
|
156
163
|
return;
|
|
157
164
|
}
|
|
158
|
-
if (
|
|
165
|
+
if (s > 0) {
|
|
159
166
|
t.preventDefault();
|
|
160
|
-
const o =
|
|
161
|
-
u(o), c(
|
|
167
|
+
const o = r.slice(0, s - 1) + r.slice(s);
|
|
168
|
+
u(o), c(s - 1);
|
|
162
169
|
}
|
|
163
170
|
return;
|
|
164
171
|
}
|
|
165
172
|
if (t.key === "ArrowLeft") {
|
|
166
|
-
t.preventDefault(),
|
|
173
|
+
t.preventDefault(), s > 0 && c(s - 1);
|
|
167
174
|
return;
|
|
168
175
|
}
|
|
169
176
|
if (t.key === "ArrowRight") {
|
|
170
|
-
t.preventDefault(),
|
|
177
|
+
t.preventDefault(), s < e - 1 && c(s + 1);
|
|
171
178
|
return;
|
|
172
179
|
}
|
|
173
180
|
if (t.key === "Home") {
|
|
@@ -175,56 +182,56 @@ const pe = x(
|
|
|
175
182
|
return;
|
|
176
183
|
}
|
|
177
184
|
t.key === "End" && (t.preventDefault(), c(e - 1));
|
|
178
|
-
},
|
|
185
|
+
}, X = (s) => (t) => {
|
|
179
186
|
const o = t.clipboardData.getData("text").replace(/\s+/g, ""), i = M(o);
|
|
180
187
|
if (!/^[0-9]+$/.test(i)) return;
|
|
181
188
|
t.preventDefault();
|
|
182
|
-
const
|
|
189
|
+
const g = d.current, k = g.slice(0, s), V = g.slice(s + i.length), D = (k + i + V).slice(0, e);
|
|
183
190
|
u(D);
|
|
184
|
-
const T = Math.min(
|
|
191
|
+
const T = Math.min(s + i.length, e - 1);
|
|
185
192
|
c(T);
|
|
186
|
-
},
|
|
187
|
-
h(
|
|
188
|
-
}, E =
|
|
193
|
+
}, Y = (s) => (t) => {
|
|
194
|
+
h(s), t.currentTarget.select();
|
|
195
|
+
}, E = b.id, Z = ae(
|
|
189
196
|
() => ({
|
|
190
197
|
getValue: () => d.current,
|
|
191
|
-
setValue: (
|
|
198
|
+
setValue: (s) => u(s),
|
|
192
199
|
clear: () => u(""),
|
|
193
200
|
focus: () => c(f),
|
|
194
201
|
isComplete: () => d.current.length === e
|
|
195
202
|
}),
|
|
196
203
|
[u, c, f, e]
|
|
197
204
|
);
|
|
198
|
-
return
|
|
199
|
-
|
|
205
|
+
return ue(le, Z, F), /* @__PURE__ */ x("div", { className: de({ className: $ }), children: [
|
|
206
|
+
w ? /* @__PURE__ */ A(
|
|
200
207
|
"label",
|
|
201
208
|
{
|
|
202
209
|
id: K,
|
|
203
210
|
htmlFor: `${E}-0`,
|
|
204
211
|
className: "type-label ds:text-foreground",
|
|
205
|
-
children:
|
|
212
|
+
children: w
|
|
206
213
|
}
|
|
207
214
|
) : null,
|
|
208
215
|
/* @__PURE__ */ A(
|
|
209
216
|
"div",
|
|
210
217
|
{
|
|
211
|
-
ref:
|
|
218
|
+
ref: z,
|
|
212
219
|
role: "group",
|
|
213
220
|
dir: "ltr",
|
|
214
221
|
"aria-labelledby": K,
|
|
215
|
-
"aria-invalid":
|
|
216
|
-
"aria-describedby":
|
|
217
|
-
"aria-disabled":
|
|
222
|
+
"aria-invalid": P || void 0,
|
|
223
|
+
"aria-describedby": b.describedBy || void 0,
|
|
224
|
+
"aria-disabled": v || void 0,
|
|
218
225
|
"data-component": "otp-input",
|
|
219
226
|
"data-component-id": F,
|
|
220
227
|
className: "ds:inline-flex ds:items-center ds:gap-[var(--spacing-sm)]",
|
|
221
|
-
children: q.map((
|
|
222
|
-
const
|
|
228
|
+
children: q.map((s, t) => {
|
|
229
|
+
const r = !v && t === f;
|
|
223
230
|
return /* @__PURE__ */ A(
|
|
224
231
|
"input",
|
|
225
232
|
{
|
|
226
233
|
ref: (o) => {
|
|
227
|
-
|
|
234
|
+
B.current[t] = o;
|
|
228
235
|
},
|
|
229
236
|
id: `${E}-${t}`,
|
|
230
237
|
type: "text",
|
|
@@ -232,18 +239,18 @@ const pe = x(
|
|
|
232
239
|
pattern: "[0-9]*",
|
|
233
240
|
autoComplete: t === 0 ? "one-time-code" : "off",
|
|
234
241
|
maxLength: 1,
|
|
235
|
-
value:
|
|
236
|
-
disabled:
|
|
237
|
-
tabIndex:
|
|
238
|
-
"aria-label":
|
|
242
|
+
value: s,
|
|
243
|
+
disabled: v,
|
|
244
|
+
tabIndex: r ? 0 : -1,
|
|
245
|
+
"aria-label": H("inputs.otp.digitLabel", {
|
|
239
246
|
current: t + 1,
|
|
240
247
|
total: e
|
|
241
248
|
}),
|
|
242
|
-
onChange:
|
|
243
|
-
onKeyDown:
|
|
244
|
-
onPaste:
|
|
245
|
-
onFocus:
|
|
246
|
-
className:
|
|
249
|
+
onChange: Q(t),
|
|
250
|
+
onKeyDown: U(t),
|
|
251
|
+
onPaste: X(t),
|
|
252
|
+
onFocus: Y(t),
|
|
253
|
+
className: pe({ tone: G })
|
|
247
254
|
},
|
|
248
255
|
t
|
|
249
256
|
);
|
|
@@ -253,8 +260,8 @@ const pe = x(
|
|
|
253
260
|
] });
|
|
254
261
|
}
|
|
255
262
|
);
|
|
256
|
-
|
|
263
|
+
me.displayName = "OTPInput";
|
|
257
264
|
export {
|
|
258
|
-
|
|
265
|
+
me as O
|
|
259
266
|
};
|
|
260
|
-
//# sourceMappingURL=otp-input-
|
|
267
|
+
//# sourceMappingURL=otp-input-CDTWT5EK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otp-input-CDTWT5EK.js","sources":["../../src/components/otp-input/otp-input.agent.ts","../../src/components/otp-input/otp-input.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — OTPInput. */\n/* */\n/* See `src/docs/26-agent-readiness.mdx` for the contract. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { OTPInputHandle } from './otp-input';\n\nexport const otpInputAgent: AgentAdapter<OTPInputHandle> = {\n id: 'otp-input',\n capabilities: ['edit_inline', 'submit'],\n state: {\n value: {\n type: 'string',\n descriptionKey: 'ui.agent.otpInput.state.value',\n description: 'Current digits entered, packed into a single string.',\n read: (handle) => handle.getValue(),\n },\n isComplete: {\n type: 'boolean',\n descriptionKey: 'ui.agent.otpInput.state.isComplete',\n description: 'Whether all required digits have been entered.',\n read: (handle) => handle.isComplete(),\n },\n },\n actions: {\n set_value: {\n safety: 'write',\n argsType: '{ value: string }',\n descriptionKey: 'ui.agent.otpInput.actions.setValue',\n description: 'Replace the entered digits.',\n invoke: (handle, args: { value: string }) => {\n handle.setValue(args.value);\n },\n },\n clear: {\n safety: 'destructive',\n descriptionKey: 'ui.agent.otpInput.actions.clear',\n description: 'Empty all digit cells. Loses any typed value.',\n invoke: (handle) => {\n handle.clear();\n },\n },\n focus: {\n safety: 'read',\n descriptionKey: 'ui.agent.otpInput.actions.focus',\n description: 'Move keyboard focus to the active digit cell.',\n invoke: (handle) => {\n handle.focus();\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'otp-input',\n description:\n 'Marks the OTPInput wrapper. Completion is observable via state.isComplete; the host onComplete callback fires once when all digits are filled.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ChangeEvent,\n type ClipboardEvent,\n type FocusEvent,\n type KeyboardEvent,\n} from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useWebOtp } from '../../hooks/use-web-otp';\nimport { useFormField } from '../form-field/form-field-context';\nimport { useAgentRegistration } from '../../agent';\nimport { otpInputAgent } from './otp-input.agent';\n\n/** Agent-readiness curated handle for OTPInput. */\nexport interface OTPInputHandle {\n getValue: () => string;\n setValue: (value: string) => void;\n clear: () => void;\n focus: () => void;\n isComplete: () => boolean;\n}\n\nconst wrapperVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]');\n\nconst boxVariants = cva(\n [\n 'ds:w-[var(--min-target-size)] ds:h-[var(--min-target-size)]',\n 'ds:rounded-[var(--radius-sm)]',\n 'ds:border ds:bg-input ds:text-foreground ds:text-center ds:font-medium ds:text-[length:var(--font-size-lg)]',\n 'ds:shadow-[var(--shadow-input)]',\n 'ds:transition-[colors,box-shadow] ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-ring ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:disabled:cursor-not-allowed ds:disabled:opacity-50',\n ].join(' '),\n {\n variants: {\n tone: {\n default: 'ds:border-border',\n error: 'ds:border-destructive',\n },\n },\n defaultVariants: { tone: 'default' },\n },\n);\n\nexport interface OTPInputProps {\n length?: 4 | 6 | 8;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n onComplete?: (code: string) => void;\n disabled?: boolean;\n error?: boolean;\n label?: string;\n /** Stable id, used to address this instance from the agent runtime. */\n id?: string;\n className?: string;\n /**\n * Opt into Chrome-Android's WebOTP API for one-tap SMS autofill. When\n * the browser surfaces a matching SMS-bound credential, the kit spreads\n * the code across every digit box and fires `onComplete` automatically.\n *\n * Falls back silently to manual entry on Firefox / desktop / iOS — those\n * platforms either don't ship the API at all or rely on the\n * `autocomplete=\"one-time-code\"` QuickType heuristic the kit already\n * applies to the first input.\n *\n * Prerequisites the prop can't enforce: page must be HTTPS, and the SMS\n * body must end with `@<origin> #<code>` on its last line (Chrome's\n * spec — without it the browser refuses to surface the message).\n * Default `false` so the listener stays opt-in.\n */\n webOtp?: boolean;\n}\n\nfunction normaliseDigits(str: string): string {\n return str\n .replace(/[\\u0660-\\u0669]/g, (c) => String(c.charCodeAt(0) - 0x0660))\n .replace(/[\\u06F0-\\u06F9]/g, (c) => String(c.charCodeAt(0) - 0x06f0));\n}\n\nfunction packValue(value: string, length: number): string {\n return normaliseDigits(value).replace(/\\D/g, '').slice(0, length);\n}\n\nfunction valueToArray(value: string, length: number): string[] {\n const packed = packValue(value, length);\n const arr = new Array<string>(length).fill('');\n for (let i = 0; i < packed.length; i += 1) {\n arr[i] = packed[i];\n }\n return arr;\n}\n\nexport const OTPInput = forwardRef<HTMLDivElement, OTPInputProps>(\n (\n {\n length = 6,\n value,\n defaultValue,\n onChange,\n onComplete,\n disabled,\n error,\n label,\n id,\n className,\n webOtp = false,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n const ctx = useFormField();\n const generatedLabelId = useId();\n const labelId = label ? generatedLabelId : undefined;\n\n const [internalValueRaw, setInternalValue] = useControllableState<string>({\n value,\n defaultValue: packValue(defaultValue ?? '', length),\n });\n const currentValue = packValue(internalValueRaw ?? '', length);\n const digits = valueToArray(currentValue, length);\n\n const effectiveDisabled = ctx.disabled || Boolean(disabled);\n const effectiveError = ctx.invalid || Boolean(error);\n const effectiveTone = effectiveError ? 'error' : 'default';\n\n const inputsRef = useRef<Array<HTMLInputElement | null>>([]);\n const completedRef = useRef(currentValue.length === length);\n const valueRef = useRef(currentValue);\n valueRef.current = currentValue;\n\n const computeInitialIndex = () => {\n const firstEmpty = currentValue.length;\n if (firstEmpty >= length) return length - 1;\n return firstEmpty;\n };\n const [activeIndex, setActiveIndex] = useState<number>(computeInitialIndex);\n\n useEffect(() => {\n if (activeIndex >= length) setActiveIndex(length - 1);\n }, [activeIndex, length]);\n\n const focusBox = useCallback(\n (index: number) => {\n const clamped = Math.max(0, Math.min(length - 1, index));\n setActiveIndex(clamped);\n const node = inputsRef.current[clamped];\n if (node) {\n node.focus();\n node.select();\n }\n },\n [length],\n );\n\n const commit = useCallback(\n (next: string) => {\n const packed = packValue(next, length);\n setInternalValue(packed);\n valueRef.current = packed;\n onChange?.(packed);\n if (packed.length === length) {\n if (!completedRef.current) {\n completedRef.current = true;\n onComplete?.(packed);\n }\n } else {\n completedRef.current = false;\n }\n },\n [setInternalValue, length, onChange, onComplete],\n );\n\n // WebOTP one-tap autofill. The hook silently no-ops when the API is\n // unavailable (Firefox / Safari / desktop Chromium) — `commit()` is\n // the same path manual entry takes, so `onChange` + `onComplete` fire\n // identically whether the digits arrived via SMS, paste, or typing.\n useWebOtp({\n enabled: webOtp && !effectiveDisabled,\n onCode: commit,\n });\n\n const handleChange =\n (index: number) => (event: ChangeEvent<HTMLInputElement>) => {\n const raw = event.target.value;\n const norm = normaliseDigits(raw).replace(/\\D/g, '');\n const current = valueRef.current;\n\n if (norm.length === 0) {\n if (raw === '' && index < current.length) {\n const newValue = current.slice(0, index) + current.slice(index + 1);\n commit(newValue);\n }\n return;\n }\n\n const before = current.slice(0, index);\n const after = current.slice(index + norm.length);\n const newValue = (before + norm + after).slice(0, length);\n commit(newValue);\n\n const focusTarget = Math.min(index + norm.length, length - 1);\n focusBox(focusTarget);\n };\n\n const handleKeyDown =\n (index: number) => (event: KeyboardEvent<HTMLInputElement>) => {\n const current = valueRef.current;\n if (event.key === 'Backspace') {\n if (current[index]) {\n event.preventDefault();\n const newValue = current.slice(0, index) + current.slice(index + 1);\n commit(newValue);\n return;\n }\n if (index > 0) {\n event.preventDefault();\n const newValue = current.slice(0, index - 1) + current.slice(index);\n commit(newValue);\n focusBox(index - 1);\n }\n return;\n }\n if (event.key === 'ArrowLeft') {\n event.preventDefault();\n if (index > 0) focusBox(index - 1);\n return;\n }\n if (event.key === 'ArrowRight') {\n event.preventDefault();\n if (index < length - 1) focusBox(index + 1);\n return;\n }\n if (event.key === 'Home') {\n event.preventDefault();\n focusBox(0);\n return;\n }\n if (event.key === 'End') {\n event.preventDefault();\n focusBox(length - 1);\n }\n };\n\n const handlePaste =\n (index: number) => (event: ClipboardEvent<HTMLInputElement>) => {\n const text = event.clipboardData.getData('text');\n const stripped = text.replace(/\\s+/g, '');\n const norm = normaliseDigits(stripped);\n if (!/^[0-9]+$/.test(norm)) return;\n event.preventDefault();\n const current = valueRef.current;\n const before = current.slice(0, index);\n const after = current.slice(index + norm.length);\n const newValue = (before + norm + after).slice(0, length);\n commit(newValue);\n const focusTarget = Math.min(index + norm.length, length - 1);\n focusBox(focusTarget);\n };\n\n const handleFocus =\n (index: number) => (event: FocusEvent<HTMLInputElement>) => {\n setActiveIndex(index);\n event.currentTarget.select();\n };\n\n const baseId = ctx.id;\n\n const agentHandle = useMemo<OTPInputHandle>(\n () => ({\n getValue: () => valueRef.current,\n setValue: (next) => commit(next),\n clear: () => commit(''),\n focus: () => focusBox(activeIndex),\n isComplete: () => valueRef.current.length === length,\n }),\n [commit, focusBox, activeIndex, length],\n );\n useAgentRegistration(otpInputAgent, agentHandle, id);\n\n return (\n <div className={wrapperVariants({ className })}>\n {label ? (\n <label\n id={labelId}\n htmlFor={`${baseId}-0`}\n className=\"type-label ds:text-foreground\"\n >\n {label}\n </label>\n ) : null}\n {/* eslint-disable-next-line jsx-a11y/role-supports-aria-props -- aria-invalid is a global ARIA state; conveys group-level validation */}\n <div\n ref={ref}\n role=\"group\"\n dir=\"ltr\"\n aria-labelledby={labelId}\n aria-invalid={effectiveError || undefined}\n aria-describedby={ctx.describedBy || undefined}\n aria-disabled={effectiveDisabled || undefined}\n data-component=\"otp-input\"\n data-component-id={id}\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-sm)]\"\n >\n {digits.map((digit, index) => {\n const isTabbable = !effectiveDisabled && index === activeIndex;\n return (\n <input\n key={index}\n ref={(node) => {\n inputsRef.current[index] = node;\n }}\n id={`${baseId}-${index}`}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete={index === 0 ? 'one-time-code' : 'off'}\n maxLength={1}\n value={digit}\n disabled={effectiveDisabled}\n tabIndex={isTabbable ? 0 : -1}\n aria-label={t('inputs.otp.digitLabel', {\n current: index + 1,\n total: length,\n })}\n onChange={handleChange(index)}\n onKeyDown={handleKeyDown(index)}\n onPaste={handlePaste(index)}\n onFocus={handleFocus(index)}\n className={boxVariants({ tone: effectiveTone })}\n />\n );\n })}\n </div>\n </div>\n );\n },\n);\n\nOTPInput.displayName = 'OTPInput';\n"],"names":["otpInputAgent","handle","args","wrapperVariants","cva","boxVariants","normaliseDigits","str","c","packValue","value","length","valueToArray","packed","arr","i","OTPInput","forwardRef","defaultValue","onChange","onComplete","disabled","error","label","id","className","webOtp","ref","t","useTranslation","ctx","useFormField","generatedLabelId","useId","labelId","internalValueRaw","setInternalValue","useControllableState","currentValue","digits","effectiveDisabled","effectiveError","effectiveTone","inputsRef","useRef","completedRef","valueRef","computeInitialIndex","firstEmpty","activeIndex","setActiveIndex","useState","useEffect","focusBox","useCallback","index","clamped","node","commit","next","useWebOtp","handleChange","event","raw","norm","current","newValue","before","after","focusTarget","handleKeyDown","handlePaste","stripped","handleFocus","baseId","agentHandle","useMemo","useAgentRegistration","jsx","digit","isTabbable"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useRef as f, useEffect as l } from "react";
|
|
2
|
+
function p({
|
|
3
|
+
onCode: i,
|
|
4
|
+
onError: o,
|
|
5
|
+
enabled: c = !0
|
|
6
|
+
}) {
|
|
7
|
+
const s = f(i), e = f(o);
|
|
8
|
+
s.current = i, e.current = o, l(() => {
|
|
9
|
+
if (!c || typeof window > "u" || !("OTPCredential" in window)) return;
|
|
10
|
+
const u = new AbortController(), a = {
|
|
11
|
+
otp: { transport: ["sms"] },
|
|
12
|
+
signal: u.signal
|
|
13
|
+
};
|
|
14
|
+
let r = !1;
|
|
15
|
+
return navigator.credentials.get(a).then((t) => {
|
|
16
|
+
if (r) return;
|
|
17
|
+
const n = t;
|
|
18
|
+
n != null && n.code && s.current(n.code);
|
|
19
|
+
}).catch((t) => {
|
|
20
|
+
var n;
|
|
21
|
+
r || t instanceof DOMException && t.name === "AbortError" || (n = e.current) == null || n.call(
|
|
22
|
+
e,
|
|
23
|
+
t instanceof Error ? t : new Error(String(t))
|
|
24
|
+
);
|
|
25
|
+
}), () => {
|
|
26
|
+
r = !0, u.abort();
|
|
27
|
+
};
|
|
28
|
+
}, [c]);
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
p as u
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=use-web-otp-D_utzp6S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-web-otp-D_utzp6S.js","sources":["../../src/hooks/use-web-otp.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\n/**\n * WebOTP API — `navigator.credentials.get({ otp: { transport: ['sms'] } })` —\n * implemented behind a React-friendly wrapper.\n *\n * Browser support is essentially **Chrome on Android** (plus Edge / Samsung\n * Internet, both Chromium-derived). Firefox declined to implement; Safari\n * never shipped it — Apple devices use the `autocomplete=\"one-time-code\"`\n * QuickType heuristic instead, which is independent of this hook.\n *\n * The hook is safe to call unconditionally — when the API is unavailable it\n * silently no-ops, when present it listens for one SMS-bound credential and\n * invokes `onCode` exactly once. Aborting is automatic on unmount, on\n * `enabled` flipping to `false`, or after one successful match. Repeated\n * `AbortError`s thrown by the spec on aborted promises are intentionally\n * swallowed — they're the spec-defined cleanup path, not a real error.\n *\n * Prerequisites the hook can't enforce (consumer / backend coordination):\n * - Page served over HTTPS.\n * - SMS body ends with `@<origin> #<code>` on its last line — without it\n * Chrome silently refuses to surface the message to the page.\n *\n * Reference implementation: HTP-3968 (booking-website App.js#_initWebOTP +\n * signature-website confirm_code.html.twig). This hook absorbs both.\n */\n\ninterface OtpCredential extends Credential {\n code: string;\n}\n\ninterface OtpCredentialRequestOptions extends CredentialRequestOptions {\n otp: { transport: 'sms'[] };\n}\n\nexport interface UseWebOtpOptions {\n /**\n * Invoked once with the full code when the browser surfaces a matching\n * SMS. Setting state from inside `onCode` is safe — the hook will not\n * fire again for the same lifecycle (it auto-aborts after success).\n */\n onCode: (code: string) => void;\n /**\n * Optional error sink. `AbortError` (the normal cleanup path) is NEVER\n * forwarded — only real failures (permission denied, malformed SMS, etc.).\n * When omitted the hook silently swallows everything so a consumer that\n * doesn't care about telemetry can call the hook bare.\n */\n onError?: (error: Error) => void;\n /**\n * Gate the listener. When `false` the hook is a no-op; flipping back to\n * `true` reinstalls the listener. Default `true`.\n */\n enabled?: boolean;\n}\n\nexport function useWebOtp({\n onCode,\n onError,\n enabled = true,\n}: UseWebOtpOptions): void {\n // Stable refs so the listener effect doesn't tear down + re-arm every\n // render when the consumer passes inline callbacks.\n const onCodeRef = useRef(onCode);\n const onErrorRef = useRef(onError);\n onCodeRef.current = onCode;\n onErrorRef.current = onError;\n\n useEffect(() => {\n if (!enabled) return;\n if (typeof window === 'undefined') return;\n if (!('OTPCredential' in window)) return;\n\n const controller = new AbortController();\n\n const options: OtpCredentialRequestOptions = {\n otp: { transport: ['sms'] },\n signal: controller.signal,\n };\n\n let cancelled = false;\n\n navigator.credentials\n .get(options as CredentialRequestOptions)\n .then((credential) => {\n if (cancelled) return;\n const otp = credential as OtpCredential | null;\n if (otp?.code) onCodeRef.current(otp.code);\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n // AbortError is the spec-defined cleanup path. Don't surface it.\n if (error instanceof DOMException && error.name === 'AbortError') {\n return;\n }\n onErrorRef.current?.(\n error instanceof Error ? error : new Error(String(error)),\n );\n });\n\n return () => {\n cancelled = true;\n controller.abort();\n };\n }, [enabled]);\n}\n"],"names":["useWebOtp","onCode","onError","enabled","onCodeRef","useRef","onErrorRef","useEffect","controller","options","cancelled","credential","otp","error","_a"],"mappings":";AAwDO,SAASA,EAAU;AAAA,EACxB,QAAAC;AAAA,EACA,SAAAC;AAAA,EACA,SAAAC,IAAU;AACZ,GAA2B;AAGzB,QAAMC,IAAYC,EAAOJ,CAAM,GACzBK,IAAaD,EAAOH,CAAO;AACjC,EAAAE,EAAU,UAAUH,GACpBK,EAAW,UAAUJ,GAErBK,EAAU,MAAM;AAGd,QAFI,CAACJ,KACD,OAAO,SAAW,OAClB,EAAE,mBAAmB,QAAS;AAElC,UAAMK,IAAa,IAAI,gBAAA,GAEjBC,IAAuC;AAAA,MAC3C,KAAK,EAAE,WAAW,CAAC,KAAK,EAAA;AAAA,MACxB,QAAQD,EAAW;AAAA,IAAA;AAGrB,QAAIE,IAAY;AAEhB,qBAAU,YACP,IAAID,CAAmC,EACvC,KAAK,CAACE,MAAe;AACpB,UAAID,EAAW;AACf,YAAME,IAAMD;AACZ,MAAIC,KAAA,QAAAA,EAAK,QAAMR,EAAU,QAAQQ,EAAI,IAAI;AAAA,IAC3C,CAAC,EACA,MAAM,CAACC,MAAmB;;AACzB,MAAIH,KAEAG,aAAiB,gBAAgBA,EAAM,SAAS,iBAGpDC,IAAAR,EAAW,YAAX,QAAAQ,EAAA;AAAA,QAAAR;AAAA,QACEO,aAAiB,QAAQA,IAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC;AAAA;AAAA,IAE5D,CAAC,GAEI,MAAM;AACX,MAAAH,IAAY,IACZF,EAAW,MAAA;AAAA,IACb;AAAA,EACF,GAAG,CAACL,CAAO,CAAC;AACd;"}
|
package/dist/agent-catalog.json
CHANGED
|
@@ -18,6 +18,22 @@ export interface OTPInputProps {
|
|
|
18
18
|
/** Stable id, used to address this instance from the agent runtime. */
|
|
19
19
|
id?: string;
|
|
20
20
|
className?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Opt into Chrome-Android's WebOTP API for one-tap SMS autofill. When
|
|
23
|
+
* the browser surfaces a matching SMS-bound credential, the kit spreads
|
|
24
|
+
* the code across every digit box and fires `onComplete` automatically.
|
|
25
|
+
*
|
|
26
|
+
* Falls back silently to manual entry on Firefox / desktop / iOS — those
|
|
27
|
+
* platforms either don't ship the API at all or rely on the
|
|
28
|
+
* `autocomplete="one-time-code"` QuickType heuristic the kit already
|
|
29
|
+
* applies to the first input.
|
|
30
|
+
*
|
|
31
|
+
* Prerequisites the prop can't enforce: page must be HTTPS, and the SMS
|
|
32
|
+
* body must end with `@<origin> #<code>` on its last line (Chrome's
|
|
33
|
+
* spec — without it the browser refuses to surface the message).
|
|
34
|
+
* Default `false` so the listener stays opt-in.
|
|
35
|
+
*/
|
|
36
|
+
webOtp?: boolean;
|
|
21
37
|
}
|
|
22
38
|
export declare const OTPInput: import("react").ForwardRefExoticComponent<OTPInputProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
23
39
|
//# sourceMappingURL=otp-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-input.d.ts","sourceRoot":"","sources":["../../../src/components/otp-input/otp-input.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"otp-input.d.ts","sourceRoot":"","sources":["../../../src/components/otp-input/otp-input.tsx"],"names":[],"mappings":"AAqBA,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,OAAO,CAAC;CAC3B;AA0BD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqBD,eAAO,MAAM,QAAQ,0GAoPpB,CAAC"}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -16,4 +16,5 @@ export { useFocusTrap, type UseFocusTrapOptions } from './use-focus-trap';
|
|
|
16
16
|
export { useDirection, useDocumentDirection, type Direction, } from './use-direction';
|
|
17
17
|
export { usePersistentState } from './use-persistent-state';
|
|
18
18
|
export { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
|
|
19
|
+
export { useWebOtp, type UseWebOtpOptions } from './use-web-otp';
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,yBAAyB,EACzB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,GACjC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,KAAK,UAAU,EACf,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,oBAAoB,EACpB,KAAK,iBAAiB,GACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,yBAAyB,EACzB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,GACjC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,KAAK,UAAU,EACf,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,oBAAoB,EACpB,KAAK,iBAAiB,GACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { u as v } from "../_chunks/use-prefers-reduced-motion-BMwIQRjB.js";
|
|
2
2
|
import { u as D } from "../_chunks/use-media-query-CcAx5SMM.js";
|
|
3
3
|
import { u as _ } from "../_chunks/use-scroll-to-first-error-4Za-u5Nw.js";
|
|
4
|
-
import { u as
|
|
5
|
-
import { u as
|
|
6
|
-
import { A as
|
|
4
|
+
import { u as O } from "../_chunks/use-count-up-BLLetaZ8.js";
|
|
5
|
+
import { u as k } from "../_chunks/use-locale-BkCIHujH.js";
|
|
6
|
+
import { A as K, T as R, a as H, r as M, t as Y, u as q } from "../_chunks/use-theme-C2dHKUAN.js";
|
|
7
7
|
import { u as G } from "../_chunks/use-controllable-state-BiY4xTzM.js";
|
|
8
8
|
import { u as U } from "../_chunks/use-copy-to-clipboard-Cyfc_dlv.js";
|
|
9
9
|
import { u as j } from "../_chunks/use-debounced-callback-BisrB-Fq.js";
|
|
10
10
|
import { useState as x, useRef as b, useEffect as E } from "react";
|
|
11
|
-
import { u as
|
|
12
|
-
import { u as
|
|
11
|
+
import { u as W, a as z } from "../_chunks/use-direction-Dp8h70PP.js";
|
|
12
|
+
import { u as N } from "../_chunks/use-persistent-state-i23OWy6G.js";
|
|
13
13
|
import { u as Z } from "../_chunks/use-isomorphic-layout-effect-BGfaCOP1.js";
|
|
14
|
+
import { u as ee } from "../_chunks/use-web-otp-D_utzp6S.js";
|
|
14
15
|
function S(e, o) {
|
|
15
|
-
const [t, c] = x(e),
|
|
16
|
+
const [t, c] = x(e), u = b(null);
|
|
16
17
|
return E(() => {
|
|
17
18
|
if (o <= 0) {
|
|
18
|
-
|
|
19
|
+
u.current !== null && (clearTimeout(u.current), u.current = null), c(e);
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
22
|
const s = setTimeout(() => {
|
|
22
|
-
c(e),
|
|
23
|
+
c(e), u.current = null;
|
|
23
24
|
}, o);
|
|
24
|
-
return
|
|
25
|
+
return u.current = s, () => clearTimeout(s);
|
|
25
26
|
}, [e, o]), t;
|
|
26
27
|
}
|
|
27
28
|
const T = [
|
|
@@ -49,57 +50,58 @@ function p(e) {
|
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
function C(e, o = {}) {
|
|
52
|
-
const { enabled: t = !0, autoFocus: c = !0, restoreFocus:
|
|
53
|
+
const { enabled: t = !0, autoFocus: c = !0, restoreFocus: u = !0 } = o, s = b(null);
|
|
53
54
|
E(() => {
|
|
54
55
|
if (!t) return;
|
|
55
56
|
const a = e.current;
|
|
56
57
|
if (!a) return;
|
|
57
58
|
if (s.current = typeof document < "u" && document.activeElement instanceof HTMLElement ? document.activeElement : null, c) {
|
|
58
|
-
const
|
|
59
|
-
|
|
59
|
+
const n = p(a)[0] ?? (a.tabIndex >= -1 ? a : null);
|
|
60
|
+
n == null || n.focus();
|
|
60
61
|
}
|
|
61
62
|
function f(r) {
|
|
62
63
|
if (r.key !== "Tab") return;
|
|
63
|
-
const
|
|
64
|
-
if (!
|
|
65
|
-
const i = p(
|
|
64
|
+
const n = e.current;
|
|
65
|
+
if (!n) return;
|
|
66
|
+
const i = p(n);
|
|
66
67
|
if (i.length === 0) {
|
|
67
68
|
r.preventDefault();
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
const d = i[0], m = i[i.length - 1], l = document.activeElement;
|
|
71
|
-
r.shiftKey ? (l === d || !
|
|
72
|
+
r.shiftKey ? (l === d || !n.contains(l)) && (r.preventDefault(), m.focus()) : (l === m || !n.contains(l)) && (r.preventDefault(), d.focus());
|
|
72
73
|
}
|
|
73
74
|
return a.addEventListener("keydown", f), () => {
|
|
74
|
-
if (a.removeEventListener("keydown", f),
|
|
75
|
+
if (a.removeEventListener("keydown", f), u && s.current) {
|
|
75
76
|
const r = s.current;
|
|
76
77
|
requestAnimationFrame(() => {
|
|
77
78
|
r.isConnected && r.focus();
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
};
|
|
81
|
-
}, [t, c,
|
|
82
|
+
}, [t, c, u, e]);
|
|
82
83
|
}
|
|
83
84
|
export {
|
|
84
|
-
|
|
85
|
+
K as ACCESSIBILITY_STORAGE_KEY,
|
|
85
86
|
R as THEME_CLASS,
|
|
86
87
|
H as THEME_STORAGE_KEY,
|
|
87
88
|
M as resolveTheme,
|
|
88
89
|
Y as themeClassList,
|
|
89
90
|
G as useControllableState,
|
|
90
91
|
U as useCopyToClipboard,
|
|
91
|
-
|
|
92
|
+
O as useCountUp,
|
|
92
93
|
j as useDebouncedCallback,
|
|
93
94
|
S as useDebouncedValue,
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
W as useDirection,
|
|
96
|
+
z as useDocumentDirection,
|
|
96
97
|
C as useFocusTrap,
|
|
97
98
|
Z as useIsomorphicLayoutEffect,
|
|
98
|
-
|
|
99
|
+
k as useLocale,
|
|
99
100
|
D as useMediaQuery,
|
|
100
|
-
|
|
101
|
+
N as usePersistentState,
|
|
101
102
|
v as usePrefersReducedMotion,
|
|
102
103
|
_ as useScrollToFirstError,
|
|
103
|
-
q as useTheme
|
|
104
|
+
q as useTheme,
|
|
105
|
+
ee as useWebOtp
|
|
104
106
|
};
|
|
105
107
|
//# sourceMappingURL=index.js.map
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/hooks/use-debounced-value.ts","../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\n/**\n * Returns a value that lags `input` by `delayMs`. Each new `input`\n * resets the timer; the returned value updates once `input` has been\n * stable for `delayMs` ms.\n *\n * Companion to `useDebouncedCallback` — use this when you want the\n * debounced *value* (to drive a derived effect, query, or render),\n * and use the callback hook when you want the debounced *action*.\n *\n * @example\n * const [query, setQuery] = useState('');\n * const debouncedQuery = useDebouncedValue(query, 250);\n * useEffect(() => { void search(debouncedQuery); }, [debouncedQuery]);\n */\nexport function useDebouncedValue<T>(input: T, delayMs: number): T {\n const [value, setValue] = useState<T>(input);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (delayMs <= 0) {\n // Flush any pending timer so a stale queued value can't land\n // after the synchronous update below.\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n setValue(input);\n return;\n }\n const timer = setTimeout(() => {\n setValue(input);\n timerRef.current = null;\n }, delayMs);\n timerRef.current = timer;\n return () => clearTimeout(timer);\n }, [input, delayMs]);\n\n return value;\n}\n","import { useEffect, useRef, type RefObject } from 'react';\n\n/* -------------------------------------------------------------------- */\n/* Focus trap */\n/* */\n/* Lightweight focus-trap hook for components that compose a Radix */\n/* primitive without the overlay primitive (Dialog, AlertDialog, Sheet) */\n/* — those handle focus trapping for free via `@radix-ui/react-dialog`. */\n/* */\n/* This hook is intentionally NOT a Radix `<FocusScope>` re-export — it */\n/* is a hook so it composes inside `forwardRef` components without */\n/* nesting another wrapper element. If you need the full Radix */\n/* contract (return-focus management, loop, asChild), use */\n/* `@radix-ui/react-focus-scope` directly. */\n/* -------------------------------------------------------------------- */\n\nexport interface UseFocusTrapOptions {\n /** When `false` the trap is inert. Defaults to `true`. */\n enabled?: boolean;\n /**\n * When `true`, focus initial autofocus into the first focusable\n * descendant on mount / enable. @default true\n */\n autoFocus?: boolean;\n /**\n * When `true`, restores focus to the previously-focused element on\n * unmount / disable. @default true\n */\n restoreFocus?: boolean;\n}\n\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n 'audio[controls]',\n 'video[controls]',\n].join(',');\n\nfunction getFocusable(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).filter((el) => {\n if (el.hidden) return false;\n // `inert` and `aria-hidden=\"true\"` block focus on the element AND\n // all its descendants — walk the chain to catch either ancestor.\n let node: HTMLElement | null = el;\n while (node) {\n if (node.hasAttribute('inert')) return false;\n if (node.getAttribute('aria-hidden') === 'true') return false;\n node = node.parentElement;\n }\n return true;\n });\n}\n\n/**\n * Constrain Tab navigation to descendants of `containerRef`. Wraps Tab\n * forward from the last focusable to the first, and Shift+Tab back from\n * the first to the last. Optionally autofocuses on enable and restores\n * focus on disable.\n *\n * Use this for inline modal patterns or focus zones that don't ship\n * with their own Radix overlay. Don't stack it inside Dialog / Sheet —\n * Radix already traps focus in those.\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * useFocusTrap(ref, { enabled: open });\n * return <div ref={ref}>…</div>;\n */\nexport function useFocusTrap<T extends HTMLElement>(\n containerRef: RefObject<T | null>,\n options: UseFocusTrapOptions = {},\n): void {\n const { enabled = true, autoFocus = true, restoreFocus = true } = options;\n const previouslyFocused = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n const container = containerRef.current;\n if (!container) return;\n\n previouslyFocused.current =\n typeof document !== 'undefined' &&\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n\n if (autoFocus) {\n const focusable = getFocusable(container);\n // Container itself is the fallback focus target — needs `tabIndex`\n // to receive focus, but `-1` is fine.\n const target =\n focusable[0] ?? (container.tabIndex >= -1 ? container : null);\n target?.focus();\n }\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab') return;\n const root = containerRef.current;\n if (!root) return;\n const focusable = getFocusable(root);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = document.activeElement;\n\n if (event.shiftKey) {\n if (active === first || !root.contains(active)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n if (active === last || !root.contains(active)) {\n event.preventDefault();\n first.focus();\n }\n }\n }\n\n container.addEventListener('keydown', onKeyDown);\n\n return () => {\n container.removeEventListener('keydown', onKeyDown);\n if (restoreFocus && previouslyFocused.current) {\n // rAF lets parent unmounts settle before refocus — a microtask\n // fires too early and can throw on a detached node.\n const target = previouslyFocused.current;\n requestAnimationFrame(() => {\n if (target.isConnected) target.focus();\n });\n }\n };\n }, [enabled, autoFocus, restoreFocus, containerRef]);\n}\n"],"names":["useDebouncedValue","input","delayMs","value","setValue","useState","timerRef","useRef","useEffect","timer","FOCUSABLE_SELECTOR","getFocusable","container","el","node","useFocusTrap","containerRef","options","enabled","autoFocus","restoreFocus","previouslyFocused","target","onKeyDown","event","root","focusable","first","last","active"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/hooks/use-debounced-value.ts","../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\n/**\n * Returns a value that lags `input` by `delayMs`. Each new `input`\n * resets the timer; the returned value updates once `input` has been\n * stable for `delayMs` ms.\n *\n * Companion to `useDebouncedCallback` — use this when you want the\n * debounced *value* (to drive a derived effect, query, or render),\n * and use the callback hook when you want the debounced *action*.\n *\n * @example\n * const [query, setQuery] = useState('');\n * const debouncedQuery = useDebouncedValue(query, 250);\n * useEffect(() => { void search(debouncedQuery); }, [debouncedQuery]);\n */\nexport function useDebouncedValue<T>(input: T, delayMs: number): T {\n const [value, setValue] = useState<T>(input);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (delayMs <= 0) {\n // Flush any pending timer so a stale queued value can't land\n // after the synchronous update below.\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n setValue(input);\n return;\n }\n const timer = setTimeout(() => {\n setValue(input);\n timerRef.current = null;\n }, delayMs);\n timerRef.current = timer;\n return () => clearTimeout(timer);\n }, [input, delayMs]);\n\n return value;\n}\n","import { useEffect, useRef, type RefObject } from 'react';\n\n/* -------------------------------------------------------------------- */\n/* Focus trap */\n/* */\n/* Lightweight focus-trap hook for components that compose a Radix */\n/* primitive without the overlay primitive (Dialog, AlertDialog, Sheet) */\n/* — those handle focus trapping for free via `@radix-ui/react-dialog`. */\n/* */\n/* This hook is intentionally NOT a Radix `<FocusScope>` re-export — it */\n/* is a hook so it composes inside `forwardRef` components without */\n/* nesting another wrapper element. If you need the full Radix */\n/* contract (return-focus management, loop, asChild), use */\n/* `@radix-ui/react-focus-scope` directly. */\n/* -------------------------------------------------------------------- */\n\nexport interface UseFocusTrapOptions {\n /** When `false` the trap is inert. Defaults to `true`. */\n enabled?: boolean;\n /**\n * When `true`, focus initial autofocus into the first focusable\n * descendant on mount / enable. @default true\n */\n autoFocus?: boolean;\n /**\n * When `true`, restores focus to the previously-focused element on\n * unmount / disable. @default true\n */\n restoreFocus?: boolean;\n}\n\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n 'audio[controls]',\n 'video[controls]',\n].join(',');\n\nfunction getFocusable(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).filter((el) => {\n if (el.hidden) return false;\n // `inert` and `aria-hidden=\"true\"` block focus on the element AND\n // all its descendants — walk the chain to catch either ancestor.\n let node: HTMLElement | null = el;\n while (node) {\n if (node.hasAttribute('inert')) return false;\n if (node.getAttribute('aria-hidden') === 'true') return false;\n node = node.parentElement;\n }\n return true;\n });\n}\n\n/**\n * Constrain Tab navigation to descendants of `containerRef`. Wraps Tab\n * forward from the last focusable to the first, and Shift+Tab back from\n * the first to the last. Optionally autofocuses on enable and restores\n * focus on disable.\n *\n * Use this for inline modal patterns or focus zones that don't ship\n * with their own Radix overlay. Don't stack it inside Dialog / Sheet —\n * Radix already traps focus in those.\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * useFocusTrap(ref, { enabled: open });\n * return <div ref={ref}>…</div>;\n */\nexport function useFocusTrap<T extends HTMLElement>(\n containerRef: RefObject<T | null>,\n options: UseFocusTrapOptions = {},\n): void {\n const { enabled = true, autoFocus = true, restoreFocus = true } = options;\n const previouslyFocused = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n const container = containerRef.current;\n if (!container) return;\n\n previouslyFocused.current =\n typeof document !== 'undefined' &&\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n\n if (autoFocus) {\n const focusable = getFocusable(container);\n // Container itself is the fallback focus target — needs `tabIndex`\n // to receive focus, but `-1` is fine.\n const target =\n focusable[0] ?? (container.tabIndex >= -1 ? container : null);\n target?.focus();\n }\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab') return;\n const root = containerRef.current;\n if (!root) return;\n const focusable = getFocusable(root);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = document.activeElement;\n\n if (event.shiftKey) {\n if (active === first || !root.contains(active)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n if (active === last || !root.contains(active)) {\n event.preventDefault();\n first.focus();\n }\n }\n }\n\n container.addEventListener('keydown', onKeyDown);\n\n return () => {\n container.removeEventListener('keydown', onKeyDown);\n if (restoreFocus && previouslyFocused.current) {\n // rAF lets parent unmounts settle before refocus — a microtask\n // fires too early and can throw on a detached node.\n const target = previouslyFocused.current;\n requestAnimationFrame(() => {\n if (target.isConnected) target.focus();\n });\n }\n };\n }, [enabled, autoFocus, restoreFocus, containerRef]);\n}\n"],"names":["useDebouncedValue","input","delayMs","value","setValue","useState","timerRef","useRef","useEffect","timer","FOCUSABLE_SELECTOR","getFocusable","container","el","node","useFocusTrap","containerRef","options","enabled","autoFocus","restoreFocus","previouslyFocused","target","onKeyDown","event","root","focusable","first","last","active"],"mappings":";;;;;;;;;;;;;;AAgBO,SAASA,EAAqBC,GAAUC,GAAoB;AACjE,QAAM,CAACC,GAAOC,CAAQ,IAAIC,EAAYJ,CAAK,GACrCK,IAAWC,EAA6C,IAAI;AAElE,SAAAC,EAAU,MAAM;AACd,QAAIN,KAAW,GAAG;AAGhB,MAAII,EAAS,YAAY,SACvB,aAAaA,EAAS,OAAO,GAC7BA,EAAS,UAAU,OAErBF,EAASH,CAAK;AACd;AAAA,IACF;AACA,UAAMQ,IAAQ,WAAW,MAAM;AAC7B,MAAAL,EAASH,CAAK,GACdK,EAAS,UAAU;AAAA,IACrB,GAAGJ,CAAO;AACV,WAAAI,EAAS,UAAUG,GACZ,MAAM,aAAaA,CAAK;AAAA,EACjC,GAAG,CAACR,GAAOC,CAAO,CAAC,GAEZC;AACT;ACTA,MAAMO,IAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAEV,SAASC,EAAaC,GAAuC;AAC3D,SAAO,MAAM;AAAA,IACXA,EAAU,iBAA8BF,CAAkB;AAAA,EAAA,EAC1D,OAAO,CAACG,MAAO;AACf,QAAIA,EAAG,OAAQ,QAAO;AAGtB,QAAIC,IAA2BD;AAC/B,WAAOC,KAAM;AAEX,UADIA,EAAK,aAAa,OAAO,KACzBA,EAAK,aAAa,aAAa,MAAM,OAAQ,QAAO;AACxD,MAAAA,IAAOA,EAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAiBO,SAASC,EACdC,GACAC,IAA+B,IACzB;AACN,QAAM,EAAE,SAAAC,IAAU,IAAM,WAAAC,IAAY,IAAM,cAAAC,IAAe,OAASH,GAC5DI,IAAoBd,EAA2B,IAAI;AAEzD,EAAAC,EAAU,MAAM;AACd,QAAI,CAACU,EAAS;AACd,UAAMN,IAAYI,EAAa;AAC/B,QAAI,CAACJ,EAAW;AAQhB,QANAS,EAAkB,UAChB,OAAO,WAAa,OACpB,SAAS,yBAAyB,cAC9B,SAAS,gBACT,MAEFF,GAAW;AAIb,YAAMG,IAHYX,EAAaC,CAAS,EAI5B,CAAC,MAAMA,EAAU,YAAY,KAAKA,IAAY;AAC1D,MAAAU,KAAA,QAAAA,EAAQ;AAAA,IACV;AAEA,aAASC,EAAUC,GAAsB;AACvC,UAAIA,EAAM,QAAQ,MAAO;AACzB,YAAMC,IAAOT,EAAa;AAC1B,UAAI,CAACS,EAAM;AACX,YAAMC,IAAYf,EAAac,CAAI;AACnC,UAAIC,EAAU,WAAW,GAAG;AAC1B,QAAAF,EAAM,eAAA;AACN;AAAA,MACF;AACA,YAAMG,IAAQD,EAAU,CAAC,GACnBE,IAAOF,EAAUA,EAAU,SAAS,CAAC,GACrCG,IAAS,SAAS;AAExB,MAAIL,EAAM,YACJK,MAAWF,KAAS,CAACF,EAAK,SAASI,CAAM,OAC3CL,EAAM,eAAA,GACNI,EAAK,MAAA,MAGHC,MAAWD,KAAQ,CAACH,EAAK,SAASI,CAAM,OAC1CL,EAAM,eAAA,GACNG,EAAM,MAAA;AAAA,IAGZ;AAEA,WAAAf,EAAU,iBAAiB,WAAWW,CAAS,GAExC,MAAM;AAEX,UADAX,EAAU,oBAAoB,WAAWW,CAAS,GAC9CH,KAAgBC,EAAkB,SAAS;AAG7C,cAAMC,IAASD,EAAkB;AACjC,8BAAsB,MAAM;AAC1B,UAAIC,EAAO,eAAaA,EAAO,MAAA;AAAA,QACjC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,GAAG,CAACJ,GAASC,GAAWC,GAAcJ,CAAY,CAAC;AACrD;"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface UseWebOtpOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Invoked once with the full code when the browser surfaces a matching
|
|
4
|
+
* SMS. Setting state from inside `onCode` is safe — the hook will not
|
|
5
|
+
* fire again for the same lifecycle (it auto-aborts after success).
|
|
6
|
+
*/
|
|
7
|
+
onCode: (code: string) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Optional error sink. `AbortError` (the normal cleanup path) is NEVER
|
|
10
|
+
* forwarded — only real failures (permission denied, malformed SMS, etc.).
|
|
11
|
+
* When omitted the hook silently swallows everything so a consumer that
|
|
12
|
+
* doesn't care about telemetry can call the hook bare.
|
|
13
|
+
*/
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Gate the listener. When `false` the hook is a no-op; flipping back to
|
|
17
|
+
* `true` reinstalls the listener. Default `true`.
|
|
18
|
+
*/
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function useWebOtp({ onCode, onError, enabled, }: UseWebOtpOptions): void;
|
|
22
|
+
//# sourceMappingURL=use-web-otp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-web-otp.d.ts","sourceRoot":"","sources":["../../src/hooks/use-web-otp.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,IAAI,CA6CzB"}
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import { F as ba } from "./_chunks/form-field-BOm9hK35.js";
|
|
|
27
27
|
import { F as Ra, u as Ia } from "./_chunks/form-field-context-B3APVHKx.js";
|
|
28
28
|
import { M as Fa, m as Ea, a as La } from "./_chunks/multi-select-DOLO3K_z.js";
|
|
29
29
|
import { N as ka, u as Oa } from "./_chunks/number-input-Dj5L3pXK.js";
|
|
30
|
-
import { O as ya } from "./_chunks/otp-input-
|
|
30
|
+
import { O as ya } from "./_chunks/otp-input-CDTWT5EK.js";
|
|
31
31
|
import { P as Ma, u as Da } from "./_chunks/use-password-requirements-DsgduV1x.js";
|
|
32
32
|
import { P as Va, c as va, p as Ha, a as Wa } from "./_chunks/phone-input-DfZbPPvh.js";
|
|
33
33
|
import { R as Ua, r as Ka } from "./_chunks/recaptcha-widget-CFYyLSEX.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfadocs/ui-kit-debug",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AlfaDocs shared design system — tokens, components, patterns, and translations for platform, booking, and alfascribe. (debug build — identical runtime to @alfadocs/ui-kit, ships source maps for symbolication).",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"otp-input-BEg_sn8A.js","sources":["../../src/components/otp-input/otp-input.agent.ts","../../src/components/otp-input/otp-input.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — OTPInput. */\n/* */\n/* See `src/docs/26-agent-readiness.mdx` for the contract. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { OTPInputHandle } from './otp-input';\n\nexport const otpInputAgent: AgentAdapter<OTPInputHandle> = {\n id: 'otp-input',\n capabilities: ['edit_inline', 'submit'],\n state: {\n value: {\n type: 'string',\n descriptionKey: 'ui.agent.otpInput.state.value',\n description: 'Current digits entered, packed into a single string.',\n read: (handle) => handle.getValue(),\n },\n isComplete: {\n type: 'boolean',\n descriptionKey: 'ui.agent.otpInput.state.isComplete',\n description: 'Whether all required digits have been entered.',\n read: (handle) => handle.isComplete(),\n },\n },\n actions: {\n set_value: {\n safety: 'write',\n argsType: '{ value: string }',\n descriptionKey: 'ui.agent.otpInput.actions.setValue',\n description: 'Replace the entered digits.',\n invoke: (handle, args: { value: string }) => {\n handle.setValue(args.value);\n },\n },\n clear: {\n safety: 'destructive',\n descriptionKey: 'ui.agent.otpInput.actions.clear',\n description: 'Empty all digit cells. Loses any typed value.',\n invoke: (handle) => {\n handle.clear();\n },\n },\n focus: {\n safety: 'read',\n descriptionKey: 'ui.agent.otpInput.actions.focus',\n description: 'Move keyboard focus to the active digit cell.',\n invoke: (handle) => {\n handle.focus();\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'otp-input',\n description:\n 'Marks the OTPInput wrapper. Completion is observable via state.isComplete; the host onComplete callback fires once when all digits are filled.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ChangeEvent,\n type ClipboardEvent,\n type FocusEvent,\n type KeyboardEvent,\n} from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useFormField } from '../form-field/form-field-context';\nimport { useAgentRegistration } from '../../agent';\nimport { otpInputAgent } from './otp-input.agent';\n\n/** Agent-readiness curated handle for OTPInput. */\nexport interface OTPInputHandle {\n getValue: () => string;\n setValue: (value: string) => void;\n clear: () => void;\n focus: () => void;\n isComplete: () => boolean;\n}\n\nconst wrapperVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]');\n\nconst boxVariants = cva(\n [\n 'ds:w-[var(--min-target-size)] ds:h-[var(--min-target-size)]',\n 'ds:rounded-[var(--radius-sm)]',\n 'ds:border ds:bg-input ds:text-foreground ds:text-center ds:font-medium ds:text-[length:var(--font-size-lg)]',\n 'ds:shadow-[var(--shadow-input)]',\n 'ds:transition-[colors,box-shadow] ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-ring ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:disabled:cursor-not-allowed ds:disabled:opacity-50',\n ].join(' '),\n {\n variants: {\n tone: {\n default: 'ds:border-border',\n error: 'ds:border-destructive',\n },\n },\n defaultVariants: { tone: 'default' },\n },\n);\n\nexport interface OTPInputProps {\n length?: 4 | 6 | 8;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n onComplete?: (code: string) => void;\n disabled?: boolean;\n error?: boolean;\n label?: string;\n /** Stable id, used to address this instance from the agent runtime. */\n id?: string;\n className?: string;\n}\n\nfunction normaliseDigits(str: string): string {\n return str\n .replace(/[\\u0660-\\u0669]/g, (c) => String(c.charCodeAt(0) - 0x0660))\n .replace(/[\\u06F0-\\u06F9]/g, (c) => String(c.charCodeAt(0) - 0x06f0));\n}\n\nfunction packValue(value: string, length: number): string {\n return normaliseDigits(value).replace(/\\D/g, '').slice(0, length);\n}\n\nfunction valueToArray(value: string, length: number): string[] {\n const packed = packValue(value, length);\n const arr = new Array<string>(length).fill('');\n for (let i = 0; i < packed.length; i += 1) {\n arr[i] = packed[i];\n }\n return arr;\n}\n\nexport const OTPInput = forwardRef<HTMLDivElement, OTPInputProps>(\n (\n {\n length = 6,\n value,\n defaultValue,\n onChange,\n onComplete,\n disabled,\n error,\n label,\n id,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n const ctx = useFormField();\n const generatedLabelId = useId();\n const labelId = label ? generatedLabelId : undefined;\n\n const [internalValueRaw, setInternalValue] = useControllableState<string>({\n value,\n defaultValue: packValue(defaultValue ?? '', length),\n });\n const currentValue = packValue(internalValueRaw ?? '', length);\n const digits = valueToArray(currentValue, length);\n\n const effectiveDisabled = ctx.disabled || Boolean(disabled);\n const effectiveError = ctx.invalid || Boolean(error);\n const effectiveTone = effectiveError ? 'error' : 'default';\n\n const inputsRef = useRef<Array<HTMLInputElement | null>>([]);\n const completedRef = useRef(currentValue.length === length);\n const valueRef = useRef(currentValue);\n valueRef.current = currentValue;\n\n const computeInitialIndex = () => {\n const firstEmpty = currentValue.length;\n if (firstEmpty >= length) return length - 1;\n return firstEmpty;\n };\n const [activeIndex, setActiveIndex] = useState<number>(computeInitialIndex);\n\n useEffect(() => {\n if (activeIndex >= length) setActiveIndex(length - 1);\n }, [activeIndex, length]);\n\n const focusBox = useCallback(\n (index: number) => {\n const clamped = Math.max(0, Math.min(length - 1, index));\n setActiveIndex(clamped);\n const node = inputsRef.current[clamped];\n if (node) {\n node.focus();\n node.select();\n }\n },\n [length],\n );\n\n const commit = useCallback(\n (next: string) => {\n const packed = packValue(next, length);\n setInternalValue(packed);\n valueRef.current = packed;\n onChange?.(packed);\n if (packed.length === length) {\n if (!completedRef.current) {\n completedRef.current = true;\n onComplete?.(packed);\n }\n } else {\n completedRef.current = false;\n }\n },\n [setInternalValue, length, onChange, onComplete],\n );\n\n const handleChange =\n (index: number) => (event: ChangeEvent<HTMLInputElement>) => {\n const raw = event.target.value;\n const norm = normaliseDigits(raw).replace(/\\D/g, '');\n const current = valueRef.current;\n\n if (norm.length === 0) {\n if (raw === '' && index < current.length) {\n const newValue = current.slice(0, index) + current.slice(index + 1);\n commit(newValue);\n }\n return;\n }\n\n const before = current.slice(0, index);\n const after = current.slice(index + norm.length);\n const newValue = (before + norm + after).slice(0, length);\n commit(newValue);\n\n const focusTarget = Math.min(index + norm.length, length - 1);\n focusBox(focusTarget);\n };\n\n const handleKeyDown =\n (index: number) => (event: KeyboardEvent<HTMLInputElement>) => {\n const current = valueRef.current;\n if (event.key === 'Backspace') {\n if (current[index]) {\n event.preventDefault();\n const newValue = current.slice(0, index) + current.slice(index + 1);\n commit(newValue);\n return;\n }\n if (index > 0) {\n event.preventDefault();\n const newValue = current.slice(0, index - 1) + current.slice(index);\n commit(newValue);\n focusBox(index - 1);\n }\n return;\n }\n if (event.key === 'ArrowLeft') {\n event.preventDefault();\n if (index > 0) focusBox(index - 1);\n return;\n }\n if (event.key === 'ArrowRight') {\n event.preventDefault();\n if (index < length - 1) focusBox(index + 1);\n return;\n }\n if (event.key === 'Home') {\n event.preventDefault();\n focusBox(0);\n return;\n }\n if (event.key === 'End') {\n event.preventDefault();\n focusBox(length - 1);\n }\n };\n\n const handlePaste =\n (index: number) => (event: ClipboardEvent<HTMLInputElement>) => {\n const text = event.clipboardData.getData('text');\n const stripped = text.replace(/\\s+/g, '');\n const norm = normaliseDigits(stripped);\n if (!/^[0-9]+$/.test(norm)) return;\n event.preventDefault();\n const current = valueRef.current;\n const before = current.slice(0, index);\n const after = current.slice(index + norm.length);\n const newValue = (before + norm + after).slice(0, length);\n commit(newValue);\n const focusTarget = Math.min(index + norm.length, length - 1);\n focusBox(focusTarget);\n };\n\n const handleFocus =\n (index: number) => (event: FocusEvent<HTMLInputElement>) => {\n setActiveIndex(index);\n event.currentTarget.select();\n };\n\n const baseId = ctx.id;\n\n const agentHandle = useMemo<OTPInputHandle>(\n () => ({\n getValue: () => valueRef.current,\n setValue: (next) => commit(next),\n clear: () => commit(''),\n focus: () => focusBox(activeIndex),\n isComplete: () => valueRef.current.length === length,\n }),\n [commit, focusBox, activeIndex, length],\n );\n useAgentRegistration(otpInputAgent, agentHandle, id);\n\n return (\n <div className={wrapperVariants({ className })}>\n {label ? (\n <label\n id={labelId}\n htmlFor={`${baseId}-0`}\n className=\"type-label ds:text-foreground\"\n >\n {label}\n </label>\n ) : null}\n {/* eslint-disable-next-line jsx-a11y/role-supports-aria-props -- aria-invalid is a global ARIA state; conveys group-level validation */}\n <div\n ref={ref}\n role=\"group\"\n dir=\"ltr\"\n aria-labelledby={labelId}\n aria-invalid={effectiveError || undefined}\n aria-describedby={ctx.describedBy || undefined}\n aria-disabled={effectiveDisabled || undefined}\n data-component=\"otp-input\"\n data-component-id={id}\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-sm)]\"\n >\n {digits.map((digit, index) => {\n const isTabbable = !effectiveDisabled && index === activeIndex;\n return (\n <input\n key={index}\n ref={(node) => {\n inputsRef.current[index] = node;\n }}\n id={`${baseId}-${index}`}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete={index === 0 ? 'one-time-code' : 'off'}\n maxLength={1}\n value={digit}\n disabled={effectiveDisabled}\n tabIndex={isTabbable ? 0 : -1}\n aria-label={t('inputs.otp.digitLabel', {\n current: index + 1,\n total: length,\n })}\n onChange={handleChange(index)}\n onKeyDown={handleKeyDown(index)}\n onPaste={handlePaste(index)}\n onFocus={handleFocus(index)}\n className={boxVariants({ tone: effectiveTone })}\n />\n );\n })}\n </div>\n </div>\n );\n },\n);\n\nOTPInput.displayName = 'OTPInput';\n"],"names":["otpInputAgent","handle","args","wrapperVariants","cva","boxVariants","normaliseDigits","str","c","packValue","value","length","valueToArray","packed","arr","i","OTPInput","forwardRef","defaultValue","onChange","onComplete","disabled","error","label","id","className","ref","t","useTranslation","ctx","useFormField","generatedLabelId","useId","labelId","internalValueRaw","setInternalValue","useControllableState","currentValue","digits","effectiveDisabled","effectiveError","effectiveTone","inputsRef","useRef","completedRef","valueRef","computeInitialIndex","firstEmpty","activeIndex","setActiveIndex","useState","useEffect","focusBox","useCallback","index","clamped","node","commit","next","handleChange","event","raw","norm","current","newValue","before","after","focusTarget","handleKeyDown","handlePaste","stripped","handleFocus","baseId","agentHandle","useMemo","useAgentRegistration","jsx","digit","isTabbable"],"mappings":"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"}
|