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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +16 -0
  3. package/README.es.md +1140 -0
  4. package/README.md +1135 -0
  5. package/dist/FileViewer-ChTf7-uY.d.cts +49 -0
  6. package/dist/FileViewer-nuD-GEdJ.d.ts +49 -0
  7. package/dist/adapters/gcs.cjs +83 -0
  8. package/dist/adapters/gcs.cjs.map +1 -0
  9. package/dist/adapters/gcs.d.cts +31 -0
  10. package/dist/adapters/gcs.d.ts +31 -0
  11. package/dist/adapters/gcs.js +81 -0
  12. package/dist/adapters/gcs.js.map +1 -0
  13. package/dist/adapters/memory.cjs +27 -0
  14. package/dist/adapters/memory.cjs.map +1 -0
  15. package/dist/adapters/memory.d.cts +18 -0
  16. package/dist/adapters/memory.d.ts +18 -0
  17. package/dist/adapters/memory.js +25 -0
  18. package/dist/adapters/memory.js.map +1 -0
  19. package/dist/adapters/s3.cjs +75 -0
  20. package/dist/adapters/s3.cjs.map +1 -0
  21. package/dist/adapters/s3.d.cts +27 -0
  22. package/dist/adapters/s3.d.ts +27 -0
  23. package/dist/adapters/s3.js +73 -0
  24. package/dist/adapters/s3.js.map +1 -0
  25. package/dist/chunk-3FI44IOW.js +150 -0
  26. package/dist/chunk-3FI44IOW.js.map +1 -0
  27. package/dist/chunk-H7BRW5IY.js +308 -0
  28. package/dist/chunk-H7BRW5IY.js.map +1 -0
  29. package/dist/chunk-PDKAF4GX.js +707 -0
  30. package/dist/chunk-PDKAF4GX.js.map +1 -0
  31. package/dist/chunk-PTEX7F4R.js +309 -0
  32. package/dist/chunk-PTEX7F4R.js.map +1 -0
  33. package/dist/chunk-T5YZG6JW.js +511 -0
  34. package/dist/chunk-T5YZG6JW.js.map +1 -0
  35. package/dist/index.cjs +220 -27
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +57 -170
  38. package/dist/index.d.ts +57 -170
  39. package/dist/index.js +1 -324
  40. package/dist/index.js.map +1 -1
  41. package/dist/presets.cjs +1665 -0
  42. package/dist/presets.cjs.map +1 -0
  43. package/dist/presets.d.cts +100 -0
  44. package/dist/presets.d.ts +100 -0
  45. package/dist/presets.js +405 -0
  46. package/dist/presets.js.map +1 -0
  47. package/dist/react.cjs +981 -0
  48. package/dist/react.cjs.map +1 -0
  49. package/dist/react.d.cts +106 -0
  50. package/dist/react.d.ts +106 -0
  51. package/dist/react.js +5 -0
  52. package/dist/react.js.map +1 -0
  53. package/dist/server/express.cjs +179 -0
  54. package/dist/server/express.cjs.map +1 -0
  55. package/dist/server/express.d.cts +64 -0
  56. package/dist/server/express.d.ts +64 -0
  57. package/dist/server/express.js +111 -0
  58. package/dist/server/express.js.map +1 -0
  59. package/dist/server/next.cjs +165 -0
  60. package/dist/server/next.cjs.map +1 -0
  61. package/dist/server/next.d.cts +38 -0
  62. package/dist/server/next.d.ts +38 -0
  63. package/dist/server/next.js +107 -0
  64. package/dist/server/next.js.map +1 -0
  65. package/dist/server.cjs +550 -0
  66. package/dist/server.cjs.map +1 -0
  67. package/dist/server.d.cts +19 -0
  68. package/dist/server.d.ts +19 -0
  69. package/dist/server.js +80 -0
  70. package/dist/server.js.map +1 -0
  71. package/dist/storage-CYkSHWZX.d.cts +133 -0
  72. package/dist/storage-Qc9epG0G.d.ts +133 -0
  73. package/dist/types-BSlJJwti.d.cts +341 -0
  74. package/dist/types-BSlJJwti.d.ts +341 -0
  75. package/dist/ui.cjs +2325 -0
  76. package/dist/ui.cjs.map +1 -0
  77. package/dist/ui.d.cts +331 -0
  78. package/dist/ui.d.ts +331 -0
  79. package/dist/ui.js +749 -0
  80. package/dist/ui.js.map +1 -0
  81. package/dist/useSlottedUploader-BzT5jV8c.d.cts +139 -0
  82. package/dist/useSlottedUploader-IkG5RhEO.d.ts +139 -0
  83. package/dist/useUploader-BiBdS-7y.d.cts +117 -0
  84. package/dist/useUploader-CQHpj_oI.d.ts +117 -0
  85. package/package.json +189 -3
  86. package/tailwind.css +80 -0
package/dist/ui.d.cts ADDED
@@ -0,0 +1,331 @@
1
+ import { f as UseSlottedUploaderOptions } from './useSlottedUploader-BzT5jV8c.cjs';
2
+ export { U as UploaderLanguage, d as UploaderProvider, e as UploaderProviderProps, j as useUploaderLabels } from './useSlottedUploader-BzT5jV8c.cjs';
3
+ import * as react from 'react';
4
+ import { ReactNode } from 'react';
5
+ import { U as UploaderLabels, o as StoredFile, d as ScopeConfig } from './types-BSlJJwti.cjs';
6
+ import { e as UploaderFile, R as RemoveStrategy, U as UseUploaderOptions } from './useUploader-BiBdS-7y.cjs';
7
+ import { V as ViewableFile, F as FileViewerProps } from './FileViewer-ChTf7-uY.cjs';
8
+ export { a as FileViewer } from './FileViewer-ChTf7-uY.cjs';
9
+
10
+ /** Minimal class joiner — the package has no runtime deps to spend on clsx. */
11
+ declare const cn: (...values: (string | false | null | undefined)[]) => string;
12
+
13
+ type ConfirmDialogProps = {
14
+ open: boolean;
15
+ title: string;
16
+ message: ReactNode;
17
+ /** Tone of the affirmative button. @defaultValue 'danger' */
18
+ variant?: 'danger' | 'primary';
19
+ confirmLabel?: string;
20
+ cancelLabel?: string;
21
+ onConfirm: () => void;
22
+ onCancel: () => void;
23
+ labels?: Partial<UploaderLabels>;
24
+ };
25
+ /**
26
+ * Second-step gate for destructive file actions — remove a stored file,
27
+ * replace a slot. Focus lands on **cancel** so a stray Enter never destroys
28
+ * anything, Escape and the backdrop cancel, and `Tab` stays inside. Exported
29
+ * for app-level use around the uploaders' callbacks.
30
+ */
31
+ declare const ConfirmDialog: ({ open, title, message, variant, confirmLabel, cancelLabel, onConfirm, onCancel, labels, }: ConfirmDialogProps) => react.ReactPortal | null;
32
+
33
+ /**
34
+ * When a styled uploader sends, as the three flows every product screen falls
35
+ * into — named at the UI level so a call site declares its intent instead of
36
+ * assembling it from `uploadOn` + button + controller by hand:
37
+ *
38
+ * - `'select'` — fires the moment a valid file lands. Avatars, quick
39
+ * replacements: surfaces where the file IS the action and there is nothing
40
+ * else to wait for.
41
+ * - `'submit'` — staged until the surrounding form sends it through
42
+ * `controllerRef`, together with the rest of the fields. The only correct
43
+ * mode when the record the file belongs to does not exist yet, or when a
44
+ * stable-key scope would overwrite what the entity serves before the user
45
+ * confirmed anything.
46
+ * - `'manual'` — staged, and the zone shows its own upload button: the user
47
+ * drops files and decides the moment. Evidence and other punctual flows
48
+ * with no form around them.
49
+ *
50
+ * The hook underneath only knows `'select' | 'manual'` (fire now vs wait for
51
+ * `upload()`): `'submit'` and `'manual'` are the same machine state with a
52
+ * different owner of the send — the form or the zone.
53
+ */
54
+ type UiUploadTrigger = 'select' | 'submit' | 'manual';
55
+ /**
56
+ * What a surrounding form needs to send the staged files from its own submit —
57
+ * the flow `uploadOn: 'manual'` exists for, where the file travels together
58
+ * with the rest of the fields instead of the moment it is picked.
59
+ *
60
+ * A ref rather than a callback prop: the form reads it inside its submit
61
+ * handler, and a controller passed down would re-render the zone on every
62
+ * keystroke elsewhere in the form.
63
+ *
64
+ * @see AGENTS.md §3 — "Arity lives on the scope", same reasoning one level up
65
+ */
66
+ type UploaderController<TResult = StoredFile[]> = {
67
+ /** Uploads everything still waiting. Resolves with what the server confirmed. */
68
+ upload: () => Promise<TResult>;
69
+ /** `true` while a file waits for `upload()`. */
70
+ hasPending: boolean;
71
+ isUploading: boolean;
72
+ };
73
+ /** Slots settle through `onChange`, so the batch itself resolves with nothing. */
74
+ type SlottedUploaderController = UploaderController<void>;
75
+ /**
76
+ * Structural rather than React's `RefObject`: that type is read-only under the
77
+ * v18 typings and writable under v19, and the package supports both.
78
+ * `useRef<UploaderController | null>(null)` satisfies it either way.
79
+ */
80
+ type UploaderControllerRef<TResult = StoredFile[]> = {
81
+ current: UploaderController<TResult> | null;
82
+ };
83
+
84
+ type DropzoneProps = {
85
+ accept: string;
86
+ multiple?: boolean;
87
+ disabled?: boolean;
88
+ /**
89
+ * Mobile camera shortcut: `'environment'` (rear) or `'user'` (front) makes
90
+ * the file picker offer the camera directly on touch devices. Desktop
91
+ * browsers ignore it.
92
+ */
93
+ capture?: 'user' | 'environment';
94
+ /** @defaultValue 'md' */
95
+ size?: 'sm' | 'md';
96
+ /**
97
+ * Global keyboard shortcut that opens this zone's picker, e.g. `'mod+u'`
98
+ * (⌘U / Ctrl+U). A small key hint renders under the prompt on fine
99
+ * pointers. With several zones on screen, give each its own combo.
100
+ */
101
+ shortcut?: string;
102
+ onFiles: (files: File[]) => void;
103
+ children: ReactNode;
104
+ className?: string;
105
+ };
106
+ /**
107
+ * The one drag-and-drop surface both uploaders share: drag state, click and
108
+ * keyboard to browse, paste while focused (screenshots land as files), and a
109
+ * hidden input. Presentation comes from `children`.
110
+ */
111
+ declare const Dropzone: ({ accept, multiple, disabled, capture, size, shortcut, onFiles, children, className, }: DropzoneProps) => react.JSX.Element;
112
+
113
+ type FileItemProps = {
114
+ file: UploaderFile;
115
+ onRemove: () => void;
116
+ onAbort: () => void;
117
+ /** Present on successes when a viewer is available. */
118
+ onView?: () => void;
119
+ /** @defaultValue 'md' */
120
+ size?: 'sm' | 'md';
121
+ labels?: Partial<UploaderLabels>;
122
+ };
123
+ /**
124
+ * One row of upload state: preview/extension badge, name, size, a progress
125
+ * bar while in flight, the error when it failed. Shared by both uploaders so
126
+ * a file always looks the same wherever it is being uploaded.
127
+ */
128
+ declare const FileItem: ({ file, onRemove, onAbort, onView, size, labels, }: FileItemProps) => react.JSX.Element;
129
+
130
+ type SlottedUploaderProps<T extends Record<string, ScopeConfig>> = Omit<UseSlottedUploaderOptions<T>, 'uploadOn'> & {
131
+ /**
132
+ * When the slots travel. This presentation has no upload button of its
133
+ * own, so `'manual'` is not offered here: staged slots can only leave
134
+ * through a form's `controllerRef`, which is `'submit'`.
135
+ * @defaultValue 'select'
136
+ * @see UiUploadTrigger
137
+ */
138
+ uploadOn?: Exclude<UiUploadTrigger, 'manual'>;
139
+ title?: string;
140
+ description?: string;
141
+ /** Hide the bulk dropzone and keep only per-row buttons. */
142
+ hideDropzone?: boolean;
143
+ /**
144
+ * Gate removing a filled slot behind a confirmation dialog. `true` uses
145
+ * the label defaults; an object overrides the copy.
146
+ */
147
+ confirmRemove?: boolean | {
148
+ title?: string;
149
+ message?: string;
150
+ };
151
+ /**
152
+ * Confirm before a picked file replaces what a slot already holds. The
153
+ * dialog names both files, so the user sees what is about to be lost.
154
+ */
155
+ confirmReplace?: boolean | {
156
+ title?: string;
157
+ message?: string;
158
+ };
159
+ /**
160
+ * Deletion transport. With it, a confirmed slot removal deletes the object
161
+ * from storage BY ITSELF — unless the scope is `keepOnRemove`, the history
162
+ * contract — same wiring as `Uploader`.
163
+ */
164
+ removeStrategy?: RemoveStrategy;
165
+ /** Fresh URL right before previewing — for private scopes whose signed url expired. */
166
+ resolveViewUrl?: (file: ViewableFile) => Promise<string>;
167
+ /** Compact paddings and glyphs everywhere. @defaultValue 'md' */
168
+ size?: 'sm' | 'md';
169
+ /** Glyph inside the bulk dropzone. `null` removes it. */
170
+ icon?: ReactNode;
171
+ /** Keyboard shortcut that opens the bulk picker, e.g. `'mod+u'`. */
172
+ shortcut?: string;
173
+ /**
174
+ * Filled with the live controller so the surrounding form can send the
175
+ * staged slots from its own submit. Required by `uploadOn: 'submit'`:
176
+ * without it a staged slot has no way to ever leave.
177
+ */
178
+ controllerRef?: UploaderControllerRef<void>;
179
+ /**
180
+ * Fires when the zone starts or stops holding files that `upload()`
181
+ * would send. The companion of `controllerRef`: a form that defers the
182
+ * send has no other way to know it has unsent work, so its save button
183
+ * would stay disabled on a pristine form the user has just dropped a
184
+ * file into.
185
+ */
186
+ onPendingChange?: (hasPending: boolean) => void;
187
+ className?: string;
188
+ };
189
+ /**
190
+ * Named-slot presentation over the same machine as `Uploader`: a status row
191
+ * per slot plus one bulk dropzone whose matcher routes each file to its slot.
192
+ */
193
+ declare const SlottedUploader: <T extends Record<string, ScopeConfig>>({ uploadOn, title, description, hideDropzone, confirmRemove, confirmReplace, removeStrategy, resolveViewUrl, size, icon, shortcut, controllerRef, onPendingChange, className, ...options }: SlottedUploaderProps<T>) => react.JSX.Element;
194
+
195
+ /**
196
+ * What {@link UploaderProps.renderFiles} is handed.
197
+ *
198
+ * Both the raw state and the default rows already built, so a custom layout can
199
+ * keep the standard item and only change where — and among what — it sits.
200
+ */
201
+ type UploaderFilesSlot = {
202
+ /** Files already persisted, exactly as passed in `stored`. */
203
+ stored: StoredFile[];
204
+ /** Files staged in this session, each with its live upload status. */
205
+ staged: UploaderFile[];
206
+ /** The default rows. Place them, wrap them, or drop them entirely. */
207
+ nodes: ReactNode;
208
+ /** Nothing persisted and nothing staged. */
209
+ isEmpty: boolean;
210
+ /** Open a persisted file in the built-in viewer. */
211
+ view: (file: StoredFile) => void;
212
+ /** Forget a staged file. */
213
+ remove: (id: string) => void;
214
+ /**
215
+ * Forget a persisted file, honouring `confirmRemove` and `removeStrategy`.
216
+ * `undefined` when this uploader accepts no removals.
217
+ */
218
+ removeStored?: (file: StoredFile) => void;
219
+ };
220
+ type UploaderProps<T extends Record<string, ScopeConfig>> = Omit<UseUploaderOptions<T>, 'uploadOn'> & {
221
+ /**
222
+ * When the files travel. @defaultValue 'select'
223
+ * @see UiUploadTrigger — which mode fits which screen
224
+ */
225
+ uploadOn?: UiUploadTrigger;
226
+ label?: string;
227
+ description?: string;
228
+ /** Files already persisted, rendered on the {@link filesPosition} side. */
229
+ stored?: StoredFile[];
230
+ /**
231
+ * Which side of the dropzone the file lists sit on. @defaultValue 'above'
232
+ *
233
+ * `'below'` keeps the drop target anchored: with the lists above it, every
234
+ * added file pushes the zone further down, so the control the user is
235
+ * repeatedly aiming at moves under the cursor.
236
+ */
237
+ filesPosition?: 'above' | 'below';
238
+ /**
239
+ * Lay the file lists out yourself: a grid of thumbnails, a single summary
240
+ * line, a count folded into your own card — whatever the screen needs.
241
+ *
242
+ * It replaces the rows, not their place: the result still renders on the
243
+ * {@link filesPosition} side. For a layout the zone itself has to be part of
244
+ * (files BESIDE the dropzone, files inside your own frame), skip this skin
245
+ * and compose `useUploader` with the exported `Dropzone` / `FileItem` /
246
+ * `StoredFileItem` — nothing here is unavailable there.
247
+ */
248
+ renderFiles?: (slot: UploaderFilesSlot) => ReactNode;
249
+ /**
250
+ * Notified when a persisted file is forgotten — the app's bookkeeping
251
+ * (clear the DB reference). Storage deletion belongs to `removeStrategy`.
252
+ */
253
+ onRemoveStored?: (file: StoredFile) => void;
254
+ /**
255
+ * Deletion transport. With it, a confirmed removal deletes the object from
256
+ * storage BY ITSELF — unless the scope is `keepOnRemove`, the history
257
+ * contract — so no consumer can orphan by forgetting a callback.
258
+ */
259
+ removeStrategy?: RemoveStrategy;
260
+ /**
261
+ * Gate `onRemoveStored` behind a confirmation dialog. `true` uses the
262
+ * label defaults; an object overrides the copy for this uploader.
263
+ */
264
+ confirmRemove?: boolean | {
265
+ title?: string;
266
+ message?: string;
267
+ };
268
+ /** Fresh URL right before previewing — for private scopes whose signed url expired. */
269
+ resolveViewUrl?: (file: ViewableFile) => Promise<string>;
270
+ /** Mobile camera shortcut, forwarded to the dropzone's input. */
271
+ capture?: 'user' | 'environment';
272
+ /** Compact paddings and glyphs everywhere. @defaultValue 'md' */
273
+ size?: 'sm' | 'md';
274
+ /** Glyph inside the dropzone. `null` removes it; defaults to an upload cloud. */
275
+ icon?: ReactNode;
276
+ /** Keyboard shortcut that opens the picker, e.g. `'mod+u'`. Hinted in the zone. */
277
+ shortcut?: string;
278
+ /**
279
+ * Filled with the live controller so the surrounding form can send the
280
+ * staged files from its own submit. Required by `uploadOn: 'submit'`,
281
+ * which has no other way to fire; readable under `'manual'` too, where
282
+ * the zone's button stays the sender.
283
+ */
284
+ controllerRef?: UploaderControllerRef;
285
+ /**
286
+ * Fires when the zone starts or stops holding files that `upload()`
287
+ * would send. The companion of `controllerRef`: a form that defers the
288
+ * send has no other way to know it has unsent work, so its save button
289
+ * would stay disabled on a pristine form the user has just dropped a
290
+ * file into.
291
+ */
292
+ onPendingChange?: (hasPending: boolean) => void;
293
+ disabled?: boolean;
294
+ className?: string;
295
+ };
296
+ /**
297
+ * The styled single-zone uploader: one dropzone, one or many files in it.
298
+ * Purely a skin over `useUploader` — apps needing custom markup use the hook
299
+ * directly and lose nothing.
300
+ *
301
+ * Defaults `uploadOn` to `'select'`. `'manual'` gives the zone its own upload
302
+ * button; `'submit'` hands the send to the surrounding form via
303
+ * `controllerRef`. @see UiUploadTrigger for which mode fits which screen.
304
+ */
305
+ declare const Uploader: <T extends Record<string, ScopeConfig>>({ uploadOn, label, description, stored, filesPosition, renderFiles, onRemoveStored, confirmRemove, resolveViewUrl, capture, size, icon, shortcut, controllerRef, onPendingChange, removeStrategy, disabled, className, ...options }: UploaderProps<T>) => react.JSX.Element;
306
+
307
+ type UseFileViewerOptions = {
308
+ /** Forwarded to the viewer — the authenticated resolver for private urls. */
309
+ resolveUrl?: FileViewerProps['resolveUrl'];
310
+ };
311
+ type UseFileViewerReturn = {
312
+ /** Currently open file, `null` when closed. */
313
+ viewing: ViewableFile | null;
314
+ open: (file: ViewableFile) => void;
315
+ close: () => void;
316
+ /** Spread into `<FileViewer {...viewerProps} />` — the whole wiring. */
317
+ viewerProps: Pick<FileViewerProps, 'file' | 'onClose' | 'resolveUrl'>;
318
+ };
319
+ /**
320
+ * The open/close state every screen with a `FileViewer` repeats. One call
321
+ * replaces the `useState` + prop plumbing:
322
+ *
323
+ * ```tsx
324
+ * const viewer = useFileViewer({ resolveUrl })
325
+ * <button onClick={() => viewer.open(stored)}>Ver</button>
326
+ * <FileViewer {...viewer.viewerProps} />
327
+ * ```
328
+ */
329
+ declare const useFileViewer: ({ resolveUrl, }?: UseFileViewerOptions) => UseFileViewerReturn;
330
+
331
+ export { ConfirmDialog, type ConfirmDialogProps, Dropzone, type DropzoneProps, FileItem, type FileItemProps, FileViewerProps, SlottedUploader, type SlottedUploaderController, type SlottedUploaderProps, type UiUploadTrigger, Uploader, type UploaderController, type UploaderControllerRef, type UploaderFilesSlot, type UploaderProps, type UseFileViewerOptions, type UseFileViewerReturn, ViewableFile, cn, useFileViewer };
package/dist/ui.d.ts ADDED
@@ -0,0 +1,331 @@
1
+ import { f as UseSlottedUploaderOptions } from './useSlottedUploader-IkG5RhEO.js';
2
+ export { U as UploaderLanguage, d as UploaderProvider, e as UploaderProviderProps, j as useUploaderLabels } from './useSlottedUploader-IkG5RhEO.js';
3
+ import * as react from 'react';
4
+ import { ReactNode } from 'react';
5
+ import { U as UploaderLabels, o as StoredFile, d as ScopeConfig } from './types-BSlJJwti.js';
6
+ import { e as UploaderFile, R as RemoveStrategy, U as UseUploaderOptions } from './useUploader-CQHpj_oI.js';
7
+ import { V as ViewableFile, F as FileViewerProps } from './FileViewer-nuD-GEdJ.js';
8
+ export { a as FileViewer } from './FileViewer-nuD-GEdJ.js';
9
+
10
+ /** Minimal class joiner — the package has no runtime deps to spend on clsx. */
11
+ declare const cn: (...values: (string | false | null | undefined)[]) => string;
12
+
13
+ type ConfirmDialogProps = {
14
+ open: boolean;
15
+ title: string;
16
+ message: ReactNode;
17
+ /** Tone of the affirmative button. @defaultValue 'danger' */
18
+ variant?: 'danger' | 'primary';
19
+ confirmLabel?: string;
20
+ cancelLabel?: string;
21
+ onConfirm: () => void;
22
+ onCancel: () => void;
23
+ labels?: Partial<UploaderLabels>;
24
+ };
25
+ /**
26
+ * Second-step gate for destructive file actions — remove a stored file,
27
+ * replace a slot. Focus lands on **cancel** so a stray Enter never destroys
28
+ * anything, Escape and the backdrop cancel, and `Tab` stays inside. Exported
29
+ * for app-level use around the uploaders' callbacks.
30
+ */
31
+ declare const ConfirmDialog: ({ open, title, message, variant, confirmLabel, cancelLabel, onConfirm, onCancel, labels, }: ConfirmDialogProps) => react.ReactPortal | null;
32
+
33
+ /**
34
+ * When a styled uploader sends, as the three flows every product screen falls
35
+ * into — named at the UI level so a call site declares its intent instead of
36
+ * assembling it from `uploadOn` + button + controller by hand:
37
+ *
38
+ * - `'select'` — fires the moment a valid file lands. Avatars, quick
39
+ * replacements: surfaces where the file IS the action and there is nothing
40
+ * else to wait for.
41
+ * - `'submit'` — staged until the surrounding form sends it through
42
+ * `controllerRef`, together with the rest of the fields. The only correct
43
+ * mode when the record the file belongs to does not exist yet, or when a
44
+ * stable-key scope would overwrite what the entity serves before the user
45
+ * confirmed anything.
46
+ * - `'manual'` — staged, and the zone shows its own upload button: the user
47
+ * drops files and decides the moment. Evidence and other punctual flows
48
+ * with no form around them.
49
+ *
50
+ * The hook underneath only knows `'select' | 'manual'` (fire now vs wait for
51
+ * `upload()`): `'submit'` and `'manual'` are the same machine state with a
52
+ * different owner of the send — the form or the zone.
53
+ */
54
+ type UiUploadTrigger = 'select' | 'submit' | 'manual';
55
+ /**
56
+ * What a surrounding form needs to send the staged files from its own submit —
57
+ * the flow `uploadOn: 'manual'` exists for, where the file travels together
58
+ * with the rest of the fields instead of the moment it is picked.
59
+ *
60
+ * A ref rather than a callback prop: the form reads it inside its submit
61
+ * handler, and a controller passed down would re-render the zone on every
62
+ * keystroke elsewhere in the form.
63
+ *
64
+ * @see AGENTS.md §3 — "Arity lives on the scope", same reasoning one level up
65
+ */
66
+ type UploaderController<TResult = StoredFile[]> = {
67
+ /** Uploads everything still waiting. Resolves with what the server confirmed. */
68
+ upload: () => Promise<TResult>;
69
+ /** `true` while a file waits for `upload()`. */
70
+ hasPending: boolean;
71
+ isUploading: boolean;
72
+ };
73
+ /** Slots settle through `onChange`, so the batch itself resolves with nothing. */
74
+ type SlottedUploaderController = UploaderController<void>;
75
+ /**
76
+ * Structural rather than React's `RefObject`: that type is read-only under the
77
+ * v18 typings and writable under v19, and the package supports both.
78
+ * `useRef<UploaderController | null>(null)` satisfies it either way.
79
+ */
80
+ type UploaderControllerRef<TResult = StoredFile[]> = {
81
+ current: UploaderController<TResult> | null;
82
+ };
83
+
84
+ type DropzoneProps = {
85
+ accept: string;
86
+ multiple?: boolean;
87
+ disabled?: boolean;
88
+ /**
89
+ * Mobile camera shortcut: `'environment'` (rear) or `'user'` (front) makes
90
+ * the file picker offer the camera directly on touch devices. Desktop
91
+ * browsers ignore it.
92
+ */
93
+ capture?: 'user' | 'environment';
94
+ /** @defaultValue 'md' */
95
+ size?: 'sm' | 'md';
96
+ /**
97
+ * Global keyboard shortcut that opens this zone's picker, e.g. `'mod+u'`
98
+ * (⌘U / Ctrl+U). A small key hint renders under the prompt on fine
99
+ * pointers. With several zones on screen, give each its own combo.
100
+ */
101
+ shortcut?: string;
102
+ onFiles: (files: File[]) => void;
103
+ children: ReactNode;
104
+ className?: string;
105
+ };
106
+ /**
107
+ * The one drag-and-drop surface both uploaders share: drag state, click and
108
+ * keyboard to browse, paste while focused (screenshots land as files), and a
109
+ * hidden input. Presentation comes from `children`.
110
+ */
111
+ declare const Dropzone: ({ accept, multiple, disabled, capture, size, shortcut, onFiles, children, className, }: DropzoneProps) => react.JSX.Element;
112
+
113
+ type FileItemProps = {
114
+ file: UploaderFile;
115
+ onRemove: () => void;
116
+ onAbort: () => void;
117
+ /** Present on successes when a viewer is available. */
118
+ onView?: () => void;
119
+ /** @defaultValue 'md' */
120
+ size?: 'sm' | 'md';
121
+ labels?: Partial<UploaderLabels>;
122
+ };
123
+ /**
124
+ * One row of upload state: preview/extension badge, name, size, a progress
125
+ * bar while in flight, the error when it failed. Shared by both uploaders so
126
+ * a file always looks the same wherever it is being uploaded.
127
+ */
128
+ declare const FileItem: ({ file, onRemove, onAbort, onView, size, labels, }: FileItemProps) => react.JSX.Element;
129
+
130
+ type SlottedUploaderProps<T extends Record<string, ScopeConfig>> = Omit<UseSlottedUploaderOptions<T>, 'uploadOn'> & {
131
+ /**
132
+ * When the slots travel. This presentation has no upload button of its
133
+ * own, so `'manual'` is not offered here: staged slots can only leave
134
+ * through a form's `controllerRef`, which is `'submit'`.
135
+ * @defaultValue 'select'
136
+ * @see UiUploadTrigger
137
+ */
138
+ uploadOn?: Exclude<UiUploadTrigger, 'manual'>;
139
+ title?: string;
140
+ description?: string;
141
+ /** Hide the bulk dropzone and keep only per-row buttons. */
142
+ hideDropzone?: boolean;
143
+ /**
144
+ * Gate removing a filled slot behind a confirmation dialog. `true` uses
145
+ * the label defaults; an object overrides the copy.
146
+ */
147
+ confirmRemove?: boolean | {
148
+ title?: string;
149
+ message?: string;
150
+ };
151
+ /**
152
+ * Confirm before a picked file replaces what a slot already holds. The
153
+ * dialog names both files, so the user sees what is about to be lost.
154
+ */
155
+ confirmReplace?: boolean | {
156
+ title?: string;
157
+ message?: string;
158
+ };
159
+ /**
160
+ * Deletion transport. With it, a confirmed slot removal deletes the object
161
+ * from storage BY ITSELF — unless the scope is `keepOnRemove`, the history
162
+ * contract — same wiring as `Uploader`.
163
+ */
164
+ removeStrategy?: RemoveStrategy;
165
+ /** Fresh URL right before previewing — for private scopes whose signed url expired. */
166
+ resolveViewUrl?: (file: ViewableFile) => Promise<string>;
167
+ /** Compact paddings and glyphs everywhere. @defaultValue 'md' */
168
+ size?: 'sm' | 'md';
169
+ /** Glyph inside the bulk dropzone. `null` removes it. */
170
+ icon?: ReactNode;
171
+ /** Keyboard shortcut that opens the bulk picker, e.g. `'mod+u'`. */
172
+ shortcut?: string;
173
+ /**
174
+ * Filled with the live controller so the surrounding form can send the
175
+ * staged slots from its own submit. Required by `uploadOn: 'submit'`:
176
+ * without it a staged slot has no way to ever leave.
177
+ */
178
+ controllerRef?: UploaderControllerRef<void>;
179
+ /**
180
+ * Fires when the zone starts or stops holding files that `upload()`
181
+ * would send. The companion of `controllerRef`: a form that defers the
182
+ * send has no other way to know it has unsent work, so its save button
183
+ * would stay disabled on a pristine form the user has just dropped a
184
+ * file into.
185
+ */
186
+ onPendingChange?: (hasPending: boolean) => void;
187
+ className?: string;
188
+ };
189
+ /**
190
+ * Named-slot presentation over the same machine as `Uploader`: a status row
191
+ * per slot plus one bulk dropzone whose matcher routes each file to its slot.
192
+ */
193
+ declare const SlottedUploader: <T extends Record<string, ScopeConfig>>({ uploadOn, title, description, hideDropzone, confirmRemove, confirmReplace, removeStrategy, resolveViewUrl, size, icon, shortcut, controllerRef, onPendingChange, className, ...options }: SlottedUploaderProps<T>) => react.JSX.Element;
194
+
195
+ /**
196
+ * What {@link UploaderProps.renderFiles} is handed.
197
+ *
198
+ * Both the raw state and the default rows already built, so a custom layout can
199
+ * keep the standard item and only change where — and among what — it sits.
200
+ */
201
+ type UploaderFilesSlot = {
202
+ /** Files already persisted, exactly as passed in `stored`. */
203
+ stored: StoredFile[];
204
+ /** Files staged in this session, each with its live upload status. */
205
+ staged: UploaderFile[];
206
+ /** The default rows. Place them, wrap them, or drop them entirely. */
207
+ nodes: ReactNode;
208
+ /** Nothing persisted and nothing staged. */
209
+ isEmpty: boolean;
210
+ /** Open a persisted file in the built-in viewer. */
211
+ view: (file: StoredFile) => void;
212
+ /** Forget a staged file. */
213
+ remove: (id: string) => void;
214
+ /**
215
+ * Forget a persisted file, honouring `confirmRemove` and `removeStrategy`.
216
+ * `undefined` when this uploader accepts no removals.
217
+ */
218
+ removeStored?: (file: StoredFile) => void;
219
+ };
220
+ type UploaderProps<T extends Record<string, ScopeConfig>> = Omit<UseUploaderOptions<T>, 'uploadOn'> & {
221
+ /**
222
+ * When the files travel. @defaultValue 'select'
223
+ * @see UiUploadTrigger — which mode fits which screen
224
+ */
225
+ uploadOn?: UiUploadTrigger;
226
+ label?: string;
227
+ description?: string;
228
+ /** Files already persisted, rendered on the {@link filesPosition} side. */
229
+ stored?: StoredFile[];
230
+ /**
231
+ * Which side of the dropzone the file lists sit on. @defaultValue 'above'
232
+ *
233
+ * `'below'` keeps the drop target anchored: with the lists above it, every
234
+ * added file pushes the zone further down, so the control the user is
235
+ * repeatedly aiming at moves under the cursor.
236
+ */
237
+ filesPosition?: 'above' | 'below';
238
+ /**
239
+ * Lay the file lists out yourself: a grid of thumbnails, a single summary
240
+ * line, a count folded into your own card — whatever the screen needs.
241
+ *
242
+ * It replaces the rows, not their place: the result still renders on the
243
+ * {@link filesPosition} side. For a layout the zone itself has to be part of
244
+ * (files BESIDE the dropzone, files inside your own frame), skip this skin
245
+ * and compose `useUploader` with the exported `Dropzone` / `FileItem` /
246
+ * `StoredFileItem` — nothing here is unavailable there.
247
+ */
248
+ renderFiles?: (slot: UploaderFilesSlot) => ReactNode;
249
+ /**
250
+ * Notified when a persisted file is forgotten — the app's bookkeeping
251
+ * (clear the DB reference). Storage deletion belongs to `removeStrategy`.
252
+ */
253
+ onRemoveStored?: (file: StoredFile) => void;
254
+ /**
255
+ * Deletion transport. With it, a confirmed removal deletes the object from
256
+ * storage BY ITSELF — unless the scope is `keepOnRemove`, the history
257
+ * contract — so no consumer can orphan by forgetting a callback.
258
+ */
259
+ removeStrategy?: RemoveStrategy;
260
+ /**
261
+ * Gate `onRemoveStored` behind a confirmation dialog. `true` uses the
262
+ * label defaults; an object overrides the copy for this uploader.
263
+ */
264
+ confirmRemove?: boolean | {
265
+ title?: string;
266
+ message?: string;
267
+ };
268
+ /** Fresh URL right before previewing — for private scopes whose signed url expired. */
269
+ resolveViewUrl?: (file: ViewableFile) => Promise<string>;
270
+ /** Mobile camera shortcut, forwarded to the dropzone's input. */
271
+ capture?: 'user' | 'environment';
272
+ /** Compact paddings and glyphs everywhere. @defaultValue 'md' */
273
+ size?: 'sm' | 'md';
274
+ /** Glyph inside the dropzone. `null` removes it; defaults to an upload cloud. */
275
+ icon?: ReactNode;
276
+ /** Keyboard shortcut that opens the picker, e.g. `'mod+u'`. Hinted in the zone. */
277
+ shortcut?: string;
278
+ /**
279
+ * Filled with the live controller so the surrounding form can send the
280
+ * staged files from its own submit. Required by `uploadOn: 'submit'`,
281
+ * which has no other way to fire; readable under `'manual'` too, where
282
+ * the zone's button stays the sender.
283
+ */
284
+ controllerRef?: UploaderControllerRef;
285
+ /**
286
+ * Fires when the zone starts or stops holding files that `upload()`
287
+ * would send. The companion of `controllerRef`: a form that defers the
288
+ * send has no other way to know it has unsent work, so its save button
289
+ * would stay disabled on a pristine form the user has just dropped a
290
+ * file into.
291
+ */
292
+ onPendingChange?: (hasPending: boolean) => void;
293
+ disabled?: boolean;
294
+ className?: string;
295
+ };
296
+ /**
297
+ * The styled single-zone uploader: one dropzone, one or many files in it.
298
+ * Purely a skin over `useUploader` — apps needing custom markup use the hook
299
+ * directly and lose nothing.
300
+ *
301
+ * Defaults `uploadOn` to `'select'`. `'manual'` gives the zone its own upload
302
+ * button; `'submit'` hands the send to the surrounding form via
303
+ * `controllerRef`. @see UiUploadTrigger for which mode fits which screen.
304
+ */
305
+ declare const Uploader: <T extends Record<string, ScopeConfig>>({ uploadOn, label, description, stored, filesPosition, renderFiles, onRemoveStored, confirmRemove, resolveViewUrl, capture, size, icon, shortcut, controllerRef, onPendingChange, removeStrategy, disabled, className, ...options }: UploaderProps<T>) => react.JSX.Element;
306
+
307
+ type UseFileViewerOptions = {
308
+ /** Forwarded to the viewer — the authenticated resolver for private urls. */
309
+ resolveUrl?: FileViewerProps['resolveUrl'];
310
+ };
311
+ type UseFileViewerReturn = {
312
+ /** Currently open file, `null` when closed. */
313
+ viewing: ViewableFile | null;
314
+ open: (file: ViewableFile) => void;
315
+ close: () => void;
316
+ /** Spread into `<FileViewer {...viewerProps} />` — the whole wiring. */
317
+ viewerProps: Pick<FileViewerProps, 'file' | 'onClose' | 'resolveUrl'>;
318
+ };
319
+ /**
320
+ * The open/close state every screen with a `FileViewer` repeats. One call
321
+ * replaces the `useState` + prop plumbing:
322
+ *
323
+ * ```tsx
324
+ * const viewer = useFileViewer({ resolveUrl })
325
+ * <button onClick={() => viewer.open(stored)}>Ver</button>
326
+ * <FileViewer {...viewer.viewerProps} />
327
+ * ```
328
+ */
329
+ declare const useFileViewer: ({ resolveUrl, }?: UseFileViewerOptions) => UseFileViewerReturn;
330
+
331
+ export { ConfirmDialog, type ConfirmDialogProps, Dropzone, type DropzoneProps, FileItem, type FileItemProps, FileViewerProps, SlottedUploader, type SlottedUploaderController, type SlottedUploaderProps, type UiUploadTrigger, Uploader, type UploaderController, type UploaderControllerRef, type UploaderFilesSlot, type UploaderProps, type UseFileViewerOptions, type UseFileViewerReturn, ViewableFile, cn, useFileViewer };