uploaderkit 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +16 -0
- package/README.es.md +1140 -0
- package/README.md +1135 -0
- package/dist/FileViewer-ChTf7-uY.d.cts +49 -0
- package/dist/FileViewer-nuD-GEdJ.d.ts +49 -0
- package/dist/adapters/gcs.cjs +83 -0
- package/dist/adapters/gcs.cjs.map +1 -0
- package/dist/adapters/gcs.d.cts +31 -0
- package/dist/adapters/gcs.d.ts +31 -0
- package/dist/adapters/gcs.js +81 -0
- package/dist/adapters/gcs.js.map +1 -0
- package/dist/adapters/memory.cjs +27 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.d.cts +18 -0
- package/dist/adapters/memory.d.ts +18 -0
- package/dist/adapters/memory.js +25 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/s3.cjs +75 -0
- package/dist/adapters/s3.cjs.map +1 -0
- package/dist/adapters/s3.d.cts +27 -0
- package/dist/adapters/s3.d.ts +27 -0
- package/dist/adapters/s3.js +73 -0
- package/dist/adapters/s3.js.map +1 -0
- package/dist/chunk-3FI44IOW.js +150 -0
- package/dist/chunk-3FI44IOW.js.map +1 -0
- package/dist/chunk-H7BRW5IY.js +308 -0
- package/dist/chunk-H7BRW5IY.js.map +1 -0
- package/dist/chunk-PDKAF4GX.js +707 -0
- package/dist/chunk-PDKAF4GX.js.map +1 -0
- package/dist/chunk-PTEX7F4R.js +309 -0
- package/dist/chunk-PTEX7F4R.js.map +1 -0
- package/dist/chunk-T5YZG6JW.js +511 -0
- package/dist/chunk-T5YZG6JW.js.map +1 -0
- package/dist/index.cjs +220 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -170
- package/dist/index.d.ts +57 -170
- package/dist/index.js +1 -324
- package/dist/index.js.map +1 -1
- package/dist/presets.cjs +1665 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +100 -0
- package/dist/presets.d.ts +100 -0
- package/dist/presets.js +405 -0
- package/dist/presets.js.map +1 -0
- package/dist/react.cjs +981 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +106 -0
- package/dist/react.d.ts +106 -0
- package/dist/react.js +5 -0
- package/dist/react.js.map +1 -0
- package/dist/server/express.cjs +179 -0
- package/dist/server/express.cjs.map +1 -0
- package/dist/server/express.d.cts +64 -0
- package/dist/server/express.d.ts +64 -0
- package/dist/server/express.js +111 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/next.cjs +165 -0
- package/dist/server/next.cjs.map +1 -0
- package/dist/server/next.d.cts +38 -0
- package/dist/server/next.d.ts +38 -0
- package/dist/server/next.js +107 -0
- package/dist/server/next.js.map +1 -0
- package/dist/server.cjs +550 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +19 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.js +80 -0
- package/dist/server.js.map +1 -0
- package/dist/storage-CYkSHWZX.d.cts +133 -0
- package/dist/storage-Qc9epG0G.d.ts +133 -0
- package/dist/types-BSlJJwti.d.cts +341 -0
- package/dist/types-BSlJJwti.d.ts +341 -0
- package/dist/ui.cjs +2325 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +331 -0
- package/dist/ui.d.ts +331 -0
- package/dist/ui.js +749 -0
- package/dist/ui.js.map +1 -0
- package/dist/useSlottedUploader-BzT5jV8c.d.cts +139 -0
- package/dist/useSlottedUploader-IkG5RhEO.d.ts +139 -0
- package/dist/useUploader-BiBdS-7y.d.cts +117 -0
- package/dist/useUploader-CQHpj_oI.d.ts +117 -0
- package/package.json +189 -3
- package/tailwind.css +80 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/compressImage.ts","../src/constants.ts","../src/file.ts","../src/react/slots.ts","../src/labels.ts","../src/react/strategy.ts","../src/react/UploaderProvider.tsx","../src/validation.ts","../src/scopes.ts","../src/warn.ts","../src/react/useUploader.ts","../src/react/useSlottedUploader.ts","../src/react/viewResolver.ts"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useCallback","useEffect"],"mappings":"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","file":"react.cjs","sourcesContent":["import type { CompressOptions } from '../types'\n// Formats a canvas can re-encode. GIF would lose animation and SVG would\n// rasterize, so both pass through untouched.\nconst COMPRESSIBLE = new Set(['image/jpeg', 'image/png', 'image/webp'])\n\n/**\n * Client-side image trim before upload: downscale to the scope's box and\n * re-encode at the scope's quality. Re-encoding through a canvas drops EXIF as\n * a side effect — GPS and camera metadata never leave the device, regardless\n * of `stripExif` (a canvas cannot keep it).\n *\n * Falls back to the original file whenever it cannot help: non-compressible\n * formats, environments without `createImageBitmap`, decode failures, or a\n * \"compressed\" result that came out larger.\n */\nexport const compressImage = async (\n\tfile: File,\n\t{ maxWidth, maxHeight, quality = 0.8 }: CompressOptions\n): Promise<File> => {\n\tif (!COMPRESSIBLE.has(file.type)) return file\n\tif (typeof createImageBitmap !== 'function') return file\n\n\ttry {\n\t\tconst bitmap = await createImageBitmap(file)\n\t\tconst scale = Math.min(\n\t\t\t1,\n\t\t\t(maxWidth ?? Infinity) / bitmap.width,\n\t\t\t(maxHeight ?? Infinity) / bitmap.height\n\t\t)\n\n\t\tconst canvas = document.createElement('canvas')\n\t\tcanvas.width = Math.max(1, Math.round(bitmap.width * scale))\n\t\tcanvas.height = Math.max(1, Math.round(bitmap.height * scale))\n\t\tconst context = canvas.getContext('2d')\n\t\tif (!context) return file\n\t\tcontext.drawImage(bitmap, 0, 0, canvas.width, canvas.height)\n\t\tbitmap.close()\n\n\t\tconst blob = await new Promise<Blob | null>(resolve =>\n\t\t\tcanvas.toBlob(resolve, file.type, quality)\n\t\t)\n\t\tif (!blob || blob.size >= file.size) return file\n\n\t\treturn new File([blob], file.name, { type: file.type })\n\t} catch {\n\t\treturn file\n\t}\n}\n","import type { FileCategory, FileCategoryConfig, FileExtension } from './types'\n\nexport const KB = 1024\nexport const MB = 1024 * KB\nexport const GB = 1024 * MB\n\n/**\n * Leading bytes that identify a format regardless of its extension. The check\n * that uses them is the only defence against an executable renamed to `.pdf`,\n * since both the extension and the browser-reported MIME come from the client.\n */\nexport const MAGIC_NUMBERS: Partial<Record<FileExtension, number[]>> = {\n\tpng: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],\n\tjpg: [0xff, 0xd8, 0xff],\n\tjpeg: [0xff, 0xd8, 0xff],\n\tgif: [0x47, 0x49, 0x46, 0x38],\n\twebp: [0x52, 0x49, 0x46, 0x46], // RIFF; bytes 8-11 spell WEBP\n\tico: [0x00, 0x00, 0x01, 0x00],\n\n\tpdf: [0x25, 0x50, 0x44, 0x46], // %PDF\n\n\t// OOXML files are ZIP containers, so docx/xlsx share the PK signature.\n\tdocx: [0x50, 0x4b, 0x03, 0x04],\n\txlsx: [0x50, 0x4b, 0x03, 0x04],\n\tdoc: [0xd0, 0xcf, 0x11, 0xe0],\n\txls: [0xd0, 0xcf, 0x11, 0xe0],\n\n\t// DER/ASN.1 sequence header.\n\tcer: [0x30, 0x82],\n\tcrt: [0x30, 0x82],\n\tkey: [0x30, 0x82],\n\tpem: [0x2d, 0x2d, 0x2d, 0x2d, 0x2d], // -----\n\n\twebm: [0x1a, 0x45, 0xdf, 0xa3],\n\tavi: [0x52, 0x49, 0x46, 0x46],\n\tmp3: [0x49, 0x44, 0x33], // ID3\n\twav: [0x52, 0x49, 0x46, 0x46],\n\togg: [0x4f, 0x67, 0x67, 0x53],\n\taac: [0xff, 0xf1],\n}\n\nexport const MIME_TYPES: Record<FileExtension, string> = {\n\tpng: 'image/png',\n\tjpg: 'image/jpeg',\n\tjpeg: 'image/jpeg',\n\tsvg: 'image/svg+xml',\n\twebp: 'image/webp',\n\tgif: 'image/gif',\n\tico: 'image/x-icon',\n\theic: 'image/heic',\n\n\tcer: 'application/x-x509-ca-cert',\n\tcrt: 'application/x-x509-ca-cert',\n\tpem: 'application/x-pem-file',\n\tkey: 'application/x-pem-file',\n\n\tpdf: 'application/pdf',\n\tdoc: 'application/msword',\n\tdocx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n\txls: 'application/vnd.ms-excel',\n\txlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n\tcsv: 'text/csv',\n\ttxt: 'text/plain',\n\n\txml: 'application/xml',\n\tjson: 'application/json',\n\n\tmp4: 'video/mp4',\n\tmov: 'video/quicktime',\n\tavi: 'video/x-msvideo',\n\twebm: 'video/webm',\n\n\tmp3: 'audio/mpeg',\n\twav: 'audio/wav',\n\togg: 'audio/ogg',\n\taac: 'audio/aac',\n}\n\n/**\n * Defaults per category. A scope always narrows these; it can never widen the\n * extension list, which is what keeps `any` from leaking into a strict scope.\n */\nexport const FILE_CATEGORY_CONFIG: Record<FileCategory, FileCategoryConfig> = {\n\timage: {\n\t\textensions: ['png', 'jpg', 'jpeg', 'svg', 'webp', 'gif'],\n\t\tmaxBytes: 5 * MB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'image',\n\t\taccept: 'image/*',\n\t},\n\tcertificate: {\n\t\textensions: ['cer', 'crt', 'pem'],\n\t\tmaxBytes: 50 * KB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'certificate',\n\t\taccept: '.cer,.crt,.pem',\n\t},\n\tkey: {\n\t\textensions: ['key'],\n\t\tmaxBytes: 50 * KB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'private key',\n\t\taccept: '.key',\n\t},\n\tpdf: {\n\t\textensions: ['pdf'],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'PDF',\n\t\taccept: '.pdf',\n\t},\n\tdocument: {\n\t\textensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt'],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'document',\n\t\taccept: '.pdf,.doc,.docx,.xls,.xlsx,.csv,.txt',\n\t},\n\tdata: {\n\t\textensions: ['xml', 'json'],\n\t\tmaxBytes: 5 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'data file',\n\t\taccept: '.xml,.json',\n\t},\n\tvideo: {\n\t\textensions: ['mp4', 'mov', 'avi', 'webm'],\n\t\tmaxBytes: 100 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'video',\n\t\taccept: 'video/*',\n\t},\n\taudio: {\n\t\textensions: ['mp3', 'wav', 'ogg', 'aac'],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'audio',\n\t\taccept: 'audio/*',\n\t},\n\tany: {\n\t\textensions: [],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'file',\n\t\taccept: '*/*',\n\t},\n}\n","import { MIME_TYPES } from './constants'\nimport type { FileExtension, FileLike } from './types'\n\n/** Lower-case extension without the dot, or `''` when the name has none. */\nexport const getFileExtension = (fileName: string): string => {\n\tconst dot = fileName.lastIndexOf('.')\n\tif (dot < 1 || dot === fileName.length - 1) return ''\n\treturn fileName.slice(dot + 1).toLowerCase()\n}\n\nexport const getMimeType = (fileName: string): string => {\n\tconst ext = getFileExtension(fileName) as FileExtension\n\treturn MIME_TYPES[ext] ?? 'application/octet-stream'\n}\n\nexport const isKnownExtension = (value: string): value is FileExtension =>\n\tvalue in MIME_TYPES\n\nexport const formatFileSize = (bytes: number): string => {\n\tif (bytes < 1024) return `${bytes} B`\n\tconst units = ['KB', 'MB', 'GB', 'TB']\n\tlet size = bytes / 1024\n\tlet unit = 0\n\twhile (size >= 1024 && unit < units.length - 1) {\n\t\tsize /= 1024\n\t\tunit++\n\t}\n\treturn `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`\n}\n\n/** Longest stem kept. Object stores cap the whole key and the prefix spends part of it. */\nconst MAX_STEM_LENGTH = 80\n\n/** What an all-punctuation name degrades to, so a key never ends in a bare dot. */\nconst FALLBACK_STEM = 'file'\n\nconst slugSegment = (value: string): string =>\n\tvalue\n\t\t.normalize('NFD')\n\t\t.replace(/[\\u0300-\\u036f]/g, '')\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '')\n\n/**\n * Turns a user's file name into a safe key segment: ASCII, lower case, one\n * extension, no path syntax.\n *\n * Call it INSIDE your scope's `path()`, not around it:\n *\n * ```ts\n * path: (id, file) => `Docs/${id}/${sanitizeFileName(file.name)}`\n * ```\n *\n * `resolveKey` deliberately does not apply it for you. It would rewrite the key\n * your `path()` returned, and a client computing the same key to decide replace\n * mode would then disagree with the server. Inside `path()` both sides run the\n * one function and cannot drift.\n *\n * This exists because a macOS screenshot took an API down. `Screenshot\n * 2026-08-31 at 4.18.54 p.m..png` ends in `p.m.` + `.png`, so the raw name\n * carries `..`; the traversal guard rejected the key and the consumer dropped\n * the rejection, killing the process.\n *\n * Only the KEY is slugged — keep the original in `StoredFile.fileName`, which\n * is what a UI should render.\n */\nexport const sanitizeFileName = (fileName: string): string => {\n\t// A name arriving as a path is already an attempt, deliberate or not.\n\tconst base = fileName.split(/[/\\\\]/).pop() ?? ''\n\n\tconst dot = base.lastIndexOf('.')\n\t// A leading dot is a dotfile, not an extension: `.env` has no stem.\n\tconst hasExtension = dot > 0\n\tconst stem = slugSegment(hasExtension ? base.slice(0, dot) : base).slice(\n\t\t0,\n\t\tMAX_STEM_LENGTH\n\t)\n\tconst extension = hasExtension ? slugSegment(base.slice(dot + 1)) : ''\n\n\t// A trailing `-` can reappear when the length cut lands mid-separator.\n\tconst safeStem = stem.replace(/-+$/, '') || FALLBACK_STEM\n\n\treturn extension ? `${safeStem}.${extension}` : safeStem\n}\n\n/** Value for an `<input accept=\"…\">` built from an extension list. */\nexport const toAcceptAttribute = (extensions: FileExtension[]): string =>\n\textensions.length === 0\n\t\t? '*/*'\n\t\t: extensions.map(extension => `.${extension}`).join(',')\n\n/**\n * Adapts a multer memory-storage file to `FileLike` so server code validates\n * with the very same function the browser ran.\n */\nexport const fromMulterFile = (file: {\n\toriginalname: string\n\tsize: number\n\tmimetype: string\n\tbuffer: Uint8Array\n}): FileLike => ({\n\tname: file.originalname,\n\tsize: file.size,\n\ttype: file.mimetype,\n\tarrayBuffer: async () =>\n\t\tfile.buffer.buffer.slice(\n\t\t\tfile.buffer.byteOffset,\n\t\t\tfile.buffer.byteOffset + file.buffer.byteLength\n\t\t) as ArrayBuffer,\n})\n","import { getFileExtension } from '../file'\nimport type { FileExtension, StoredFile } from '../types'\n/** A named position a single file can fill (letterhead, CSF, acta, …). */\nexport type SlotDefinition = {\n\t/** Canonical id — becomes the stored file name (`{id}.{ext}`), so the\n\t * scope's `path` yields a stable key and re-uploads overwrite. */\n\tid: string\n\t/** Human label shown in the row. */\n\tlabel: string\n\t/** Lower-case extensions this slot takes. Narrower than the scope, never wider. */\n\textensions: FileExtension[]\n\thint?: string\n}\n\n/** A stored file tagged with the slot it fills. The app persists these. */\nexport type SlottedFile = {\n\tslot: string\n\tstored: StoredFile\n}\n\n/** Resolves which slot an incoming file belongs to, or `null` for none. */\nexport type SlotMatcher = (\n\tslots: SlotDefinition[],\n\tfileName: string\n) => string | null\n\n/**\n * Default matcher: the file's extension picks the slot. Prefers an empty slot\n * so dropping three files fills three positions; falls back to the first\n * extension match, which reads as \"replace what is there\".\n */\nexport const matchSlotByExtension =\n\t(filled: ReadonlySet<string>): SlotMatcher =>\n\t(slots, fileName) => {\n\t\tconst extension = getFileExtension(fileName) as FileExtension\n\t\tconst candidates = slots.filter(slot => slot.extensions.includes(extension))\n\t\tif (candidates.length === 0) return null\n\t\treturn (candidates.find(slot => !filled.has(slot.id)) ?? candidates[0]!).id\n\t}\n\n/** Matcher for uploads named after their slot (`letterhead-a4.pdf` → slot `letterhead-a4`). */\nexport const matchSlotByName: SlotMatcher = (slots, fileName) => {\n\tconst base = fileName.toLowerCase()\n\treturn slots.find(slot => base.startsWith(slot.id.toLowerCase()))?.id ?? null\n}\n\n/** The name a file adopts when it fills a slot. */\nexport const slotFileName = (slotId: string, originalName: string): string =>\n\t`${slotId}.${getFileExtension(originalName)}`\n\n/** The slot a stored file fills, recovered from its name. `null` when foreign. */\nexport const slotOfStored = (\n\tslots: SlotDefinition[],\n\tstored: StoredFile\n): string | null =>\n\tslots.find(slot => stored.fileName.startsWith(`${slot.id}.`))?.id ?? null\n","/**\n * Every user-facing string the package renders, so no copy is hardcoded\n * inside a component and apps translate or reword without forking.\n *\n * English is the default so the kit ships globalized; `ES_LABELS` ships for\n * Spanish apps, selected once through `UploaderProvider language='es'`. Hooks\n * take `labels` in their options, components as a prop — always a `Partial`,\n * merged over the provider's base.\n */\nexport type UploaderLabels = {\n\t/** Prompt inside the single dropzone of `Uploader` (fine pointers). */\n\tdropPrompt: string\n\t/** Same zone on a touch device, where dragging barely exists. */\n\ttapPrompt: string\n\t/** Prompt inside the bulk dropzone of `SlottedUploader` (fine pointers). */\n\tbulkDropPrompt: string\n\t/** Same bulk zone on a touch device. */\n\tbulkTapPrompt: string\n\t/** Button that fires the batch when `uploadOn` is `'manual'`. */\n\tuploadAll: string\n\t/** Row action: preview a file. */\n\tview: string\n\t/** Row action: forget a file. */\n\tremove: string\n\t/** Row action: abort an in-flight upload. */\n\tcancel: string\n\t/** Suffix on a row the server confirmed. */\n\tready: string\n\t/**\n\t * Suffix on a row holding a picked file that has NOT been sent yet.\n\t * Distinct from {@link UploaderLabels.ready}, which means the server\n\t * confirmed it — two states, two strings.\n\t */\n\tstaged: string\n\t/** Slot action when the position is empty. */\n\tupload: string\n\t/** Slot action when the position is filled. */\n\treplace: string\n\t/** Pill shown while a file hovers over a filled slot row. */\n\tdropToReplace: string\n\t/** Viewer: aria-label of the previous-image arrow. */\n\tprevious: string\n\t/** Viewer: aria-label of the next-image arrow. */\n\tnext: string\n\t/** Fallback when a strategy rejects without a message. */\n\tuploadFailed: string\n\t/** The request never reached the server — offline, DNS, CORS preflight. */\n\tuploadNetworkFailed: string\n\t/** Message when the DELETE transport refused or failed. */\n\tremoveFailed: string\n\t/** A stored file could not be fetched for preview or for bytes. */\n\treadFailed: string\n\t/** A batch past `maxFiles`. Receives the cap. */\n\tmaxFilesReached: (max: number) => string\n\t/** A file picked for a slot whose extension the slot rejects. */\n\tslotFormatNotAllowed: (slotLabel: string, extensions: string[]) => string\n\t/** A bulk-dropped file no slot matched. */\n\tfileWithoutSlot: (fileName: string) => string\n\t/** Viewer: while the URL resolves. */\n\tviewerLoading: string\n\t/** Viewer: the URL could not be resolved (expired signature, network). */\n\tviewerError: string\n\t/** Second line of the error panel — what the reader can do about it. */\n\tviewerErrorHint: string\n\t/** Viewer: button that retries the resolution. */\n\tviewerRetry: string\n\t/** Viewer: open the file in a browser tab. */\n\topenInTab: string\n\t/** Header action; the long `download` names a file, this labels a button. */\n\tdownloadShort: string\n\t/** Viewer: close button. */\n\tclose: string\n\t/** Viewer: formats without an inline preview. */\n\tnoPreview: string\n\t/** Viewer: download button. Receives the file name. */\n\tdownload: (fileName: string) => string\n\t/** Viewer: fallback title when the file carries no name. */\n\tfilePreview: string\n\t/** Confirm dialog: affirmative button. */\n\tconfirm: string\n\t/** Confirm dialog heading before forgetting a file. */\n\tconfirmRemoveTitle: string\n\t/** Confirm dialog body before forgetting. Receives the file name. */\n\tconfirmRemoveMessage: (fileName: string) => string\n\t/** Confirm dialog heading before replacing a slot's file. */\n\tconfirmReplaceTitle: string\n\t/** Confirm dialog body before replacing. Receives current and incoming names. */\n\tconfirmReplaceMessage: (current: string, incoming: string) => string\n\t/** `DropAnywhereOverlay` heading while a drag is over the window. */\n\tdropAnywhereTitle: string\n\t/** `DropAnywhereOverlay` second line. */\n\tdropAnywhereHint: string\n\t/** `AvatarUploader`: hover label and aria-label of the picture. */\n\tavatarChange: string\n\t/** `AvatarUploader`: overlay while a file hovers the picture. */\n\tavatarDropHere: string\n\t/** `AvatarUploader`: the remove link under the picture. */\n\tavatarRemove: string\n\t/** `GalleryUploader`: caption of the add tile. */\n\tgalleryAdd: string\n\n\t// Validation — `validateFile` and everything that calls it. These run on\n\t// BOTH sides: the browser shows them, the server answers them as JSON.\n\t/** A name the extension check cannot read at all. */\n\tfileHasNoExtension: string\n\t/** The extension is not on the scope's list. Receives that list. */\n\tformatNotAllowed: (extensions: string[]) => string\n\t/** Zero bytes — a failed drag, a cancelled save. */\n\tfileIsEmpty: string\n\t/** Over the scope ceiling. Receives both sizes already formatted. */\n\tfileTooLarge: (size: string, max: string) => string\n\t/** Fewer bytes than the signature needs: a truncated transfer. */\n\tfileIncomplete: string\n\t/** The leading bytes contradict the extension — renamed, or corrupt. */\n\tcontentDoesNotMatchExtension: string\n\n\t// Server — the HTTP surface. `createStorage` and both framework adapters\n\t// resolve the same object, so one `labels` wires the whole round trip.\n\t/** 400: a route parameter or body field the handler needs is missing. */\n\trequestIncomplete: string\n\t/** 401: `getUser` answered null. */\n\tunauthorized: string\n\t/** 400: an upload arrived with no file part. */\n\tfileRequired: string\n\t/** 404: the URL named a scope the registry does not declare. */\n\tunknownScope: string\n\t/** 500: the catch-all when a handler throws something unrecognized. */\n\tprocessingFailed: string\n\t/** Download name for a key that ends in nothing usable. */\n\tunnamedFile: string\n}\n\n/** Spanish copy — select it once: `<UploaderProvider language='es'>`. */\nexport const ES_LABELS: UploaderLabels = {\n\tdropPrompt: 'Selecciona un archivo o arrástralo aquí',\n\ttapPrompt: 'Toca para elegir un archivo',\n\tbulkDropPrompt: 'Arrastra varios archivos — cada uno cae en su documento',\n\tbulkTapPrompt: 'Toca para agregar varios archivos',\n\tuploadAll: 'Subir archivos',\n\tview: 'Ver',\n\tremove: 'Quitar',\n\tcancel: 'Cancelar',\n\tready: 'Listo',\n\tstaged: 'Listo para subir',\n\tupload: 'Subir',\n\treplace: 'Reemplazar',\n\tdropToReplace: 'Suelta para reemplazar',\n\tprevious: 'Anterior',\n\tnext: 'Siguiente',\n\tuploadFailed: 'No se pudo subir el archivo',\n\tuploadNetworkFailed: 'No se pudo subir el archivo. Revisa tu conexión',\n\tremoveFailed: 'No se pudo borrar el archivo del almacenamiento',\n\treadFailed: 'No se pudo cargar el archivo',\n\tmaxFilesReached: max => `Máximo ${max} archivo(s)`,\n\tslotFormatNotAllowed: (slotLabel, extensions) =>\n\t\t`${slotLabel}: formato no permitido. Se aceptan: ${extensions.join(', ')}`,\n\tfileWithoutSlot: fileName =>\n\t\t`\"${fileName}\" no corresponde a ningún documento`,\n\tviewerLoading: 'Cargando…',\n\tviewerError: 'No se pudo cargar la vista previa',\n\tviewerErrorHint:\n\t\t'El archivo puede haberse movido o el enlace expiró. Puedes reintentar o descargarlo.',\n\tviewerRetry: 'Reintentar',\n\topenInTab: 'Abrir en pestaña',\n\tdownloadShort: 'Descargar',\n\tclose: 'Cerrar',\n\tnoPreview: 'Este formato no tiene vista previa',\n\tdownload: fileName => `Descargar ${fileName}`,\n\tfilePreview: 'Archivo',\n\tconfirm: 'Confirmar',\n\tconfirmRemoveTitle: 'Quitar archivo',\n\tconfirmRemoveMessage: fileName =>\n\t\t`¿Quitar \"${fileName}\"? Podrás subir otro después.`,\n\tconfirmReplaceTitle: 'Reemplazar archivo',\n\tconfirmReplaceMessage: (current, incoming) =>\n\t\t`¿Reemplazar \"${current}\" por \"${incoming}\"?`,\n\tdropAnywhereTitle: 'Suelta los archivos aquí',\n\tdropAnywhereHint: 'Cualquier lugar de la pantalla cuenta',\n\tavatarChange: 'Cambiar foto',\n\tavatarDropHere: 'Suelta aquí',\n\tavatarRemove: 'Quitar foto',\n\tgalleryAdd: 'Agregar',\n\n\tfileHasNoExtension: 'El archivo no tiene extensión',\n\tformatNotAllowed: extensions =>\n\t\t`Formato no permitido. Se aceptan: ${extensions.join(', ')}`,\n\tfileIsEmpty: 'El archivo está vacío',\n\tfileTooLarge: (size, max) => `El archivo pesa ${size} y el máximo es ${max}`,\n\tfileIncomplete: 'El archivo está incompleto',\n\tcontentDoesNotMatchExtension:\n\t\t'El contenido del archivo no corresponde a su extensión',\n\n\trequestIncomplete: 'Solicitud incompleta',\n\tunauthorized: 'No autorizado',\n\tfileRequired: 'Archivo requerido',\n\tunknownScope: 'Destino de archivo no válido',\n\tprocessingFailed: 'No se pudo procesar el archivo',\n\tunnamedFile: 'archivo',\n}\n\n/** English copy — the package default, so the kit ships globalized. */\nexport const DEFAULT_LABELS: UploaderLabels = {\n\tdropPrompt: 'Choose a file or drag it here',\n\ttapPrompt: 'Tap to choose a file',\n\tbulkDropPrompt: 'Drop several files — each lands on its document',\n\tbulkTapPrompt: 'Tap to add several files',\n\tuploadAll: 'Upload files',\n\tview: 'View',\n\tremove: 'Remove',\n\tcancel: 'Cancel',\n\tready: 'Done',\n\tstaged: 'Ready to upload',\n\tupload: 'Upload',\n\treplace: 'Replace',\n\tdropToReplace: 'Drop to replace',\n\tprevious: 'Previous',\n\tnext: 'Next',\n\tuploadFailed: 'The file could not be uploaded',\n\tuploadNetworkFailed: 'The file could not be uploaded. Check your connection',\n\tremoveFailed: 'The file could not be removed from storage',\n\treadFailed: 'The file could not be loaded',\n\tmaxFilesReached: max => `At most ${max} file(s)`,\n\tslotFormatNotAllowed: (slotLabel, extensions) =>\n\t\t`${slotLabel}: format not allowed. Accepted: ${extensions.join(', ')}`,\n\tfileWithoutSlot: fileName => `\"${fileName}\" does not match any document`,\n\tviewerLoading: 'Loading…',\n\tviewerError: 'The preview could not be loaded',\n\tviewerErrorHint:\n\t\t'The file may have moved or the link expired. You can retry or download it.',\n\tviewerRetry: 'Retry',\n\topenInTab: 'Open in tab',\n\tdownloadShort: 'Download',\n\tclose: 'Close',\n\tnoPreview: 'This format has no preview',\n\tdownload: fileName => `Download ${fileName}`,\n\tfilePreview: 'File',\n\tconfirm: 'Confirm',\n\tconfirmRemoveTitle: 'Remove file',\n\tconfirmRemoveMessage: fileName =>\n\t\t`Remove \"${fileName}\"? You can upload another one later.`,\n\tconfirmReplaceTitle: 'Replace file',\n\tconfirmReplaceMessage: (current, incoming) =>\n\t\t`Replace \"${current}\" with \"${incoming}\"?`,\n\tdropAnywhereTitle: 'Drop the files here',\n\tdropAnywhereHint: 'Anywhere on the screen counts',\n\tavatarChange: 'Change photo',\n\tavatarDropHere: 'Drop here',\n\tavatarRemove: 'Remove photo',\n\tgalleryAdd: 'Add',\n\n\tfileHasNoExtension: 'The file has no extension',\n\tformatNotAllowed: extensions =>\n\t\t`Format not allowed. Accepted: ${extensions.join(', ')}`,\n\tfileIsEmpty: 'The file is empty',\n\tfileTooLarge: (size, max) =>\n\t\t`The file weighs ${size} and the maximum is ${max}`,\n\tfileIncomplete: 'The file is incomplete',\n\tcontentDoesNotMatchExtension: 'The file contents do not match its extension',\n\n\trequestIncomplete: 'Incomplete request',\n\tunauthorized: 'Unauthorized',\n\tfileRequired: 'A file is required',\n\tunknownScope: 'Unknown file destination',\n\tprocessingFailed: 'The file could not be processed',\n\tunnamedFile: 'file',\n}\n\n/** The merge every entry point runs: partial overrides over the default. */\nexport const resolveLabels = (\n\tlabels?: Partial<UploaderLabels>,\n\tbase: UploaderLabels = DEFAULT_LABELS\n): UploaderLabels => (labels ? { ...base, ...labels } : base)\n\n/**\n * @deprecated English IS {@link DEFAULT_LABELS} now. A shallow copy, kept so\n * 1.x consumers passing it keep working.\n */\nexport const EN_LABELS: UploaderLabels = { ...DEFAULT_LABELS }\n","import { resolveLabels, type UploaderLabels } from '../labels'\nimport type { StoredFile } from '../types'\nimport type { RemoveStrategy, UploadStrategy } from './types'\n\nexport type XhrUploadStrategyOptions = {\n\t/** Base URL of the storage router, e.g. `${apiUrl}/storage`. */\n\tendpoint: string\n\t/** Evaluated per upload so a rotating JWT is read at send time, not at setup. */\n\theaders?: () => Record<string, string>\n\t/** Multipart field name the server reads. @defaultValue 'file' */\n\tfieldName?: string\n\t/**\n\t * `'include'` sends the session cookie cross-origin, which a cookie-session\n\t * app needs: its api answers on a different origin than the SPA, and the\n\t * browser drops the cookie otherwise. Maps to `XMLHttpRequest.withCredentials`.\n\t *\n\t * @defaultValue 'same-origin'\n\t */\n\tcredentials?: RequestCredentials\n\t/**\n\t * Copy for the two failures the transport itself words. Pass the same\n\t * object the uploader renders with, or the request that fails speaks a\n\t * different language than the row reporting it.\n\t */\n\tlabels?: Partial<UploaderLabels>\n}\n\n/**\n * Default transport: `POST {endpoint}/{scope}/{entityId}/upload` as multipart.\n *\n * XHR instead of `fetch` on purpose — `fetch` still has no usable upload\n * progress in browsers, and per-file progress is half the point of the hook.\n */\nexport const createXhrUploadStrategy =\n\t({\n\t\tendpoint,\n\t\theaders,\n\t\tfieldName = 'file',\n\t\tcredentials,\n\t\tlabels,\n\t}: XhrUploadStrategyOptions): UploadStrategy =>\n\t(file, scope, entityId, { onProgress, signal }) =>\n\t\tnew Promise<StoredFile>((resolve, reject) => {\n\t\t\tconst copy = resolveLabels(labels)\n\t\t\tconst xhr = new XMLHttpRequest()\n\t\t\tconst url = `${endpoint.replace(/\\/$/, '')}/${encodeURIComponent(scope)}/${encodeURIComponent(entityId)}/upload`\n\t\t\txhr.open('POST', url)\n\t\t\txhr.responseType = 'json'\n\t\t\txhr.withCredentials = credentials === 'include'\n\t\t\tfor (const [name, value] of Object.entries(headers?.() ?? {})) {\n\t\t\t\txhr.setRequestHeader(name, value)\n\t\t\t}\n\n\t\t\tconst abort = () => xhr.abort()\n\t\t\tsignal.addEventListener('abort', abort, { once: true })\n\n\t\t\txhr.upload.onprogress = event => {\n\t\t\t\tif (event.lengthComputable) {\n\t\t\t\t\tonProgress(Math.round((event.loaded / event.total) * 100))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\txhr.onload = () => {\n\t\t\t\tsignal.removeEventListener('abort', abort)\n\t\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\t\tresolve(xhr.response as StoredFile)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// The server handler answers { message } already worded for the\n\t\t\t\t// user; the label is only the fallback when it answered nothing.\n\t\t\t\tconst message =\n\t\t\t\t\t(xhr.response as { message?: string } | null)?.message ??\n\t\t\t\t\tcopy.uploadFailed\n\t\t\t\treject(new Error(message))\n\t\t\t}\n\n\t\t\txhr.onerror = () => {\n\t\t\t\tsignal.removeEventListener('abort', abort)\n\t\t\t\treject(new Error(copy.uploadNetworkFailed))\n\t\t\t}\n\n\t\t\txhr.onabort = () => {\n\t\t\t\tsignal.removeEventListener('abort', abort)\n\t\t\t\tconst error = new Error('Upload cancelled')\n\t\t\t\terror.name = 'AbortError'\n\t\t\t\treject(error)\n\t\t\t}\n\n\t\t\tconst form = new FormData()\n\t\t\tform.append(fieldName, file)\n\t\t\txhr.send(form)\n\t\t})\n\nexport type RemoveStrategyOptions = Omit<XhrUploadStrategyOptions, 'fieldName'>\n\n/**\n * Default deletion transport: `DELETE {endpoint}/{scope}/{entityId}` with\n * `{ key }` as JSON — the route the package's own Express/Next handlers\n * document (`router.delete('/:scope/:entityId', handlers.remove)`).\n *\n * Plain `fetch`, not XHR: a delete has no body worth a progress bar, which is\n * the only reason the upload half needs XMLHttpRequest.\n */\nexport const createRemoveStrategy =\n\t({ endpoint, headers, credentials }: RemoveStrategyOptions): RemoveStrategy =>\n\tasync (stored, scope, entityId) => {\n\t\tconst url = `${endpoint.replace(/\\/$/, '')}/${encodeURIComponent(scope)}/${encodeURIComponent(entityId)}`\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: 'DELETE',\n\t\t\tcredentials,\n\t\t\theaders: { 'Content-Type': 'application/json', ...headers?.() },\n\t\t\tbody: JSON.stringify({ key: stored.key }),\n\t\t})\n\t\tif (!response.ok) return false\n\t\tconst body = (await response.json().catch(() => null)) as {\n\t\t\tdeleted?: boolean\n\t\t} | null\n\t\treturn body?.deleted === true\n\t}\n","import { createContext, type ReactNode, useContext, useMemo } from 'react'\n\nimport {\n\tDEFAULT_LABELS,\n\tES_LABELS,\n\tresolveLabels,\n\ttype UploaderLabels,\n} from '../labels'\n\nconst LabelsContext = createContext<UploaderLabels>(DEFAULT_LABELS)\n\n/** The two built-in copy sets. */\nexport type UploaderLanguage = 'en' | 'es'\n\nexport type UploaderProviderProps = {\n\tchildren: ReactNode\n\t/**\n\t * Which built-in copy set every uploader under it speaks. English is the\n\t * default so the kit ships globalized; a Spanish app opts in ONCE here\n\t * instead of passing `labels` to each component.\n\t * @defaultValue 'en'\n\t */\n\tlanguage?: UploaderLanguage\n\t/** Per-key overrides on top of the selected language. */\n\tlabels?: Partial<UploaderLabels>\n}\n\n/**\n * Optional app-root provider carrying the copy. Components work without it\n * (they fall back to {@link DEFAULT_LABELS}), and a per-component `labels`\n * prop still wins for one-off rewording.\n */\nexport const UploaderProvider = ({\n\tchildren,\n\tlanguage = 'en',\n\tlabels,\n}: UploaderProviderProps) => {\n\tconst value = useMemo(\n\t\t() => resolveLabels(labels, language === 'es' ? ES_LABELS : DEFAULT_LABELS),\n\t\t[language, labels]\n\t)\n\n\treturn (\n\t\t<LabelsContext.Provider value={value}>{children}</LabelsContext.Provider>\n\t)\n}\n\n/**\n * The active copy, merged: provider base ← per-call `overrides`. What every\n * component and hook in the package reads instead of `resolveLabels` directly,\n * so a single provider re-words all of them at once.\n */\nexport const useUploaderLabels = (\n\toverrides?: Partial<UploaderLabels>\n): UploaderLabels => {\n\tconst base = useContext(LabelsContext)\n\treturn useMemo(\n\t\t() => (overrides ? { ...base, ...overrides } : base),\n\t\t[base, overrides]\n\t)\n}\n","import { MAGIC_NUMBERS } from './constants'\nimport { formatFileSize, getFileExtension, isKnownExtension } from './file'\nimport { resolveLabels, type UploaderLabels } from './labels'\nimport type {\n\tFileExtension,\n\tFileLike,\n\tValidationOptions,\n\tValidationResult,\n} from './types'\n\nconst ok: ValidationResult = { valid: true }\n\nconst fail = (\n\tcode: Exclude<ValidationResult, { valid: true }>['code'],\n\tmessage: string\n): ValidationResult => ({ valid: false, code, message })\n\nexport const validateExtension = (\n\tfileName: string,\n\tallowed: FileExtension[],\n\tlabels?: Partial<UploaderLabels>\n): ValidationResult => {\n\tif (allowed.length === 0) return ok\n\n\tconst copy = resolveLabels(labels)\n\tconst extension = getFileExtension(fileName)\n\tif (!extension) {\n\t\treturn fail('extension-not-allowed', copy.fileHasNoExtension)\n\t}\n\tif (!allowed.includes(extension as FileExtension)) {\n\t\treturn fail('extension-not-allowed', copy.formatNotAllowed(allowed))\n\t}\n\treturn ok\n}\n\nexport const validateSize = (\n\tsize: number,\n\tmaxBytes: number,\n\tlabels?: Partial<UploaderLabels>\n): ValidationResult => {\n\tconst copy = resolveLabels(labels)\n\tif (size === 0) return fail('empty-file', copy.fileIsEmpty)\n\tif (size > maxBytes) {\n\t\treturn fail(\n\t\t\t'too-large',\n\t\t\tcopy.fileTooLarge(formatFileSize(size), formatFileSize(maxBytes))\n\t\t)\n\t}\n\treturn ok\n}\n\n/**\n * Compares the file's leading bytes against the signature its extension\n * claims. Extensions without a known signature pass — absence of a signature\n * is not evidence of tampering.\n */\nexport const validateMagicNumbers = async (\n\tfile: FileLike,\n\tlabels?: Partial<UploaderLabels>\n): Promise<ValidationResult> => {\n\tconst copy = resolveLabels(labels)\n\tconst extension = getFileExtension(file.name)\n\tif (!isKnownExtension(extension)) return ok\n\n\tconst expected = MAGIC_NUMBERS[extension]\n\tif (!expected) return ok\n\n\tconst head = new Uint8Array(await file.arrayBuffer()).slice(\n\t\t0,\n\t\texpected.length\n\t)\n\tif (head.length < expected.length) {\n\t\treturn fail('magic-number-mismatch', copy.fileIncomplete)\n\t}\n\n\tconst matches = expected.every((byte, index) => head[index] === byte)\n\treturn matches\n\t\t? ok\n\t\t: fail('magic-number-mismatch', copy.contentDoesNotMatchExtension)\n}\n\n/**\n * Runs the checks cheapest-first so a 2 GB file is rejected on its size before\n * anything reads its bytes.\n */\nexport const validateFile = async (\n\tfile: FileLike,\n\toptions: ValidationOptions = {}\n): Promise<ValidationResult> => {\n\tconst {\n\t\tmaxBytes = Infinity,\n\t\tallowedExtensions = [],\n\t\tvalidateMagicNumbers: checkMagic = false,\n\t\tlabels,\n\t\tcustomValidation,\n\t} = options\n\n\tconst size = validateSize(file.size, maxBytes, labels)\n\tif (!size.valid) return size\n\n\tconst extension = validateExtension(file.name, allowedExtensions, labels)\n\tif (!extension.valid) return extension\n\n\tif (checkMagic) {\n\t\tconst magic = await validateMagicNumbers(file, labels)\n\t\tif (!magic.valid) return magic\n\t}\n\n\tif (customValidation) return customValidation(file)\n\n\treturn ok\n}\n\nexport type FileValidation = {\n\tfile: FileLike\n\tresult: ValidationResult\n}\n\n/** Validates every file and reports each result; never short-circuits. */\nexport const validateFiles = async (\n\tfiles: FileLike[],\n\toptions: ValidationOptions = {}\n): Promise<FileValidation[]> =>\n\tPromise.all(\n\t\tfiles.map(async file => ({\n\t\t\tfile,\n\t\t\tresult: await validateFile(file, options),\n\t\t}))\n\t)\n","import { FILE_CATEGORY_CONFIG } from './constants'\nimport { toAcceptAttribute } from './file'\nimport type { UploaderLabels } from './labels'\nimport type {\n\tFileLike,\n\tReplaceMode,\n\tScopeConfig,\n\tScopeRegistry,\n\tStorageProvider,\n\tValidationResult,\n} from './types'\nimport { validateFile } from './validation'\n\n/** Thrown for a definition or wiring mistake — never for a user's bad file. */\nexport class ScopeError extends Error {\n\toverride name = 'ScopeError'\n}\n\n/** Stand-in entity used to compare the SHAPE of two `path` callbacks. */\nconst PROBE_ENTITY = '__entity__'\n\nconst probeFile = (name: string): FileLike => ({\n\tname,\n\tsize: 0,\n\ttype: '',\n\tarrayBuffer: async () => new ArrayBuffer(0),\n})\n\n/** True when `path` returns the same key whatever the file is called. */\nexport const hasStableKey = (scope: ScopeConfig): boolean =>\n\tscope.path(PROBE_ENTITY, probeFile('a.png')) ===\n\tscope.path(PROBE_ENTITY, probeFile('b.png'))\n\n/**\n * The mode a scope runs in when it does not name one.\n *\n * @see AGENTS.md §3 — why a stable key needs no sweep and a name-carrying one does\n */\nexport const resolveReplaceMode = (scope: ScopeConfig): ReplaceMode => {\n\tif (scope.replace !== undefined) return scope.replace\n\tif ((scope.maxFiles ?? 1) > 1) return 'key'\n\treturn hasStableKey(scope) ? 'key' : 'entity'\n}\n\nconst dirname = (key: string) => {\n\tconst cut = key.lastIndexOf('/')\n\treturn cut === -1 ? '' : key.slice(0, cut)\n}\n\n/** Folder an `'entity'` replace is allowed to sweep. */\nexport const resolveScopePrefix = (\n\tscope: ScopeConfig,\n\tentityId: string\n): string =>\n\tscope.prefix?.(entityId) ??\n\tdirname(scope.path(entityId, probeFile('probe.bin')))\n\n/** `a` contains `b`, counting only whole path segments. */\nconst containsPath = (a: string, b: string) => b === a || b.startsWith(`${a}/`)\n\nconst assertDefinition = (name: string, scope: ScopeConfig): void => {\n\tif (typeof scope.path !== 'function') {\n\t\tthrow new ScopeError(`Scope \"${name}\": \"path\" must be a function`)\n\t}\n\tif (!Number.isFinite(scope.maxBytes) || scope.maxBytes <= 0) {\n\t\tthrow new ScopeError(\n\t\t\t`Scope \"${name}\": \"maxBytes\" must be a positive number`\n\t\t)\n\t}\n\tif (scope.accept.length === 0) {\n\t\tthrow new ScopeError(\n\t\t\t`Scope \"${name}\": \"accept\" cannot be empty — list the extensions this scope takes`\n\t\t)\n\t}\n\n\t// A scope narrows its category, never widens it. Otherwise a 'pdf' scope\n\t// could quietly start taking executables.\n\tif (scope.category && scope.category !== 'any') {\n\t\tconst allowed = FILE_CATEGORY_CONFIG[scope.category].extensions\n\t\tconst outside = scope.accept.filter(\n\t\t\textension => !allowed.includes(extension)\n\t\t)\n\t\tif (outside.length > 0) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Scope \"${name}\": ${outside.join(', ')} not allowed by category \"${scope.category}\"`\n\t\t\t)\n\t\t}\n\t}\n\n\tif (scope.compress) {\n\t\tconst imageOnly = FILE_CATEGORY_CONFIG.image.extensions\n\t\tconst notImages = scope.accept.filter(\n\t\t\textension => !imageOnly.includes(extension)\n\t\t)\n\t\tif (notImages.length > 0) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Scope \"${name}\": \"compress\" only applies to images, but it accepts ${notImages.join(', ')}`\n\t\t\t)\n\t\t}\n\t}\n\n\tif (scope.maxFiles !== undefined) {\n\t\tif (!Number.isInteger(scope.maxFiles) || scope.maxFiles < 1) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Scope \"${name}\": \"maxFiles\" must be a positive integer, received ${String(scope.maxFiles)}`\n\t\t\t)\n\t\t}\n\t}\n\n\t// The contradiction the derived default exists to prevent: a scope that\n\t// holds a collection cannot also erase the collection on every upload.\n\tif (scope.replace === 'entity' && (scope.maxFiles ?? 1) > 1) {\n\t\tthrow new ScopeError(\n\t\t\t`Scope \"${name}\": replace \"entity\" contradicts maxFiles ${scope.maxFiles} — ` +\n\t\t\t\t'an entity-wide replace would delete the other files on the next upload. ' +\n\t\t\t\t\"Use 'key' to overwrite one slot, or false to keep every version.\"\n\t\t)\n\t}\n\n\tif (\n\t\tresolveReplaceMode(scope) === 'entity' &&\n\t\tresolveScopePrefix(scope, PROBE_ENTITY) === ''\n\t) {\n\t\tthrow new ScopeError(\n\t\t\t`Scope \"${name}\": replace \"entity\" needs a folder to sweep, but \"path\" resolves to a ` +\n\t\t\t\t'bucket-root key. Nest the key under a folder, or declare \"prefix\".'\n\t\t)\n\t}\n}\n\n/**\n * Rejects a registry where sweeping one scope would reach another's objects.\n *\n * `path` shapes are compared through a placeholder entity, so this catches the\n * real hazard — two scopes sharing a folder for the SAME entity — while two\n * scopes rooted at different entity folders stay independent. A false positive\n * is answered with an explicit `prefix`, and the message says so.\n */\nconst assertNoSweepCollision = (scopes: Record<string, ScopeConfig>): void => {\n\tconst entries = Object.entries(scopes)\n\n\tfor (const [name, scope] of entries) {\n\t\tif (resolveReplaceMode(scope) !== 'entity') continue\n\t\tconst prefix = resolveScopePrefix(scope, PROBE_ENTITY)\n\n\t\tfor (const [otherName, other] of entries) {\n\t\t\tif (otherName === name) continue\n\t\t\tconst otherKey = other.path(PROBE_ENTITY, probeFile('probe.bin'))\n\t\t\tif (!containsPath(prefix, otherKey)) continue\n\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Scope \"${name}\" replaces per entity and would sweep \"${prefix}\", which also holds ` +\n\t\t\t\t\t`scope \"${otherName}\" — an upload to one would delete the other's files. ` +\n\t\t\t\t\t'Give each its own folder, or declare a narrower \"prefix\".'\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * Declares every destination an app can write to. The returned registry is\n * imported by the client (to configure inputs and pre-validate) and by the\n * server (to authorize and re-validate), which is what keeps the two in sync.\n *\n * Definitions are checked eagerly: a malformed scope throws at import time,\n * not on the first upload. That includes the two replace mistakes no runtime\n * check could recover from — an entity-wide replace on a collection, and two\n * scopes whose folders overlap, where an avatar upload would delete the same\n * user's documents.\n */\nexport const defineScopes = <T extends Record<string, ScopeConfig>>(\n\tscopes: T\n): ScopeRegistry<T> => {\n\tfor (const [name, scope] of Object.entries(scopes)) {\n\t\tassertDefinition(name, scope)\n\t}\n\tassertNoSweepCollision(scopes)\n\n\tconst names = Object.keys(scopes) as (keyof T & string)[]\n\n\tconst get = (name: string): ScopeConfig => {\n\t\tconst scope = scopes[name as keyof T]\n\t\tif (!scope) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Unknown scope \"${name}\". Declared: ${names.join(', ')}`\n\t\t\t)\n\t\t}\n\t\treturn scope\n\t}\n\n\treturn {\n\t\tscopes,\n\t\tnames,\n\t\tget,\n\t\thas: name => name in scopes,\n\t\taccept: name => toAcceptAttribute(get(name).accept),\n\t}\n}\n\n/**\n * The one validation call. Client runs it for feedback, server runs it for\n * safety, both against the same scope.\n *\n * `labels` is what keeps the two answers identical in wording as well as in\n * verdict: the hook passes the copy it renders with, `createStorage` passes\n * the copy it was configured with.\n */\nexport const validateForScope = async <T extends Record<string, ScopeConfig>>(\n\tregistry: ScopeRegistry<T>,\n\tname: string,\n\tfile: FileLike,\n\tlabels?: Partial<UploaderLabels>\n): Promise<ValidationResult> => {\n\tconst scope = registry.get(name)\n\tconst category = scope.category\n\t\t? FILE_CATEGORY_CONFIG[scope.category]\n\t\t: undefined\n\n\treturn validateFile(file, {\n\t\tmaxBytes: scope.maxBytes,\n\t\tallowedExtensions: scope.accept,\n\t\tvalidateMagicNumbers: category?.validateMagicNumbers ?? true,\n\t\tlabels,\n\t})\n}\n\n/**\n * Fails fast when a provider cannot honour what the scopes promise — a private\n * scope on a provider that cannot sign URLs is a runtime 403 waiting to happen.\n */\nexport const assertProviderSupports = <T extends Record<string, ScopeConfig>>(\n\tregistry: ScopeRegistry<T>,\n\tprovider: StorageProvider\n): void => {\n\tconst needsSigning = registry.names.filter(\n\t\tname => registry.get(name).visibility === 'private'\n\t)\n\n\tif (needsSigning.length > 0 && !provider.capabilities.signedUrl) {\n\t\tthrow new ScopeError(\n\t\t\t`Provider \"${provider.name}\" cannot sign URLs, but these scopes are private: ${needsSigning.join(', ')}`\n\t\t)\n\t}\n}\n\n/**\n * Whether a key would escape its own prefix.\n *\n * Judged per SEGMENT. A `key.includes('..')` test reads as the same check and\n * is not: `report..pdf` and `Screenshot at 4.18.54 p.m..png` carry two dots\n * without ever being traversal, and a macOS screenshot is the common case, not\n * a corner one. Traversal needs a segment that IS `..`, so that is what this\n * asks.\n */\nconst escapesPrefix = (key: string): boolean =>\n\tkey.startsWith('/') ||\n\tkey.split('/').some(segment => segment === '..' || segment === '.')\n\n/**\n * Resolves the storage key for an upload.\n *\n * The guard is a backstop, not a sanitizer: it refuses a key rather than\n * rewriting one, because a client computing the same key to decide replace\n * mode would then disagree with the server. Run {@link sanitizeFileName}\n * inside the scope's `path()` and both sides stay identical.\n */\nexport const resolveKey = <T extends Record<string, ScopeConfig>>(\n\tregistry: ScopeRegistry<T>,\n\tname: string,\n\tentityId: string,\n\tfile: FileLike\n): string => {\n\tconst key = registry.get(name).path(entityId, file)\n\tif (escapesPrefix(key)) {\n\t\tthrow new ScopeError(`Scope \"${name}\" produced an unsafe key: ${key}`)\n\t}\n\treturn key\n}\n","const seen = new Set<string>()\n\n/**\n * Dev-only warning, once per `key` — for paths that run per render or per\n * file, where the same misconfiguration would otherwise print hundreds of\n * times. Same contract as listkit's `warnDev`.\n *\n * @see AGENTS.md §3.1 — this tier vs the `ScopeError` throw\n */\nexport const warnDev = (key: string, message: string): void => {\n\tif (process.env.NODE_ENV === 'production') return\n\tif (seen.has(key)) return\n\tseen.add(key)\n\tconsole.warn(`[uploaderkit] ${message}`)\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { UploaderLabels } from '../labels'\nimport { validateForScope } from '../scopes'\nimport type { ScopeConfig, ScopeRegistry } from '../types'\nimport type { StoredFile } from '../types'\nimport { warnDev } from '../warn'\nimport { compressImage } from './compressImage'\nimport type { UploaderFile, UploadStrategy } from './types'\nimport { useUploaderLabels } from './UploaderProvider'\n\n/**\n * When the machine sends: `'manual'` holds files in `idle` until the app calls\n * `upload()` — the form-submit flow, where the file travels together with the\n * rest of the fields' action. `'select'` fires as soon as a valid file lands.\n */\nexport type UploadTrigger = 'select' | 'manual'\n\nexport type RetryOptions = {\n\t/** Total attempts per file, the first one included. @defaultValue 1 */\n\tattempts?: number\n\t/** Delay before the first retry; doubles on each further one. @defaultValue 500 */\n\tbackoffMs?: number\n}\n\nexport type UseUploaderOptions<T extends Record<string, ScopeConfig>> = {\n\t/** The app's registry — the same object the server authorizes against. */\n\tscopes: ScopeRegistry<T>\n\tscope: keyof T & string\n\t/** Owner of the uploads (customerId, userId, …), forwarded to the strategy. */\n\tentityId: string\n\t/** Transport. Omit for local-only selection + validation (no `upload`). */\n\tstrategy?: UploadStrategy\n\t/**\n\t * Overrides the arity derived from the scope's `maxFiles`. Rarely needed:\n\t * the registry is what the server validates against.\n\t */\n\tmultiple?: boolean\n\t/** Overrides the scope's own `maxFiles`, downward. */\n\tmaxFiles?: number\n\t/** @defaultValue 'manual' — the styled `Uploader` flips it to `'select'`. */\n\tuploadOn?: UploadTrigger\n\t/**\n\t * Fired when a batch actually starts travelling — from either trigger — with\n\t * the files it carries. The place to flip a form into its \"sending\" state.\n\t */\n\tonUploadStart?: (files: UploaderFile[]) => void\n\t/** Fired once per batch with the files the server confirmed. */\n\tonUploaded?: (stored: StoredFile[]) => void\n\tonError?: (message: string) => void\n\t/**\n\t * Re-run a failed strategy call before surfacing the error. A number is\n\t * shorthand for `{ attempts }`. Aborts never retry, and validation failures\n\t * never reach the strategy in the first place.\n\t */\n\tretry?: number | RetryOptions\n\t/** Max uploads in flight per batch; the rest queue. @defaultValue Infinity */\n\tconcurrency?: number\n\t/**\n\t * Rename each file before it enters the machine — a client-side input, a\n\t * folio, a slug. Runs before validation, so a rename that changes the\n\t * extension is rejected like any other invalid file. The stored key comes\n\t * from the scope's `path(entityId, file)`, which reads this name.\n\t */\n\trename?: (file: File) => string\n\tlabels?: Partial<UploaderLabels>\n}\n\nexport type UseUploaderReturn = {\n\tfiles: UploaderFile[]\n\t/** Ready-made value for `<input accept>`, derived from the scope. */\n\taccept: string\n\tisUploading: boolean\n\t/** `true` while any file waits in `idle` — a manual trigger has work to do. */\n\thasPending: boolean\n\taddFiles: (incoming: FileList | File[]) => Promise<void>\n\t/** Uploads every file still in `idle`. Resolves with the confirmed ones. */\n\tupload: () => Promise<StoredFile[]>\n\t/** Abort one in-flight upload, or all of them when no id is given. */\n\tabort: (id?: string) => void\n\tremoveFile: (id: string) => void\n\tclear: () => void\n}\n\nconst createId = () =>\n\ttypeof crypto !== 'undefined' && 'randomUUID' in crypto\n\t\t? crypto.randomUUID()\n\t\t: Math.random().toString(36).slice(2)\n\nconst previewOf = (file: File): string | undefined =>\n\tfile.type.startsWith('image/') &&\n\ttypeof URL !== 'undefined' &&\n\ttypeof URL.createObjectURL === 'function'\n\t\t? URL.createObjectURL(file)\n\t\t: undefined\n\nconst revokePreview = (file: UploaderFile) => {\n\tif (file.preview) URL.revokeObjectURL(file.preview)\n}\n\nconst normalizeRetry = (retry: number | RetryOptions | undefined) => {\n\tconst options =\n\t\ttypeof retry === 'number' ? { attempts: retry } : (retry ?? {})\n\treturn {\n\t\tattempts: Math.max(1, options.attempts ?? 1),\n\t\tbackoffMs: options.backoffMs ?? 500,\n\t}\n}\n\n/** Abortable sleep: the backoff between retries must die with the upload. */\nconst wait = (ms: number, signal: AbortSignal) =>\n\tnew Promise<void>((resolve, reject) => {\n\t\tconst abort = () => {\n\t\t\tclearTimeout(timer)\n\t\t\tconst error = new Error('Upload cancelled')\n\t\t\terror.name = 'AbortError'\n\t\t\treject(error)\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tsignal.removeEventListener('abort', abort)\n\t\t\tresolve()\n\t\t}, ms)\n\t\tsignal.addEventListener('abort', abort, { once: true })\n\t})\n\n/**\n * Run `run` over every item with at most `limit` in flight. Order of results\n * matches the input; a settled slot immediately frees a worker for the next.\n */\nconst runPool = async <T, R>(\n\titems: T[],\n\tlimit: number,\n\trun: (item: T) => Promise<R>\n): Promise<R[]> => {\n\tconst results = new Array<R>(items.length)\n\tlet cursor = 0\n\tconst workers = Array.from(\n\t\t{ length: Math.min(Math.max(1, limit), items.length) },\n\t\tasync () => {\n\t\t\twhile (cursor < items.length) {\n\t\t\t\tconst index = cursor++\n\t\t\t\tresults[index] = await run(items[index]!)\n\t\t\t}\n\t\t}\n\t)\n\tawait Promise.all(workers)\n\treturn results\n}\n\n/**\n * Headless upload state machine: selection → validation → (compression) →\n * upload with progress and abort. Owns no markup — render `files` however the\n * screen needs and wire `addFiles` to an input or a drop zone.\n *\n * Validation runs here with the same `validateForScope` the server runs, so\n * the user sees the rejection before any byte leaves the machine.\n */\nexport const useUploader = <T extends Record<string, ScopeConfig>>({\n\tscopes,\n\tscope,\n\tentityId,\n\tstrategy,\n\tmultiple: multipleOption,\n\tmaxFiles: maxFilesOption,\n\tuploadOn = 'manual',\n\tonUploadStart,\n\tonUploaded,\n\tonError,\n\tretry,\n\tconcurrency = Infinity,\n\trename,\n\tlabels,\n}: UseUploaderOptions<T>): UseUploaderReturn => {\n\t// Derived from the registry, never from the call site — see AGENTS.md §3.\n\t// The props stay as a narrowing override.\n\tconst scopeMaxFiles = scopes.get(scope).maxFiles\n\tconst maxFiles = maxFilesOption ?? scopeMaxFiles\n\tconst multiple = multipleOption ?? (maxFiles !== undefined && maxFiles > 1)\n\n\tconst [files, setFiles] = useState<UploaderFile[]>([])\n\tconst controllers = useRef(new Map<string, AbortController>())\n\t// Kept in a ref so `addFiles` → auto-upload reads the batch it just built,\n\t// not the state snapshot from before the setFiles commit.\n\tconst filesRef = useRef<UploaderFile[]>([])\n\tfilesRef.current = files\n\tconst copy = useUploaderLabels(labels)\n\tconst { attempts, backoffMs } = normalizeRetry(retry)\n\n\tconst patch = useCallback((id: string, changes: Partial<UploaderFile>) => {\n\t\tsetFiles(previous =>\n\t\t\tprevious.map(file => (file.id === id ? { ...file, ...changes } : file))\n\t\t)\n\t}, [])\n\n\tconst uploadOne = useCallback(\n\t\tasync (target: UploaderFile): Promise<StoredFile | null> => {\n\t\t\tif (!strategy) {\n\t\t\t\twarnDev(\n\t\t\t\t\t'upload-without-strategy',\n\t\t\t\t\t'upload() ran without a `strategy` — files stay local. Pass `strategy` (e.g. createXhrUploadStrategy) to useUploader.'\n\t\t\t\t)\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst config = scopes.get(scope)\n\n\t\t\tconst controller = new AbortController()\n\t\t\tcontrollers.current.set(target.id, controller)\n\t\t\tpatch(target.id, { status: 'uploading', progress: 0 })\n\n\t\t\ttry {\n\t\t\t\tconst payload = config.compress\n\t\t\t\t\t? await compressImage(target.file, config.compress)\n\t\t\t\t\t: target.file\n\n\t\t\t\t// Compression runs once; only the transport retries.\n\t\t\t\tfor (let attempt = 1; ; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst stored = await strategy(payload, scope, entityId, {\n\t\t\t\t\t\t\tonProgress: percent => patch(target.id, { progress: percent }),\n\t\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tpatch(target.id, { status: 'success', progress: 100, stored })\n\t\t\t\t\t\treturn stored\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst aborted =\n\t\t\t\t\t\t\terror instanceof Error && error.name === 'AbortError'\n\t\t\t\t\t\tif (aborted || attempt >= attempts) throw error\n\t\t\t\t\t\tpatch(target.id, { progress: 0 })\n\t\t\t\t\t\tawait wait(backoffMs * 2 ** (attempt - 1), controller.signal)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst aborted = error instanceof Error && error.name === 'AbortError'\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error && error.message\n\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t: copy.uploadFailed\n\t\t\t\tpatch(target.id, {\n\t\t\t\t\tstatus: aborted ? 'idle' : 'error',\n\t\t\t\t\tprogress: 0,\n\t\t\t\t\t...(aborted ? {} : { error: message }),\n\t\t\t\t})\n\t\t\t\tif (!aborted) onError?.(message)\n\t\t\t\treturn null\n\t\t\t} finally {\n\t\t\t\tcontrollers.current.delete(target.id)\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tstrategy,\n\t\t\tscopes,\n\t\t\tscope,\n\t\t\tentityId,\n\t\t\tpatch,\n\t\t\tonError,\n\t\t\tattempts,\n\t\t\tbackoffMs,\n\t\t\tcopy,\n\t\t]\n\t)\n\n\tconst uploadBatch = useCallback(\n\t\tasync (targets: UploaderFile[]): Promise<StoredFile[]> => {\n\t\t\tif (targets.length === 0) return []\n\t\t\tonUploadStart?.(targets)\n\t\t\tconst results = await runPool(targets, concurrency, uploadOne)\n\t\t\tconst stored = results.filter((r): r is StoredFile => r !== null)\n\t\t\tif (stored.length > 0) onUploaded?.(stored)\n\t\t\treturn stored\n\t\t},\n\t\t[uploadOne, onUploadStart, onUploaded, concurrency]\n\t)\n\n\tconst addFiles = useCallback(\n\t\tasync (incoming: FileList | File[]) => {\n\t\t\tlet batch = Array.from(incoming)\n\t\t\tif (!multiple && batch.length > 1) {\n\t\t\t\twarnDev(\n\t\t\t\t\t'single-mode-multi-drop',\n\t\t\t\t\t`addFiles received ${batch.length} files but \\`multiple\\` is false — only the first is kept. Pass multiple: true if that is not what you meant.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (!multiple) batch = batch.slice(0, 1)\n\n\t\t\tif (maxFiles !== undefined) {\n\t\t\t\tconst room = maxFiles - (multiple ? filesRef.current.length : 0)\n\t\t\t\tif (batch.length > room) {\n\t\t\t\t\tbatch = batch.slice(0, Math.max(0, room))\n\t\t\t\t\tonError?.(copy.maxFilesReached(maxFiles))\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (batch.length === 0) return\n\n\t\t\tif (rename) {\n\t\t\t\tbatch = batch.map(\n\t\t\t\t\tfile => new File([file], rename(file), { type: file.type })\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst next: UploaderFile[] = []\n\t\t\tfor (const file of batch) {\n\t\t\t\tconst result = await validateForScope(scopes, scope, file, copy)\n\t\t\t\tnext.push({\n\t\t\t\t\tid: createId(),\n\t\t\t\t\tfile,\n\t\t\t\t\tstatus: result.valid ? 'idle' : 'error',\n\t\t\t\t\tprogress: 0,\n\t\t\t\t\t...(result.valid\n\t\t\t\t\t\t? { preview: previewOf(file) }\n\t\t\t\t\t\t: { error: result.message }),\n\t\t\t\t})\n\t\t\t\tif (!result.valid) onError?.(next[next.length - 1]!.error!)\n\t\t\t}\n\n\t\t\tsetFiles(previous => {\n\t\t\t\tif (multiple) return [...previous, ...next]\n\t\t\t\tprevious.forEach(revokePreview)\n\t\t\t\treturn next\n\t\t\t})\n\n\t\t\tif (uploadOn === 'select') {\n\t\t\t\tawait uploadBatch(next.filter(file => file.status === 'idle'))\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tmultiple,\n\t\t\tmaxFiles,\n\t\t\tscopes,\n\t\t\tscope,\n\t\t\tuploadOn,\n\t\t\tuploadBatch,\n\t\t\tonError,\n\t\t\trename,\n\t\t\tcopy,\n\t\t]\n\t)\n\n\tconst upload = useCallback(\n\t\t() => uploadBatch(filesRef.current.filter(f => f.status === 'idle')),\n\t\t[uploadBatch]\n\t)\n\n\tconst abort = useCallback((id?: string) => {\n\t\tif (id) {\n\t\t\tcontrollers.current.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tcontrollers.current.forEach(controller => controller.abort())\n\t}, [])\n\n\tconst removeFile = useCallback(\n\t\t(id: string) => {\n\t\t\tabort(id)\n\t\t\tsetFiles(previous => {\n\t\t\t\tconst target = previous.find(file => file.id === id)\n\t\t\t\tif (target) revokePreview(target)\n\t\t\t\treturn previous.filter(file => file.id !== id)\n\t\t\t})\n\t\t},\n\t\t[abort]\n\t)\n\n\tconst clear = useCallback(() => {\n\t\tabort()\n\t\tsetFiles(previous => {\n\t\t\tprevious.forEach(revokePreview)\n\t\t\treturn []\n\t\t})\n\t}, [abort])\n\n\t// Object URLs leak until revoked; the unmount sweep catches whatever the\n\t// screen never removed explicitly.\n\tuseEffect(\n\t\t() => () => {\n\t\t\tfilesRef.current.forEach(revokePreview)\n\t\t\tcontrollers.current.forEach(controller => controller.abort())\n\t\t},\n\t\t[]\n\t)\n\n\tconst accept = useMemo(() => scopes.accept(scope), [scopes, scope])\n\n\treturn {\n\t\tfiles,\n\t\taccept,\n\t\tisUploading: files.some(file => file.status === 'uploading'),\n\t\thasPending: files.some(file => file.status === 'idle'),\n\t\taddFiles,\n\t\tupload,\n\t\tabort,\n\t\tremoveFile,\n\t\tclear,\n\t}\n}\n","import { useCallback, useMemo, useRef } from 'react'\n\nimport { getFileExtension, toAcceptAttribute } from '../file'\nimport type { UploaderLabels } from '../labels'\nimport { resolveReplaceMode } from '../scopes'\nimport { ScopeError } from '../scopes'\nimport type { ScopeConfig, ScopeRegistry } from '../types'\nimport type { FileExtension } from '../types'\nimport { warnDev } from '../warn'\nimport {\n\tmatchSlotByExtension,\n\tmatchSlotByName,\n\ttype SlotDefinition,\n\tslotFileName,\n\ttype SlotMatcher,\n\ttype SlottedFile,\n} from './slots'\nimport type { RemoveStrategy, UploaderFile, UploadStrategy } from './types'\nimport { useUploaderLabels } from './UploaderProvider'\nimport {\n\ttype RetryOptions,\n\ttype UploadTrigger,\n\tuseUploader,\n} from './useUploader'\n\nexport type UseSlottedUploaderOptions<T extends Record<string, ScopeConfig>> = {\n\tscopes: ScopeRegistry<T>\n\tscope: keyof T & string\n\tentityId: string\n\tstrategy?: UploadStrategy\n\t/**\n\t * Deletion transport. With it, a confirmed `removeSlot` deletes the object\n\t * from storage BY ITSELF — unless the scope is `keepOnRemove`, the history\n\t * contract — so no consumer can orphan by forgetting a callback.\n\t * `onRemoveStored` still fires for the app's own bookkeeping.\n\t */\n\tremoveStrategy?: RemoveStrategy\n\tslots: SlotDefinition[]\n\t/** Currently persisted files. The caller owns persistence — controlled. */\n\tvalue: SlottedFile[]\n\t/** Called with the next full set after an upload lands or a slot empties. */\n\tonChange: (next: SlottedFile[]) => void | Promise<void>\n\t/** How dropped files find their slot. @defaultValue 'extension' */\n\tmatchBy?: 'extension' | 'name'\n\t/** Custom matcher for special rules; overrides `matchBy`. */\n\tmatch?: SlotMatcher\n\t/** @defaultValue 'select' — a slot names its file, so it usually travels at once. */\n\tuploadOn?: UploadTrigger\n\t/** Fired when a batch starts travelling. Same contract as `useUploader`. */\n\tonUploadStart?: (files: UploaderFile[]) => void\n\t/**\n\t * Fired with the PERSISTED file a `removeSlot` just forgot, before\n\t * `onChange` delivers the filtered set. This is where the app deletes the\n\t * object from storage — without it the consumer had to diff old-vs-new\n\t * arrays to even learn which key vanished, and every stable-key scope\n\t * orphaned on remove.\n\t */\n\tonRemoveStored?: (stored: SlottedFile) => void\n\tonError?: (message: string) => void\n\t/** Forwarded to the underlying `useUploader`. */\n\tretry?: number | RetryOptions\n\t/** Forwarded to the underlying `useUploader`. */\n\tconcurrency?: number\n\tlabels?: Partial<UploaderLabels>\n}\n\nexport type SlotState = {\n\tdefinition: SlotDefinition\n\t/** Persisted file occupying the slot, if any. */\n\tfilled?: SlottedFile\n\t/** In-flight or failed upload for the slot, if any. */\n\tpending?: UploaderFile\n}\n\nexport type UseSlottedUploaderReturn = {\n\tslots: SlotState[]\n\t/** `<input accept>` derived from the union of the slots' extensions. */\n\taccept: string\n\tisUploading: boolean\n\t/** Route a dropped/browsed batch to slots via the matcher. */\n\taddFiles: (incoming: FileList | File[]) => Promise<void>\n\t/** Put one file in one specific slot (per-row browse button). */\n\taddToSlot: (slotId: string, file: File) => Promise<void>\n\t/**\n\t * Forget the persisted file of a slot. Remote deletion stays the app's\n\t * call — made through `onRemoveStored`, which receives the forgotten file.\n\t */\n\tremoveSlot: (slotId: string) => void\n\tabort: (slotId?: string) => void\n\t/** Uploads every slot still holding an `idle` file (`uploadOn: 'manual'`). */\n\tupload: () => Promise<void>\n\t/** `true` while any slot holds an `idle` file waiting for `upload()`. */\n\thasPending: boolean\n}\n\n/**\n * Named-slot collection over the SAME machine as the plain uploader: this hook\n * only decides *which slot* a file lands in and renames it to `{slot}.{ext}`;\n * validation, compression, progress and abort are `useUploader` verbatim. One\n * behavior, two presentations — a fix in the machine reaches both.\n */\nexport const useSlottedUploader = <T extends Record<string, ScopeConfig>>({\n\tscopes,\n\tscope,\n\tentityId,\n\tstrategy,\n\tslots,\n\tvalue,\n\tonChange,\n\tmatchBy = 'extension',\n\tmatch,\n\tuploadOn = 'select',\n\tonUploadStart,\n\tremoveStrategy,\n\tonRemoveStored,\n\tonError,\n\tretry,\n\tconcurrency,\n\tlabels,\n}: UseSlottedUploaderOptions<T>): UseSlottedUploaderReturn => {\n\tconst copy = useUploaderLabels(labels)\n\t// Impossible configs throw, \"will fail at upload time\" configs warn once.\n\t// See AGENTS.md §3.1.\n\tuseMemo(() => {\n\t\tconst ids = new Set<string>()\n\t\tfor (const slot of slots) {\n\t\t\tif (ids.has(slot.id)) {\n\t\t\t\tthrow new ScopeError(\n\t\t\t\t\t`useSlottedUploader: duplicate slot id \"${slot.id}\" — slot ids are storage keys and must be unique`\n\t\t\t\t)\n\t\t\t}\n\t\t\tids.add(slot.id)\n\t\t}\n\t\t// A slot per position means the scope holds several files at once, so a\n\t\t// sweep would delete the sibling slots on every upload — silently, and\n\t\t// only in production. Same tier as a duplicate slot id: it can never\n\t\t// work, so it fails at import.\n\t\tif (\n\t\t\tslots.length > 1 &&\n\t\t\tresolveReplaceMode(scopes.get(scope)) === 'entity'\n\t\t) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`useSlottedUploader: scope \"${scope}\" replaces per entity, so filling one of its ` +\n\t\t\t\t\t`${slots.length} slots would delete the others. Declare \"maxFiles\" on the scope ` +\n\t\t\t\t\t\"(one per slot, at least), or set replace: 'key'.\"\n\t\t\t)\n\t\t}\n\n\t\tconst accepted = scopes.get(scope).accept\n\t\tfor (const slot of slots) {\n\t\t\tconst outside = slot.extensions.filter(\n\t\t\t\textension => !accepted.includes(extension)\n\t\t\t)\n\t\t\tif (outside.length > 0) {\n\t\t\t\twarnDev(\n\t\t\t\t\t`slot-outside-scope:${slot.id}`,\n\t\t\t\t\t`slot \"${slot.id}\" accepts ${outside.join(', ')} but scope \"${scope}\" does not — those uploads will be rejected by validation. Align the slot's extensions with the scope's accept list.`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}, [slots, scopes, scope])\n\n\t// Latest persisted set, readable from inside onUploaded without re-binding\n\t// the uploader callbacks on every render.\n\tconst valueRef = useRef(value)\n\tvalueRef.current = value\n\tconst onChangeRef = useRef(onChange)\n\tonChangeRef.current = onChange\n\n\t// Uploads the parent has not absorbed into `value` yet. Two quick drops\n\t// would otherwise race the controlled state: the second onChange reads a\n\t// `value` that misses the first drop's entry and silently loses it.\n\tconst landedRef = useRef(new Map<string, SlottedFile>())\n\tfor (const [slot, item] of landedRef.current) {\n\t\tconst absorbed = value.some(\n\t\t\tentry =>\n\t\t\t\tentry.slot === slot &&\n\t\t\t\tentry.stored.key === item.stored.key &&\n\t\t\t\tentry.stored.uploadedAt === item.stored.uploadedAt\n\t\t)\n\t\tif (absorbed) landedRef.current.delete(slot)\n\t}\n\n\tconst uploader = useUploader({\n\t\tscopes,\n\t\tscope,\n\t\tentityId,\n\t\tstrategy,\n\t\tmultiple: true,\n\t\tuploadOn,\n\t\tonUploadStart,\n\t\tonError,\n\t\t...(retry !== undefined ? { retry } : {}),\n\t\t...(concurrency !== undefined ? { concurrency } : {}),\n\t\t...(labels ? { labels } : {}),\n\t\tonUploaded: stored => {\n\t\t\t// Files were renamed to `{slot}.{ext}` before entering the machine, so\n\t\t\t// the slot is recoverable from the confirmed name.\n\t\t\tfor (const item of stored) {\n\t\t\t\tconst slot = slots.find(s => item.fileName.startsWith(`${s.id}.`))\n\t\t\t\tif (slot)\n\t\t\t\t\tlandedRef.current.set(slot.id, { slot: slot.id, stored: item })\n\t\t\t}\n\t\t\tconst merged = new Map(valueRef.current.map(entry => [entry.slot, entry]))\n\t\t\tlandedRef.current.forEach((entry, slot) => merged.set(slot, entry))\n\t\t\tvoid onChangeRef.current([...merged.values()])\n\t\t},\n\t})\n\n\tconst resolveSlot = useMemo<SlotMatcher>(() => {\n\t\tif (match) return match\n\t\tif (matchBy === 'name') return matchSlotByName\n\t\treturn (all, fileName) =>\n\t\t\tmatchSlotByExtension(new Set(valueRef.current.map(item => item.slot)))(\n\t\t\t\tall,\n\t\t\t\tfileName\n\t\t\t)\n\t}, [match, matchBy])\n\n\t// Renames a file after its slot, or reports why it cannot fill it.\n\tconst assign = useCallback(\n\t\t(slotId: string, file: File): File | null => {\n\t\t\tconst slot = slots.find(s => s.id === slotId)\n\t\t\tif (!slot) return null\n\n\t\t\tconst extension = getFileExtension(file.name) as FileExtension\n\t\t\tif (!slot.extensions.includes(extension)) {\n\t\t\t\tonError?.(copy.slotFormatNotAllowed(slot.label, slot.extensions))\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn new File([file], slotFileName(slotId, file.name), {\n\t\t\t\ttype: file.type,\n\t\t\t})\n\t\t},\n\t\t[slots, onError, copy]\n\t)\n\n\tconst addToSlot = useCallback(\n\t\tasync (slotId: string, file: File) => {\n\t\t\tconst renamed = assign(slotId, file)\n\t\t\tif (renamed) await uploader.addFiles([renamed])\n\t\t},\n\t\t[assign, uploader]\n\t)\n\n\tconst addFiles = useCallback(\n\t\tasync (incoming: FileList | File[]) => {\n\t\t\t// One batch for the whole drop: a single onUploaded → a single\n\t\t\t// onChange carrying every slot that landed.\n\t\t\tconst batch: File[] = []\n\t\t\tfor (const file of Array.from(incoming)) {\n\t\t\t\tconst slotId = resolveSlot(slots, file.name)\n\t\t\t\tif (!slotId) {\n\t\t\t\t\tonError?.(copy.fileWithoutSlot(file.name))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst renamed = assign(slotId, file)\n\t\t\t\tif (renamed) batch.push(renamed)\n\t\t\t}\n\t\t\tif (batch.length > 0) await uploader.addFiles(batch)\n\t\t},\n\t\t[slots, resolveSlot, assign, uploader, onError, copy]\n\t)\n\n\tconst removeSlot = useCallback(\n\t\t(slotId: string) => {\n\t\t\t// Drop any in-flight attempt for the slot too, or a slow upload would\n\t\t\t// resurrect the file the user just removed.\n\t\t\tuploader.files\n\t\t\t\t.filter(file => file.file.name.startsWith(`${slotId}.`))\n\t\t\t\t.forEach(file => uploader.removeFile(file.id))\n\t\t\tlandedRef.current.delete(slotId)\n\t\t\tconst forgotten = valueRef.current.find(item => item.slot === slotId)\n\t\t\tif (forgotten) {\n\t\t\t\t// The package deletes; the app only observes. Fire-and-forget\n\t\t\t\t// with the reference ordering: forget the reference regardless,\n\t\t\t\t// because a dangling pointer is worse than an orphan — and\n\t\t\t\t// surface a refused delete instead of swallowing it.\n\t\t\t\tif (removeStrategy && !scopes.get(scope).keepOnRemove) {\n\t\t\t\t\tremoveStrategy(forgotten.stored, scope, entityId)\n\t\t\t\t\t\t.then(deleted => {\n\t\t\t\t\t\t\tif (!deleted) onError?.(copy.removeFailed)\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(() => onError?.(copy.removeFailed))\n\t\t\t\t}\n\t\t\t\tonRemoveStored?.(forgotten)\n\t\t\t}\n\t\t\tvoid onChangeRef.current(\n\t\t\t\tvalueRef.current.filter(item => item.slot !== slotId)\n\t\t\t)\n\t\t},\n\t\t[\n\t\t\tuploader,\n\t\t\tremoveStrategy,\n\t\t\tscopes,\n\t\t\tscope,\n\t\t\tentityId,\n\t\t\tonRemoveStored,\n\t\t\tonError,\n\t\t\tcopy,\n\t\t]\n\t)\n\n\tconst abort = useCallback(\n\t\t(slotId?: string) => {\n\t\t\tif (!slotId) {\n\t\t\t\tuploader.abort()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tuploader.files\n\t\t\t\t.filter(file => file.file.name.startsWith(`${slotId}.`))\n\t\t\t\t.forEach(file => uploader.abort(file.id))\n\t\t},\n\t\t[uploader]\n\t)\n\n\tconst slotStates = useMemo<SlotState[]>(\n\t\t() =>\n\t\t\tslots.map(definition => ({\n\t\t\t\tdefinition,\n\t\t\t\tfilled: value.find(item => item.slot === definition.id),\n\t\t\t\tpending: uploader.files.find(\n\t\t\t\t\tfile =>\n\t\t\t\t\t\tfile.file.name.startsWith(`${definition.id}.`) &&\n\t\t\t\t\t\tfile.status !== 'success'\n\t\t\t\t),\n\t\t\t})),\n\t\t[slots, value, uploader.files]\n\t)\n\n\tconst accept = useMemo(\n\t\t() => toAcceptAttribute([...new Set(slots.flatMap(s => s.extensions))]),\n\t\t[slots]\n\t)\n\n\tconst upload = useCallback(async () => {\n\t\tawait uploader.upload()\n\t}, [uploader])\n\n\treturn {\n\t\tslots: slotStates,\n\t\taccept,\n\t\tisUploading: uploader.isUploading,\n\t\thasPending: uploader.hasPending,\n\t\taddFiles,\n\t\taddToSlot,\n\t\tremoveSlot,\n\t\tabort,\n\t\tupload,\n\t}\n}\n","import { resolveLabels, type UploaderLabels } from '../labels'\n\nexport type BlobUrlResolverOptions = {\n\t/** Prefix for app-relative urls (`/storage/...`) — usually the api origin. */\n\tbaseUrl: string\n\t/** Evaluated per request, so a rotating JWT is read at fetch time. */\n\theaders?: () => Record<string, string>\n\t/**\n\t * Forwarded to `fetch` for urls the app owns. Cookie-session apps need\n\t * `'include'`: their api lives on a different origin than the SPA, so the\n\t * browser omits the session cookie under the default.\n\t *\n\t * @defaultValue `fetch`'s own (`'same-origin'`)\n\t */\n\tcredentials?: RequestCredentials\n\t/** Copy for the failure this throws. Defaults to English. */\n\tlabels?: Partial<UploaderLabels>\n}\n\n/**\n * True when `baseUrl` serves this url: app-relative, or absolute on the same\n * origin. The persisted url of an encrypted scope is whatever the server's\n * `encryptedUrl` returned, which is commonly absolute — treating it as foreign\n * would fetch the app's own authenticated endpoint with no credentials.\n */\nconst isOwnUrl = (url: string, baseUrl: string): boolean => {\n\tif (url.startsWith('/')) return true\n\ttry {\n\t\treturn new URL(url).origin === new URL(baseUrl).origin\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * The one rule both resolvers follow — a change to the `/view` shape lands\n * here, not once per output format.\n *\n * @see AGENTS.md §3 — why a foreign url must never carry the app's headers\n */\nconst fetchStored = async (\n\turl: string,\n\t{ baseUrl, headers, credentials, labels }: BlobUrlResolverOptions\n): Promise<Response> => {\n\tconst own = isOwnUrl(url, baseUrl)\n\tconst response = await fetch(\n\t\turl.startsWith('/') ? `${baseUrl.replace(/\\/$/, '')}${url}` : url,\n\t\town ? { headers: headers?.(), credentials } : undefined\n\t)\n\tif (!response.ok) {\n\t\tthrow new Error(resolveLabels(labels).readFailed)\n\t}\n\treturn response\n}\n\n/**\n * An object URL carries the response's Content-Type, and that alone decides how\n * a browser renders a `blob:` — a PDF served as `octet-stream` never reaches\n * the built-in viewer, it silently downloads. When the file declares a type and\n * the response does not, believe the file.\n */\nconst retyped = (blob: Blob, mimeType?: string): Blob =>\n\tmimeType && (!blob.type || blob.type === 'application/octet-stream')\n\t\t? new Blob([blob], { type: mimeType })\n\t\t: blob\n\n/**\n * `resolveUrl` for the FileViewer over authenticated endpoints. An `<img>` or\n * `<iframe>` cannot send an Authorization header or a cross-site cookie, so\n * protected content (the decrypting `/view` route of encrypted scopes) is\n * fetched here with the app's credentials and handed to the viewer as an\n * object URL.\n *\n * Urls on a foreign origin pass through untouched — public objects and legacy\n * hosts render directly. `FileViewer` owns what this mints: it revokes the\n * object URL when it moves to another file and when it closes.\n */\nexport const createBlobUrlResolver =\n\t(options: BlobUrlResolverOptions) =>\n\tasync (file: { url: string; mimeType?: string }): Promise<string> => {\n\t\tif (!isOwnUrl(file.url, options.baseUrl)) return file.url\n\t\tconst blob = await (await fetchStored(file.url, options)).blob()\n\t\treturn URL.createObjectURL(retyped(blob, file.mimeType))\n\t}\n\n/**\n * The same read, as BYTES — for code that processes a stored file rather than\n * displaying it: a pdf-lib render, a canvas, a parser.\n *\n * Takes the url directly, not a `{ url }`: its sibling's shape is dictated by\n * the viewer's `resolveUrl` prop, while this one is called from app code that\n * usually holds a `StoredFile.url` and nothing else.\n */\nexport const createBytesResolver =\n\t(options: BlobUrlResolverOptions) =>\n\tasync (url: string): Promise<Uint8Array> =>\n\t\tnew Uint8Array(await (await fetchStored(url, options)).arrayBuffer())\n\n/**\n * Display name of a persisted url. Understands the `/view?key=…` shape the\n * decrypting endpoint uses (the file name is the key's last segment) and\n * falls back to the url's own last segment for direct links.\n */\nexport const viewUrlFileName = (url: string): string | undefined => {\n\tconst query = url.split('?')[1]\n\tconst key = query ? new URLSearchParams(query).get('key') : null\n\treturn (key ?? url.split('?')[0]!).split('/').pop() || undefined\n}\n"]}
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { h as CompressOptions, U as UploaderLabels } from './types-BSlJJwti.cjs';
|
|
2
|
+
export { S as SlotDefinition, a as SlotMatcher, b as SlotState, c as SlottedFile, U as UploaderLanguage, d as UploaderProvider, e as UploaderProviderProps, f as UseSlottedUploaderOptions, g as UseSlottedUploaderReturn, m as matchSlotByExtension, h as matchSlotByName, s as slotFileName, i as slotOfStored, u as useSlottedUploader, j as useUploaderLabels } from './useSlottedUploader-BzT5jV8c.cjs';
|
|
3
|
+
import { R as RemoveStrategy, a as UploadStrategy } from './useUploader-BiBdS-7y.cjs';
|
|
4
|
+
export { b as RetryOptions, c as UploadStrategyOptions, d as UploadTrigger, e as UploaderFile, U as UseUploaderOptions, f as UseUploaderReturn, u as useUploader } from './useUploader-BiBdS-7y.cjs';
|
|
5
|
+
import 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client-side image trim before upload: downscale to the scope's box and
|
|
9
|
+
* re-encode at the scope's quality. Re-encoding through a canvas drops EXIF as
|
|
10
|
+
* a side effect — GPS and camera metadata never leave the device, regardless
|
|
11
|
+
* of `stripExif` (a canvas cannot keep it).
|
|
12
|
+
*
|
|
13
|
+
* Falls back to the original file whenever it cannot help: non-compressible
|
|
14
|
+
* formats, environments without `createImageBitmap`, decode failures, or a
|
|
15
|
+
* "compressed" result that came out larger.
|
|
16
|
+
*/
|
|
17
|
+
declare const compressImage: (file: File, { maxWidth, maxHeight, quality }: CompressOptions) => Promise<File>;
|
|
18
|
+
|
|
19
|
+
type XhrUploadStrategyOptions = {
|
|
20
|
+
/** Base URL of the storage router, e.g. `${apiUrl}/storage`. */
|
|
21
|
+
endpoint: string;
|
|
22
|
+
/** Evaluated per upload so a rotating JWT is read at send time, not at setup. */
|
|
23
|
+
headers?: () => Record<string, string>;
|
|
24
|
+
/** Multipart field name the server reads. @defaultValue 'file' */
|
|
25
|
+
fieldName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* `'include'` sends the session cookie cross-origin, which a cookie-session
|
|
28
|
+
* app needs: its api answers on a different origin than the SPA, and the
|
|
29
|
+
* browser drops the cookie otherwise. Maps to `XMLHttpRequest.withCredentials`.
|
|
30
|
+
*
|
|
31
|
+
* @defaultValue 'same-origin'
|
|
32
|
+
*/
|
|
33
|
+
credentials?: RequestCredentials;
|
|
34
|
+
/**
|
|
35
|
+
* Copy for the two failures the transport itself words. Pass the same
|
|
36
|
+
* object the uploader renders with, or the request that fails speaks a
|
|
37
|
+
* different language than the row reporting it.
|
|
38
|
+
*/
|
|
39
|
+
labels?: Partial<UploaderLabels>;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Default transport: `POST {endpoint}/{scope}/{entityId}/upload` as multipart.
|
|
43
|
+
*
|
|
44
|
+
* XHR instead of `fetch` on purpose — `fetch` still has no usable upload
|
|
45
|
+
* progress in browsers, and per-file progress is half the point of the hook.
|
|
46
|
+
*/
|
|
47
|
+
declare const createXhrUploadStrategy: ({ endpoint, headers, fieldName, credentials, labels, }: XhrUploadStrategyOptions) => UploadStrategy;
|
|
48
|
+
type RemoveStrategyOptions = Omit<XhrUploadStrategyOptions, 'fieldName'>;
|
|
49
|
+
/**
|
|
50
|
+
* Default deletion transport: `DELETE {endpoint}/{scope}/{entityId}` with
|
|
51
|
+
* `{ key }` as JSON — the route the package's own Express/Next handlers
|
|
52
|
+
* document (`router.delete('/:scope/:entityId', handlers.remove)`).
|
|
53
|
+
*
|
|
54
|
+
* Plain `fetch`, not XHR: a delete has no body worth a progress bar, which is
|
|
55
|
+
* the only reason the upload half needs XMLHttpRequest.
|
|
56
|
+
*/
|
|
57
|
+
declare const createRemoveStrategy: ({ endpoint, headers, credentials }: RemoveStrategyOptions) => RemoveStrategy;
|
|
58
|
+
|
|
59
|
+
type BlobUrlResolverOptions = {
|
|
60
|
+
/** Prefix for app-relative urls (`/storage/...`) — usually the api origin. */
|
|
61
|
+
baseUrl: string;
|
|
62
|
+
/** Evaluated per request, so a rotating JWT is read at fetch time. */
|
|
63
|
+
headers?: () => Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Forwarded to `fetch` for urls the app owns. Cookie-session apps need
|
|
66
|
+
* `'include'`: their api lives on a different origin than the SPA, so the
|
|
67
|
+
* browser omits the session cookie under the default.
|
|
68
|
+
*
|
|
69
|
+
* @defaultValue `fetch`'s own (`'same-origin'`)
|
|
70
|
+
*/
|
|
71
|
+
credentials?: RequestCredentials;
|
|
72
|
+
/** Copy for the failure this throws. Defaults to English. */
|
|
73
|
+
labels?: Partial<UploaderLabels>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* `resolveUrl` for the FileViewer over authenticated endpoints. An `<img>` or
|
|
77
|
+
* `<iframe>` cannot send an Authorization header or a cross-site cookie, so
|
|
78
|
+
* protected content (the decrypting `/view` route of encrypted scopes) is
|
|
79
|
+
* fetched here with the app's credentials and handed to the viewer as an
|
|
80
|
+
* object URL.
|
|
81
|
+
*
|
|
82
|
+
* Urls on a foreign origin pass through untouched — public objects and legacy
|
|
83
|
+
* hosts render directly. `FileViewer` owns what this mints: it revokes the
|
|
84
|
+
* object URL when it moves to another file and when it closes.
|
|
85
|
+
*/
|
|
86
|
+
declare const createBlobUrlResolver: (options: BlobUrlResolverOptions) => (file: {
|
|
87
|
+
url: string;
|
|
88
|
+
mimeType?: string;
|
|
89
|
+
}) => Promise<string>;
|
|
90
|
+
/**
|
|
91
|
+
* The same read, as BYTES — for code that processes a stored file rather than
|
|
92
|
+
* displaying it: a pdf-lib render, a canvas, a parser.
|
|
93
|
+
*
|
|
94
|
+
* Takes the url directly, not a `{ url }`: its sibling's shape is dictated by
|
|
95
|
+
* the viewer's `resolveUrl` prop, while this one is called from app code that
|
|
96
|
+
* usually holds a `StoredFile.url` and nothing else.
|
|
97
|
+
*/
|
|
98
|
+
declare const createBytesResolver: (options: BlobUrlResolverOptions) => (url: string) => Promise<Uint8Array>;
|
|
99
|
+
/**
|
|
100
|
+
* Display name of a persisted url. Understands the `/view?key=…` shape the
|
|
101
|
+
* decrypting endpoint uses (the file name is the key's last segment) and
|
|
102
|
+
* falls back to the url's own last segment for direct links.
|
|
103
|
+
*/
|
|
104
|
+
declare const viewUrlFileName: (url: string) => string | undefined;
|
|
105
|
+
|
|
106
|
+
export { type BlobUrlResolverOptions, RemoveStrategy, type RemoveStrategyOptions, UploadStrategy, type XhrUploadStrategyOptions, compressImage, createBlobUrlResolver, createBytesResolver, createRemoveStrategy, createXhrUploadStrategy, viewUrlFileName };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { h as CompressOptions, U as UploaderLabels } from './types-BSlJJwti.js';
|
|
2
|
+
export { S as SlotDefinition, a as SlotMatcher, b as SlotState, c as SlottedFile, U as UploaderLanguage, d as UploaderProvider, e as UploaderProviderProps, f as UseSlottedUploaderOptions, g as UseSlottedUploaderReturn, m as matchSlotByExtension, h as matchSlotByName, s as slotFileName, i as slotOfStored, u as useSlottedUploader, j as useUploaderLabels } from './useSlottedUploader-IkG5RhEO.js';
|
|
3
|
+
import { R as RemoveStrategy, a as UploadStrategy } from './useUploader-CQHpj_oI.js';
|
|
4
|
+
export { b as RetryOptions, c as UploadStrategyOptions, d as UploadTrigger, e as UploaderFile, U as UseUploaderOptions, f as UseUploaderReturn, u as useUploader } from './useUploader-CQHpj_oI.js';
|
|
5
|
+
import 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client-side image trim before upload: downscale to the scope's box and
|
|
9
|
+
* re-encode at the scope's quality. Re-encoding through a canvas drops EXIF as
|
|
10
|
+
* a side effect — GPS and camera metadata never leave the device, regardless
|
|
11
|
+
* of `stripExif` (a canvas cannot keep it).
|
|
12
|
+
*
|
|
13
|
+
* Falls back to the original file whenever it cannot help: non-compressible
|
|
14
|
+
* formats, environments without `createImageBitmap`, decode failures, or a
|
|
15
|
+
* "compressed" result that came out larger.
|
|
16
|
+
*/
|
|
17
|
+
declare const compressImage: (file: File, { maxWidth, maxHeight, quality }: CompressOptions) => Promise<File>;
|
|
18
|
+
|
|
19
|
+
type XhrUploadStrategyOptions = {
|
|
20
|
+
/** Base URL of the storage router, e.g. `${apiUrl}/storage`. */
|
|
21
|
+
endpoint: string;
|
|
22
|
+
/** Evaluated per upload so a rotating JWT is read at send time, not at setup. */
|
|
23
|
+
headers?: () => Record<string, string>;
|
|
24
|
+
/** Multipart field name the server reads. @defaultValue 'file' */
|
|
25
|
+
fieldName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* `'include'` sends the session cookie cross-origin, which a cookie-session
|
|
28
|
+
* app needs: its api answers on a different origin than the SPA, and the
|
|
29
|
+
* browser drops the cookie otherwise. Maps to `XMLHttpRequest.withCredentials`.
|
|
30
|
+
*
|
|
31
|
+
* @defaultValue 'same-origin'
|
|
32
|
+
*/
|
|
33
|
+
credentials?: RequestCredentials;
|
|
34
|
+
/**
|
|
35
|
+
* Copy for the two failures the transport itself words. Pass the same
|
|
36
|
+
* object the uploader renders with, or the request that fails speaks a
|
|
37
|
+
* different language than the row reporting it.
|
|
38
|
+
*/
|
|
39
|
+
labels?: Partial<UploaderLabels>;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Default transport: `POST {endpoint}/{scope}/{entityId}/upload` as multipart.
|
|
43
|
+
*
|
|
44
|
+
* XHR instead of `fetch` on purpose — `fetch` still has no usable upload
|
|
45
|
+
* progress in browsers, and per-file progress is half the point of the hook.
|
|
46
|
+
*/
|
|
47
|
+
declare const createXhrUploadStrategy: ({ endpoint, headers, fieldName, credentials, labels, }: XhrUploadStrategyOptions) => UploadStrategy;
|
|
48
|
+
type RemoveStrategyOptions = Omit<XhrUploadStrategyOptions, 'fieldName'>;
|
|
49
|
+
/**
|
|
50
|
+
* Default deletion transport: `DELETE {endpoint}/{scope}/{entityId}` with
|
|
51
|
+
* `{ key }` as JSON — the route the package's own Express/Next handlers
|
|
52
|
+
* document (`router.delete('/:scope/:entityId', handlers.remove)`).
|
|
53
|
+
*
|
|
54
|
+
* Plain `fetch`, not XHR: a delete has no body worth a progress bar, which is
|
|
55
|
+
* the only reason the upload half needs XMLHttpRequest.
|
|
56
|
+
*/
|
|
57
|
+
declare const createRemoveStrategy: ({ endpoint, headers, credentials }: RemoveStrategyOptions) => RemoveStrategy;
|
|
58
|
+
|
|
59
|
+
type BlobUrlResolverOptions = {
|
|
60
|
+
/** Prefix for app-relative urls (`/storage/...`) — usually the api origin. */
|
|
61
|
+
baseUrl: string;
|
|
62
|
+
/** Evaluated per request, so a rotating JWT is read at fetch time. */
|
|
63
|
+
headers?: () => Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Forwarded to `fetch` for urls the app owns. Cookie-session apps need
|
|
66
|
+
* `'include'`: their api lives on a different origin than the SPA, so the
|
|
67
|
+
* browser omits the session cookie under the default.
|
|
68
|
+
*
|
|
69
|
+
* @defaultValue `fetch`'s own (`'same-origin'`)
|
|
70
|
+
*/
|
|
71
|
+
credentials?: RequestCredentials;
|
|
72
|
+
/** Copy for the failure this throws. Defaults to English. */
|
|
73
|
+
labels?: Partial<UploaderLabels>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* `resolveUrl` for the FileViewer over authenticated endpoints. An `<img>` or
|
|
77
|
+
* `<iframe>` cannot send an Authorization header or a cross-site cookie, so
|
|
78
|
+
* protected content (the decrypting `/view` route of encrypted scopes) is
|
|
79
|
+
* fetched here with the app's credentials and handed to the viewer as an
|
|
80
|
+
* object URL.
|
|
81
|
+
*
|
|
82
|
+
* Urls on a foreign origin pass through untouched — public objects and legacy
|
|
83
|
+
* hosts render directly. `FileViewer` owns what this mints: it revokes the
|
|
84
|
+
* object URL when it moves to another file and when it closes.
|
|
85
|
+
*/
|
|
86
|
+
declare const createBlobUrlResolver: (options: BlobUrlResolverOptions) => (file: {
|
|
87
|
+
url: string;
|
|
88
|
+
mimeType?: string;
|
|
89
|
+
}) => Promise<string>;
|
|
90
|
+
/**
|
|
91
|
+
* The same read, as BYTES — for code that processes a stored file rather than
|
|
92
|
+
* displaying it: a pdf-lib render, a canvas, a parser.
|
|
93
|
+
*
|
|
94
|
+
* Takes the url directly, not a `{ url }`: its sibling's shape is dictated by
|
|
95
|
+
* the viewer's `resolveUrl` prop, while this one is called from app code that
|
|
96
|
+
* usually holds a `StoredFile.url` and nothing else.
|
|
97
|
+
*/
|
|
98
|
+
declare const createBytesResolver: (options: BlobUrlResolverOptions) => (url: string) => Promise<Uint8Array>;
|
|
99
|
+
/**
|
|
100
|
+
* Display name of a persisted url. Understands the `/view?key=…` shape the
|
|
101
|
+
* decrypting endpoint uses (the file name is the key's last segment) and
|
|
102
|
+
* falls back to the url's own last segment for direct links.
|
|
103
|
+
*/
|
|
104
|
+
declare const viewUrlFileName: (url: string) => string | undefined;
|
|
105
|
+
|
|
106
|
+
export { type BlobUrlResolverOptions, RemoveStrategy, type RemoveStrategyOptions, UploadStrategy, type XhrUploadStrategyOptions, compressImage, createBlobUrlResolver, createBytesResolver, createRemoveStrategy, createXhrUploadStrategy, viewUrlFileName };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createBlobUrlResolver, createBytesResolver, createRemoveStrategy, createXhrUploadStrategy, matchSlotByExtension, matchSlotByName, slotFileName, slotOfStored, useSlottedUploader, viewUrlFileName } from './chunk-PTEX7F4R.js';
|
|
2
|
+
export { UploaderProvider, compressImage, useUploader, useUploaderLabels } from './chunk-H7BRW5IY.js';
|
|
3
|
+
import './chunk-T5YZG6JW.js';
|
|
4
|
+
//# sourceMappingURL=react.js.map
|
|
5
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"react.js"}
|