Skip to content

Commit

Permalink
feat: added onCompleted callback
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Mar 9, 2025
1 parent 696cbe2 commit 6f340ac
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 10 deletions.
40 changes: 31 additions & 9 deletions packages/core/src/useOtpField/useOtpField.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { computed, provide, ref, toValue } from 'vue';
import { Reactivify, StandardSchema } from '../types';
import { computed, provide, ref, toValue, watch } from 'vue';
import { MaybeAsync, Reactivify, StandardSchema } from '../types';
import { OtpContextKey, OtpSlotAcceptType } from './types';
import { createDescribedByProps, normalizeProps, useUniqId, withRefCapture } from '../utils/common';
import { FieldTypePrefixes } from '../constants';
Expand Down Expand Up @@ -80,18 +80,23 @@ export interface OTPFieldProps {
* The prefix of the OTP field. If you prefix your codes with a character, you can set it here (e.g "G-").
*/
prefix?: string;

/**
* The callback function that is called when the OTP field is completed.
*/
onCompleted?: (value: string) => MaybeAsync<void>;
}

export function useOtpField(_props: Reactivify<OTPFieldProps, 'schema'>) {
const props = normalizeProps(_props, ['schema']);
export function useOtpField(_props: Reactivify<OTPFieldProps, 'schema' | 'onCompleted'>) {
const props = normalizeProps(_props, ['schema', 'onCompleted']);
const controlEl = ref<HTMLElement>();
const id = useUniqId(FieldTypePrefixes.OTPField);
const isDisabled = createDisabledContext(props.disabled);

function withPrefix(value: string | undefined) {
const prefix = toValue(props.prefix);
if (!prefix) {
return value;
return value ?? '';
}

value = value ?? '';
Expand All @@ -116,7 +121,7 @@ export function useOtpField(_props: Reactivify<OTPFieldProps, 'schema'>) {
schema: props.schema,
});

const inputsState = ref<string[]>(field.fieldValue.value?.split('') ?? []);
const inputsState = ref<string[]>(withPrefix(toValue(props.modelValue) ?? toValue(props.value)).split(''));

const { element: inputEl } = useConstraintsValidator({
type: 'text',
Expand Down Expand Up @@ -212,6 +217,20 @@ export function useOtpField(_props: Reactivify<OTPFieldProps, 'schema'>) {
return slots.indexOf(currentSlot);
}

watch(field.fieldValue, value => {
if (!value) {
inputsState.value = withPrefix('').split('');
return;
}

const expected = withPrefix(inputsState.value.join(''));
if (expected === value) {
return;
}

inputsState.value = value.split('');
});

provide(OtpContextKey, {
useSlotRegistration() {
const slotId = useUniqId(FieldTypePrefixes.OTPSlot);
Expand All @@ -227,9 +246,12 @@ export function useOtpField(_props: Reactivify<OTPFieldProps, 'schema'>) {
}

inputsState.value[index] = value;
const nextValue = withPrefix(inputsState.value.join(''));

field.setValue(nextValue?.length === getRequiredLength() ? nextValue : withPrefix(''));
const nextValue = inputsState.value.join('');
const isCompleted = nextValue?.length === getRequiredLength();
field.setValue(nextValue);
if (isCompleted) {
props.onCompleted?.(nextValue);
}
},
};
},
Expand Down
9 changes: 8 additions & 1 deletion packages/playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<script setup lang="ts">
import OtpField from '@/components/OtpField.vue';
import { useForm } from '@formwerk/core';
const form = useForm();
function onCompleted(value: string) {
console.log('onCompleted', value);
}
</script>

<template>
<OtpField name="otp" label="OTP" :length="4" accept="numeric" prefix="G-" required masked />
<OtpField name="otp" label="OTP" :length="4" accept="numeric" prefix="G-" required @completed="onCompleted" />
<button @click="form.setValue('otp', 'G-4321')">Set OTP</button>
</template>

0 comments on commit 6f340ac

Please sign in to comment.