CTextField
CTextField — основной компонент текстового поля ввода. Построен поверх CInput и предоставляет полностью стилизованный, доступный и валидируемый <input> с плавающим лейблом, иконками, подсказками и поддержкой тем.
Использование
Показать код
<template>
<c-text-field v-model="value" id="basic-email" label="Email" placeholder="Enter your email" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>Состояния
CTextField поддерживает стандартные состояния: обычное, отключённое (disabled), только для чтения (readonly) и с очисткой (clearable).
Показать код
<template>
<c-text-field v-model="value" label="Default" />
<c-text-field v-model="value" label="Disabled" disabled />
<c-text-field v-model="readonly" label="Readonly" readonly />
<c-text-field v-model="value" label="Clearable" clearable />
</template>Валидация
Передайте массив функций-правил в prop rules. Каждое правило принимает текущее значение и возвращает объект { valid: boolean, message: string }. Prop validate-on управляет моментом запуска: 'input' (по умолчанию) или 'blur'.
Если в modelValue хранится отображаемый текст, а проверять нужно другое значение, передайте его в prop validation-value — правила получат его вместо modelValue. Именно так CSelect валидирует выбранную модель, а не строку, показанную в поле.
Показать код
<template>
<c-text-field
v-model="email"
label="Email"
:rules="emailRules"
validate-on="blur"
details="We'll never share your email"
/>
<c-text-field
v-model="password"
label="Password"
type="password"
:rules="passwordRules"
validate-on="blur"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const email = ref('')
const password = ref('')
const emailRules = [
(v: string) => ({ valid: !!v, message: 'Email is required' }),
(v: string) => ({ valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), message: 'Invalid email' }),
]
const passwordRules = [
(v: string) => ({ valid: !!v, message: 'Password is required' }),
(v: string) => ({ valid: v.length >= 8, message: 'Minimum 8 characters' }),
]
</script>Слоты prepend, append и details
Слоты prepend и append позволяют разместить иконку или текст внутри поля. Слот details полностью заменяет блок подсказки/ошибки.
Показать код
<template>
<!-- Prepend icon -->
<c-text-field v-model="search" label="Search">
<template #prepend>
<c-icon name="mdi-magnify" />
</template>
</c-text-field>
<!-- Append text -->
<c-text-field v-model="amount" label="Amount" type="number">
<template #append>
<span style="opacity: .6; font-size: 13px">USD</span>
</template>
</c-text-field>
<!-- Custom details slot -->
<c-text-field v-model="nickname" label="Nickname" :rules="nicknameRules" validate-on="input">
<template #details="{ errorMessage, hasError }">
<span :style="{ color: hasError ? 'var(--c-sys-color-error)' : 'inherit' }">
{{ errorMessage || `${nickname.length}/20 characters` }}
</span>
</template>
</c-text-field>
</template>Асинхронная валидация
Правила могут возвращать Promise. Пока идёт проверка, слот details получает validating: true.
Try: admin, user, root (taken) or test@taken.com
Показать код
<template>
<c-text-field v-model="username" label="Username" :rules="usernameRules" validate-on="blur">
<template #details="{ errorMessage, hasError, validating }">
<span v-if="validating" style="color: var(--c-sys-color-primary)">
Checking availability…
</span>
<span v-else-if="hasError" style="color: var(--c-sys-color-error)">
{{ errorMessage }}
</span>
<span v-else style="opacity: .6">Must be unique</span>
</template>
</c-text-field>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const username = ref('')
const taken = ['admin', 'user', 'root']
const usernameRules = [
(v: string) => ({ valid: v.length >= 3, message: 'Minimum 3 characters' }),
async (v: string) => {
await new Promise((resolve) => setTimeout(resolve, 800))
return { valid: !taken.includes(v.toLowerCase()), message: `"${v}" is already taken` }
},
]
</script>Пресеты
Пресеты позволяют задать внешний вид поля (цвет лейбла, рамки) централизованно — один раз при инициализации плагина, а потом использовать по имени через prop preset.
Показать код
<template>
<c-text-field v-model="value" label="Email" preset="input.blue">
<template #prepend><c-icon name="fas:envelope" :size="16" source="fa" /></template>
</c-text-field>
</template>Регистрация пресетов при инициализации плагина:
import { createVuelandUI } from '@vueland/ui'
import type { CInputPreset } from '@vueland/ui/types'
createVuelandUI({
presets: {
input: {
blue: {
base: {
field: {
base: { label: ['text-blue'] },
focused: { label: ['text-blue'], root: ['text-blue'] },
filled: { label: ['text-blue'] },
error: { label: ['text-red'] },
},
},
error: { details: ['text-red'] },
} satisfies CInputPreset,
},
},
})Структура CInputPreset
Пресет — это набор снимков по состояниям: base плюс опциональные оверрайды. Свои зоны у CInput — root и details, а пресет поля (CFieldPreset) вкладывается по значению:
type CInputZone = 'root' | 'details'
type CInputState = 'focused' | 'filled' | 'error' | 'disabled' | 'readonly'
type CInputSnapshot = Partial<Record<CInputZone, string[]>> & {
field?: CFieldPreset
menu?: CMenuPreset
list?: CListPreset
}
type CInputPreset = Partial<Record<'base' | CInputState, CInputSnapshot>>Компонент всегда в одном текущем состоянии — применяется снимок этого состояния, его зоны подменяют одноимённые зоны base, без стека и без приоритетов. Полная модель — в разделе CInput → Система пресетов.
Пресет распределяется автоматически: CInput применяет root и details и раздаёт набор в поддерево через provide/inject, а CField берёт вложенный field-пресет из base-снимка и резолвит свои состояния (root поля, input, label, prepend, append) самостоятельно.
API
Props
CTextField принимает props CInput, включая label, details, clearable, disabled, readonly, focused, dirty, rules, validateOn, validationValue и preset.
v-model работает со значением string | number | null | undefined.
Нативные атрибуты
CTextField не оборачивает атрибуты <input> в собственные пропсы. Благодаря inheritAttrs любой нестандартный атрибут пробрасывается как есть на внутренний <input> — поэтому используйте обычные HTML-атрибуты напрямую:
<c-text-field
type="number"
placeholder="0"
:min="0"
:max="100"
:step="5"
inputmode="numeric"
maxlength="10"
autocomplete="off"
name="amount"
required
/>Так же пробрасываются pattern, minlength, tabindex, enterkeyhint и любые data-* / aria-* атрибуты. Эти значения не документируются как пропсы — это стандартный контракт нативного <input>.
Slots
prependappendmenu{ id: string }detailsCInputDetailsSlotPropsПропсы слота menu
idstringuid-menu)Пропсы слота details
errorMessagestring | undefinedhasErrorbooleanvalidatingbooleanuidstringid нативного <input>)detailsstring | undefineddetailsEvents
update:modelValuestring | number | undefinedfocusblurExpose
Методы, доступные через template ref:
validate() => Promise<boolean>reset() => voidfocus() => voiddisabled/readonlyblur() => voidisReadonly() => boolean | undefinedreadonlyisDisabled() => boolean | undefineddisabled<template>
<c-text-field ref="fieldRef" v-model="value" label="Name" :rules="rules" />
<c-btn @click="fieldRef?.validate()">Validate</c-btn>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const fieldRef = ref()
const value = ref('')
const rules = [(v: string) => ({ valid: !!v, message: 'Required' })]
</script>Тип ValidateFn
type ValidateResult = { valid: boolean; message: string }
type ValidateFn = (value: any) => ValidateResult | Promise<ValidateResult>CSS-переменные
CInput (корневой элемент)
--c-input-details-heightvar(--c-sys-control-height-sm)--c-input-transition-durationvar(--c-sys-motion-duration-medium)--c-input-primary-colorvar(--c-sys-color-primary)--c-input-error-colorvar(--c-sys-color-error)--c-input-disabled-colorvar(--c-sys-color-disabled)--c-input-readonly-colorvar(--c-sys-color-readonly)CField (рамка и лейбл)
--c-field-min-heightvar(--c-sys-control-height-md)--c-field-prepend-min-widthvar(--c-sys-control-height-md)--c-field-append-min-widthvar(--c-sys-control-icon-size)--c-field-padding-inlinevar(--c-sys-control-padding-inline)--c-field-border-radiusvar(--c-sys-shape-md)--c-field-transition-durationvar(--c-sys-motion-duration-medium)--c-field-density-offsetvar(--c-sys-density-scale)--c-field-bg-colorvar(--c-sys-color-surface)--c-field-focused-bg-colorvar(--c-sys-color-surface-bright)--c-field-disabled-bg-colorvar(--c-sys-color-surface-dim)--c-field-border-colorvar(--c-sys-color-outline)--c-field-border-widthvar(--c-sys-border-width-thin)--c-field-input-text-colorvar(--c-sys-color-on-surface)--c-field-placeholder-colorvar(--c-sys-color-placeholder)--c-field-error-bg-colorvar(--c-sys-color-surface-bright)--c-field-error-border-colorvar(--c-sys-color-error)--c-field-readonly-bg-colorvar(--c-sys-color-readonly-container)--c-field-disabled-opacityvar(--c-sys-state-disabled-opacity)Пример переопределения
<c-text-field
v-model="value"
label="Custom styled"
style="
--c-input-primary-color: #7c3aed;
--c-field-border-color: #ddd6fe;
"
/>CSS-классы состояний
c-input--focusedc-input--has-errordisabled, и не readonly)c-input--disableddisabled = truec-input--readonlyreadonly = truec-input--clearableclearable = truec-field--focusedc-field--filledc-field--errorc-field--disabledc-field--readonlyc-field--has-prepend