FileUpload
Visual wrapper for Soma FileUpload: drag/drop, click-to-browse, paste,
validation, accepted file list, previews, removal and progress.
Baseline
- Air: no
src/uix/air/components/file-upload exists in gita/glm-5.
- Soma owns: native file input, picker opening, drag/drop, paste, validation,
accepted/rejected callbacks, item context and object URL previews.
- Eidos owns: dropzone treatment, file row layout, preview shell, action
buttons, progress visuals and selected accent palette.
git ls-tree -r --name-only gita/glm-5 -- src/uix/air/components/file-upload
Parts
| Eidos part |
Soma part |
Contract attrs |
Responsibility |
| Root |
Provider |
data-file-upload, data-empty, data-invalid |
Upload state/context |
| Label |
Label |
data-file-upload-label |
Label linked to hidden input |
| Dropzone |
Dropzone |
data-file-upload-dropzone, data-dragging |
Drop/paste/click surface |
| Trigger |
Trigger |
data-file-upload-trigger |
Explicit picker button |
| HiddenInput |
HiddenInput |
data-file-upload-hidden-input |
Native file input |
| FileList |
FileList |
data-file-upload-file-list |
Accepted file list |
| Item |
Item |
data-file-upload-item, data-file-name, data-file-type |
File row |
| ItemName |
ItemName |
data-file-upload-item-name |
File name |
| ItemSize |
ItemSize |
data-file-upload-item-size |
Formatted size |
| ItemPreview |
ItemPreview |
data-file-upload-item-preview, data-type |
Preview slot |
| ItemRemove |
ItemRemove |
data-file-upload-item-remove |
Remove one file |
| ItemProgress |
ItemProgress |
data-file-upload-item-progress, data-value |
Upload progress |
| ClearTrigger |
ClearTrigger |
data-file-upload-clear-trigger |
Clear all files |
Morfo / Sema
FileUpload is interactive and mixed: some actions are contact-only
(trigger-picker), some are committed state changes and rejection is a
signal.
| Event |
Family |
Verb |
Intent |
Target |
Sequence |
Soma trigger |
trigger-picker |
contact |
trigger |
none |
trigger |
coincident |
Trigger/Dropzone opens picker |
commit-add |
commit |
set |
affirm |
provider |
post |
Accepted files added |
signal-reject |
signal |
warn |
risk |
provider |
post |
File validation rejection |
commit-remove |
commit |
remove |
neutral |
item |
post |
ItemRemove |
commit-clear |
commit |
reset |
neutral |
provider |
post |
ClearTrigger |
Drag hover is not emitted as Sema. The structural state is data-dragging;
continuous pointer/drag movement would be noisy and adds no committed meaning.
External Comparison
| Feature |
Eidos/Soma |
Ark UI File Upload |
React Aria FileTrigger |
shadcn/ui core |
| Compound upload primitive |
yes |
yes |
no, trigger only |
no core component |
| Dropzone |
yes |
yes |
compose manually |
community blocks |
| Hidden native input |
yes |
yes |
yes |
manual |
| Accepted file list |
yes |
yes |
consumer-owned |
manual |
| Rejected files storage |
callback only |
yes |
no |
manual |
| Validation type/size/duplicates/max |
yes |
yes |
accept only |
manual |
| Directory upload |
gap in Soma |
yes |
yes |
manual |
| Transform files async |
gap in Soma |
yes |
no |
manual |
| Clipboard paste |
yes |
yes |
no |
manual |
| Preview by MIME type |
yes |
yes |
consumer-owned |
manual |
| Progress part |
yes |
not upload-transport-owned |
no |
manual |
References:
Decisions
- Eidos keeps
size, variant and color on the root. Dropzone, trigger,
item progress and focus styling inherit those values through CSS variables.
directory, capture and async transformFiles are Soma-level gaps. They
change input attributes, validation and callback timing; Eidos cannot add
them honestly.
- Rejected-file rendering is not an Eidos part yet. Soma currently exposes
onReject only, so rendering rejected rows belongs either to consumer code
or a future Soma rejected-list contract.
ItemProgress projects --_file-upload-progress-value from its value
prop so the recipe can render a bar without adding another child part.
API
<script lang="ts">
import { FileUpload } from '$uix/eidos/components/file-upload';
let files = $state<File[]>([]);
</script>
<FileUpload bind:files accept="image/*" maxFiles={5} color="fulfill">
<FileUpload.Label>Upload images</FileUpload.Label>
<FileUpload.Dropzone>
<FileUpload.HiddenInput />
<FileUpload.Trigger>Select files</FileUpload.Trigger>
</FileUpload.Dropzone>
<FileUpload.FileList>
{#each files as file, index (file.name)}
<FileUpload.Item {file} {index}>
<FileUpload.ItemPreview type="image/" />
<FileUpload.ItemName />
<FileUpload.ItemSize />
<FileUpload.ItemRemove>Remove</FileUpload.ItemRemove>
</FileUpload.Item>
{/each}
</FileUpload.FileList>
</FileUpload>
Comparativa
| Capacidad |
Bits UI |
Ark UI |
React Aria |
shadcn-svelte |
UIX |
| Surface compound (root + parts) |
✓ |
✓ |
✓ |
✓ |
✓ |
| ARIA semantics + keyboard nav |
✓ |
✓ |
✓ |
parcial |
✓ via morfo |
| Variantes visuales (size / variant) |
parcial |
parcial |
n/a |
tailwind |
✓ recipe |
| Form integration |
parcial |
parcial |
✓ |
parcial |
✓ via Field |
| Eventos sema declarativos |
n/a |
n/a |
n/a |
n/a |
✓ |
Decisiones
- El wrapper Eidos se mantiene fino: ergonomía visual + tokens.
Comportamiento y ARIA viven en Soma + Morfo.
- Strings públicos usan idlangref bajo
components.{kebab}.*,
con catálogo en src/uix/langs/components/{kebab}.ts.
- Eventos sema declarados en el morfo cubren los cambios de estado
que cargan peso perceptivo (commit, signal). Las navegaciones
internas por teclado son focus moves, no eventos.
- Cuando la API necesita comportamiento nuevo, va a Soma / Morfo
primero; Eidos sólo expone la receta visual del resultado.
Gaps
| Gap |
Disposición |
Detalle |
| API extendida sobre las referencias externas |
diferir |
Sólo si llega un caso de uso real. Mantener la superficie estable. |
| Cobertura adicional de variantes visuales |
diferir |
El recipe cubre sm/md/lg + solid/outline/ghost. Más variantes requieren caso concreto. |
| Documentación per-prop exhaustiva |
implementar |
Cuando se cierre el ciclo de remediación de cada componente. |
| Tests browser-level del flujo completo (Playwright) |
implementar |
Cobertura visual + interacciones. Se hace en una pasada conjunta de tests. |