VerburgtJimmy / svelte-input-otp

One time passcode Input. Accessible & unstyled.
https://svelte-input-otp.jimmyverburgt.com
MIT License
7 stars 1 forks source link

Pasting doesn't respect the pattern #1

Closed dankobg closed 1 month ago

dankobg commented 1 month ago

Describe the bug

When i am using pattern="digits" and paste the code, it doesn't respect the pattern of only numbers. Sometimes it works but usually i can paste any garbled mess if i add some random digit in a string.

bug demo

Reproduction

copy and paste some trash with pattern="digits"

Logs

No response

System Info

System:
  OS: Linux 6.9 Fedora Linux 40 (Workstation Edition)
  CPU: (8) x64 Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
  Memory: 11.13 GB / 15.57 GB
  Container: Yes
  Shell: 5.9 - /usr/bin/zsh
Binaries:
  Node: 21.5.0 - ~/.asdf/installs/nodejs/21.5.0/bin/node
  npm: 10.2.4 - ~/.asdf/plugins/nodejs/shims/npm
Browsers:
  Chrome: 126.0.6478.126
npmPackages:
  @sveltejs/kit: ^2.5.18 => 2.5.18 
  svelte: ^4.2.18 => 4.2.18
  typescript: ^5.5.3 => 5.5.3

Severity

annoyance

VerburgtJimmy commented 1 month ago

Can you provide the code you used to create the otp component? And can you also try to paste the random values into the otp input component on my example website (https://svelte-input-otp.jimmyverburgt.com/) to make sure you have the same experience there?

dankobg commented 1 month ago

yes i tried again, this is the same reproduction: You reset the value on onComplete to empty string, which is what i removed though at first. If i clear the value on onComplete, how to use it then like a normal form input? My form would have that not allowed pattern string value

<script lang="ts">
    import { OTPInput, OTPRoot } from '@jimmyverburgt/svelte-input-otp';
    import Minus from 'lucide-svelte/icons/minus';

    let otpref: any;
    let value = '';

    function handleOtpComplete(otp: string) {
        console.log('OTP Complete:', otp);
        // value = '';
    }

    function handleOtpChange(event: { detail: string }) {
        console.log('OTP changed:', value);
    }
</script>

<OTPRoot
    bind:this={otpref}
    maxLength={6}
    on:change={handleOtpChange}
    bind:value
    autoFocus={true}
    onComplete={handleOtpComplete}
    className="flex items-center gap-2"
>
    <div class="flex items-center">
        <OTPInput
            index={0}
            className="relative flex h-20 w-16 items-center justify-center border-y border-r border-input text-3xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
            focusClassName="z-10 ring-2 ring-ring ring-offset-background"
        />
        <OTPInput
            index={1}
            className="relative flex h-20 w-16 items-center justify-center border-y border-r border-input text-3xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
            focusClassName="z-10 ring-2 ring-ring ring-offset-background"
        />
        <OTPInput
            index={2}
            className="relative flex h-20 w-16 items-center justify-center border-y border-r border-input text-3xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
            focusClassName="z-10 ring-2 ring-ring ring-offset-background"
        />
    </div>
    <div class="mx-1">
        <Minus />
    </div>
    <div class="flex items-center">
        <OTPInput
            index={3}
            className="relative flex h-20 w-16 items-center justify-center border-y border-r border-input text-3xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
            focusClassName="z-10 ring-2 ring-ring ring-offset-background"
        />
        <OTPInput
            index={4}
            className="relative flex h-20 w-16 items-center justify-center border-y border-r border-input text-3xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
            focusClassName="z-10 ring-2 ring-ring ring-offset-background"
        />
        <OTPInput
            index={5}
            className="relative flex h-20 w-16 items-center justify-center border-y border-r border-input text-3xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
            focusClassName="z-10 ring-2 ring-ring ring-offset-background"
        />
    </div>
</OTPRoot>

copy: <pre>_* .c9</pre>

i have this another example with sveltekit superform:

<script lang="ts">
    import { OTPInput, OTPRoot } from '@jimmyverburgt/svelte-input-otp';
    import { superForm } from 'sveltekit-superforms';
    import { zod } from 'sveltekit-superforms/adapters';
    import * as z from 'zod';
    import * as Form from '$lib/components/ui/form/index';

    const testSchema = z.object({
        code: z.string().length(6, { message: 'Code must have exactly 6 characters' })
    });

    type TestSchema = z.infer<typeof testSchema>;

    const initialTestSchema: TestSchema = {
        code: ''
    };

    const supForm = superForm(initialTestSchema, {
        validators: zod(testSchema),
        SPA: true,
        dataType: 'json',
        scrollToError: 'smooth',
        autoFocusOnError: 'detect',
        stickyNavbar: undefined,
        async onUpdated({ form }) {
            if (!form.valid) {
                console.error('INVALID');
                return;
            }
            console.log('SUCCESS', form);
        }
    });

    function handleOtpComplete(otp: string) {
        console.log('OTP Complete:', otp);
    }

    const { form, enhance, errors } = supForm;
</script>

<form method="POST" use:enhance class="grid w-[400px] gap-4 p-8">
    <div class="grid gap-2">
        <Form.Field form={supForm} name="code">
            <Form.Control let:attrs>
                <Form.Label>Code</Form.Label>
                <OTPRoot
                    {...attrs}
                    bind:value={$form.code}
                    inputName="code"
                    inputType="numeric"
                    pattern="digits"
                    maxLength={6}
                    autoFocus={true}
                    onComplete={handleOtpComplete}
                    className="h-9 w-full"
                >
                    <div class="flex items-center">
                        {#each Array(6) as _, idx (idx)}
                            <OTPInput
                                index={idx}
                                className="relative flex h-9 w-full items-center justify-center border-y border-r border-input text-2xl transition-all first:rounded-l-md first:border-l last:rounded-r-md"
                                focusClassName="z-10 ring-2 ring-ring ring-offset-background"
                            />
                        {/each}
                    </div>
                </OTPRoot>
            </Form.Control>
            <Form.Description />
            <Form.FieldErrors />
        </Form.Field>
    </div>
    <Form.Button class="w-full font-bold">GO</Form.Button>
</form>

<pre>{JSON.stringify($form, null, 2)}</pre>

copy: <pre>_* .c9</pre>
dankobg commented 1 month ago

it says that on complete gets called if it gets filled correctly:

/**
     * The function that is called when the input otp is correctly filled in.
     * @param args
     * @returns
     */
    onComplete?: (...args: any[]) => unknown;

but it still gets called even with this wrong input

VerburgtJimmy commented 1 month ago

I have changed the regex pattern for digits only which should fix the issue as it would not allow the wrong input to be inserted. The on complete got called because it is also relying on this regex. If the issue persists please let me know but for now try updating the package.