pota/use/uploadpota/use/upload is an imperative file-upload primitive plus ref
factories — the same upload pipeline (progress, optional
content-addressed dedup, cancellation) regardless of how the files
arrive. The uploadFile primitive does the
work; upload wires it to a file <input>, and
dropzone wires it to a drop target.
uploadFile(file, options) — imperative
primitive returning Promise<UploadResult>upload(options) — ref factory for <input type="file">
(documented below)dropzone(options) — ref factory for a drop
targetupload(options) attaches to an <input type="file"> and uploads its
selection on every change event. The input is cleared as soon as the
selection is read, so re-selecting the same file fires change again
— opt out with clearOnUpload: false. In-flight uploads abort when
the surrounding reactive scope is disposed.
upload(options) takes a single options object and returns a ref
function (node: HTMLInputElement) => void.
| Option | Type | Description |
|---|---|---|
endpoint |
string |
URL the file is POSTed to as multipart/form-data. |
field |
string |
Form field name for the file (default 'file'). |
existsUrl |
(hash, file) => string |
Optional content-addressed dedup: HEAD this URL first, skip upload on a 2xx. |
parseResponse |
(text, xhr) => string |
Extract the result URL from the response body (default: trimmed body text). |
accept |
string |
<input accept>-style filter; non-matching files fire onReject(file, 'type'). |
maxSize |
number |
Max file size in bytes; larger files fire onReject(file, 'size'). |
onProgress |
({ file, loaded, total }) => void |
Fires during the POST; cached/HEAD hits get one event with loaded === total. |
onUpload |
(results) => void |
Fires once at the end with the array of successful UploadResults; skipped when none succeeded. Required. |
onFile |
(result) => void |
Fires per successfully uploaded file. |
onError |
(error, file) => void |
Fires per file that failed (aborted uploads are silent). |
onReject |
(file, reason) => void |
Fires per file rejected by accept/maxSize; reason is 'type' or 'size'. |
clearOnUpload |
boolean |
Clear the input once the selection is read so the same file can be re-selected (default true). |
Returns: a ref function (node: HTMLInputElement) => void for
use:ref.
accept mirrors <input accept>: MIME types (image/png), wildcard
MIME (image/*), extensions with leading dot (.pdf), comma-
separated lists. maxSize is in bytes. Files that don't match fire
onReject(file, 'type' | 'size') and never hit the network. These
options apply to upload and dropzone
alike.
Attaches upload to a multiple file input with type and size filters
and the full set of lifecycle callbacks.
import { render, signal } from 'pota'
import { upload } from 'pota/use/upload'
function App() {
const log = signal('')
return (
<div>
<input
type="file"
multiple
use:ref={upload({
endpoint: '/api/upload',
accept: 'image/*',
maxSize: 5 * 1024 * 1024,
onUpload: results =>
log.write(`all done ${results.length}`),
onFile: r => log.write(`done ${r.url}`),
onError: (err, file) =>
log.write(`${file.name}: ${err.message}`),
onReject: (file, reason) =>
log.write(`${file.name} rejected: ${reason}`),
})}
/>
<p>{log.read}</p>
</div>
)
}
render(App)
Tracks per-file progress in a signal-backed map and renders
a bar for each upload. onProgress fires repeatedly during the POST.
import { render, signal } from 'pota'
import { For } from 'pota/components'
import { upload } from 'pota/use/upload'
function App() {
const progress = signal<Record<string, number>>({})
const status = signal('')
return (
<>
<input
type="file"
multiple
use:ref={upload({
endpoint: '/api/upload',
onProgress: ({ file, loaded, total }) =>
progress.update(p => ({
...p,
[file.name]: loaded / total,
})),
onUpload: () => status.write('done'),
})}
/>
<p>{status.read}</p>
<For each={() => Object.entries(progress.read())}>
{([name, ratio]) => (
<div>
{name}: {() => Math.round(ratio * 100)}%
</div>
)}
</For>
</>
)
}
render(App)