@ttt-productions/upload-ui 0.7.16 → 0.7.18

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 (84) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/react/guard/guarded-link.d.ts +11 -0
  6. package/dist/react/guard/guarded-link.d.ts.map +1 -0
  7. package/dist/react/guard/guarded-link.js.map +1 -0
  8. package/dist/react/guard/index.d.ts +3 -3
  9. package/dist/react/guard/index.d.ts.map +1 -1
  10. package/dist/react/guard/index.js +3 -3
  11. package/dist/react/guard/index.js.map +1 -1
  12. package/dist/react/guard/local-upload-guard-provider.d.ts +31 -0
  13. package/dist/react/guard/local-upload-guard-provider.d.ts.map +1 -0
  14. package/dist/react/{local-upload-guard-provider.js → guard/local-upload-guard-provider.js} +3 -60
  15. package/dist/react/guard/local-upload-guard-provider.js.map +1 -0
  16. package/dist/react/guard/use-guarded-navigation.d.ts +3 -0
  17. package/dist/react/guard/use-guarded-navigation.d.ts.map +1 -0
  18. package/dist/react/guard/use-guarded-navigation.js +12 -0
  19. package/dist/react/guard/use-guarded-navigation.js.map +1 -0
  20. package/dist/react/tray/in-flight-uploads-provider.d.ts +141 -0
  21. package/dist/react/tray/in-flight-uploads-provider.d.ts.map +1 -0
  22. package/dist/react/{in-flight-uploads-provider.js → tray/in-flight-uploads-provider.js} +16 -136
  23. package/dist/react/tray/in-flight-uploads-provider.js.map +1 -0
  24. package/dist/react/tray/index.d.ts +4 -4
  25. package/dist/react/tray/index.d.ts.map +1 -1
  26. package/dist/react/tray/index.js +4 -4
  27. package/dist/react/tray/index.js.map +1 -1
  28. package/dist/react/tray/use-clear-upload-activity.d.ts +8 -0
  29. package/dist/react/tray/use-clear-upload-activity.d.ts.map +1 -0
  30. package/dist/react/tray/use-clear-upload-activity.js +13 -0
  31. package/dist/react/tray/use-clear-upload-activity.js.map +1 -0
  32. package/dist/react/{use-mark-upload-activity-seen.d.ts → tray/use-mark-upload-activity-seen.d.ts} +0 -4
  33. package/dist/react/tray/use-mark-upload-activity-seen.d.ts.map +1 -0
  34. package/dist/react/{use-mark-upload-activity-seen.js → tray/use-mark-upload-activity-seen.js} +0 -21
  35. package/dist/react/tray/use-mark-upload-activity-seen.js.map +1 -0
  36. package/dist/react/{use-upload-processing.d.ts → tray/use-upload-processing.d.ts} +0 -10
  37. package/dist/react/tray/use-upload-processing.d.ts.map +1 -0
  38. package/dist/react/{use-upload-processing.js → tray/use-upload-processing.js} +10 -20
  39. package/dist/react/tray/use-upload-processing.js.map +1 -0
  40. package/dist/react/upload/deferred-upload-form-shell.d.ts +28 -0
  41. package/dist/react/upload/deferred-upload-form-shell.d.ts.map +1 -0
  42. package/dist/react/{deferred-upload-form-shell.js → upload/deferred-upload-form-shell.js} +5 -2
  43. package/dist/react/upload/deferred-upload-form-shell.js.map +1 -0
  44. package/dist/react/upload/index.d.ts +2 -2
  45. package/dist/react/upload/index.d.ts.map +1 -1
  46. package/dist/react/upload/index.js +2 -2
  47. package/dist/react/upload/index.js.map +1 -1
  48. package/dist/react/upload/use-guarded-upload.d.ts +16 -0
  49. package/dist/react/upload/use-guarded-upload.d.ts.map +1 -0
  50. package/dist/react/{use-guarded-upload.js → upload/use-guarded-upload.js} +2 -15
  51. package/dist/react/upload/use-guarded-upload.js.map +1 -0
  52. package/dist/upload-display.d.ts +0 -14
  53. package/dist/upload-display.d.ts.map +1 -1
  54. package/dist/upload-display.js +11 -12
  55. package/dist/upload-display.js.map +1 -1
  56. package/package.json +5 -5
  57. package/dist/react/deferred-upload-form-shell.d.ts +0 -46
  58. package/dist/react/deferred-upload-form-shell.d.ts.map +0 -1
  59. package/dist/react/deferred-upload-form-shell.js.map +0 -1
  60. package/dist/react/guarded-link.d.ts +0 -38
  61. package/dist/react/guarded-link.d.ts.map +0 -1
  62. package/dist/react/guarded-link.js.map +0 -1
  63. package/dist/react/in-flight-uploads-provider.d.ts +0 -266
  64. package/dist/react/in-flight-uploads-provider.d.ts.map +0 -1
  65. package/dist/react/in-flight-uploads-provider.js.map +0 -1
  66. package/dist/react/local-upload-guard-provider.d.ts +0 -112
  67. package/dist/react/local-upload-guard-provider.d.ts.map +0 -1
  68. package/dist/react/local-upload-guard-provider.js.map +0 -1
  69. package/dist/react/use-clear-upload-activity.d.ts +0 -33
  70. package/dist/react/use-clear-upload-activity.d.ts.map +0 -1
  71. package/dist/react/use-clear-upload-activity.js +0 -31
  72. package/dist/react/use-clear-upload-activity.js.map +0 -1
  73. package/dist/react/use-guarded-navigation.d.ts +0 -10
  74. package/dist/react/use-guarded-navigation.d.ts.map +0 -1
  75. package/dist/react/use-guarded-navigation.js +0 -40
  76. package/dist/react/use-guarded-navigation.js.map +0 -1
  77. package/dist/react/use-guarded-upload.d.ts +0 -45
  78. package/dist/react/use-guarded-upload.d.ts.map +0 -1
  79. package/dist/react/use-guarded-upload.js.map +0 -1
  80. package/dist/react/use-mark-upload-activity-seen.d.ts.map +0 -1
  81. package/dist/react/use-mark-upload-activity-seen.js.map +0 -1
  82. package/dist/react/use-upload-processing.d.ts.map +0 -1
  83. package/dist/react/use-upload-processing.js.map +0 -1
  84. /package/dist/react/{guarded-link.js → guard/guarded-link.js} +0 -0
@@ -1,9 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- // ─────────────────────────────────────────────────────────────────────────────
4
- // SANCTIONED EXCEPTION to the "packages read/write Firestore only through
5
- // @ttt-productions/query-core" rule (see docs/packages/package-architecture.md →
6
- // "Firestore access goes through query-core").
3
+ // SANCTIONED EXCEPTION to RULE-STD-ARCH-206 (packages read and write Firestore only
4
+ // through @ttt-productions/query-core).
7
5
  //
8
6
  // WHY this provider reads Firestore directly instead of via a query-core hook:
9
7
  // 1. It needs snapshot *transition events* — onSnapshot's `docChanges()` with
@@ -18,41 +16,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
18
16
  // Turbopack module identity (see defaultFirestoreSubscribe's comment).
19
17
  //
20
18
  // Do NOT "migrate" this onto query-core without preserving docChanges() semantics.
21
- // ─────────────────────────────────────────────────────────────────────────────
22
19
  import { collection, onSnapshot, query, where } from 'firebase/firestore';
23
- /**
24
- * Generic in-flight uploads provider.
25
- *
26
- * Provides a global Map of in-flight upload state by `pendingMediaId`, derived
27
- * from a Firestore listener over the consumer-supplied collection. On terminal
28
- * status transitions observed THIS SESSION, fires consumer-supplied success /
29
- * failure / rejection callbacks. Records monitoring breadcrumbs and parse
30
- * errors through the injected monitoring callback.
31
- *
32
- * EVERY app-specific value is supplied by the consumer:
33
- * - userId resolution
34
- * - Firestore `db` reference
35
- * - collection path
36
- * - listener time window (ms)
37
- * - parser/schema for raw Firestore doc → typed PendingMedia
38
- * - success copy per origin
39
- * - notification callback (cache invalidation, etc.)
40
- * - toast callback
41
- * - rejection callback (for the media-rejection dialog or equivalent)
42
- * - monitoring callback (breadcrumbs + captured errors)
43
- *
44
- * Read APIs:
45
- * - `useInFlightUploadsState()` — full Map.
46
- * - `useInFlightUpload(id)` — single doc, narrowable on status.
47
- * - `useUploadActivityState()` — tray-visible items, sorted newest-first.
48
- *
49
- * The package owns the deduplication, initial-snapshot suppression, and
50
- * non-terminal → terminal transition tracking. It does NOT own anything about
51
- * what a file-origin is or what message text appears in toasts.
52
- */
53
20
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
54
21
  import { isPendingMediaNonTerminalStatus, isPendingMediaTerminalStatus, PendingMediaStatusSchema, } from '@ttt-productions/media-schemas';
55
- // Status values derive from media-schemas' one declaration.
56
22
  const STATUS = PendingMediaStatusSchema.enum;
57
23
  const InFlightUploadsContext = createContext(undefined);
58
24
  function fromParsed(parsed) {
@@ -92,7 +58,6 @@ function fromParsed(parsed) {
92
58
  uploadTraySeenBy: parsed.uploadTraySeenBy,
93
59
  };
94
60
  }
95
- // rejected
96
61
  return {
97
62
  ...base,
98
63
  status: STATUS.rejected,
@@ -102,10 +67,7 @@ function fromParsed(parsed) {
102
67
  violationId: parsed.violationId,
103
68
  uploadTrayClearedAt: parsed.uploadTrayClearedAt,
104
69
  uploadTrayClearedBy: parsed.uploadTrayClearedBy,
105
- // Carry the seen fields through (round-9 finding 9): dropping them on the
106
- // `rejected` branch — while `completed`/`failed` keep them — made a seen
107
- // rejected upload relight the Files dot forever, because the tray's
108
- // unseen-terminal check never saw `uploadTraySeenAt`.
70
+ // Without the seen fields a seen rejected upload relights the Files dot forever.
109
71
  uploadTraySeenAt: parsed.uploadTraySeenAt,
110
72
  uploadTraySeenBy: parsed.uploadTraySeenBy,
111
73
  };
@@ -116,36 +78,19 @@ export function InFlightUploadsProvider(props) {
116
78
  const [sourceState, setSourceState] = useState('connecting');
117
79
  const [restarts, setRestarts] = useState(0);
118
80
  const [retrying, setRetrying] = useState(false);
119
- /** Set by `retry` for the subscription the restart opens; that run keeps the source state. */
120
81
  const restartRequestedRef = useRef(false);
121
82
  const seenTerminalIdsRef = useRef(new Set());
122
83
  const statusByIdRef = useRef(new Map());
123
84
  const hasProcessedInitialSnapshotRef = useRef(false);
124
- /** The identity the dedup/tracking refs belong to — they outlive resubscribes
125
- * for the SAME user (auth-settling flaps) and reset only on a real identity
126
- * change. See the reset block in the subscription effect. */
127
85
  const trackedUserIdRef = useRef(null);
128
- // Stable refs for adapter callbacks — read from latest adapter inside effect.
129
86
  const adapterRef = useRef(adapter);
130
87
  useEffect(() => {
131
88
  adapterRef.current = adapter;
132
89
  });
133
90
  const userId = adapter.getCurrentUserId();
134
91
  useEffect(() => {
135
- // The uploads map and source state rebuild from every subscription's initial
136
- // snapshot, so they reset on every effect run. The terminal-dedup and
137
- // status-tracking refs are IDENTITY-scoped, not subscription-scoped: an
138
- // auth-settling flap (uid → null → same uid re-subscribes) must neither
139
- // re-fire terminals the previous subscription already fired (seen set
140
- // persists) nor lose the observed non-terminal statuses that let the new
141
- // subscription's initial listing detect a transition that completed during
142
- // the gap. A real identity change — including UID-A → UID-B with no
143
- // intermediate null user — starts clean; never rely on an intermediate
144
- // sign-out to clear refs between two authenticated UIDs.
145
- // A restart the person asked for keeps the source state it had (a failure stays a failure
146
- // until the restarted listener answers); every other run starts over as `connecting`.
147
- // A restart belongs to the identity it was asked for: one that lands with a sign-out or a switch
148
- // of account is an ordinary new subscription, which starts over.
92
+ // The dedup and tracking refs are identity-scoped: they survive a same-user resubscribe and reset
93
+ // on any identity change, UID-A to UID-B with no sign-out between included.
149
94
  let restarting = restartRequestedRef.current && userId !== null && userId === trackedUserIdRef.current;
150
95
  restartRequestedRef.current = false;
151
96
  setUploads(new Map());
@@ -195,12 +140,8 @@ export function InFlightUploadsProvider(props) {
195
140
  const currentIsTerminal = isPendingMediaTerminalStatus(currentStatus);
196
141
  const previousWasNonTerminal = isPendingMediaNonTerminalStatus(previousStatus);
197
142
  if (isInitialSnapshot) {
198
- // A subscription's first snapshot is a (re-)listing of current state,
199
- // not a live event: already-terminal docs stay suppressed so a page
200
- // refresh never re-toasts. ONE exception — this provider instance
201
- // tracked the doc as non-terminal before (same-identity resubscribe
202
- // gap; the tracking refs survive those), so the terminal listing IS
203
- // the transition, observed across the gap, and must fire.
143
+ // A first snapshot re-lists current state: its terminal docs fire only when this instance tracked
144
+ // them as non-terminal before.
204
145
  if (currentIsTerminal) {
205
146
  if (previousWasNonTerminal && !seenTerminalIdsRef.current.has(parsed.id)) {
206
147
  newlyTerminal.push(parsed);
@@ -209,25 +150,20 @@ export function InFlightUploadsProvider(props) {
209
150
  }
210
151
  }
211
152
  else if (currentIsTerminal && !seenTerminalIdsRef.current.has(parsed.id)) {
212
- // Any unseen terminal in a LIVE snapshot fires — including a doc whose
213
- // FIRST client-visible state is already terminal. A fast backend
214
- // outcome (moderation rejection in ~2s) can coalesce created→terminal
215
- // into one delivery, and the uploader is actively waiting on exactly
216
- // this callback; swallowing it silently loses a rejection dialog.
153
+ // A fast outcome can coalesce created and terminal into one delivery, so any unseen terminal fires.
217
154
  seenTerminalIdsRef.current.add(parsed.id);
218
155
  newlyTerminal.push(parsed);
219
156
  }
220
157
  statusByIdRef.current.set(parsed.id, currentStatus);
221
158
  }
222
159
  hasProcessedInitialSnapshotRef.current = true;
223
- // Metadata-derived source state: stay `connecting` until a server-confirmed
224
- // snapshot, `offline` if only cached data arrives after being live.
225
160
  const fromCache = snapshot.metadata?.fromCache ?? false;
226
- // A restart ends on the server's answer: a cached snapshot during it keeps the failure on screen
227
- // (the server may refuse again). Otherwise a snapshot after a failed listener comes from a new
228
- // subscription: not yet live, never offline.
229
161
  if (!(restarting && fromCache)) {
230
- setSourceState((prev) => fromCache ? (prev === 'connecting' || prev === 'error' ? 'connecting' : 'offline') : 'live');
162
+ setSourceState((prev) => fromCache
163
+ ? prev === 'connecting' || prev === 'error'
164
+ ? 'connecting'
165
+ : 'offline'
166
+ : 'live');
231
167
  restarting = false;
232
168
  setRetrying(false);
233
169
  }
@@ -270,13 +206,6 @@ export function InFlightUploadsProvider(props) {
270
206
  adapterRef.current.onUploadRejected(parsed);
271
207
  }
272
208
  }
273
- // Bounded RESUBSCRIBE on listener error: a cold-session race (e.g. Firestore
274
- // App Check enforcement rejecting the first attach before the token is
275
- // minted) must not kill the listener for the whole session — observed live:
276
- // one permission-denied at first login → sourceState 'error' forever, every
277
- // tray file status dead until reload. Retry on a short ladder; a snapshot
278
- // resets the ladder (healthy again); exhausting it goes terminal 'error'
279
- // (a real rules denial stays denied — retries are cheap and bounded).
280
209
  const RESUBSCRIBE_DELAYS_MS = [1_000, 3_000, 10_000, 30_000];
281
210
  let attempt = 0;
282
211
  let disposed = false;
@@ -289,7 +218,7 @@ export function InFlightUploadsProvider(props) {
289
218
  userId,
290
219
  windowStartMs,
291
220
  onSnapshot: (snapshot) => {
292
- attempt = 0; // healthy — future errors get a fresh ladder
221
+ attempt = 0;
293
222
  handleSnapshot(snapshot);
294
223
  },
295
224
  onError: (error) => {
@@ -319,8 +248,6 @@ export function InFlightUploadsProvider(props) {
319
248
  clearTimeout(retryTimer);
320
249
  unsubscribe?.();
321
250
  };
322
- // adapter is intentionally NOT in the deps array — adapter is read via
323
- // adapterRef on every snapshot, so identity changes don't re-subscribe.
324
251
  }, [userId, subscribe, restarts]);
325
252
  const retry = useCallback(() => {
326
253
  restartRequestedRef.current = true;
@@ -330,9 +257,6 @@ export function InFlightUploadsProvider(props) {
330
257
  const value = useMemo(() => ({ uploads, sourceState, retry, retrying }), [uploads, sourceState, retry, retrying]);
331
258
  return (_jsx(InFlightUploadsContext.Provider, { value: value, children: children }));
332
259
  }
333
- // =============================================================================
334
- // Read APIs
335
- // =============================================================================
336
260
  export function useInFlightUploadsState() {
337
261
  const ctx = useContext(InFlightUploadsContext);
338
262
  if (ctx === undefined) {
@@ -349,12 +273,6 @@ export function useInFlightUpload(id) {
349
273
  return undefined;
350
274
  return ctx.uploads.get(id);
351
275
  }
352
- /**
353
- * Metadata-derived source state of the uploads listener
354
- * (`connecting | live | offline | error`). The Files tray uses this to show a
355
- * degraded indicator and never display "all caught up" while the source is
356
- * `offline`/`error`/`connecting`.
357
- */
358
276
  export function useUploadsSourceState() {
359
277
  const ctx = useContext(InFlightUploadsContext);
360
278
  if (ctx === undefined) {
@@ -362,10 +280,6 @@ export function useUploadsSourceState() {
362
280
  }
363
281
  return ctx.sourceState;
364
282
  }
365
- /**
366
- * The uploads listener's restart — the Try again of a failure shown on `useUploadsSourceState` —
367
- * and whether it is running.
368
- */
369
283
  export function useUploadsSourceRetry() {
370
284
  const ctx = useContext(InFlightUploadsContext);
371
285
  if (ctx === undefined) {
@@ -373,24 +287,9 @@ export function useUploadsSourceRetry() {
373
287
  }
374
288
  return { retry: ctx.retry, retrying: ctx.retrying };
375
289
  }
376
- // =============================================================================
377
- // Tray helpers
378
- // =============================================================================
379
- /**
380
- * `true` for `completed` / `failed` / `rejected`. Used by tray UI to decide
381
- * whether a Clear action is available for an item.
382
- */
383
290
  export function isTerminalUpload(item) {
384
291
  return isPendingMediaTerminalStatus(item.status);
385
292
  }
386
- /**
387
- * Tray-visible items. Includes every pending/processing upload, plus terminal
388
- * uploads that have NOT been cleared from the tray. Sorted newest-first by
389
- * createdAt.
390
- *
391
- * The public selector name in upload-ui is `useUploadActivityState`. The
392
- * mutation hook is `useClearUploadActivity` (in a separate file).
393
- */
394
293
  export function useUploadActivityState() {
395
294
  const uploads = useInFlightUploadsState();
396
295
  return useMemo(() => {
@@ -403,29 +302,10 @@ export function useUploadActivityState() {
403
302
  .sort((a, b) => b.createdAt - a.createdAt);
404
303
  }, [uploads]);
405
304
  }
406
- // =============================================================================
407
- // Default Firestore subscription implementation
408
- // =============================================================================
409
- /**
410
- * Default Firestore subscription using the consumer's `firebase/firestore`
411
- * module instance (via static ESM import at the top of this file).
412
- *
413
- * Using static ESM import — NOT `require()` — is load-bearing. CommonJS
414
- * `require('firebase/firestore')` and ESM `import 'firebase/firestore'` resolve
415
- * to different module instances under Next.js + Turbopack. If the consumer
416
- * constructs `db` via ESM and we receive it here, but we then call
417
- * `collection()` from a CJS-loaded copy of firebase, the firebase runtime
418
- * does not recognize the db as a Firestore instance and throws
419
- * "Expected first argument to collection() to be a CollectionReference,
420
- * a DocumentReference or FirebaseFirestore".
421
- *
422
- * Consumers who want to skip the firebase code path entirely (tests, alt
423
- * backends) supply their own `subscribe` prop to `InFlightUploadsProvider`.
424
- */
305
+ // A static ESM import, never require(): under Next.js + Turbopack a CJS-loaded firebase/firestore
306
+ // is another module instance and rejects a db built through ESM.
425
307
  const defaultFirestoreSubscribe = (args) => {
426
308
  const q = query(collection(args.db, args.collectionPath), where('userId', '==', args.userId), where('createdAt', '>=', args.windowStartMs));
427
- // includeMetadataChanges so the provider can derive a `connecting|live|offline|error`
428
- // source state from `snapshot.metadata.fromCache` (the Files tray degraded indicator).
429
309
  return onSnapshot(q, { includeMetadataChanges: true }, args.onSnapshot, args.onError);
430
310
  };
431
311
  //# sourceMappingURL=in-flight-uploads-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-flight-uploads-provider.js","sourceRoot":"","sources":["../../../src/react/tray/in-flight-uploads-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// SANCTIONED EXCEPTION to RULE-STD-ARCH-206 (packages read and write Firestore only\n// through @ttt-productions/query-core).\n//\n// WHY this provider reads Firestore directly instead of via a query-core hook:\n// 1. It needs snapshot *transition events* — onSnapshot's `docChanges()` with\n// added / modified / removed — to fire one-shot terminal callbacks and to\n// suppress the initial snapshot. query-core's hooks return \"the current\n// list\", not per-doc transitions, so they cannot express this state machine.\n// 2. The subscription is already abstracted behind an injectable\n// `FirestoreSubscribeFn` (the `subscribe` prop); the direct firebase code\n// path lives ONLY in `defaultFirestoreSubscribe` below and is fully\n// swappable (tests / alternate backends inject their own).\n// 3. The static ESM `firebase/firestore` import is load-bearing for Next.js +\n// Turbopack module identity (see defaultFirestoreSubscribe's comment).\n//\n// Do NOT \"migrate\" this onto query-core without preserving docChanges() semantics.\nimport { collection, onSnapshot, query, where } from 'firebase/firestore';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport {\n isPendingMediaNonTerminalStatus,\n isPendingMediaTerminalStatus,\n PendingMediaStatusSchema,\n type PendingMediaNonTerminalStatus,\n type PendingMediaStatus,\n} from '@ttt-productions/media-schemas';\n\nconst STATUS = PendingMediaStatusSchema.enum;\n\nexport type InFlightUploadStatus = PendingMediaStatus;\n\nexport type InFlightUploadBase<TFileOrigin extends string = string> = {\n id: string;\n fileOrigin: TFileOrigin;\n surface: string;\n createdAt: number;\n targetIds?: string[];\n originalContentType?: string;\n originalSize?: number;\n};\n\nexport type InFlightUploadActive<TFileOrigin extends string = string> =\n InFlightUploadBase<TFileOrigin> & {\n status: PendingMediaNonTerminalStatus;\n };\n\nexport type InFlightUploadCompleted<TFileOrigin extends string = string> =\n InFlightUploadBase<TFileOrigin> & {\n status: typeof PendingMediaStatusSchema.enum.completed;\n completedAt: number;\n uploadTrayClearedAt?: number;\n uploadTrayClearedBy?: string;\n uploadTraySeenAt?: number;\n uploadTraySeenBy?: string;\n };\n\nexport type InFlightUploadFailed<TFileOrigin extends string = string> =\n InFlightUploadBase<TFileOrigin> & {\n status: typeof PendingMediaStatusSchema.enum.failed;\n failedAt: number;\n errorCategory: string;\n errorMessage: string;\n uploadTrayClearedAt?: number;\n uploadTrayClearedBy?: string;\n uploadTraySeenAt?: number;\n uploadTraySeenBy?: string;\n };\n\nexport type InFlightUploadRejected<TFileOrigin extends string = string> =\n InFlightUploadBase<TFileOrigin> & {\n status: typeof PendingMediaStatusSchema.enum.rejected;\n rejectedAt: number;\n rejectionType: 'text' | 'media';\n errorMessage: string;\n violationId?: string;\n uploadTrayClearedAt?: number;\n uploadTrayClearedBy?: string;\n uploadTraySeenAt?: number;\n uploadTraySeenBy?: string;\n };\n\nexport type InFlightUpload<TFileOrigin extends string = string> =\n | InFlightUploadActive<TFileOrigin>\n | InFlightUploadCompleted<TFileOrigin>\n | InFlightUploadFailed<TFileOrigin>\n | InFlightUploadRejected<TFileOrigin>;\n\nexport type ParsedPendingMedia<TFileOrigin extends string = string> = {\n id: string;\n userId: string;\n fileOrigin: TFileOrigin;\n status: InFlightUploadStatus;\n createdAt: number;\n surface: string;\n targetIds?: string[];\n originalContentType?: string;\n originalSize?: number;\n completedAt?: number;\n failedAt?: number;\n rejectedAt?: number;\n errorCategory?: string;\n errorMessage?: string;\n rejectionType?: 'text' | 'media';\n violationId?: string;\n uploadTrayClearedAt?: number;\n uploadTrayClearedBy?: string;\n uploadTraySeenAt?: number;\n uploadTraySeenBy?: string;\n domainEvents?: unknown;\n};\n\nexport type InFlightUploadsAdapter<TFileOrigin extends string = string> = {\n getCurrentUserId: () => string | null;\n\n db: unknown;\n\n collectionPath: string;\n\n listenerWindowMs: number;\n\n parsePendingMedia: (raw: unknown) => ParsedPendingMedia<TFileOrigin>;\n\n onUploadCompleted: (parsed: ParsedPendingMedia<TFileOrigin>) => void;\n\n onUploadFailed: (parsed: ParsedPendingMedia<TFileOrigin>) => void;\n\n onUploadRejected: (parsed: ParsedPendingMedia<TFileOrigin>) => void;\n\n onMonitoringEvent: (event: InFlightUploadsMonitoringEvent<TFileOrigin>) => void;\n};\n\nexport type InFlightUploadsMonitoringEvent<TFileOrigin extends string = string> =\n | {\n type: 'breadcrumb';\n category: 'upload.terminal';\n level: 'info';\n message: string;\n data: {\n id: string;\n fileOrigin: TFileOrigin;\n status: InFlightUploadStatus;\n surface: string;\n };\n }\n | {\n type: 'parse-error';\n docId: string;\n error: unknown;\n data: unknown;\n }\n | {\n type: 'listener-error';\n error: unknown;\n };\n\nexport type FirestoreSubscribeFn = (args: {\n db: unknown;\n collectionPath: string;\n userId: string;\n windowStartMs: number;\n onSnapshot: (snapshot: FirestoreLikeSnapshot) => void;\n onError: (error: unknown) => void;\n}) => () => void;\n\nexport type FirestoreLikeSnapshot = {\n docChanges: () => FirestoreLikeDocChange[];\n metadata?: { fromCache: boolean };\n};\n\n/** Firestore serves cached snapshots offline without firing the error callback, so liveness comes from metadata. */\nexport type UploadsSourceState = 'connecting' | 'live' | 'offline' | 'error';\n\nexport type FirestoreLikeDocChange = {\n type: 'added' | 'modified' | 'removed';\n doc: {\n id: string;\n data: () => unknown;\n };\n};\n\ninterface InFlightUploadsContextValue<TFileOrigin extends string = string> {\n uploads: Map<string, InFlightUpload<TFileOrigin>>;\n sourceState: UploadsSourceState;\n retry: () => void;\n retrying: boolean;\n}\n\nexport interface UploadsSourceRetry {\n retry: () => void;\n retrying: boolean;\n}\n\nconst InFlightUploadsContext = createContext<InFlightUploadsContextValue | undefined>(undefined);\n\nexport interface InFlightUploadsProviderProps<TFileOrigin extends string = string> {\n adapter: InFlightUploadsAdapter<TFileOrigin>;\n subscribe?: FirestoreSubscribeFn;\n children: ReactNode;\n}\n\nfunction fromParsed<TFileOrigin extends string>(\n parsed: ParsedPendingMedia<TFileOrigin>,\n): InFlightUpload<TFileOrigin> {\n const base: InFlightUploadBase<TFileOrigin> = {\n id: parsed.id,\n fileOrigin: parsed.fileOrigin,\n surface: parsed.surface,\n createdAt: parsed.createdAt,\n targetIds: parsed.targetIds,\n originalContentType: parsed.originalContentType,\n originalSize: parsed.originalSize,\n };\n if (isPendingMediaNonTerminalStatus(parsed.status)) {\n return { ...base, status: parsed.status };\n }\n if (parsed.status === STATUS.completed) {\n return {\n ...base,\n status: STATUS.completed,\n completedAt: parsed.completedAt ?? parsed.createdAt,\n uploadTrayClearedAt: parsed.uploadTrayClearedAt,\n uploadTrayClearedBy: parsed.uploadTrayClearedBy,\n uploadTraySeenAt: parsed.uploadTraySeenAt,\n uploadTraySeenBy: parsed.uploadTraySeenBy,\n };\n }\n if (parsed.status === STATUS.failed) {\n return {\n ...base,\n status: STATUS.failed,\n failedAt: parsed.failedAt ?? parsed.createdAt,\n errorCategory: parsed.errorCategory ?? 'system',\n errorMessage: parsed.errorMessage ?? '',\n uploadTrayClearedAt: parsed.uploadTrayClearedAt,\n uploadTrayClearedBy: parsed.uploadTrayClearedBy,\n uploadTraySeenAt: parsed.uploadTraySeenAt,\n uploadTraySeenBy: parsed.uploadTraySeenBy,\n };\n }\n return {\n ...base,\n status: STATUS.rejected,\n rejectedAt: parsed.rejectedAt ?? parsed.createdAt,\n rejectionType: parsed.rejectionType ?? 'media',\n errorMessage: parsed.errorMessage ?? '',\n violationId: parsed.violationId,\n uploadTrayClearedAt: parsed.uploadTrayClearedAt,\n uploadTrayClearedBy: parsed.uploadTrayClearedBy,\n // Without the seen fields a seen rejected upload relights the Files dot forever.\n uploadTraySeenAt: parsed.uploadTraySeenAt,\n uploadTraySeenBy: parsed.uploadTraySeenBy,\n };\n}\n\nexport function InFlightUploadsProvider<TFileOrigin extends string = string>(\n props: InFlightUploadsProviderProps<TFileOrigin>,\n) {\n const { adapter, subscribe = defaultFirestoreSubscribe, children } = props;\n\n const [uploads, setUploads] = useState<Map<string, InFlightUpload<TFileOrigin>>>(new Map());\n const [sourceState, setSourceState] = useState<UploadsSourceState>('connecting');\n const [restarts, setRestarts] = useState(0);\n const [retrying, setRetrying] = useState(false);\n const restartRequestedRef = useRef(false);\n const seenTerminalIdsRef = useRef<Set<string>>(new Set());\n const statusByIdRef = useRef<Map<string, InFlightUploadStatus>>(new Map());\n const hasProcessedInitialSnapshotRef = useRef(false);\n const trackedUserIdRef = useRef<string | null>(null);\n\n const adapterRef = useRef(adapter);\n useEffect(() => {\n adapterRef.current = adapter;\n });\n\n const userId = adapter.getCurrentUserId();\n\n useEffect(() => {\n // The dedup and tracking refs are identity-scoped: they survive a same-user resubscribe and reset\n // on any identity change, UID-A to UID-B with no sign-out between included.\n let restarting =\n restartRequestedRef.current && userId !== null && userId === trackedUserIdRef.current;\n restartRequestedRef.current = false;\n setUploads(new Map());\n if (!restarting) {\n setSourceState('connecting');\n setRetrying(false);\n }\n if (userId !== null && userId !== trackedUserIdRef.current) {\n seenTerminalIdsRef.current = new Set();\n statusByIdRef.current = new Map();\n trackedUserIdRef.current = userId;\n }\n hasProcessedInitialSnapshotRef.current = false;\n\n if (!userId) {\n return;\n }\n\n const a = adapterRef.current;\n const windowStartMs = Date.now() - a.listenerWindowMs;\n\n const handleSnapshot = (snapshot: FirestoreLikeSnapshot) => {\n const updates: Array<{ id: string; upload: InFlightUpload<TFileOrigin> | null }> = [];\n const newlyTerminal: Array<ParsedPendingMedia<TFileOrigin>> = [];\n const isInitialSnapshot = !hasProcessedInitialSnapshotRef.current;\n\n for (const change of snapshot.docChanges()) {\n if (change.type === 'removed') {\n updates.push({ id: change.doc.id, upload: null });\n statusByIdRef.current.delete(change.doc.id);\n seenTerminalIdsRef.current.delete(change.doc.id);\n continue;\n }\n\n let parsed: ParsedPendingMedia<TFileOrigin>;\n try {\n parsed = adapterRef.current.parsePendingMedia(change.doc.data());\n } catch (error) {\n adapterRef.current.onMonitoringEvent({\n type: 'parse-error',\n docId: change.doc.id,\n error,\n data: change.doc.data(),\n });\n continue;\n }\n\n const upload = fromParsed(parsed);\n updates.push({ id: parsed.id, upload });\n\n const previousStatus = statusByIdRef.current.get(parsed.id);\n const currentStatus = parsed.status;\n const currentIsTerminal = isPendingMediaTerminalStatus(currentStatus);\n const previousWasNonTerminal = isPendingMediaNonTerminalStatus(previousStatus);\n\n if (isInitialSnapshot) {\n // A first snapshot re-lists current state: its terminal docs fire only when this instance tracked\n // them as non-terminal before.\n if (currentIsTerminal) {\n if (previousWasNonTerminal && !seenTerminalIdsRef.current.has(parsed.id)) {\n newlyTerminal.push(parsed);\n }\n seenTerminalIdsRef.current.add(parsed.id);\n }\n } else if (currentIsTerminal && !seenTerminalIdsRef.current.has(parsed.id)) {\n // A fast outcome can coalesce created and terminal into one delivery, so any unseen terminal fires.\n seenTerminalIdsRef.current.add(parsed.id);\n newlyTerminal.push(parsed);\n }\n\n statusByIdRef.current.set(parsed.id, currentStatus);\n }\n\n hasProcessedInitialSnapshotRef.current = true;\n\n const fromCache = snapshot.metadata?.fromCache ?? false;\n if (!(restarting && fromCache)) {\n setSourceState((prev) =>\n fromCache\n ? prev === 'connecting' || prev === 'error'\n ? 'connecting'\n : 'offline'\n : 'live',\n );\n restarting = false;\n setRetrying(false);\n }\n\n if (updates.length > 0) {\n setUploads((prev) => {\n const next = new Map(prev);\n for (const { id, upload } of updates) {\n if (upload === null) next.delete(id);\n else next.set(id, upload);\n }\n return next;\n });\n }\n\n for (const parsed of newlyTerminal) {\n handleTerminal(parsed);\n }\n };\n\n function handleTerminal(parsed: ParsedPendingMedia<TFileOrigin>) {\n adapterRef.current.onMonitoringEvent({\n type: 'breadcrumb',\n category: 'upload.terminal',\n level: 'info',\n message: `${parsed.fileOrigin}:${parsed.status}`,\n data: {\n id: parsed.id,\n fileOrigin: parsed.fileOrigin,\n status: parsed.status,\n surface: parsed.surface,\n },\n });\n\n if (parsed.status === STATUS.completed) {\n adapterRef.current.onUploadCompleted(parsed);\n } else if (parsed.status === STATUS.failed) {\n adapterRef.current.onUploadFailed(parsed);\n } else if (parsed.status === STATUS.rejected) {\n adapterRef.current.onUploadRejected(parsed);\n }\n }\n\n const RESUBSCRIBE_DELAYS_MS = [1_000, 3_000, 10_000, 30_000];\n let attempt = 0;\n let disposed = false;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const start = () => {\n unsubscribe = subscribe({\n db: a.db,\n collectionPath: a.collectionPath,\n userId,\n windowStartMs,\n onSnapshot: (snapshot) => {\n attempt = 0;\n handleSnapshot(snapshot);\n },\n onError: (error) => {\n adapterRef.current.onMonitoringEvent({ type: 'listener-error', error });\n unsubscribe?.();\n unsubscribe = undefined;\n if (disposed || attempt >= RESUBSCRIBE_DELAYS_MS.length) {\n setSourceState('error');\n setRetrying(false);\n return;\n }\n if (!restarting) setSourceState('connecting');\n retryTimer = setTimeout(() => {\n if (disposed) return;\n attempt += 1;\n start();\n }, RESUBSCRIBE_DELAYS_MS[attempt]);\n },\n });\n };\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n unsubscribe?.();\n };\n }, [userId, subscribe, restarts]);\n\n const retry = useCallback(() => {\n restartRequestedRef.current = true;\n setRetrying(true);\n setRestarts((count) => count + 1);\n }, []);\n\n const value = useMemo<InFlightUploadsContextValue<TFileOrigin>>(\n () => ({ uploads, sourceState, retry, retrying }),\n [uploads, sourceState, retry, retrying],\n );\n\n return (\n <InFlightUploadsContext.Provider value={value as InFlightUploadsContextValue}>\n {children}\n </InFlightUploadsContext.Provider>\n );\n}\n\nexport function useInFlightUploadsState<TFileOrigin extends string = string>(): Map<\n string,\n InFlightUpload<TFileOrigin>\n> {\n const ctx = useContext(InFlightUploadsContext);\n if (ctx === undefined) {\n throw new Error('useInFlightUploadsState must be used within InFlightUploadsProvider');\n }\n return ctx.uploads as Map<string, InFlightUpload<TFileOrigin>>;\n}\n\nexport function useInFlightUpload<TFileOrigin extends string = string>(\n id: string | null | undefined,\n): InFlightUpload<TFileOrigin> | undefined {\n const ctx = useContext(InFlightUploadsContext);\n if (ctx === undefined) {\n throw new Error('useInFlightUpload must be used within InFlightUploadsProvider');\n }\n if (!id) return undefined;\n return ctx.uploads.get(id) as InFlightUpload<TFileOrigin> | undefined;\n}\n\nexport function useUploadsSourceState(): UploadsSourceState {\n const ctx = useContext(InFlightUploadsContext);\n if (ctx === undefined) {\n throw new Error('useUploadsSourceState must be used within InFlightUploadsProvider');\n }\n return ctx.sourceState;\n}\n\nexport function useUploadsSourceRetry(): UploadsSourceRetry {\n const ctx = useContext(InFlightUploadsContext);\n if (ctx === undefined) {\n throw new Error('useUploadsSourceRetry must be used within InFlightUploadsProvider');\n }\n return { retry: ctx.retry, retrying: ctx.retrying };\n}\n\nexport function isTerminalUpload<TFileOrigin extends string = string>(\n item: InFlightUpload<TFileOrigin>,\n): item is Exclude<InFlightUpload<TFileOrigin>, { status: PendingMediaNonTerminalStatus }> {\n return isPendingMediaTerminalStatus(item.status);\n}\n\nexport function useUploadActivityState<\n TFileOrigin extends string = string,\n>(): InFlightUpload<TFileOrigin>[] {\n const uploads = useInFlightUploadsState<TFileOrigin>();\n return useMemo(() => {\n return Array.from(uploads.values())\n .filter((item) => {\n if (!isTerminalUpload(item)) return true;\n return item.uploadTrayClearedAt === undefined;\n })\n .sort((a, b) => b.createdAt - a.createdAt);\n }, [uploads]);\n}\n\n// A static ESM import, never require(): under Next.js + Turbopack a CJS-loaded firebase/firestore\n// is another module instance and rejects a db built through ESM.\nconst defaultFirestoreSubscribe: FirestoreSubscribeFn = (args) => {\n const q = query(\n collection(args.db as any, args.collectionPath),\n where('userId', '==', args.userId),\n where('createdAt', '>=', args.windowStartMs),\n );\n return onSnapshot(\n q,\n { includeMetadataChanges: true },\n args.onSnapshot as never,\n args.onError as never,\n );\n};\n"]}
@@ -1,5 +1,5 @@
1
- export { InFlightUploadsProvider, useInFlightUploadsState, useInFlightUpload, useUploadActivityState, useUploadsSourceState, useUploadsSourceRetry, isTerminalUpload, type UploadsSourceState, type UploadsSourceRetry, type InFlightUpload, type InFlightUploadActive, type InFlightUploadCompleted, type InFlightUploadFailed, type InFlightUploadRejected, type InFlightUploadBase, type InFlightUploadStatus, type InFlightUploadsAdapter, type InFlightUploadsMonitoringEvent, type InFlightUploadsProviderProps, type ParsedPendingMedia, type FirestoreSubscribeFn, type FirestoreLikeSnapshot, type FirestoreLikeDocChange, } from '../in-flight-uploads-provider.js';
2
- export { useClearUploadActivity, type ClearUploadActivityOptions, } from '../use-clear-upload-activity.js';
3
- export { useMarkUploadActivitySeen, MarkUploadActivitySeenError, type MarkUploadActivitySeenOptions, } from '../use-mark-upload-activity-seen.js';
4
- export { useUploadProcessing, type UploadProcessingState, type UseUploadProcessingOptions, } from '../use-upload-processing.js';
1
+ export { InFlightUploadsProvider, useInFlightUploadsState, useInFlightUpload, useUploadActivityState, useUploadsSourceState, useUploadsSourceRetry, isTerminalUpload, type UploadsSourceState, type UploadsSourceRetry, type InFlightUpload, type InFlightUploadActive, type InFlightUploadCompleted, type InFlightUploadFailed, type InFlightUploadRejected, type InFlightUploadBase, type InFlightUploadStatus, type InFlightUploadsAdapter, type InFlightUploadsMonitoringEvent, type InFlightUploadsProviderProps, type ParsedPendingMedia, type FirestoreSubscribeFn, type FirestoreLikeSnapshot, type FirestoreLikeDocChange, } from './in-flight-uploads-provider.js';
2
+ export { useClearUploadActivity, type ClearUploadActivityOptions, } from './use-clear-upload-activity.js';
3
+ export { useMarkUploadActivitySeen, MarkUploadActivitySeenError, type MarkUploadActivitySeenOptions, } from './use-mark-upload-activity-seen.js';
4
+ export { useUploadProcessing, type UploadProcessingState, type UseUploadProcessingOptions, } from './use-upload-processing.js';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/tray/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,sBAAsB,EACtB,KAAK,0BAA0B,GAChC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,KAAK,6BAA6B,GACnC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/tray/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,KAAK,0BAA0B,GAChC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,KAAK,6BAA6B,GACnC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,4BAA4B,CAAC"}
@@ -1,5 +1,5 @@
1
- export { InFlightUploadsProvider, useInFlightUploadsState, useInFlightUpload, useUploadActivityState, useUploadsSourceState, useUploadsSourceRetry, isTerminalUpload, } from '../in-flight-uploads-provider.js';
2
- export { useClearUploadActivity, } from '../use-clear-upload-activity.js';
3
- export { useMarkUploadActivitySeen, MarkUploadActivitySeenError, } from '../use-mark-upload-activity-seen.js';
4
- export { useUploadProcessing, } from '../use-upload-processing.js';
1
+ export { InFlightUploadsProvider, useInFlightUploadsState, useInFlightUpload, useUploadActivityState, useUploadsSourceState, useUploadsSourceRetry, isTerminalUpload, } from './in-flight-uploads-provider.js';
2
+ export { useClearUploadActivity, } from './use-clear-upload-activity.js';
3
+ export { useMarkUploadActivitySeen, MarkUploadActivitySeenError, } from './use-mark-upload-activity-seen.js';
4
+ export { useUploadProcessing, } from './use-upload-processing.js';
5
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/tray/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,GAiBjB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,sBAAsB,GAEvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,yBAAyB,EACzB,2BAA2B,GAE5B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,mBAAmB,GAGpB,MAAM,6BAA6B,CAAC","sourcesContent":["export {\n InFlightUploadsProvider,\n useInFlightUploadsState,\n useInFlightUpload,\n useUploadActivityState,\n useUploadsSourceState,\n useUploadsSourceRetry,\n isTerminalUpload,\n type UploadsSourceState,\n type UploadsSourceRetry,\n type InFlightUpload,\n type InFlightUploadActive,\n type InFlightUploadCompleted,\n type InFlightUploadFailed,\n type InFlightUploadRejected,\n type InFlightUploadBase,\n type InFlightUploadStatus,\n type InFlightUploadsAdapter,\n type InFlightUploadsMonitoringEvent,\n type InFlightUploadsProviderProps,\n type ParsedPendingMedia,\n type FirestoreSubscribeFn,\n type FirestoreLikeSnapshot,\n type FirestoreLikeDocChange,\n} from '../in-flight-uploads-provider.js';\nexport {\n useClearUploadActivity,\n type ClearUploadActivityOptions,\n} from '../use-clear-upload-activity.js';\nexport {\n useMarkUploadActivitySeen,\n MarkUploadActivitySeenError,\n type MarkUploadActivitySeenOptions,\n} from '../use-mark-upload-activity-seen.js';\nexport {\n useUploadProcessing,\n type UploadProcessingState,\n type UseUploadProcessingOptions,\n} from '../use-upload-processing.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/tray/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,GAiBjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,GAEvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,yBAAyB,EACzB,2BAA2B,GAE5B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,mBAAmB,GAGpB,MAAM,4BAA4B,CAAC","sourcesContent":["export {\n InFlightUploadsProvider,\n useInFlightUploadsState,\n useInFlightUpload,\n useUploadActivityState,\n useUploadsSourceState,\n useUploadsSourceRetry,\n isTerminalUpload,\n type UploadsSourceState,\n type UploadsSourceRetry,\n type InFlightUpload,\n type InFlightUploadActive,\n type InFlightUploadCompleted,\n type InFlightUploadFailed,\n type InFlightUploadRejected,\n type InFlightUploadBase,\n type InFlightUploadStatus,\n type InFlightUploadsAdapter,\n type InFlightUploadsMonitoringEvent,\n type InFlightUploadsProviderProps,\n type ParsedPendingMedia,\n type FirestoreSubscribeFn,\n type FirestoreLikeSnapshot,\n type FirestoreLikeDocChange,\n} from './in-flight-uploads-provider.js';\nexport {\n useClearUploadActivity,\n type ClearUploadActivityOptions,\n} from './use-clear-upload-activity.js';\nexport {\n useMarkUploadActivitySeen,\n MarkUploadActivitySeenError,\n type MarkUploadActivitySeenOptions,\n} from './use-mark-upload-activity-seen.js';\nexport {\n useUploadProcessing,\n type UploadProcessingState,\n type UseUploadProcessingOptions,\n} from './use-upload-processing.js';\n"]}
@@ -0,0 +1,8 @@
1
+ import { type MutationKey } from '@tanstack/react-query';
2
+ export interface ClearUploadActivityOptions {
3
+ clearFn: (pendingMediaId: string) => Promise<void>;
4
+ onError?: (error: Error, pendingMediaId: string) => void;
5
+ mutationKey?: MutationKey;
6
+ }
7
+ export declare function useClearUploadActivity(options: ClearUploadActivityOptions): import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
8
+ //# sourceMappingURL=use-clear-upload-activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-clear-upload-activity.d.ts","sourceRoot":"","sources":["../../../src/react/tray/use-clear-upload-activity.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEtE,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,0BAA0B,mFASzE"}
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import { useMutation } from '@tanstack/react-query';
3
+ export function useClearUploadActivity(options) {
4
+ const { clearFn, onError, mutationKey } = options;
5
+ return useMutation({
6
+ mutationKey,
7
+ mutationFn: clearFn,
8
+ onError: (error, pendingMediaId) => {
9
+ onError?.(error, pendingMediaId);
10
+ },
11
+ });
12
+ }
13
+ //# sourceMappingURL=use-clear-upload-activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-clear-upload-activity.js","sourceRoot":"","sources":["../../../src/react/tray/use-clear-upload-activity.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAoB,MAAM,uBAAuB,CAAC;AAQtE,MAAM,UAAU,sBAAsB,CAAC,OAAmC;IACxE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAClD,OAAO,WAAW,CAAsB;QACtC,WAAW;QACX,UAAU,EAAE,OAAO;QACnB,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE;YACjC,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QACnC,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useMutation, type MutationKey } from '@tanstack/react-query';\n\nexport interface ClearUploadActivityOptions {\n clearFn: (pendingMediaId: string) => Promise<void>;\n onError?: (error: Error, pendingMediaId: string) => void;\n mutationKey?: MutationKey;\n}\n\nexport function useClearUploadActivity(options: ClearUploadActivityOptions) {\n const { clearFn, onError, mutationKey } = options;\n return useMutation<void, Error, string>({\n mutationKey,\n mutationFn: clearFn,\n onError: (error, pendingMediaId) => {\n onError?.(error, pendingMediaId);\n },\n });\n}\n"]}
@@ -1,12 +1,8 @@
1
1
  export interface MarkUploadActivitySeenOptions {
2
- /** Network call performing the mark-seen for at most `maxIdsPerCall` pendingMediaIds. */
3
2
  markFn: (pendingMediaIds: string[]) => Promise<void>;
4
- /** The most ids one `markFn` call accepts — the app's own per-call cap. */
5
3
  maxIdsPerCall: number;
6
- /** Optional error callback. Receives the first failure and every id that was not marked. */
7
4
  onError?: (error: Error, pendingMediaIds: string[]) => void;
8
5
  }
9
- /** The mutation's failure when one or more calls failed: the first failure, and the ids left unmarked. */
10
6
  export declare class MarkUploadActivitySeenError extends Error {
11
7
  readonly failure: Error;
12
8
  readonly unmarkedIds: string[];
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mark-upload-activity-seen.d.ts","sourceRoot":"","sources":["../../../src/react/tray/use-mark-upload-activity-seen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7D;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;gBAEnB,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE;CAMlD;AAwBD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,6BAA6B,qFAS/E"}
@@ -1,26 +1,5 @@
1
1
  'use client';
2
- /**
3
- * Generic mutation hook to mark terminal pendingMedia items as seen in the tray.
4
- *
5
- * Pure adapter: the consumer supplies `markFn` (typically a wrapped Firebase
6
- * callable in the consuming app that writes `uploadTraySeenAt`/`uploadTraySeenBy` on the
7
- * caller's own terminal pendingMedia docs). The package owns the useMutation
8
- * wiring and the optional `onError` hook; the consumer owns the network call.
9
- *
10
- * The provider's Firestore listener observes the `uploadTraySeenAt` write and
11
- * updates the in-flight upload records automatically — no optimistic update is
12
- * needed in this hook. Marking seen is tray UX state, not a sensitive action,
13
- * so it mirrors `useClearUploadActivity` exactly (no audit event).
14
- *
15
- * The mutation takes every id to mark (typically every currently-unseen terminal
16
- * item when the File-status tab opens) and sends them to `markFn` in sequential
17
- * calls of at most `maxIdsPerCall` — the app's own per-call cap, so no call is
18
- * refused for its size. A failed call does not stop the rest: its ids simply stay
19
- * unseen. When any call failed, the mutation fails, and `onError` receives the first
20
- * failure and every id left unmarked. An empty array makes no call.
21
- */
22
2
  import { useMutation } from '@tanstack/react-query';
23
- /** The mutation's failure when one or more calls failed: the first failure, and the ids left unmarked. */
24
3
  export class MarkUploadActivitySeenError extends Error {
25
4
  failure;
26
5
  unmarkedIds;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mark-upload-activity-seen.js","sourceRoot":"","sources":["../../../src/react/tray/use-mark-upload-activity-seen.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAQpD,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAC3C,OAAO,CAAQ;IACf,WAAW,CAAW;IAE/B,YAAY,OAAc,EAAE,WAAqB;QAC/C,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF;AAED,KAAK,UAAU,aAAa,CAC1B,MAAoD,EACpD,aAAqB,EACrB,eAAyB;IAEzB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,sDAAsD,aAAa,GAAG,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,YAAY,GAAiB,IAAI,CAAC;IACtC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,IAAI,aAAa,EAAE,CAAC;QAC3E,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,YAAY,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACrE,WAAW,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,IAAI,YAAY;QAAE,MAAM,IAAI,2BAA2B,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAsC;IAC9E,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACnD,OAAO,WAAW,CAAwB;QACxC,UAAU,EAAE,CAAC,eAAe,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,eAAe,CAAC;QACtF,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE;YAClC,IAAI,KAAK,YAAY,2BAA2B;gBAAE,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;;gBACzF,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QACzC,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useMutation } from '@tanstack/react-query';\n\nexport interface MarkUploadActivitySeenOptions {\n markFn: (pendingMediaIds: string[]) => Promise<void>;\n maxIdsPerCall: number;\n onError?: (error: Error, pendingMediaIds: string[]) => void;\n}\n\nexport class MarkUploadActivitySeenError extends Error {\n readonly failure: Error;\n readonly unmarkedIds: string[];\n\n constructor(failure: Error, unmarkedIds: string[]) {\n super(failure.message);\n this.name = 'MarkUploadActivitySeenError';\n this.failure = failure;\n this.unmarkedIds = unmarkedIds;\n }\n}\n\nasync function markInBatches(\n markFn: (pendingMediaIds: string[]) => Promise<void>,\n maxIdsPerCall: number,\n pendingMediaIds: string[],\n): Promise<void> {\n if (!Number.isInteger(maxIdsPerCall) || maxIdsPerCall < 1) {\n throw new Error(`maxIdsPerCall must be a positive whole number, got ${maxIdsPerCall}.`);\n }\n let firstFailure: Error | null = null;\n const unmarkedIds: string[] = [];\n for (let start = 0; start < pendingMediaIds.length; start += maxIdsPerCall) {\n const batch = pendingMediaIds.slice(start, start + maxIdsPerCall);\n try {\n await markFn(batch);\n } catch (err) {\n firstFailure ??= err instanceof Error ? err : new Error(String(err));\n unmarkedIds.push(...batch);\n }\n }\n if (firstFailure) throw new MarkUploadActivitySeenError(firstFailure, unmarkedIds);\n}\n\nexport function useMarkUploadActivitySeen(options: MarkUploadActivitySeenOptions) {\n const { markFn, maxIdsPerCall, onError } = options;\n return useMutation<void, Error, string[]>({\n mutationFn: (pendingMediaIds) => markInBatches(markFn, maxIdsPerCall, pendingMediaIds),\n onError: (error, pendingMediaIds) => {\n if (error instanceof MarkUploadActivitySeenError) onError?.(error.failure, error.unmarkedIds);\n else onError?.(error, pendingMediaIds);\n },\n });\n}\n"]}
@@ -8,20 +8,10 @@ export interface UploadProcessingState {
8
8
  isRejected: boolean;
9
9
  }
10
10
  export interface UseUploadProcessingOptions {
11
- /** Override the processing-phase message. */
12
11
  processingMessage?: string;
13
- /** Override the success-phase message. */
14
12
  successMessage?: string;
15
- /** Override the failed-phase message. */
16
13
  failedMessage?: string;
17
- /** Override the rejected-phase message. */
18
14
  rejectedMessage?: string;
19
15
  }
20
- /**
21
- * Returns local UI processing state for the given pendingMediaId. If the
22
- * `onTerminal` callback is provided, it fires once when the doc reaches a
23
- * terminal status (completed / failed / rejected). Use `onTerminal` for
24
- * local UI cleanup — global toast/dialog routing belongs in the provider.
25
- */
26
16
  export declare function useUploadProcessing<TFileOrigin extends string = string>(pendingMediaId: string | null | undefined, onTerminal?: (state: UploadProcessingState) => void, options?: UseUploadProcessingOptions): UploadProcessingState;
27
17
  //# sourceMappingURL=use-upload-processing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-upload-processing.d.ts","sourceRoot":"","sources":["../../../src/react/tray/use-upload-processing.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,qBAAqB;IACpC,MAAM,EACJ,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,eAAe,GAAG,gBAAgB,CAAC;IAChG,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAOD,MAAM,WAAW,0BAA0B;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAyED,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,MAAM,GAAG,MAAM,EACrE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,EACnD,OAAO,GAAE,0BAA+B,GACvC,qBAAqB,CA6BvB"}
@@ -1,19 +1,7 @@
1
1
  'use client';
2
- /**
3
- * Adapter that maps `useInFlightUpload` state to the legacy
4
- * `{ isProcessing, message, isComplete, isRejected, isFailed }` shape used
5
- * by per-component spinners and overlays.
6
- *
7
- * Routing of toasts and rejection dialogs lives in `InFlightUploadsProvider`
8
- * via the consumer-supplied callbacks — `useUploadProcessing` only surfaces
9
- * local UI state. The `onTerminal` callback fires once per terminal status
10
- * (completed / failed / rejected) and is intended for local UI cleanup
11
- * (clearing form state, closing modals, etc.).
12
- */
13
2
  import { useEffect, useRef } from 'react';
14
- import { isPendingMediaNonTerminalStatus, PendingMediaStatusSchema } from '@ttt-productions/media-schemas';
3
+ import { isPendingMediaNonTerminalStatus, PendingMediaStatusSchema, } from '@ttt-productions/media-schemas';
15
4
  import { useInFlightUpload } from './in-flight-uploads-provider.js';
16
- // Pending-media status values derive from media-schemas' one declaration.
17
5
  const STATUS = PendingMediaStatusSchema.enum;
18
6
  const DEFAULT_PROCESSING_MESSAGE = 'Processing your content...';
19
7
  const DEFAULT_SUCCESS_MESSAGE = 'Complete!';
@@ -85,12 +73,6 @@ function buildState(upload, opts) {
85
73
  isRejected: false,
86
74
  };
87
75
  }
88
- /**
89
- * Returns local UI processing state for the given pendingMediaId. If the
90
- * `onTerminal` callback is provided, it fires once when the doc reaches a
91
- * terminal status (completed / failed / rejected). Use `onTerminal` for
92
- * local UI cleanup — global toast/dialog routing belongs in the provider.
93
- */
94
76
  export function useUploadProcessing(pendingMediaId, onTerminal, options = {}) {
95
77
  const upload = useInFlightUpload(pendingMediaId);
96
78
  const state = buildState(upload, options);
@@ -109,7 +91,15 @@ export function useUploadProcessing(pendingMediaId, onTerminal, options = {}) {
109
91
  onTerminal(state);
110
92
  }
111
93
  }
112
- }, [pendingMediaId, state.status, state.isComplete, state.isFailed, state.isRejected, state, onTerminal]);
94
+ }, [
95
+ pendingMediaId,
96
+ state.status,
97
+ state.isComplete,
98
+ state.isFailed,
99
+ state.isRejected,
100
+ state,
101
+ onTerminal,
102
+ ]);
113
103
  return state;
114
104
  }
115
105
  //# sourceMappingURL=use-upload-processing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-upload-processing.js","sourceRoot":"","sources":["../../../src/react/tray/use-upload-processing.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport {\n isPendingMediaNonTerminalStatus,\n PendingMediaStatusSchema,\n} from '@ttt-productions/media-schemas';\nimport { useInFlightUpload, type InFlightUpload } from './in-flight-uploads-provider.js';\n\nconst STATUS = PendingMediaStatusSchema.enum;\n\nexport interface UploadProcessingState {\n status:\n 'idle' | 'pending' | 'processing' | 'success' | 'failed' | 'rejected-text' | 'rejected-media';\n message: string;\n errorMessage?: string;\n isProcessing: boolean;\n isComplete: boolean;\n isFailed: boolean;\n isRejected: boolean;\n}\n\nconst DEFAULT_PROCESSING_MESSAGE = 'Processing your content...';\nconst DEFAULT_SUCCESS_MESSAGE = 'Complete!';\nconst DEFAULT_FAILED_MESSAGE = 'Processing failed';\nconst DEFAULT_REJECTED_MESSAGE = 'Content rejected';\n\nexport interface UseUploadProcessingOptions {\n processingMessage?: string;\n successMessage?: string;\n failedMessage?: string;\n rejectedMessage?: string;\n}\n\nfunction buildState<TFileOrigin extends string>(\n upload: InFlightUpload<TFileOrigin> | undefined,\n opts: UseUploadProcessingOptions,\n): UploadProcessingState {\n const processingMessage = opts.processingMessage ?? DEFAULT_PROCESSING_MESSAGE;\n const successMessage = opts.successMessage ?? DEFAULT_SUCCESS_MESSAGE;\n const failedMessage = opts.failedMessage ?? DEFAULT_FAILED_MESSAGE;\n const rejectedMessage = opts.rejectedMessage ?? DEFAULT_REJECTED_MESSAGE;\n\n if (!upload) {\n return {\n status: 'idle',\n message: '',\n isProcessing: false,\n isComplete: false,\n isFailed: false,\n isRejected: false,\n };\n }\n if (isPendingMediaNonTerminalStatus(upload.status)) {\n return {\n status: upload.status,\n message: processingMessage,\n isProcessing: true,\n isComplete: false,\n isFailed: false,\n isRejected: false,\n };\n }\n if (upload.status === STATUS.completed) {\n return {\n status: 'success',\n message: successMessage,\n isProcessing: false,\n isComplete: true,\n isFailed: false,\n isRejected: false,\n };\n }\n if (upload.status === STATUS.failed) {\n return {\n status: 'failed',\n message: failedMessage,\n errorMessage: upload.errorMessage,\n isProcessing: false,\n isComplete: false,\n isFailed: true,\n isRejected: false,\n };\n }\n if (upload.status === STATUS.rejected) {\n return {\n status: upload.rejectionType === 'text' ? 'rejected-text' : 'rejected-media',\n message: rejectedMessage,\n errorMessage: upload.errorMessage,\n isProcessing: false,\n isComplete: false,\n isFailed: false,\n isRejected: true,\n };\n }\n return {\n status: 'processing',\n message: processingMessage,\n isProcessing: true,\n isComplete: false,\n isFailed: false,\n isRejected: false,\n };\n}\n\nexport function useUploadProcessing<TFileOrigin extends string = string>(\n pendingMediaId: string | null | undefined,\n onTerminal?: (state: UploadProcessingState) => void,\n options: UseUploadProcessingOptions = {},\n): UploadProcessingState {\n const upload = useInFlightUpload<TFileOrigin>(pendingMediaId);\n const state = buildState(upload, options);\n const lastFiredRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!onTerminal) return;\n if (!pendingMediaId) {\n lastFiredRef.current = null;\n return;\n }\n if (state.isComplete || state.isFailed || state.isRejected) {\n const sig = `${pendingMediaId}:${state.status}`;\n if (lastFiredRef.current !== sig) {\n lastFiredRef.current = sig;\n onTerminal(state);\n }\n }\n }, [\n pendingMediaId,\n state.status,\n state.isComplete,\n state.isFailed,\n state.isRejected,\n state,\n onTerminal,\n ]);\n\n return state;\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import React, { type ReactNode, type Ref } from 'react';
2
+ import type { ClientMediaClaim, MediaOriginSpec, UploadState } from '@ttt-productions/media-schemas';
3
+ export interface DeferredUploadFormShellHandle {
4
+ submit: () => void;
5
+ }
6
+ export interface DeferredUploadFormShellProps<TVariables extends {
7
+ onProgress?: (s: UploadState | null) => void;
8
+ signal?: AbortSignal;
9
+ }, TResult = unknown> {
10
+ spec: MediaOriginSpec;
11
+ mutation: {
12
+ mutateAsync: (vars: TVariables) => Promise<TResult>;
13
+ isPending: boolean;
14
+ };
15
+ buildVariables: (file: File | null, onProgress: (s: UploadState | null) => void, signal: AbortSignal, claim?: ClientMediaClaim) => TVariables;
16
+ onSuccess?: (result: TResult) => void;
17
+ onError?: (err: unknown) => void;
18
+ onFileChange?: (file: File | null) => void;
19
+ onBeforeSelect?: () => boolean | Promise<boolean>;
20
+ children?: ReactNode;
21
+ }
22
+ export declare const DeferredUploadFormShell: <TVariables extends {
23
+ onProgress?: (s: UploadState | null) => void;
24
+ signal?: AbortSignal;
25
+ }, TResult = unknown>(props: DeferredUploadFormShellProps<TVariables, TResult> & {
26
+ ref?: Ref<DeferredUploadFormShellHandle>;
27
+ }) => React.ReactElement;
28
+ //# sourceMappingURL=deferred-upload-form-shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deferred-upload-form-shell.d.ts","sourceRoot":"","sources":["../../../src/react/upload/deferred-upload-form-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,WAAW,EACZ,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B,CAC3C,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,EACD,OAAO,GAAG,OAAO;IAEjB,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE;QACR,WAAW,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,cAAc,EAAE,CACd,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,UAAU,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,EAC3C,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,gBAAgB,KACrB,UAAU,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,cAAc,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AA8GD,eAAO,MAAM,uBAAuB,EAA+C,CACjF,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,EACD,OAAO,GAAG,OAAO,EAEjB,KAAK,EAAE,4BAA4B,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG;IACzD,GAAG,CAAC,EAAE,GAAG,CAAC,6BAA6B,CAAC,CAAC;CAC1C,KACE,KAAK,CAAC,YAAY,CAAC"}