@academix-admin/pin-input 0.1.0 → 0.2.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.
package/dist/index.cjs CHANGED
@@ -11,6 +11,7 @@ function PinInput({
11
11
  onChange,
12
12
  disabled = false,
13
13
  error = false,
14
+ loading = false,
14
15
  mask = false,
15
16
  revealed = false,
16
17
  autoFocus = true,
@@ -19,6 +20,7 @@ function PinInput({
19
20
  inputProps
20
21
  }) {
21
22
  const inputs = Array(length).fill(0);
23
+ const isDisabled = disabled || loading;
22
24
  const getFirstInvalidIndex = (val) => {
23
25
  for (let i = 0; i < length; i++) {
24
26
  if (!val[i]) return i;
@@ -82,7 +84,7 @@ function PinInput({
82
84
  if (mask && !revealed) return value[index] ? "*" : "";
83
85
  return value[index] || "";
84
86
  };
85
- const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}`.trim() || void 0;
87
+ const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ""}`.trim() || void 0;
86
88
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: containerClass, children: inputs.map((_, index) => /* @__PURE__ */ react.createElement(
87
89
  "input",
88
90
  {
@@ -96,7 +98,7 @@ function PinInput({
96
98
  onChange: (e) => handleChange(e, index),
97
99
  onKeyDown: (e) => handleKeyDown(e, index),
98
100
  onPaste: handlePaste,
99
- disabled,
101
+ disabled: isDisabled,
100
102
  className: classNames.input,
101
103
  autoFocus: autoFocus && index === 0 && !error,
102
104
  onFocus: (e) => handleFocus(e, index)
@@ -110,6 +112,8 @@ function Keypad({
110
112
  length = 6,
111
113
  disabled = false,
112
114
  error,
115
+ loading = false,
116
+ loadingIndicator,
113
117
  showMaskToggle = false,
114
118
  revealed = false,
115
119
  onToggleReveal,
@@ -117,6 +121,7 @@ function Keypad({
117
121
  classNames = {},
118
122
  className
119
123
  }) {
124
+ const busy = disabled || loading;
120
125
  const handleDigitInput = (digit) => {
121
126
  if (value.length < length) {
122
127
  onChange(value + digit);
@@ -132,41 +137,45 @@ function Keypad({
132
137
  }
133
138
  };
134
139
  const btn = classNames.button ?? "";
135
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: className ?? classNames.keypad, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.grid, children: [
136
- DIGITS.map((digit) => /* @__PURE__ */ jsxRuntime.jsx(
137
- "button",
138
- {
139
- type: "button",
140
- onClick: () => handleDigitInput(digit),
141
- disabled,
142
- className: btn || void 0,
143
- children: digit
144
- },
145
- digit
146
- )),
147
- /* @__PURE__ */ jsxRuntime.jsx(
148
- "button",
149
- {
150
- type: "button",
151
- onClick: handleBackspace,
152
- disabled,
153
- className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
154
- children: backspaceLabel
155
- }
156
- ),
157
- showMaskToggle && /* @__PURE__ */ jsxRuntime.jsx(
158
- "button",
159
- {
160
- type: "button",
161
- onClick: () => onToggleReveal?.(!revealed),
162
- disabled,
163
- className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
164
- title: revealed ? "Hide" : "Show",
165
- "aria-label": revealed ? "Hide" : "Show",
166
- children: revealed ? /* @__PURE__ */ jsxRuntime.jsx(EyeOpen, {}) : /* @__PURE__ */ jsxRuntime.jsx(EyeClosed, {})
167
- }
168
- )
169
- ] }) });
140
+ const wrapperClass = `${className ?? classNames.keypad ?? ""}${loading && classNames.loading ? ` ${classNames.loading}` : ""}`.trim() || void 0;
141
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, "aria-busy": loading || void 0, children: [
142
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.grid, children: [
143
+ DIGITS.map((digit) => /* @__PURE__ */ jsxRuntime.jsx(
144
+ "button",
145
+ {
146
+ type: "button",
147
+ onClick: () => handleDigitInput(digit),
148
+ disabled: busy,
149
+ className: btn || void 0,
150
+ children: digit
151
+ },
152
+ digit
153
+ )),
154
+ /* @__PURE__ */ jsxRuntime.jsx(
155
+ "button",
156
+ {
157
+ type: "button",
158
+ onClick: handleBackspace,
159
+ disabled: busy,
160
+ className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
161
+ children: backspaceLabel
162
+ }
163
+ ),
164
+ showMaskToggle && /* @__PURE__ */ jsxRuntime.jsx(
165
+ "button",
166
+ {
167
+ type: "button",
168
+ onClick: () => onToggleReveal?.(!revealed),
169
+ disabled: busy,
170
+ className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
171
+ title: revealed ? "Hide" : "Show",
172
+ "aria-label": revealed ? "Hide" : "Show",
173
+ children: revealed ? /* @__PURE__ */ jsxRuntime.jsx(EyeOpen, {}) : /* @__PURE__ */ jsxRuntime.jsx(EyeClosed, {})
174
+ }
175
+ )
176
+ ] }),
177
+ loading && loadingIndicator
178
+ ] });
170
179
  }
171
180
  function EyeOpen() {
172
181
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/PinInput.tsx","../src/Keypad.tsx"],"names":["els","jsx","createElement","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client';\n\nimport React from 'react';\n\n/**\n * PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible\n * markup and you style every slot via `classNames` (bring your own classes). React-only,\n * no app/theme coupling, so it is safe for any consumer.\n *\n * The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the\n * device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits\n * render as `*` unless `revealed`); leave it off for a visible OTP.\n */\n\nexport interface PinInputClassNames {\n /** the boxes wrapper. */\n container?: string;\n /** appended to the container while `error` is true. */\n containerError?: string;\n /** each single-character box. */\n input?: string;\n}\n\nexport interface PinInputProps {\n length?: number;\n value: string;\n onChange: (value: string) => void;\n disabled?: boolean;\n error?: boolean;\n /** render digits masked (as `*`) unless `revealed`. */\n mask?: boolean;\n /** when `mask`, show the real digits. */\n revealed?: boolean;\n /** autofocus the first box on mount (when not in error). Default true. */\n autoFocus?: boolean;\n classNames?: PinInputClassNames;\n /** shortcut for `classNames.container`. */\n className?: string;\n /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;\n}\n\nexport function PinInput({\n length = 6,\n value,\n onChange,\n disabled = false,\n error = false,\n mask = false,\n revealed = false,\n autoFocus = true,\n classNames = {},\n className,\n inputProps,\n}: PinInputProps) {\n const inputs = Array(length).fill(0);\n\n const getFirstInvalidIndex = (val: string) => {\n for (let i = 0; i < length; i++) {\n if (!val[i]) return i;\n }\n return length - 1; // all full → last field\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {\n const digit = e.target.value.replace(/\\D/g, '').slice(-1);\n\n const otpArray = value.split('');\n otpArray[index] = digit || '';\n const nextValue = otpArray.join('');\n\n onChange(nextValue);\n\n // compute allowed index from the new value\n const allowedIndex = getFirstInvalidIndex(nextValue);\n\n // Auto-advance only if digit is valid and index < allowed position\n if (digit && index < length - 1 && index < allowedIndex) {\n const next = e.target.nextElementSibling as HTMLInputElement | null;\n next?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {\n if (e.key !== 'Backspace') return;\n\n // Error mode: clear everything\n if (error && value.length === length) {\n onChange('');\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n (els[0] as HTMLInputElement).focus();\n return;\n }\n\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Build a snapshot of the current actual values from DOM, not from state.\n const domValues = Array.from(els).map((input) => (input as HTMLInputElement).value);\n\n // If current field already empty → move back\n if (!domValues[index] && index > 0) {\n const prev = els[index - 1] as HTMLInputElement;\n prev.focus();\n }\n\n // Clear current char (and everything after) and update parent state\n const otpArray = value.split('');\n for (let i = index; i < otpArray.length; i++) {\n otpArray[i] = '';\n }\n onChange(otpArray.join(''));\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasted) {\n onChange(pasted.slice(0, length));\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>, index: number) => {\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Determine focusable index based on ACTUAL input values\n let allowedIndex = 0;\n for (let i = 0; i < length; i++) {\n if ((els[i] as HTMLInputElement).value === '') {\n allowedIndex = i;\n break;\n }\n if (i === length - 1) allowedIndex = length - 1;\n }\n\n // Prevent skipping ahead\n if (index > allowedIndex) {\n (els[allowedIndex] as HTMLInputElement).focus();\n }\n };\n\n const display = (index: number) => {\n if (mask && !revealed) return value[index] ? '*' : '';\n return value[index] || '';\n };\n\n const containerClass = `${className ?? classNames.container ?? ''}${\n error && classNames.containerError ? ` ${classNames.containerError}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={containerClass}>\n {inputs.map((_, index) => (\n <input\n {...inputProps}\n key={index}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n value={display(index)}\n onChange={(e) => handleChange(e, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onPaste={handlePaste}\n disabled={disabled}\n className={classNames.input}\n autoFocus={autoFocus && index === 0 && !error}\n onFocus={(e) => handleFocus(e, index)}\n />\n ))}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)\n * that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.\n *\n * Behavior preserved from academix: append up to `length` digits; while `error` and the\n * value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`\n * for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.\n */\n\nexport interface KeypadClassNames {\n /** outer wrapper. */\n keypad?: string;\n /** the button grid. */\n grid?: string;\n /** every key (digits, backspace, toggle). */\n button?: string;\n /** appended to the backspace key. */\n backspace?: string;\n /** appended to the mask-toggle key. */\n toggle?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** render the reveal/mask (eye) toggle key. */\n showMaskToggle?: boolean;\n /** current reveal state (for the toggle icon/label). */\n revealed?: boolean;\n onToggleReveal?: (revealed: boolean) => void;\n /** backspace key content. Default `✕`. */\n backspaceLabel?: React.ReactNode;\n classNames?: KeypadClassNames;\n /** shortcut for `classNames.keypad`. */\n className?: string;\n}\n\nconst DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\nexport function Keypad({\n value,\n onChange,\n length = 6,\n disabled = false,\n error,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const handleDigitInput = (digit: number) => {\n if (value.length < length) {\n onChange(value + digit);\n } else if (!!error && value.length === length) {\n onChange(`${digit}`);\n }\n };\n\n const handleBackspace = () => {\n if (value.length > 0 && !error) {\n onChange(value.slice(0, -1));\n } else if (!!error && value.length === length) {\n onChange('');\n }\n };\n\n const btn = classNames.button ?? '';\n\n return (\n <div className={className ?? classNames.keypad}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={disabled}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={disabled}\n className={`${btn} ${classNames.backspace ?? ''}`.trim() || undefined}\n >\n {backspaceLabel}\n </button>\n {showMaskToggle && (\n <button\n type=\"button\"\n onClick={() => onToggleReveal?.(!revealed)}\n disabled={disabled}\n className={`${btn} ${classNames.toggle ?? ''}`.trim() || undefined}\n title={revealed ? 'Hide' : 'Show'}\n aria-label={revealed ? 'Hide' : 'Show'}\n >\n {revealed ? <EyeOpen /> : <EyeClosed />}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction EyeOpen() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" strokeWidth=\"2\" />\n </svg>\n );\n}\nfunction EyeClosed() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/PinInput.tsx","../src/Keypad.tsx"],"names":["els","jsx","createElement","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client';\n\nimport React from 'react';\n\n/**\n * PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible\n * markup and you style every slot via `classNames` (bring your own classes). React-only,\n * no app/theme coupling, so it is safe for any consumer.\n *\n * The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the\n * device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits\n * render as `*` unless `revealed`); leave it off for a visible OTP.\n */\n\nexport interface PinInputClassNames {\n /** the boxes wrapper. */\n container?: string;\n /** appended to the container while `error` is true. */\n containerError?: string;\n /** appended to the container while `loading` is true. */\n containerLoading?: string;\n /** each single-character box. */\n input?: string;\n}\n\nexport interface PinInputProps {\n length?: number;\n value: string;\n onChange: (value: string) => void;\n disabled?: boolean;\n error?: boolean;\n /** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */\n loading?: boolean;\n /** render digits masked (as `*`) unless `revealed`. */\n mask?: boolean;\n /** when `mask`, show the real digits. */\n revealed?: boolean;\n /** autofocus the first box on mount (when not in error). Default true. */\n autoFocus?: boolean;\n classNames?: PinInputClassNames;\n /** shortcut for `classNames.container`. */\n className?: string;\n /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;\n}\n\nexport function PinInput({\n length = 6,\n value,\n onChange,\n disabled = false,\n error = false,\n loading = false,\n mask = false,\n revealed = false,\n autoFocus = true,\n classNames = {},\n className,\n inputProps,\n}: PinInputProps) {\n const inputs = Array(length).fill(0);\n const isDisabled = disabled || loading;\n\n const getFirstInvalidIndex = (val: string) => {\n for (let i = 0; i < length; i++) {\n if (!val[i]) return i;\n }\n return length - 1; // all full → last field\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {\n const digit = e.target.value.replace(/\\D/g, '').slice(-1);\n\n const otpArray = value.split('');\n otpArray[index] = digit || '';\n const nextValue = otpArray.join('');\n\n onChange(nextValue);\n\n // compute allowed index from the new value\n const allowedIndex = getFirstInvalidIndex(nextValue);\n\n // Auto-advance only if digit is valid and index < allowed position\n if (digit && index < length - 1 && index < allowedIndex) {\n const next = e.target.nextElementSibling as HTMLInputElement | null;\n next?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {\n if (e.key !== 'Backspace') return;\n\n // Error mode: clear everything\n if (error && value.length === length) {\n onChange('');\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n (els[0] as HTMLInputElement).focus();\n return;\n }\n\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Build a snapshot of the current actual values from DOM, not from state.\n const domValues = Array.from(els).map((input) => (input as HTMLInputElement).value);\n\n // If current field already empty → move back\n if (!domValues[index] && index > 0) {\n const prev = els[index - 1] as HTMLInputElement;\n prev.focus();\n }\n\n // Clear current char (and everything after) and update parent state\n const otpArray = value.split('');\n for (let i = index; i < otpArray.length; i++) {\n otpArray[i] = '';\n }\n onChange(otpArray.join(''));\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasted) {\n onChange(pasted.slice(0, length));\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>, index: number) => {\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Determine focusable index based on ACTUAL input values\n let allowedIndex = 0;\n for (let i = 0; i < length; i++) {\n if ((els[i] as HTMLInputElement).value === '') {\n allowedIndex = i;\n break;\n }\n if (i === length - 1) allowedIndex = length - 1;\n }\n\n // Prevent skipping ahead\n if (index > allowedIndex) {\n (els[allowedIndex] as HTMLInputElement).focus();\n }\n };\n\n const display = (index: number) => {\n if (mask && !revealed) return value[index] ? '*' : '';\n return value[index] || '';\n };\n\n const containerClass = `${className ?? classNames.container ?? ''}${\n error && classNames.containerError ? ` ${classNames.containerError}` : ''\n }${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ''}`.trim() || undefined;\n\n return (\n <div className={containerClass}>\n {inputs.map((_, index) => (\n <input\n {...inputProps}\n key={index}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n value={display(index)}\n onChange={(e) => handleChange(e, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onPaste={handlePaste}\n disabled={isDisabled}\n className={classNames.input}\n autoFocus={autoFocus && index === 0 && !error}\n onFocus={(e) => handleFocus(e, index)}\n />\n ))}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)\n * that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.\n *\n * Behavior preserved from academix: append up to `length` digits; while `error` and the\n * value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`\n * for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.\n */\n\nexport interface KeypadClassNames {\n /** outer wrapper. */\n keypad?: string;\n /** the button grid. */\n grid?: string;\n /** every key (digits, backspace, toggle). */\n button?: string;\n /** appended to the backspace key. */\n backspace?: string;\n /** appended to the mask-toggle key. */\n toggle?: string;\n /** appended to the outer wrapper while `loading` (style your overlay/dimming here). */\n loading?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** disables every key (a verify/submit in flight) and applies `classNames.loading`. */\n loading?: boolean;\n /** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */\n loadingIndicator?: React.ReactNode;\n /** render the reveal/mask (eye) toggle key. */\n showMaskToggle?: boolean;\n /** current reveal state (for the toggle icon/label). */\n revealed?: boolean;\n onToggleReveal?: (revealed: boolean) => void;\n /** backspace key content. Default `✕`. */\n backspaceLabel?: React.ReactNode;\n classNames?: KeypadClassNames;\n /** shortcut for `classNames.keypad`. */\n className?: string;\n}\n\nconst DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\nexport function Keypad({\n value,\n onChange,\n length = 6,\n disabled = false,\n error,\n loading = false,\n loadingIndicator,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const busy = disabled || loading;\n\n const handleDigitInput = (digit: number) => {\n if (value.length < length) {\n onChange(value + digit);\n } else if (!!error && value.length === length) {\n onChange(`${digit}`);\n }\n };\n\n const handleBackspace = () => {\n if (value.length > 0 && !error) {\n onChange(value.slice(0, -1));\n } else if (!!error && value.length === length) {\n onChange('');\n }\n };\n\n const btn = classNames.button ?? '';\n const wrapperClass = `${className ?? classNames.keypad ?? ''}${\n loading && classNames.loading ? ` ${classNames.loading}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={wrapperClass} aria-busy={loading || undefined}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={busy}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={busy}\n className={`${btn} ${classNames.backspace ?? ''}`.trim() || undefined}\n >\n {backspaceLabel}\n </button>\n {showMaskToggle && (\n <button\n type=\"button\"\n onClick={() => onToggleReveal?.(!revealed)}\n disabled={busy}\n className={`${btn} ${classNames.toggle ?? ''}`.trim() || undefined}\n title={revealed ? 'Hide' : 'Show'}\n aria-label={revealed ? 'Hide' : 'Show'}\n >\n {revealed ? <EyeOpen /> : <EyeClosed />}\n </button>\n )}\n </div>\n {loading && loadingIndicator}\n </div>\n );\n}\n\nfunction EyeOpen() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" strokeWidth=\"2\" />\n </svg>\n );\n}\nfunction EyeClosed() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -14,6 +14,8 @@ interface PinInputClassNames {
14
14
  container?: string;
15
15
  /** appended to the container while `error` is true. */
16
16
  containerError?: string;
17
+ /** appended to the container while `loading` is true. */
18
+ containerLoading?: string;
17
19
  /** each single-character box. */
18
20
  input?: string;
19
21
  }
@@ -23,6 +25,8 @@ interface PinInputProps {
23
25
  onChange: (value: string) => void;
24
26
  disabled?: boolean;
25
27
  error?: boolean;
28
+ /** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */
29
+ loading?: boolean;
26
30
  /** render digits masked (as `*`) unless `revealed`. */
27
31
  mask?: boolean;
28
32
  /** when `mask`, show the real digits. */
@@ -35,7 +39,7 @@ interface PinInputProps {
35
39
  /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */
36
40
  inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;
37
41
  }
38
- declare function PinInput({ length, value, onChange, disabled, error, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
42
+ declare function PinInput({ length, value, onChange, disabled, error, loading, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
39
43
 
40
44
  /**
41
45
  * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)
@@ -56,6 +60,8 @@ interface KeypadClassNames {
56
60
  backspace?: string;
57
61
  /** appended to the mask-toggle key. */
58
62
  toggle?: string;
63
+ /** appended to the outer wrapper while `loading` (style your overlay/dimming here). */
64
+ loading?: string;
59
65
  }
60
66
  interface KeypadProps {
61
67
  value: string;
@@ -63,6 +69,10 @@ interface KeypadProps {
63
69
  length?: number;
64
70
  disabled?: boolean;
65
71
  error?: boolean;
72
+ /** disables every key (a verify/submit in flight) and applies `classNames.loading`. */
73
+ loading?: boolean;
74
+ /** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */
75
+ loadingIndicator?: React.ReactNode;
66
76
  /** render the reveal/mask (eye) toggle key. */
67
77
  showMaskToggle?: boolean;
68
78
  /** current reveal state (for the toggle icon/label). */
@@ -74,6 +84,6 @@ interface KeypadProps {
74
84
  /** shortcut for `classNames.keypad`. */
75
85
  className?: string;
76
86
  }
77
- declare function Keypad({ value, onChange, length, disabled, error, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
87
+ declare function Keypad({ value, onChange, length, disabled, error, loading, loadingIndicator, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
78
88
 
79
89
  export { Keypad, type KeypadClassNames, type KeypadProps, PinInput, type PinInputClassNames, type PinInputProps };
package/dist/index.d.ts CHANGED
@@ -14,6 +14,8 @@ interface PinInputClassNames {
14
14
  container?: string;
15
15
  /** appended to the container while `error` is true. */
16
16
  containerError?: string;
17
+ /** appended to the container while `loading` is true. */
18
+ containerLoading?: string;
17
19
  /** each single-character box. */
18
20
  input?: string;
19
21
  }
@@ -23,6 +25,8 @@ interface PinInputProps {
23
25
  onChange: (value: string) => void;
24
26
  disabled?: boolean;
25
27
  error?: boolean;
28
+ /** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */
29
+ loading?: boolean;
26
30
  /** render digits masked (as `*`) unless `revealed`. */
27
31
  mask?: boolean;
28
32
  /** when `mask`, show the real digits. */
@@ -35,7 +39,7 @@ interface PinInputProps {
35
39
  /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */
36
40
  inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;
37
41
  }
38
- declare function PinInput({ length, value, onChange, disabled, error, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
42
+ declare function PinInput({ length, value, onChange, disabled, error, loading, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
39
43
 
40
44
  /**
41
45
  * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)
@@ -56,6 +60,8 @@ interface KeypadClassNames {
56
60
  backspace?: string;
57
61
  /** appended to the mask-toggle key. */
58
62
  toggle?: string;
63
+ /** appended to the outer wrapper while `loading` (style your overlay/dimming here). */
64
+ loading?: string;
59
65
  }
60
66
  interface KeypadProps {
61
67
  value: string;
@@ -63,6 +69,10 @@ interface KeypadProps {
63
69
  length?: number;
64
70
  disabled?: boolean;
65
71
  error?: boolean;
72
+ /** disables every key (a verify/submit in flight) and applies `classNames.loading`. */
73
+ loading?: boolean;
74
+ /** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */
75
+ loadingIndicator?: React.ReactNode;
66
76
  /** render the reveal/mask (eye) toggle key. */
67
77
  showMaskToggle?: boolean;
68
78
  /** current reveal state (for the toggle icon/label). */
@@ -74,6 +84,6 @@ interface KeypadProps {
74
84
  /** shortcut for `classNames.keypad`. */
75
85
  className?: string;
76
86
  }
77
- declare function Keypad({ value, onChange, length, disabled, error, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
87
+ declare function Keypad({ value, onChange, length, disabled, error, loading, loadingIndicator, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
78
88
 
79
89
  export { Keypad, type KeypadClassNames, type KeypadProps, PinInput, type PinInputClassNames, type PinInputProps };
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ function PinInput({
9
9
  onChange,
10
10
  disabled = false,
11
11
  error = false,
12
+ loading = false,
12
13
  mask = false,
13
14
  revealed = false,
14
15
  autoFocus = true,
@@ -17,6 +18,7 @@ function PinInput({
17
18
  inputProps
18
19
  }) {
19
20
  const inputs = Array(length).fill(0);
21
+ const isDisabled = disabled || loading;
20
22
  const getFirstInvalidIndex = (val) => {
21
23
  for (let i = 0; i < length; i++) {
22
24
  if (!val[i]) return i;
@@ -80,7 +82,7 @@ function PinInput({
80
82
  if (mask && !revealed) return value[index] ? "*" : "";
81
83
  return value[index] || "";
82
84
  };
83
- const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}`.trim() || void 0;
85
+ const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ""}`.trim() || void 0;
84
86
  return /* @__PURE__ */ jsx("div", { className: containerClass, children: inputs.map((_, index) => /* @__PURE__ */ createElement(
85
87
  "input",
86
88
  {
@@ -94,7 +96,7 @@ function PinInput({
94
96
  onChange: (e) => handleChange(e, index),
95
97
  onKeyDown: (e) => handleKeyDown(e, index),
96
98
  onPaste: handlePaste,
97
- disabled,
99
+ disabled: isDisabled,
98
100
  className: classNames.input,
99
101
  autoFocus: autoFocus && index === 0 && !error,
100
102
  onFocus: (e) => handleFocus(e, index)
@@ -108,6 +110,8 @@ function Keypad({
108
110
  length = 6,
109
111
  disabled = false,
110
112
  error,
113
+ loading = false,
114
+ loadingIndicator,
111
115
  showMaskToggle = false,
112
116
  revealed = false,
113
117
  onToggleReveal,
@@ -115,6 +119,7 @@ function Keypad({
115
119
  classNames = {},
116
120
  className
117
121
  }) {
122
+ const busy = disabled || loading;
118
123
  const handleDigitInput = (digit) => {
119
124
  if (value.length < length) {
120
125
  onChange(value + digit);
@@ -130,41 +135,45 @@ function Keypad({
130
135
  }
131
136
  };
132
137
  const btn = classNames.button ?? "";
133
- return /* @__PURE__ */ jsx("div", { className: className ?? classNames.keypad, children: /* @__PURE__ */ jsxs("div", { className: classNames.grid, children: [
134
- DIGITS.map((digit) => /* @__PURE__ */ jsx(
135
- "button",
136
- {
137
- type: "button",
138
- onClick: () => handleDigitInput(digit),
139
- disabled,
140
- className: btn || void 0,
141
- children: digit
142
- },
143
- digit
144
- )),
145
- /* @__PURE__ */ jsx(
146
- "button",
147
- {
148
- type: "button",
149
- onClick: handleBackspace,
150
- disabled,
151
- className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
152
- children: backspaceLabel
153
- }
154
- ),
155
- showMaskToggle && /* @__PURE__ */ jsx(
156
- "button",
157
- {
158
- type: "button",
159
- onClick: () => onToggleReveal?.(!revealed),
160
- disabled,
161
- className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
162
- title: revealed ? "Hide" : "Show",
163
- "aria-label": revealed ? "Hide" : "Show",
164
- children: revealed ? /* @__PURE__ */ jsx(EyeOpen, {}) : /* @__PURE__ */ jsx(EyeClosed, {})
165
- }
166
- )
167
- ] }) });
138
+ const wrapperClass = `${className ?? classNames.keypad ?? ""}${loading && classNames.loading ? ` ${classNames.loading}` : ""}`.trim() || void 0;
139
+ return /* @__PURE__ */ jsxs("div", { className: wrapperClass, "aria-busy": loading || void 0, children: [
140
+ /* @__PURE__ */ jsxs("div", { className: classNames.grid, children: [
141
+ DIGITS.map((digit) => /* @__PURE__ */ jsx(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ onClick: () => handleDigitInput(digit),
146
+ disabled: busy,
147
+ className: btn || void 0,
148
+ children: digit
149
+ },
150
+ digit
151
+ )),
152
+ /* @__PURE__ */ jsx(
153
+ "button",
154
+ {
155
+ type: "button",
156
+ onClick: handleBackspace,
157
+ disabled: busy,
158
+ className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
159
+ children: backspaceLabel
160
+ }
161
+ ),
162
+ showMaskToggle && /* @__PURE__ */ jsx(
163
+ "button",
164
+ {
165
+ type: "button",
166
+ onClick: () => onToggleReveal?.(!revealed),
167
+ disabled: busy,
168
+ className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
169
+ title: revealed ? "Hide" : "Show",
170
+ "aria-label": revealed ? "Hide" : "Show",
171
+ children: revealed ? /* @__PURE__ */ jsx(EyeOpen, {}) : /* @__PURE__ */ jsx(EyeClosed, {})
172
+ }
173
+ )
174
+ ] }),
175
+ loading && loadingIndicator
176
+ ] });
168
177
  }
169
178
  function EyeOpen() {
170
179
  return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/PinInput.tsx","../src/Keypad.tsx"],"names":["els","jsx"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport React from 'react';\n\n/**\n * PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible\n * markup and you style every slot via `classNames` (bring your own classes). React-only,\n * no app/theme coupling, so it is safe for any consumer.\n *\n * The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the\n * device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits\n * render as `*` unless `revealed`); leave it off for a visible OTP.\n */\n\nexport interface PinInputClassNames {\n /** the boxes wrapper. */\n container?: string;\n /** appended to the container while `error` is true. */\n containerError?: string;\n /** each single-character box. */\n input?: string;\n}\n\nexport interface PinInputProps {\n length?: number;\n value: string;\n onChange: (value: string) => void;\n disabled?: boolean;\n error?: boolean;\n /** render digits masked (as `*`) unless `revealed`. */\n mask?: boolean;\n /** when `mask`, show the real digits. */\n revealed?: boolean;\n /** autofocus the first box on mount (when not in error). Default true. */\n autoFocus?: boolean;\n classNames?: PinInputClassNames;\n /** shortcut for `classNames.container`. */\n className?: string;\n /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;\n}\n\nexport function PinInput({\n length = 6,\n value,\n onChange,\n disabled = false,\n error = false,\n mask = false,\n revealed = false,\n autoFocus = true,\n classNames = {},\n className,\n inputProps,\n}: PinInputProps) {\n const inputs = Array(length).fill(0);\n\n const getFirstInvalidIndex = (val: string) => {\n for (let i = 0; i < length; i++) {\n if (!val[i]) return i;\n }\n return length - 1; // all full → last field\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {\n const digit = e.target.value.replace(/\\D/g, '').slice(-1);\n\n const otpArray = value.split('');\n otpArray[index] = digit || '';\n const nextValue = otpArray.join('');\n\n onChange(nextValue);\n\n // compute allowed index from the new value\n const allowedIndex = getFirstInvalidIndex(nextValue);\n\n // Auto-advance only if digit is valid and index < allowed position\n if (digit && index < length - 1 && index < allowedIndex) {\n const next = e.target.nextElementSibling as HTMLInputElement | null;\n next?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {\n if (e.key !== 'Backspace') return;\n\n // Error mode: clear everything\n if (error && value.length === length) {\n onChange('');\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n (els[0] as HTMLInputElement).focus();\n return;\n }\n\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Build a snapshot of the current actual values from DOM, not from state.\n const domValues = Array.from(els).map((input) => (input as HTMLInputElement).value);\n\n // If current field already empty → move back\n if (!domValues[index] && index > 0) {\n const prev = els[index - 1] as HTMLInputElement;\n prev.focus();\n }\n\n // Clear current char (and everything after) and update parent state\n const otpArray = value.split('');\n for (let i = index; i < otpArray.length; i++) {\n otpArray[i] = '';\n }\n onChange(otpArray.join(''));\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasted) {\n onChange(pasted.slice(0, length));\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>, index: number) => {\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Determine focusable index based on ACTUAL input values\n let allowedIndex = 0;\n for (let i = 0; i < length; i++) {\n if ((els[i] as HTMLInputElement).value === '') {\n allowedIndex = i;\n break;\n }\n if (i === length - 1) allowedIndex = length - 1;\n }\n\n // Prevent skipping ahead\n if (index > allowedIndex) {\n (els[allowedIndex] as HTMLInputElement).focus();\n }\n };\n\n const display = (index: number) => {\n if (mask && !revealed) return value[index] ? '*' : '';\n return value[index] || '';\n };\n\n const containerClass = `${className ?? classNames.container ?? ''}${\n error && classNames.containerError ? ` ${classNames.containerError}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={containerClass}>\n {inputs.map((_, index) => (\n <input\n {...inputProps}\n key={index}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n value={display(index)}\n onChange={(e) => handleChange(e, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onPaste={handlePaste}\n disabled={disabled}\n className={classNames.input}\n autoFocus={autoFocus && index === 0 && !error}\n onFocus={(e) => handleFocus(e, index)}\n />\n ))}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)\n * that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.\n *\n * Behavior preserved from academix: append up to `length` digits; while `error` and the\n * value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`\n * for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.\n */\n\nexport interface KeypadClassNames {\n /** outer wrapper. */\n keypad?: string;\n /** the button grid. */\n grid?: string;\n /** every key (digits, backspace, toggle). */\n button?: string;\n /** appended to the backspace key. */\n backspace?: string;\n /** appended to the mask-toggle key. */\n toggle?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** render the reveal/mask (eye) toggle key. */\n showMaskToggle?: boolean;\n /** current reveal state (for the toggle icon/label). */\n revealed?: boolean;\n onToggleReveal?: (revealed: boolean) => void;\n /** backspace key content. Default `✕`. */\n backspaceLabel?: React.ReactNode;\n classNames?: KeypadClassNames;\n /** shortcut for `classNames.keypad`. */\n className?: string;\n}\n\nconst DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\nexport function Keypad({\n value,\n onChange,\n length = 6,\n disabled = false,\n error,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const handleDigitInput = (digit: number) => {\n if (value.length < length) {\n onChange(value + digit);\n } else if (!!error && value.length === length) {\n onChange(`${digit}`);\n }\n };\n\n const handleBackspace = () => {\n if (value.length > 0 && !error) {\n onChange(value.slice(0, -1));\n } else if (!!error && value.length === length) {\n onChange('');\n }\n };\n\n const btn = classNames.button ?? '';\n\n return (\n <div className={className ?? classNames.keypad}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={disabled}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={disabled}\n className={`${btn} ${classNames.backspace ?? ''}`.trim() || undefined}\n >\n {backspaceLabel}\n </button>\n {showMaskToggle && (\n <button\n type=\"button\"\n onClick={() => onToggleReveal?.(!revealed)}\n disabled={disabled}\n className={`${btn} ${classNames.toggle ?? ''}`.trim() || undefined}\n title={revealed ? 'Hide' : 'Show'}\n aria-label={revealed ? 'Hide' : 'Show'}\n >\n {revealed ? <EyeOpen /> : <EyeClosed />}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction EyeOpen() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" strokeWidth=\"2\" />\n </svg>\n );\n}\nfunction EyeClosed() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/PinInput.tsx","../src/Keypad.tsx"],"names":["els","jsx"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport React from 'react';\n\n/**\n * PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible\n * markup and you style every slot via `classNames` (bring your own classes). React-only,\n * no app/theme coupling, so it is safe for any consumer.\n *\n * The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the\n * device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits\n * render as `*` unless `revealed`); leave it off for a visible OTP.\n */\n\nexport interface PinInputClassNames {\n /** the boxes wrapper. */\n container?: string;\n /** appended to the container while `error` is true. */\n containerError?: string;\n /** appended to the container while `loading` is true. */\n containerLoading?: string;\n /** each single-character box. */\n input?: string;\n}\n\nexport interface PinInputProps {\n length?: number;\n value: string;\n onChange: (value: string) => void;\n disabled?: boolean;\n error?: boolean;\n /** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */\n loading?: boolean;\n /** render digits masked (as `*`) unless `revealed`. */\n mask?: boolean;\n /** when `mask`, show the real digits. */\n revealed?: boolean;\n /** autofocus the first box on mount (when not in error). Default true. */\n autoFocus?: boolean;\n classNames?: PinInputClassNames;\n /** shortcut for `classNames.container`. */\n className?: string;\n /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;\n}\n\nexport function PinInput({\n length = 6,\n value,\n onChange,\n disabled = false,\n error = false,\n loading = false,\n mask = false,\n revealed = false,\n autoFocus = true,\n classNames = {},\n className,\n inputProps,\n}: PinInputProps) {\n const inputs = Array(length).fill(0);\n const isDisabled = disabled || loading;\n\n const getFirstInvalidIndex = (val: string) => {\n for (let i = 0; i < length; i++) {\n if (!val[i]) return i;\n }\n return length - 1; // all full → last field\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {\n const digit = e.target.value.replace(/\\D/g, '').slice(-1);\n\n const otpArray = value.split('');\n otpArray[index] = digit || '';\n const nextValue = otpArray.join('');\n\n onChange(nextValue);\n\n // compute allowed index from the new value\n const allowedIndex = getFirstInvalidIndex(nextValue);\n\n // Auto-advance only if digit is valid and index < allowed position\n if (digit && index < length - 1 && index < allowedIndex) {\n const next = e.target.nextElementSibling as HTMLInputElement | null;\n next?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {\n if (e.key !== 'Backspace') return;\n\n // Error mode: clear everything\n if (error && value.length === length) {\n onChange('');\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n (els[0] as HTMLInputElement).focus();\n return;\n }\n\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Build a snapshot of the current actual values from DOM, not from state.\n const domValues = Array.from(els).map((input) => (input as HTMLInputElement).value);\n\n // If current field already empty → move back\n if (!domValues[index] && index > 0) {\n const prev = els[index - 1] as HTMLInputElement;\n prev.focus();\n }\n\n // Clear current char (and everything after) and update parent state\n const otpArray = value.split('');\n for (let i = index; i < otpArray.length; i++) {\n otpArray[i] = '';\n }\n onChange(otpArray.join(''));\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasted) {\n onChange(pasted.slice(0, length));\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>, index: number) => {\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Determine focusable index based on ACTUAL input values\n let allowedIndex = 0;\n for (let i = 0; i < length; i++) {\n if ((els[i] as HTMLInputElement).value === '') {\n allowedIndex = i;\n break;\n }\n if (i === length - 1) allowedIndex = length - 1;\n }\n\n // Prevent skipping ahead\n if (index > allowedIndex) {\n (els[allowedIndex] as HTMLInputElement).focus();\n }\n };\n\n const display = (index: number) => {\n if (mask && !revealed) return value[index] ? '*' : '';\n return value[index] || '';\n };\n\n const containerClass = `${className ?? classNames.container ?? ''}${\n error && classNames.containerError ? ` ${classNames.containerError}` : ''\n }${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ''}`.trim() || undefined;\n\n return (\n <div className={containerClass}>\n {inputs.map((_, index) => (\n <input\n {...inputProps}\n key={index}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n value={display(index)}\n onChange={(e) => handleChange(e, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onPaste={handlePaste}\n disabled={isDisabled}\n className={classNames.input}\n autoFocus={autoFocus && index === 0 && !error}\n onFocus={(e) => handleFocus(e, index)}\n />\n ))}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)\n * that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.\n *\n * Behavior preserved from academix: append up to `length` digits; while `error` and the\n * value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`\n * for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.\n */\n\nexport interface KeypadClassNames {\n /** outer wrapper. */\n keypad?: string;\n /** the button grid. */\n grid?: string;\n /** every key (digits, backspace, toggle). */\n button?: string;\n /** appended to the backspace key. */\n backspace?: string;\n /** appended to the mask-toggle key. */\n toggle?: string;\n /** appended to the outer wrapper while `loading` (style your overlay/dimming here). */\n loading?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** disables every key (a verify/submit in flight) and applies `classNames.loading`. */\n loading?: boolean;\n /** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */\n loadingIndicator?: React.ReactNode;\n /** render the reveal/mask (eye) toggle key. */\n showMaskToggle?: boolean;\n /** current reveal state (for the toggle icon/label). */\n revealed?: boolean;\n onToggleReveal?: (revealed: boolean) => void;\n /** backspace key content. Default `✕`. */\n backspaceLabel?: React.ReactNode;\n classNames?: KeypadClassNames;\n /** shortcut for `classNames.keypad`. */\n className?: string;\n}\n\nconst DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\nexport function Keypad({\n value,\n onChange,\n length = 6,\n disabled = false,\n error,\n loading = false,\n loadingIndicator,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const busy = disabled || loading;\n\n const handleDigitInput = (digit: number) => {\n if (value.length < length) {\n onChange(value + digit);\n } else if (!!error && value.length === length) {\n onChange(`${digit}`);\n }\n };\n\n const handleBackspace = () => {\n if (value.length > 0 && !error) {\n onChange(value.slice(0, -1));\n } else if (!!error && value.length === length) {\n onChange('');\n }\n };\n\n const btn = classNames.button ?? '';\n const wrapperClass = `${className ?? classNames.keypad ?? ''}${\n loading && classNames.loading ? ` ${classNames.loading}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={wrapperClass} aria-busy={loading || undefined}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={busy}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={busy}\n className={`${btn} ${classNames.backspace ?? ''}`.trim() || undefined}\n >\n {backspaceLabel}\n </button>\n {showMaskToggle && (\n <button\n type=\"button\"\n onClick={() => onToggleReveal?.(!revealed)}\n disabled={busy}\n className={`${btn} ${classNames.toggle ?? ''}`.trim() || undefined}\n title={revealed ? 'Hide' : 'Show'}\n aria-label={revealed ? 'Hide' : 'Show'}\n >\n {revealed ? <EyeOpen /> : <EyeClosed />}\n </button>\n )}\n </div>\n {loading && loadingIndicator}\n </div>\n );\n}\n\nfunction EyeOpen() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" strokeWidth=\"2\" />\n </svg>\n );\n}\nfunction EyeClosed() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@academix-admin/pin-input",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Headless segmented code input (OTP / PIN) + on-screen numeric Keypad — accessible, device-proven focus/paste behavior, style every slot via classNames.",
5
5
  "keywords": [
6
6
  "react",