GrFormFile

  • ядро
  • Формы
  • API · 22 props

Берут, когда файл — значение поля.

import { GrFormFile } from '@feugene/granularity/components/GrFormFile'

Обзор

Когда брать

  • файл — значение поля — резюме, скан, вложение: отправляет форма, а не компонент;
  • файл проверяется до отправки — размер, тип, количество через validators;
  • файлов несколько — multiple и limit вместе со списком выбранного;
  • нужен предпросмотр — preview показывает миниатюру изображения до отправки.

Когда взять другое

НужноБерите
Файл уходит на сервер сразу, с прогрессомGrFileUpload
Изображение нужно рассмотретьGrImageViewer
Значение — не файлGrInput

Граница с GrFileUpload проходит по тому, кто отправляет. Здесь файл — обычное значение v-model, и оно уезжает вместе с остальной формой; там компонент грузит сам и показывает прогресс каждого файла.

Playground контролы выводятся из API, а не пишутся руками

Сцена

Загружается…

0 изменено· состояние — в адресе

Примеры 3 у каждого — живое демо и его код

  • Резюме, которое уходит вместе с откликом

    Поле только собирает файл: это значение модели, как и имя, и ничего не загружается, пока не отправлен весь отклик. Правило file у GrForm проверяет тип и размер на отправке рядом с остальными правилами, а accept на поле фильтрует системный диалог.

    Senior product designer · Dublin or remoteNorthwind Traders · Apply in two minutes
    Обязательное поле
    PDF or Word, up to 5 MB
  • Фото объявления с обложкой

    Первое фото становится обложкой, поэтому порядок принадлежит пользователю: reorderable даёт строке ручку, и перенос меняет модель. Новый выбор добавляется к набору — кнопка говорит об этом через add-text, — а limit проверяет весь набор: седьмое фото отбивается, а не обрезается молча. preview рисует квадратные миниатюры, чтобы строки не прыгали по пропорциям снимков.

    2-room flat in Alfama · draftCover photo: none yet

    Drag to reorder — the first one is the cover

    Файлы не выбраны
  • Чек, который уже заявлен

    validate выполняется после встроенных проверок и может спросить сервер — здесь, не заявлен ли чек раньше. v-model:errors — единый канал для найденного проверками и сказанного сервером, а слот error формулирует каждую претензию по её коду, со ссылкой на прежнюю заявку.

    Taxi, 16 Oct · €24.60Client visit, Lisbon
    Photo or PDF of the receipt

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalseНедоступен: выбор, перетаскивание и удаление выключены. Складывается по «или» с `GrFormField` и формой.
multipleboolean | undefinedfalseНабор из нескольких файлов: модель — массив, у каждой строки своё удаление.
readonlyboolean | undefinedfalseТолько для чтения: значение видно и уходит в форму, но не редактируется.
invalidboolean | undefinedfalseВизуальное и ARIA-состояние ошибки.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`).
size"xs" | "sm" | "md" | "lg" | undefinedundefinedРазмер кнопок, иконок и подписей.
placeholderstring | undefinedundefinedТекст у кнопки, пока ничего не выбрано. По умолчанию — переведённое «No files selected».
ariaLabelstring | undefinedundefinedДоступное имя вне `GrFormField`.
replaceboolean | undefinedfalseНовый выбор в `multiple` **заменяет** набор, а не добавляется к нему — старое поведение. Без него набор растёт, дубликаты (то же имя, размер и время изменения) отбрасываются, а `limit` и валидаторы проверяют набор целиком.
limitnumber | undefinedundefinedМаксимум файлов в наборе. Лишние не обрезаются молча — набор отбивается ошибкой.
validatorsFileValidator[] | undefinedundefinedПроверки набора (`maxFileSize`, `allowedExtensionsValidator` и т. п.) — после `accept` и `limit`, до `validate`. Отбитый набор в модель не попадает.
acceptstring | undefinedundefinedW3C `accept` для `<input type="file">` + sugar к `acceptValidator(...)`.
previewboolean | undefinedfalseМиниатюры для картинок в наборе. Файлы других типов остаются строкой.
validate
((files: File[]) => FileValidationIssue[] | Promise<FileVali((files: File[]) => FileValidationIssue[] | Promise<FileValidationIssue[]>) | undefined
undefinedДополнительная (кастомная) валидация на стороне потребителя.

Слоты

SlotTypeОписание
error{ errors: FileValidationIssue[]; }Собственный вывод ошибок вместо списка по умолчанию.

События

EventСигнатураОписание
update:modelValue(event: "update:modelValue", value: File | File[] | null): void—
change(event: "change", value: File | File[] | null): void—
clear(event: "clear"): void—
focus(event: "focus", event: FocusEvent): void—
blur(event: "blur", event: FocusEvent): void—
update:errors(event: "update:errors", errors: FileValidationIssue[]): void—

Методы

MethodsСигнатураОписание
focus() => voidПоставить фокус на кнопку выбора файла.
blur() => voidСнять фокус с кнопки выбора файла.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrFormFileError, GrFormFileProps, GrFormFileEmits } from '@feugene/granularity/components/GrFormFile'

Файлdist/types/components/GrFormFile/config.d.ts

grFormFileConfigconst
const grFormFileConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrFormFile">;

Файлdist/types/components/GrFormFile/GrFormFile.vue.d.ts

GrFormFileErrortype
type GrFormFileError = FileValidationIssue;
GrFormFilePropsinterface

Пропы GrFormFile — форма-поле для выбора файлов с встроенной валидацией, drag&drop (через v-dropzone) и списком выбранных файлов. Все *Text/placeholder — i18n-friendly, принимают готовые строки локали.

interface GrFormFileProps {
    /** Выбранный файл или `null`; при `multiple` — массив файлов (`v-model`). */
    modelValue: File | File[] | null;
    /** Набор из нескольких файлов: модель — массив, у каждой строки своё удаление. */
    multiple?: boolean;
    /**
     * Недоступен: выбор, перетаскивание и удаление выключены. Складывается по «или» с
     * `GrFormField` и формой.
     */
    disabled?: boolean;
    /** Только для чтения: значение видно и уходит в форму, но не редактируется. */
    readonly?: boolean;
    /** Визуальное и ARIA-состояние ошибки. */
    invalid?: boolean;
    /** Обязательное поле (`aria-required`). */
    required?: boolean;
    /** Доступное имя вне `GrFormField`. */
    ariaLabel?: string;
    /** W3C `accept` для `<input type="file">` + sugar к `acceptValidator(...)`. */
    accept?: string;
    /**
     * Проверки набора (`maxFileSize`, `allowedExtensionsValidator` и т. п.) — после `accept` и
     * `limit`, до `validate`. Отбитый набор в модель не попадает.
     */
    validators?: FileValidator[];
    /** Максимум файлов в наборе. Лишние не обрезаются молча — набор отбивается ошибкой. */
    limit?: number;
    /** Подпись кнопки выбора, пока набор пуст. По умолчанию — переведённое «Upload file». */
    uploadText?: string;
    /** Подпись кнопки выбора, когда файлы уже есть. По умолчанию — переведённое «Change file». */
    changeText?: string;
    /**
     * Подпись кнопки выбора в `multiple`, когда файлы уже есть: новый выбор
     * добавляется к набору. По умолчанию — переведённое «Add files».
     */
    addText?: string;
    /**
     * Новый выбор в `multiple` **заменяет** набор, а не добавляется к нему —
     * старое поведение. Без него набор растёт, дубликаты (то же имя, размер и
     * время изменения) отбрасываются, а `limit` и валидаторы проверяют набор
     * целиком.
     */
    replace?: boolean;
    /** Подпись удаления у одиночного файла и у строк набора. По умолчанию — переведённое «Remove». */
    removeText?: string;
    /** Подпись сброса всего набора при `multiple`. По умолчанию — переведённое «Clear all». */
    clearAllText?: string;
    /** Текст у кнопки, пока ничего не выбрано. По умолчанию — переведённое «No files selected». */
    placeholder?: string;
    /** Размер кнопок, иконок и подписей. */
    size?: GrFormFileSize;
    /** Миниатюры для картинок в наборе. Файлы других типов остаются строкой. */
    preview?: boolean;
    /**
     * Порядок набора задаёт пользователь: у строки появляется ручка переноса.
     *
     * Нужно там, где последовательность файлов что-то значит, — галерея,
     * страницы документа, очередь вложений. Осмысленно только с `multiple`.
     */
    reorderable?: boolean;
    /** Дополнительная (кастомная) валидация на стороне потребителя. */
    validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>;
GrFormFileEmitsinterface
interface GrFormFileEmits {
    /**
     * Новый набор для `v-model`: выбор, перетаскивание, удаление, перестановка. Набор, отбитый
     * валидацией, сюда не приходит.
     */
    (e: 'update:modelValue', value: File | File[] | null): void;
    /** Набор изменился — тот же payload, что у `update:modelValue`. */
    (e: 'change', value: File | File[] | null): void;
    /** Набор сброшен целиком: «Очистить всё» или удаление единственного файла; модель — следом. */
    (e: 'clear'): void;
    /** Результат валидации. Канал один: `validation` дублировал эту же нагрузку. */
    (e: 'update:errors', errors: GrFormFileError[]): void;
    /** Фокус вошёл в поле. Переходы между его кнопками событием не считаются. */
    (e: 'focus', event: FocusEvent): void;
    /** Фокус ушёл из поля целиком. */
    (e: 'blur', event: FocusEvent): void;
}

Файлdist/types/components/GrFormFile/defaults.d.ts

GrFormFileConfigurablePropsinterface

Пропы GrFormFile, настраиваемые глобально через componentDefaults.

interface GrFormFileConfigurableProps {
    size: GrComponentSize;
}

Файлdist/types/components/GrFormFile/grFormFileStyles.d.ts

GrFormFileSizetype
type GrFormFileSize = GrComponentSize;

Файлdist/types/components/GrFormFile/safelist.d.ts

grFormFileSafelistconst
const grFormFileSafelist: string[];

Файлdist/types/components/GrFormFile/index.d.ts

GrFormFileInstancetype
type GrFormFileInstance = ComponentExposed<typeof GrFormFileComponent>;

Использование из прозы библиотеки

Ошибки — контролируемое значение

v-model:errors — двусторонний канал: туда пишет внутренняя валидация, и туда же потребитель кладёт ошибки, пришедшие с сервера. Пока проп задан, он сильнее внутреннего списка (та же схема, что sortKey у GrDataTable).

vue
<GrFormFile v-model="files" v-model:errors="errors" accept="application/pdf" multiple :limit="3" />

Канал один: эмит validation дублировал update:errors той же нагрузкой и снят.

Список ошибок объявляется role="alert" и связан с кнопкой выбора через aria-describedby — вместе с aria-describedby от GrFormField, если поле внутри него. Пока ошибки есть, кнопка несёт aria-invalid. До этого «уронил файл не того типа» выглядело для скринридера как «ничего не произошло».

Валидация одна на оба пути ввода

Набор валидаторов собирается в одном месте и уходит и в выбор через диалог, и в v-dropzone: две копии этой сборки разъезжаются при первой же правке, и перетаскивание начинает вести себя не так, как диалог.

Порядок: accept → limit → validators потребителя → validate. limit — сахар к maxCountValidator: лишние файлы не обрезаются молча, набор отбивается ошибкой, как любым другим правилом.

Выбор в multiple добавляется к набору

Новый выбор — диалогом или перетаскиванием — дописывается к уже выбранным: галерея и список вложений растут, а не теряют выбранное при каждом диалоге. Повтор уже выбранного файла (то же имя, размер и время изменения) отбрасывается. Кнопка у непустого набора — «Add files» (gr.formFile.add, свой текст — addText), начать заново — «Clear all».

Валидаторы видят набор целиком: limit, maxTotalSizeBytesValidator и validate проверяют то, что получится после добавления, а не последний выбор. Отбитый выбор набор не трогает — остаётся прежний и ошибка.

Заменять набор при каждом выборе — старое поведение — можно явно: replace. Кнопка тогда снова «Change file».

Список файлов

В multiple каждая строка показывает имя и размер, а кнопка удаления называет свой файл (aria-label) — три подряд кнопки «Удалить» для скринридера неразличимы. Строка — во всю ширину колонки: длинное имя усекается многоточием (полное — в title), размер и «Удалить» стоят у общего правого края, и в узком контейнере кнопку не выталкивает.

Disabled гасится курсором и состоянием самих кнопок: opacity на контейнере разбавляла бы и подписи, и имена файлов.

Порядок задаёт пользователь

reorderable добавляет строке ручку переноса. Нужно там, где последовательность файлов что-то значит: галерея, страницы документа, очередь вложений. Осмысленно только с multiple.

Механика берётся у GrSortableList, а не пишется заново: перенос указателем, клавиатура (Space берёт строку, стрелки двигают, Esc отменяет), автопрокрутка и объявления у него уже есть. Цена честная — его стили приезжают каждому, кто берёт GrFormFile.

Тянется ручка, а не строка: в строке стоит кнопка удаления, и перетаскивание всей строки отняло бы у неё клик.

Перестановка отдаёт update:modelValue и change — как удаление и добавление. Без второго порядок остался бы только на экране, а в форму ушёл бы прежний. При readonly и disabled перенос запрещён.

Превью картинок

preview включает миниатюры: они появляются у файлов image/*, файл любого другого типа остаётся обычной строкой. Миниатюра квадратная и обрезается по object-cover — иначе строки списка скакали бы по высоте вслед за пропорциями снимков.

vue
<GrFormFile v-model="gallery" multiple preview accept="image/*" />

alt у миниатюры пустой: имя файла стоит вплотную, и озвучивать его дважды незачем. object URL живёт ровно столько, сколько файл в наборе, — он отзывается, как только файл из набора ушёл, чем бы его ни убрало.

readonly

Набор виден и уходит в форму, но не меняется ничем: ни диалогом выбора, ни перетаскиванием, ни кнопками — они в этом состоянии не рендерятся. Кнопка выбора остаётся в таб-порядке и объявляет aria-readonly: поле должно быть достижимо с клавиатуры и уметь объяснить, почему не поддаётся.

Отличие от disabled: тот выключает и саму кнопку, то есть поле выпадает из обхода целиком.

Внутри GrForm

Те же ограничения можно объявить правилом формы — рядом с остальными:

ts
const rules: GrFormRules = {
  contract: [{ required: true, file: { accept: '.pdf', maxSizeMb: 1 } }],
}

Проверку ведут те же валидаторы, поэтому текст ошибки не меняется — меняется момент: правило поля не пускает плохой файл в модель сразу, правило формы отбивает submit и попадает в invalid и в скролл к первой ошибке. Подробности и полный список ключей — GrForm.md.

Типичное разделение: ограничения в rules, а на поле accept как фильтр диалога выбора.

Чего нет

Загрузки на сервер: GrFormFile — поле формы, файлы уходят вместе с ней. Прогресс и отмену закрывает GrFileUpload.