GrFormFile

  • core
  • forms
  • API · 22 props

Binds file uploads to a form field and validation.

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

Overview

When to take it

  • the file is the value of a field — a CV, a scan, an attachment: it is sent by the form, not by the component;
  • the file is checked before submission — the size, the type, the number through validators;
  • there are several files — multiple and limit together with a list of what has been chosen;
  • a preview is needed — preview shows a thumbnail of an image before the submission.

When to take something else

NeedTake
The file leaves for the server at once, with progressGrFileUpload
The image has to be examinedGrImageViewer
The value is not a fileGrInput

The border with GrFileUpload runs along who does the sending. Here a file is an ordinary v-model value, and it leaves together with the rest of the form; there the component uploads by itself and shows the progress of every file.

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

  • A CV that travels with the application

    The field only collects the file: it is a value of the model like the name, and nothing is uploaded until the whole application is sent. The file rule of GrForm checks type and size on submit next to the other rules, while accept on the field filters the system dialog.

    Senior product designer · Dublin or remoteNorthwind Traders · Apply in two minutes
    This field is required
    PDF or Word, up to 5 MB
  • Listing photos with a cover

    The first photo becomes the cover, so the order belongs to the user: reorderable adds a handle to each row, and moving one changes the model. A new pick is added to the set — the button says so with add-text — and limit checks the whole set: a seventh photo is refused, not trimmed silently. preview draws square thumbnails so rows do not jump with the shape of a photo.

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

    Drag to reorder — the first one is the cover

    No files selected
  • A receipt already claimed

    validate runs after the built-in checks and may ask a server — here whether the receipt was already claimed. v-model:errors is the one channel for what the checks found and what the server said, and the error slot words each issue by its code, with a link to the earlier claim.

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

API generated from the package · cannot drift

Props

PropTypedefaultDescription
disabledboolean | undefinedfalseUnavailable: choosing, dragging in and removal are switched off. Combined by "or" with `GrFormField` and the form.
multipleboolean | undefinedfalseA set of several files: the model is an array, and every row has its own removal.
readonlyboolean | undefinedfalseRead-only: the value is visible and leaves with the form, but is not edited.
invalidboolean | undefinedfalseThe visual and ARIA state of an error.
requiredboolean | undefinedfalseA required field (`aria-required`).
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe size of the buttons, the icons and the labels.
placeholderstring | undefinedundefinedThe text by the button while nothing is chosen. By default — the translated "No files selected".
ariaLabelstring | undefinedundefinedThe accessible name outside `GrFormField`.
replaceboolean | undefinedfalseA new choice in `multiple` **replaces** the set rather than adding to it — the old behaviour. Without it the set grows, duplicates (the same name, size and modification time) are dropped, and `limit` and the validators check the set as a whole.
limitnumber | undefinedundefinedThe maximum number of files in the set. Extra ones are not truncated silently — the set is rejected with an error.
validatorsFileValidator[] | undefinedundefinedChecks of the set (`maxFileSize`, `allowedExtensionsValidator` and so on) — after `accept` and `limit`, before `validate`. A rejected set does not get into the model.
acceptstring | undefinedundefinedThe W3C `accept` for the `<input type="file">` plus sugar over `acceptValidator(...)`.
previewboolean | undefinedfalseThumbnails for the pictures in the set. Files of other types stay a row.
validate
((files: File[]) => FileValidationIssue[] | Promise<FileVali((files: File[]) => FileValidationIssue[] | Promise<FileValidationIssue[]>) | undefined
undefinedAdditional (custom) validation on the side of the consumer.

Slots

SlotTypeDescription
error{ errors: FileValidationIssue[]; }Your own presentation of the errors instead of the default list.

Events

EventSignatureDescription
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

MethodsSignatureDescription
focus() => voidPut the focus on the choose-file button.
blur() => voidRemove the focus from the choose-file button.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrFormFileError, GrFormFileProps, GrFormFileEmits } from '@feugene/granularity/components/GrFormFile'

Filedist/types/components/GrFormFile/config.d.ts

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

Filedist/types/components/GrFormFile/GrFormFile.vue.d.ts

GrFormFileErrortype
type GrFormFileError = FileValidationIssue;
GrFormFilePropsinterface
interface GrFormFileProps {
    modelValue: File | File[] | null;
    multiple?: boolean;
    disabled?: boolean;
    readonly?: boolean;
    invalid?: boolean;
    required?: boolean;
    ariaLabel?: string;
    accept?: string;
    validators?: FileValidator[];
    limit?: number;
    uploadText?: string;
    changeText?: string;
    addText?: string;
    replace?: boolean;
    removeText?: string;
    clearAllText?: string;
    placeholder?: string;
    size?: GrFormFileSize;
    preview?: boolean;
    reorderable?: boolean;
    validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>;
GrFormFileEmitsinterface
interface GrFormFileEmits {
    (e: 'update:modelValue', value: File | File[] | null): void;
    (e: 'change', value: File | File[] | null): void;
    (e: 'clear'): void;
    (e: 'update:errors', errors: GrFormFileError[]): void;
    (e: 'focus', event: FocusEvent): void;
    (e: 'blur', event: FocusEvent): void;
}

Filedist/types/components/GrFormFile/defaults.d.ts

GrFormFileConfigurablePropsinterface
interface GrFormFileConfigurableProps {
    size: GrComponentSize;
}

Filedist/types/components/GrFormFile/grFormFileStyles.d.ts

GrFormFileSizetype
type GrFormFileSize = GrComponentSize;

Filedist/types/components/GrFormFile/safelist.d.ts

grFormFileSafelistconst
const grFormFileSafelist: string[];

Filedist/types/components/GrFormFile/index.d.ts

GrFormFileInstancetype
type GrFormFileInstance = ComponentExposed<typeof GrFormFileComponent>;

Usage from the library prose

The errors are a controlled value

v-model:errors is a two-way channel: the internal validation writes into it, and the consumer puts the errors that came from the server into the same place. While the prop is set, it is stronger than the internal list (the same scheme as sortKey in GrDataTable).

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

There is one channel: the validation emit duplicated update:errors with the same payload and has been removed.

The list of errors is declared role="alert" and is linked to the choose button through aria-describedby — together with the aria-describedby from GrFormField, if the field is inside one. While there are errors, the button carries aria-invalid. Before that, “dropped a file of the wrong type” looked to a screen reader like “nothing happened”.

The validation is one for both ways of input

The set of validators is assembled in one place and goes both into the choice through the dialog and into v-dropzone: two copies of that assembly drift apart at the very first edit, and dragging starts behaving differently from the dialog.

The order: accept → limit → the consumer’s validators → validate. limit is sugar over maxCountValidator: extra files are not truncated silently, the set is rejected with an error, as by any other rule.

A choice in multiple is added to the set

A new choice — through the dialog or by dragging — is appended to the files already chosen: a gallery and a list of attachments grow rather than lose what was chosen with every dialog. A repeat of a file already chosen (the same name, size and modification time) is discarded. The button of a non-empty set is “Add files” (gr.formFile.add, a text of your own — addText), and starting over is “Clear all”.

The validators see the set as a whole: limit, maxTotalSizeBytesValidator and validate check what will result after the addition, not the last choice. A rejected choice leaves the set alone — the previous one remains, together with an error.

Replacing the set on every choice — the old behaviour — is available explicitly: replace. The button then reads “Change file” again.

The list of files

In multiple every row shows the name and the size, and the remove button names its file (aria-label) — three “Remove” buttons in a row are indistinguishable to a screen reader. A row takes the full width of the column: a long name is truncated with an ellipsis (the full one is in title), the size and “Remove” stand at a shared right edge, and in a narrow container the button is not pushed out.

Disabled is dimmed with the cursor and with the state of the buttons themselves: an opacity on the container would dilute both the labels and the names of the files.

The order is set by the user

reorderable adds a drag handle to a row. Needed where the sequence of the files means something: a gallery, the pages of a document, a queue of attachments. Meaningful only with multiple.

The mechanics are taken from GrSortableList rather than written anew: dragging with the pointer, the keyboard (Space picks the row up, the arrows move it, Esc cancels), auto-scrolling and announcements are already there. The price is honest — its styles arrive to everyone who takes GrFormFile.

It is the handle that is dragged, not the row: the row holds the remove button, and dragging the whole row would take the click away from it.

Reordering emits update:modelValue and change — like removing and adding. Without the second the order would remain on the screen only, and the previous one would leave for the form. With readonly and disabled dragging is forbidden.

Previews of pictures

preview switches thumbnails on: they appear for image/* files, and a file of any other type stays an ordinary row. A thumbnail is square and is cropped with object-cover — otherwise the rows of the list would jump in height following the proportions of the shots.

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

The alt of a thumbnail is empty: the name of the file stands right beside it, and there is no point announcing it twice. The object URL lives exactly as long as the file is in the set — it is revoked as soon as the file has left the set, whatever removed it.

readonly

The set is visible and leaves with the form, but is changed by nothing: not by the choice dialog, not by dragging, not by the buttons — they are not rendered in this state. The choose button stays in the tab order and announces aria-readonly: a field has to be reachable from the keyboard and able to explain why it does not yield.

The difference from disabled: that one switches the button off as well, that is, the field drops out of the walk entirely.

Inside GrForm

The same constraints can be declared as a rule of the form — beside the rest:

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

The checking is done by the same validators, so the text of the error does not change — what changes is the moment: the rule of the field keeps a bad file out of the model at once, the rule of the form rejects the submit and enters invalid and the scroll to the first error. The details and the full list of keys — GrForm.md.

The typical division: the constraints in rules and accept on the field as a filter for the choice dialog.

What is missing

Uploading to a server: GrFormFile is a form field, and the files leave together with it. Progress and cancellation are covered by GrFileUpload.