Files
dotter/resources/js/components/ui/input-otp/InputOTP.svelte
T
salam e687b4437b
linter / quality (push) Canceled after 0s
tests / ci (8.3) (push) Canceled after 0s
tests / ci (8.4) (push) Canceled after 0s
tests / ci (8.5) (push) Canceled after 0s
first commit
2026-10-06 18:53:03 +02:00

72 lines
1.9 KiB
Svelte

<script lang="ts">
import type { Snippet } from 'svelte';
import { cn } from '@/lib/utils';
import { setInputOTPContext } from './context';
let {
value = $bindable(''),
maxlength = 6,
disabled = false,
id,
class: className = '',
children,
...rest
}: {
value?: string;
maxlength?: number;
disabled?: boolean;
id?: string;
class?: string;
children?: Snippet;
[key: string]: unknown;
} = $props();
let inputRef: HTMLInputElement;
let isFocused = $state(false);
const activeIndex = $derived(Math.min(value.length, maxlength - 1));
setInputOTPContext({
value: () => value,
activeIndex: () => (isFocused ? activeIndex : -1),
isFocused: () => isFocused,
focus: () => inputRef?.focus(),
});
function handleInput(e: Event) {
const target = e.target as HTMLInputElement;
value = target.value.replace(/[^0-9]/g, '').slice(0, maxlength);
}
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Backspace' && value.length > 0) {
e.preventDefault();
value = value.slice(0, -1);
}
}
</script>
<div
data-slot="input-otp"
class={cn('relative flex items-center gap-2 has-disabled:opacity-50', className)}
role="group"
{...rest}
>
<input
bind:this={inputRef}
{id}
{disabled}
type="text"
inputmode="numeric"
autocomplete="one-time-code"
{value}
oninput={handleInput}
onkeydown={handleKeyDown}
onfocus={() => (isFocused = true)}
onblur={() => (isFocused = false)}
class="absolute inset-0 z-10 h-full w-full cursor-text opacity-0 disabled:cursor-not-allowed"
/>
<div class="pointer-events-none">
{@render children?.()}
</div>
</div>