Forms and validation
Native forms
With a name, a hidden input carries the model value (90, or PT1H30M with 'iso'), so the form submits what v-model holds, not the typed text. Plain objects and arrays from a custom valueFormat are submitted as JSON; other objects (like Temporal.Duration) use their toString(). This also works with as. The renderless slot gets hiddenInputProps to bind it yourself.
While the text is invalid, the input reports it to the browser through setCustomValidity(), with the error message as text. So the browser blocks submitting the form instead of sending the last valid value, just like it does for an empty required field. This happens with the built-in field and with as. With the renderless slot, call setCustomValidity() yourself, e.g. from rawErrorDetail.
<form method="post">
<DurationInput name="estimate" required />
</form>Required, min and max
requiredmakes empty input anemptyerror.v-modelstill becomesnullwhen the field is cleared, so the model always matches the field.minandmaxare inclusive. They take a number in the model's unit or text like'8h'. Values outside them areout_of_rangeerrors, or withclamp, get clamped. In development, amingreater thanmaxlogs a warning;durationSchemathrows aRangeErrorfor it.
Show errors on submit
Call validate() through a template ref. It shows the current error, whatever validateOn is set to, and returns whether the input is valid:
<script setup lang="ts">
import { ref } from 'vue'
import type { DurationInput } from 'semantic-duration-input'
const estimate = ref<InstanceType<typeof DurationInput>>()
function onSubmit(event: Event) {
if (!estimate.value?.validate()) event.preventDefault()
}
</script>
<template>
<form @submit="onSubmit">
<DurationInput ref="estimate" v-model="minutes" required />
</form>
</template>Error messages
Override texts per key with messages, or pass false to hide the built-in message (the error event and aria-invalid still work):
<DurationInput :messages="{ empty: 'How long will it take?', out_of_range: 'Between {min} and {max}, please.' }" />| Key | Used for |
|---|---|
empty | empty |
invalid_format | invalid_format |
unknown_unit | unknown_unit without a suggestion |
unknown_unit_suggestion | unknown_unit with a suggestion |
missing_unit | missing_unit |
out_of_range | out_of_range with both bounds |
out_of_range_min | out_of_range with only min |
out_of_range_max | out_of_range with only max |
- Overriding a base key also covers its variants:
unknown_unitcoversunknown_unit_suggestion, andout_of_rangecoversout_of_range_minandout_of_range_max. - Placeholders:
{min},{max},{token}and{suggestion}. Each is filled in once per message.
Schema validation
durationSchema() is a Standard Schema, so it works directly with Valibot, ArkType, TanStack Form, VeeValidate and others.
- Text is parsed into the model value.
- Values that already have the output type (e.g. numbers) are only range-checked. Values it can't convert fail with
invalid_format.
import { durationSchema } from 'semantic-duration-input/core'
const estimate = durationSchema({ required: true, max: '8h', valueFormat: 'minutes' })
estimate['~standard'].validate('1h30') // { value: 90 }
estimate['~standard'].validate('9h') // { issues: [{ message: 'Must be at most 8h.' }] }
estimate['~standard'].validate(90) // { value: 90 }Options:
- Everything the component uses for parsing:
valueFormat,min,max,required,locales,precision,implicitUnitsanddefaultUnit. localeandmessagesfor the issue texts.
Without required, empty input, null and undefined validate to { value: null }.
Zod
With Zod, use parseDuration in a transform:
import { z } from 'zod'
import { formatErrorMessage, parseDuration } from 'semantic-duration-input/core'
const minutes = z.string().transform((text, ctx) => {
const result = parseDuration(text)
if (result.ok) return result.minutes // null for empty input
ctx.addIssue({ code: 'custom', message: formatErrorMessage(result) })
return z.NEVER
})