@masters-union/union-stack 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs ADDED
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ var chunkQE2P5WH4_cjs = require('./chunk-QE2P5WH4.cjs');
4
+ var React3 = require('react');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
25
+
26
+ var UnionStackContext = React3__namespace.createContext(null);
27
+ function UnionStackProvider({ children, ...cfg }) {
28
+ const client = React3__namespace.useMemo(
29
+ () => new chunkQE2P5WH4_cjs.UnionStackClient(cfg),
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ [cfg.apiKey, cfg.apiBase, cfg.chunkSize]
32
+ );
33
+ return React3__namespace.createElement(UnionStackContext.Provider, { value: client }, children);
34
+ }
35
+ function useUnionStack() {
36
+ const ctx = React3__namespace.useContext(UnionStackContext);
37
+ if (!ctx) {
38
+ throw new Error(
39
+ "[union-stack] useUnionStack/useUploader must be used inside <UnionStackProvider>."
40
+ );
41
+ }
42
+ return ctx;
43
+ }
44
+ var INITIAL_STATE = {
45
+ isUploading: false,
46
+ progress: 0,
47
+ uploaded: [],
48
+ failed: [],
49
+ error: null
50
+ };
51
+ function useUploader(globalOpts = {}) {
52
+ const client = useUnionStack();
53
+ const [state, setState] = React3__namespace.useState(INITIAL_STATE);
54
+ const abortRef = React3__namespace.useRef(null);
55
+ const progressRef = React3__namespace.useRef(/* @__PURE__ */ new Map());
56
+ const updateAggregateProgress = React3__namespace.useCallback(() => {
57
+ let total = 0, loaded = 0;
58
+ progressRef.current.forEach((v) => {
59
+ total += v.totalBytes;
60
+ loaded += v.loaded;
61
+ });
62
+ const pct = total > 0 ? Math.min(100, Math.round(loaded / total * 100)) : 0;
63
+ setState((s) => ({ ...s, progress: pct }));
64
+ }, []);
65
+ const upload = React3__namespace.useCallback(async (input, opts = {}) => {
66
+ const files = Array.isArray(input) ? input : [input];
67
+ const ctrl = new AbortController();
68
+ abortRef.current = ctrl;
69
+ progressRef.current = /* @__PURE__ */ new Map();
70
+ setState({ ...INITIAL_STATE, isUploading: true });
71
+ const wrappedOpts = {
72
+ ...globalOpts,
73
+ ...opts,
74
+ signal: opts.signal ?? ctrl.signal,
75
+ onUploadStarted: (fs) => {
76
+ fs.forEach((f) => progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: 0 }));
77
+ opts.onUploadStarted?.(fs);
78
+ globalOpts.onUploadStarted?.(fs);
79
+ },
80
+ onFileUploadStarted: (f) => {
81
+ opts.onFileUploadStarted?.(f);
82
+ globalOpts.onFileUploadStarted?.(f);
83
+ },
84
+ onFileUploadProgress: (f, ev) => {
85
+ progressRef.current.set(f.uploadId, { totalBytes: ev.totalBytes, loaded: ev.loaded });
86
+ updateAggregateProgress();
87
+ opts.onFileUploadProgress?.(f, ev);
88
+ globalOpts.onFileUploadProgress?.(f, ev);
89
+ },
90
+ onFileUploadFinished: (f) => {
91
+ progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });
92
+ updateAggregateProgress();
93
+ setState((s) => ({ ...s, uploaded: [...s.uploaded, f] }));
94
+ opts.onFileUploadFinished?.(f);
95
+ globalOpts.onFileUploadFinished?.(f);
96
+ },
97
+ onFileUploadFailed: (f, error) => {
98
+ progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });
99
+ updateAggregateProgress();
100
+ setState((s) => ({ ...s, failed: [...s.failed, { file: f, error }] }));
101
+ opts.onFileUploadFailed?.(f, error);
102
+ globalOpts.onFileUploadFailed?.(f, error);
103
+ },
104
+ onUploadDone: (result) => {
105
+ setState((s) => ({ ...s, isUploading: false }));
106
+ opts.onUploadDone?.(result);
107
+ globalOpts.onUploadDone?.(result);
108
+ },
109
+ onError: (err) => {
110
+ setState((s) => ({ ...s, isUploading: false, error: err }));
111
+ opts.onError?.(err);
112
+ }
113
+ };
114
+ try {
115
+ return await client.uploadMany(files, wrappedOpts);
116
+ } catch (err) {
117
+ setState((s) => ({ ...s, isUploading: false, error: err }));
118
+ throw err;
119
+ }
120
+ }, [client, globalOpts, updateAggregateProgress]);
121
+ const cancel = React3__namespace.useCallback(() => {
122
+ abortRef.current?.abort();
123
+ setState((s) => ({ ...s, isUploading: false }));
124
+ }, []);
125
+ const reset = React3__namespace.useCallback(() => {
126
+ progressRef.current = /* @__PURE__ */ new Map();
127
+ setState(INITIAL_STATE);
128
+ }, []);
129
+ return { ...state, upload, cancel, reset };
130
+ }
131
+ function UnionStackPicker(props) {
132
+ const client = useUnionStack();
133
+ const handleRef = React3__namespace.useRef(null);
134
+ const optsRef = React3__namespace.useRef(props);
135
+ optsRef.current = props;
136
+ const open = React3__namespace.useCallback(async () => {
137
+ const { children: _children, ...opts } = optsRef.current;
138
+ handleRef.current = client.picker(opts);
139
+ await handleRef.current.open();
140
+ }, [client]);
141
+ const close = React3__namespace.useCallback(() => {
142
+ handleRef.current?.close();
143
+ }, []);
144
+ const cancel = React3__namespace.useCallback(() => {
145
+ handleRef.current?.cancel();
146
+ }, []);
147
+ React3__namespace.useEffect(() => () => {
148
+ handleRef.current?.close();
149
+ }, []);
150
+ return React3__namespace.createElement(React3__namespace.Fragment, null, props.children({ open, close, cancel }));
151
+ }
152
+
153
+ exports.UnionStackPicker = UnionStackPicker;
154
+ exports.UnionStackProvider = UnionStackProvider;
155
+ exports.useUnionStack = useUnionStack;
156
+ exports.useUploader = useUploader;
157
+ //# sourceMappingURL=react.cjs.map
158
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/provider.tsx","../src/react/useUploader.ts","../src/react/UnionStackPicker.tsx"],"names":["React","UnionStackClient","React2","React3"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAM,iBAAA,GAA0BA,gCAAuC,IAAI,CAAA;AAapE,SAAS,kBAAA,CAAmB,EAAE,QAAA,EAAU,GAAG,KAAI,EAA4B;AAEhF,EAAA,MAAM,MAAA,GAAeA,iBAAA,CAAA,OAAA;AAAA,IACnB,MAAM,IAAIC,kCAAA,CAAiB,GAAG,CAAA;AAAA;AAAA,IAE9B,CAAC,GAAA,CAAI,MAAA,EAAQ,GAAA,CAAI,OAAA,EAAS,IAAI,SAAS;AAAA,GACzC;AACA,EAAA,OAAaD,gCAAc,iBAAA,CAAkB,QAAA,EAAU,EAAE,KAAA,EAAO,MAAA,IAAU,QAAQ,CAAA;AACpF;AAEO,SAAS,aAAA,GAAkC;AAChD,EAAA,MAAM,GAAA,GAAYA,6BAAW,iBAAiB,CAAA;AAC9C,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;ACFA,IAAM,aAAA,GAAkC;AAAA,EACtC,WAAA,EAAa,KAAA;AAAA,EACb,QAAA,EAAU,CAAA;AAAA,EACV,UAAU,EAAC;AAAA,EACX,QAAQ,EAAC;AAAA,EACT,KAAA,EAAO;AACT,CAAA;AASO,SAAS,WAAA,CAAY,UAAA,GAAiC,EAAC,EAAsB;AAClF,EAAA,MAAM,SAAS,aAAA,EAAc;AAC7B,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAUE,2BAA2B,aAAa,CAAA;AACxE,EAAA,MAAM,QAAA,GAAiBA,yBAA+B,IAAI,CAAA;AAG1D,EAAA,MAAM,WAAA,GAAoBA,iBAAA,CAAA,MAAA,iBAA4D,IAAI,GAAA,EAAK,CAAA;AAE/F,EAAA,MAAM,uBAAA,GAAgCA,8BAAY,MAAM;AACtD,IAAA,IAAI,KAAA,GAAQ,GAAG,MAAA,GAAS,CAAA;AACxB,IAAA,WAAA,CAAY,OAAA,CAAQ,QAAQ,CAAA,CAAA,KAAK;AAAE,MAAA,KAAA,IAAS,CAAA,CAAE,UAAA;AAAY,MAAA,MAAA,IAAU,CAAA,CAAE,MAAA;AAAA,IAAQ,CAAC,CAAA;AAC/E,IAAA,MAAM,GAAA,GAAM,KAAA,GAAQ,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,KAAA,CAAO,MAAA,GAAS,KAAA,GAAS,GAAG,CAAC,CAAA,GAAI,CAAA;AAC5E,IAAA,QAAA,CAAS,QAAM,EAAE,GAAG,CAAA,EAAG,QAAA,EAAU,KAAI,CAAE,CAAA;AAAA,EACzC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,SAAeA,iBAAA,CAAA,WAAA,CAAY,OAC/B,KAAA,EACA,IAAA,GAA2B,EAAC,KACF;AAC1B,IAAA,MAAM,QAAQ,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,GAAQ,CAAC,KAAK,CAAA;AACnD,IAAA,MAAM,IAAA,GAAO,IAAI,eAAA,EAAgB;AACjC,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,WAAA,CAAY,OAAA,uBAAc,GAAA,EAAI;AAC9B,IAAA,QAAA,CAAS,EAAE,GAAG,aAAA,EAAe,WAAA,EAAa,MAAM,CAAA;AAEhD,IAAA,MAAM,WAAA,GAAkC;AAAA,MACtC,GAAG,UAAA;AAAA,MACH,GAAG,IAAA;AAAA,MACH,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,IAAA,CAAK,MAAA;AAAA,MAC5B,eAAA,EAAiB,CAAC,EAAA,KAAO;AACvB,QAAA,EAAA,CAAG,OAAA,CAAQ,CAAA,CAAA,KAAK,WAAA,CAAY,OAAA,CAAQ,IAAI,CAAA,CAAE,QAAA,EAAU,EAAE,UAAA,EAAY,CAAA,CAAE,IAAA,EAAM,MAAA,EAAQ,CAAA,EAAG,CAAC,CAAA;AACtF,QAAA,IAAA,CAAK,kBAAkB,EAAE,CAAA;AACzB,QAAA,UAAA,CAAW,kBAAkB,EAAE,CAAA;AAAA,MACjC,CAAA;AAAA,MACA,mBAAA,EAAqB,CAAC,CAAA,KAAM;AAC1B,QAAA,IAAA,CAAK,sBAAsB,CAAC,CAAA;AAC5B,QAAA,UAAA,CAAW,sBAAsB,CAAC,CAAA;AAAA,MACpC,CAAA;AAAA,MACA,oBAAA,EAAsB,CAAC,CAAA,EAAG,EAAA,KAAwB;AAChD,QAAA,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,QAAA,EAAU,EAAE,UAAA,EAAY,EAAA,CAAG,UAAA,EAAY,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAA;AACpF,QAAA,uBAAA,EAAwB;AACxB,QAAA,IAAA,CAAK,oBAAA,GAAuB,GAAG,EAAE,CAAA;AACjC,QAAA,UAAA,CAAW,oBAAA,GAAuB,GAAG,EAAE,CAAA;AAAA,MACzC,CAAA;AAAA,MACA,oBAAA,EAAsB,CAAC,CAAA,KAAM;AAC3B,QAAA,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,QAAA,EAAU,EAAE,UAAA,EAAY,CAAA,CAAE,IAAA,EAAM,MAAA,EAAQ,CAAA,CAAE,IAAA,EAAM,CAAA;AAC1E,QAAA,uBAAA,EAAwB;AACxB,QAAA,QAAA,CAAS,CAAA,CAAA,MAAM,EAAE,GAAG,CAAA,EAAG,QAAA,EAAU,CAAC,GAAG,CAAA,CAAE,QAAA,EAAU,CAAC,CAAA,EAAE,CAAE,CAAA;AACtD,QAAA,IAAA,CAAK,uBAAuB,CAAC,CAAA;AAC7B,QAAA,UAAA,CAAW,uBAAuB,CAAC,CAAA;AAAA,MACrC,CAAA;AAAA,MACA,kBAAA,EAAoB,CAAC,CAAA,EAAG,KAAA,KAAU;AAEhC,QAAA,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,QAAA,EAAU,EAAE,UAAA,EAAY,CAAA,CAAE,IAAA,EAAM,MAAA,EAAQ,CAAA,CAAE,IAAA,EAAM,CAAA;AAC1E,QAAA,uBAAA,EAAwB;AACxB,QAAA,QAAA,CAAS,CAAA,CAAA,MAAM,EAAE,GAAG,CAAA,EAAG,QAAQ,CAAC,GAAG,CAAA,CAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,CAAA,EAAG,KAAA,EAAO,GAAE,CAAE,CAAA;AACnE,QAAA,IAAA,CAAK,kBAAA,GAAqB,GAAG,KAAK,CAAA;AAClC,QAAA,UAAA,CAAW,kBAAA,GAAqB,GAAG,KAAK,CAAA;AAAA,MAC1C,CAAA;AAAA,MACA,YAAA,EAAc,CAAC,MAAA,KAAW;AACxB,QAAA,QAAA,CAAS,QAAM,EAAE,GAAG,CAAA,EAAG,WAAA,EAAa,OAAM,CAAE,CAAA;AAC5C,QAAA,IAAA,CAAK,eAAe,MAAM,CAAA;AAC1B,QAAA,UAAA,CAAW,eAAe,MAAM,CAAA;AAAA,MAClC,CAAA;AAAA,MACA,OAAA,EAAS,CAAC,GAAA,KAAQ;AAChB,QAAA,QAAA,CAAS,CAAA,CAAA,MAAM,EAAE,GAAG,CAAA,EAAG,aAAa,KAAA,EAAO,KAAA,EAAO,KAAI,CAAE,CAAA;AACxD,QAAA,IAAA,CAAK,UAAU,GAAG,CAAA;AAAA,MACpB;AAAA,KACF;AAEA,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,MAAA,CAAO,UAAA,CAAW,KAAA,EAAO,WAAW,CAAA;AAAA,IACnD,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA,CAAS,CAAA,CAAA,MAAM,EAAE,GAAG,CAAA,EAAG,aAAa,KAAA,EAAO,KAAA,EAAO,KAAmB,CAAE,CAAA;AACvE,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,UAAA,EAAY,uBAAuB,CAAC,CAAA;AAEhD,EAAA,MAAM,MAAA,GAAeA,8BAAY,MAAM;AACrC,IAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AACxB,IAAA,QAAA,CAAS,QAAM,EAAE,GAAG,CAAA,EAAG,WAAA,EAAa,OAAM,CAAE,CAAA;AAAA,EAC9C,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAcA,8BAAY,MAAM;AACpC,IAAA,WAAA,CAAY,OAAA,uBAAc,GAAA,EAAI;AAC9B,IAAA,QAAA,CAAS,aAAa,CAAA;AAAA,EACxB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAA,EAAQ,QAAQ,KAAA,EAAM;AAC3C;ACpHO,SAAS,iBAAiB,KAAA,EAAkD;AACjF,EAAA,MAAM,SAAS,aAAA,EAAc;AAC7B,EAAA,MAAM,SAAA,GAAkBC,yBAA4B,IAAI,CAAA;AAGxD,EAAA,MAAM,OAAA,GAAgBA,yBAAO,KAAK,CAAA;AAClC,EAAA,OAAA,CAAQ,OAAA,GAAU,KAAA;AAElB,EAAA,MAAM,IAAA,GAAaA,8BAAY,YAAY;AACzC,IAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAW,GAAG,IAAA,KAAS,OAAA,CAAQ,OAAA;AACjD,IAAA,SAAA,CAAU,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AACtC,IAAA,MAAM,SAAA,CAAU,QAAQ,IAAA,EAAK;AAAA,EAC/B,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,KAAA,GAAcA,8BAAY,MAAM;AAAE,IAAA,SAAA,CAAU,SAAS,KAAA,EAAM;AAAA,EAAG,CAAA,EAAG,EAAE,CAAA;AACzE,EAAA,MAAM,MAAA,GAAeA,8BAAY,MAAM;AAAE,IAAA,SAAA,CAAU,SAAS,MAAA,EAAO;AAAA,EAAG,CAAA,EAAG,EAAE,CAAA;AAG3E,EAAMA,iBAAA,CAAA,SAAA,CAAU,MAAM,MAAM;AAAE,IAAA,SAAA,CAAU,SAAS,KAAA,EAAM;AAAA,EAAG,CAAA,EAAG,EAAE,CAAA;AAE/D,EAAA,OAAaA,iBAAA,CAAA,aAAA,CAAoBA,iBAAA,CAAA,QAAA,EAAU,IAAA,EAAM,KAAA,CAAM,QAAA,CAAS,EAAE,IAAA,EAAM,KAAA,EAAO,MAAA,EAAQ,CAAC,CAAA;AAC1F","file":"react.cjs","sourcesContent":["import * as React from 'react';\nimport { UnionStackClient } from '../client.js';\nimport type { ClientConfig } from '../types.js';\n\nconst UnionStackContext = React.createContext<UnionStackClient | null>(null);\n\nexport interface UnionStackProviderProps extends ClientConfig {\n children: React.ReactNode;\n}\n\n/**\n * Provide an SDK client to the React tree.\n *\n * <UnionStackProvider apiKey=\"unionstack_live_…\" apiBase=\"…\">\n * <App />\n * </UnionStackProvider>\n */\nexport function UnionStackProvider({ children, ...cfg }: UnionStackProviderProps) {\n // Re-create the client when apiKey or apiBase changes; otherwise keep stable.\n const client = React.useMemo(\n () => new UnionStackClient(cfg),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [cfg.apiKey, cfg.apiBase, cfg.chunkSize],\n );\n return React.createElement(UnionStackContext.Provider, { value: client }, children);\n}\n\nexport function useUnionStack(): UnionStackClient {\n const ctx = React.useContext(UnionStackContext);\n if (!ctx) {\n throw new Error(\n '[union-stack] useUnionStack/useUploader must be used inside <UnionStackProvider>.',\n );\n }\n return ctx;\n}\n","import * as React from 'react';\nimport { useUnionStack } from './provider.js';\nimport type {\n BatchUploadOptions,\n PickedFile,\n PickResponse,\n ProgressEvent as USProgressEvent,\n UploadError,\n UploadedFile,\n} from '../types.js';\n\nexport interface UseUploaderState {\n /** True between `upload()` start and its terminal callback. */\n isUploading: boolean;\n /** 0-100, averaged across files in a batch. */\n progress: number;\n /** Files that successfully uploaded — populated as each completes. */\n uploaded: UploadedFile[];\n /** Files that failed — populated as each fails. */\n failed: Array<{ file: PickedFile; error: UploadError }>;\n /** Terminal error for the *batch* (auth fail etc.). Per-file errors land in `failed`. */\n error: UploadError | null;\n}\n\nexport interface UseUploaderResult extends UseUploaderState {\n /** Upload one or more files. Returns the same PickResponse shape as the SDK. */\n upload: (files: File | Blob | (File | Blob)[], opts?: BatchUploadOptions) => Promise<PickResponse>;\n /** Cancel any in-flight uploads from this hook. */\n cancel: () => void;\n /** Reset state to initial. */\n reset: () => void;\n}\n\nconst INITIAL_STATE: UseUploaderState = {\n isUploading: false,\n progress: 0,\n uploaded: [],\n failed: [],\n error: null,\n};\n\n/**\n * React hook wrapping `client.uploadMany`. Tracks aggregate progress,\n * accumulates uploaded/failed lists, and exposes a cancel handle.\n *\n * const { upload, isUploading, progress, uploaded } = useUploader();\n * <input type=\"file\" onChange={(e) => upload(e.target.files![0])} />\n */\nexport function useUploader(globalOpts: BatchUploadOptions = {}): UseUploaderResult {\n const client = useUnionStack();\n const [state, setState] = React.useState<UseUploaderState>(INITIAL_STATE);\n const abortRef = React.useRef<AbortController | null>(null);\n\n // Track in-flight progress per file so we can compute an aggregate.\n const progressRef = React.useRef<Map<string, { totalBytes: number; loaded: number }>>(new Map());\n\n const updateAggregateProgress = React.useCallback(() => {\n let total = 0, loaded = 0;\n progressRef.current.forEach(v => { total += v.totalBytes; loaded += v.loaded; });\n const pct = total > 0 ? Math.min(100, Math.round((loaded / total) * 100)) : 0;\n setState(s => ({ ...s, progress: pct }));\n }, []);\n\n const upload = React.useCallback(async (\n input: File | Blob | (File | Blob)[],\n opts: BatchUploadOptions = {},\n ): Promise<PickResponse> => {\n const files = Array.isArray(input) ? input : [input];\n const ctrl = new AbortController();\n abortRef.current = ctrl;\n progressRef.current = new Map();\n setState({ ...INITIAL_STATE, isUploading: true });\n\n const wrappedOpts: BatchUploadOptions = {\n ...globalOpts,\n ...opts,\n signal: opts.signal ?? ctrl.signal,\n onUploadStarted: (fs) => {\n fs.forEach(f => progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: 0 }));\n opts.onUploadStarted?.(fs);\n globalOpts.onUploadStarted?.(fs);\n },\n onFileUploadStarted: (f) => {\n opts.onFileUploadStarted?.(f);\n globalOpts.onFileUploadStarted?.(f);\n },\n onFileUploadProgress: (f, ev: USProgressEvent) => {\n progressRef.current.set(f.uploadId, { totalBytes: ev.totalBytes, loaded: ev.loaded });\n updateAggregateProgress();\n opts.onFileUploadProgress?.(f, ev);\n globalOpts.onFileUploadProgress?.(f, ev);\n },\n onFileUploadFinished: (f) => {\n progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });\n updateAggregateProgress();\n setState(s => ({ ...s, uploaded: [...s.uploaded, f] }));\n opts.onFileUploadFinished?.(f);\n globalOpts.onFileUploadFinished?.(f);\n },\n onFileUploadFailed: (f, error) => {\n // Treat failed bytes as \"fully accounted for\" so aggregate progress completes.\n progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });\n updateAggregateProgress();\n setState(s => ({ ...s, failed: [...s.failed, { file: f, error }] }));\n opts.onFileUploadFailed?.(f, error);\n globalOpts.onFileUploadFailed?.(f, error);\n },\n onUploadDone: (result) => {\n setState(s => ({ ...s, isUploading: false }));\n opts.onUploadDone?.(result);\n globalOpts.onUploadDone?.(result);\n },\n onError: (err) => {\n setState(s => ({ ...s, isUploading: false, error: err }));\n opts.onError?.(err);\n },\n };\n\n try {\n return await client.uploadMany(files, wrappedOpts);\n } catch (err) {\n setState(s => ({ ...s, isUploading: false, error: err as UploadError }));\n throw err;\n }\n }, [client, globalOpts, updateAggregateProgress]);\n\n const cancel = React.useCallback(() => {\n abortRef.current?.abort();\n setState(s => ({ ...s, isUploading: false }));\n }, []);\n\n const reset = React.useCallback(() => {\n progressRef.current = new Map();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, upload, cancel, reset };\n}\n","import * as React from 'react';\nimport { useUnionStack } from './provider.js';\nimport type { PickerOptions, PickerHandle } from '../picker/types.js';\n\nexport interface UnionStackPickerProps extends PickerOptions {\n /**\n * Render-prop receives an `open()` function the caller wires to a button\n * or any other trigger.\n *\n * <UnionStackPicker onUploadDone={…}>\n * {({ open }) => <button onClick={open}>Upload</button>}\n * </UnionStackPicker>\n */\n children: (api: { open: () => Promise<void>; close: () => void; cancel: () => void }) => React.ReactNode;\n}\n\n/**\n * React wrapper around `client.picker()`. The picker bundle is lazy-loaded\n * the first time `open()` is called — the component itself is just a\n * render-prop and doesn't pull in the modal code at import time.\n */\nexport function UnionStackPicker(props: UnionStackPickerProps): React.ReactElement {\n const client = useUnionStack();\n const handleRef = React.useRef<PickerHandle | null>(null);\n\n // Stabilize the latest options without re-creating the handle on every render.\n const optsRef = React.useRef(props);\n optsRef.current = props;\n\n const open = React.useCallback(async () => {\n const { children: _children, ...opts } = optsRef.current;\n handleRef.current = client.picker(opts) as PickerHandle;\n await handleRef.current.open();\n }, [client]);\n\n const close = React.useCallback(() => { handleRef.current?.close(); }, []);\n const cancel = React.useCallback(() => { handleRef.current?.cancel(); }, []);\n\n // Make sure we tear down the modal if the component unmounts mid-upload.\n React.useEffect(() => () => { handleRef.current?.close(); }, []);\n\n return React.createElement(React.Fragment, null, props.children({ open, close, cancel }));\n}\n"]}
@@ -0,0 +1,72 @@
1
+ import * as React from 'react';
2
+ import { C as ClientConfig, U as UnionStackClient, f as UploadedFile, a as PickedFile, c as UploadError, B as BatchUploadOptions, P as PickResponse, g as PickerOptions } from './client-xFfk4uu4.cjs';
3
+ export { i as PickerBranding, h as PickerHandle, j as PickerTheme, b as ProgressEvent, d as UploadErrorCode, e as UploadOptions } from './client-xFfk4uu4.cjs';
4
+
5
+ interface UnionStackProviderProps extends ClientConfig {
6
+ children: React.ReactNode;
7
+ }
8
+ /**
9
+ * Provide an SDK client to the React tree.
10
+ *
11
+ * <UnionStackProvider apiKey="unionstack_live_…" apiBase="…">
12
+ * <App />
13
+ * </UnionStackProvider>
14
+ */
15
+ declare function UnionStackProvider({ children, ...cfg }: UnionStackProviderProps): React.FunctionComponentElement<React.ProviderProps<UnionStackClient | null>>;
16
+ declare function useUnionStack(): UnionStackClient;
17
+
18
+ interface UseUploaderState {
19
+ /** True between `upload()` start and its terminal callback. */
20
+ isUploading: boolean;
21
+ /** 0-100, averaged across files in a batch. */
22
+ progress: number;
23
+ /** Files that successfully uploaded — populated as each completes. */
24
+ uploaded: UploadedFile[];
25
+ /** Files that failed — populated as each fails. */
26
+ failed: Array<{
27
+ file: PickedFile;
28
+ error: UploadError;
29
+ }>;
30
+ /** Terminal error for the *batch* (auth fail etc.). Per-file errors land in `failed`. */
31
+ error: UploadError | null;
32
+ }
33
+ interface UseUploaderResult extends UseUploaderState {
34
+ /** Upload one or more files. Returns the same PickResponse shape as the SDK. */
35
+ upload: (files: File | Blob | (File | Blob)[], opts?: BatchUploadOptions) => Promise<PickResponse>;
36
+ /** Cancel any in-flight uploads from this hook. */
37
+ cancel: () => void;
38
+ /** Reset state to initial. */
39
+ reset: () => void;
40
+ }
41
+ /**
42
+ * React hook wrapping `client.uploadMany`. Tracks aggregate progress,
43
+ * accumulates uploaded/failed lists, and exposes a cancel handle.
44
+ *
45
+ * const { upload, isUploading, progress, uploaded } = useUploader();
46
+ * <input type="file" onChange={(e) => upload(e.target.files![0])} />
47
+ */
48
+ declare function useUploader(globalOpts?: BatchUploadOptions): UseUploaderResult;
49
+
50
+ interface UnionStackPickerProps extends PickerOptions {
51
+ /**
52
+ * Render-prop receives an `open()` function the caller wires to a button
53
+ * or any other trigger.
54
+ *
55
+ * <UnionStackPicker onUploadDone={…}>
56
+ * {({ open }) => <button onClick={open}>Upload</button>}
57
+ * </UnionStackPicker>
58
+ */
59
+ children: (api: {
60
+ open: () => Promise<void>;
61
+ close: () => void;
62
+ cancel: () => void;
63
+ }) => React.ReactNode;
64
+ }
65
+ /**
66
+ * React wrapper around `client.picker()`. The picker bundle is lazy-loaded
67
+ * the first time `open()` is called — the component itself is just a
68
+ * render-prop and doesn't pull in the modal code at import time.
69
+ */
70
+ declare function UnionStackPicker(props: UnionStackPickerProps): React.ReactElement;
71
+
72
+ export { BatchUploadOptions, ClientConfig, PickResponse, PickedFile, PickerOptions, UnionStackPicker, type UnionStackPickerProps, UnionStackProvider, UploadError, UploadedFile, type UseUploaderResult, type UseUploaderState, useUnionStack, useUploader };
@@ -0,0 +1,72 @@
1
+ import * as React from 'react';
2
+ import { C as ClientConfig, U as UnionStackClient, f as UploadedFile, a as PickedFile, c as UploadError, B as BatchUploadOptions, P as PickResponse, g as PickerOptions } from './client-xFfk4uu4.js';
3
+ export { i as PickerBranding, h as PickerHandle, j as PickerTheme, b as ProgressEvent, d as UploadErrorCode, e as UploadOptions } from './client-xFfk4uu4.js';
4
+
5
+ interface UnionStackProviderProps extends ClientConfig {
6
+ children: React.ReactNode;
7
+ }
8
+ /**
9
+ * Provide an SDK client to the React tree.
10
+ *
11
+ * <UnionStackProvider apiKey="unionstack_live_…" apiBase="…">
12
+ * <App />
13
+ * </UnionStackProvider>
14
+ */
15
+ declare function UnionStackProvider({ children, ...cfg }: UnionStackProviderProps): React.FunctionComponentElement<React.ProviderProps<UnionStackClient | null>>;
16
+ declare function useUnionStack(): UnionStackClient;
17
+
18
+ interface UseUploaderState {
19
+ /** True between `upload()` start and its terminal callback. */
20
+ isUploading: boolean;
21
+ /** 0-100, averaged across files in a batch. */
22
+ progress: number;
23
+ /** Files that successfully uploaded — populated as each completes. */
24
+ uploaded: UploadedFile[];
25
+ /** Files that failed — populated as each fails. */
26
+ failed: Array<{
27
+ file: PickedFile;
28
+ error: UploadError;
29
+ }>;
30
+ /** Terminal error for the *batch* (auth fail etc.). Per-file errors land in `failed`. */
31
+ error: UploadError | null;
32
+ }
33
+ interface UseUploaderResult extends UseUploaderState {
34
+ /** Upload one or more files. Returns the same PickResponse shape as the SDK. */
35
+ upload: (files: File | Blob | (File | Blob)[], opts?: BatchUploadOptions) => Promise<PickResponse>;
36
+ /** Cancel any in-flight uploads from this hook. */
37
+ cancel: () => void;
38
+ /** Reset state to initial. */
39
+ reset: () => void;
40
+ }
41
+ /**
42
+ * React hook wrapping `client.uploadMany`. Tracks aggregate progress,
43
+ * accumulates uploaded/failed lists, and exposes a cancel handle.
44
+ *
45
+ * const { upload, isUploading, progress, uploaded } = useUploader();
46
+ * <input type="file" onChange={(e) => upload(e.target.files![0])} />
47
+ */
48
+ declare function useUploader(globalOpts?: BatchUploadOptions): UseUploaderResult;
49
+
50
+ interface UnionStackPickerProps extends PickerOptions {
51
+ /**
52
+ * Render-prop receives an `open()` function the caller wires to a button
53
+ * or any other trigger.
54
+ *
55
+ * <UnionStackPicker onUploadDone={…}>
56
+ * {({ open }) => <button onClick={open}>Upload</button>}
57
+ * </UnionStackPicker>
58
+ */
59
+ children: (api: {
60
+ open: () => Promise<void>;
61
+ close: () => void;
62
+ cancel: () => void;
63
+ }) => React.ReactNode;
64
+ }
65
+ /**
66
+ * React wrapper around `client.picker()`. The picker bundle is lazy-loaded
67
+ * the first time `open()` is called — the component itself is just a
68
+ * render-prop and doesn't pull in the modal code at import time.
69
+ */
70
+ declare function UnionStackPicker(props: UnionStackPickerProps): React.ReactElement;
71
+
72
+ export { BatchUploadOptions, ClientConfig, PickResponse, PickedFile, PickerOptions, UnionStackPicker, type UnionStackPickerProps, UnionStackProvider, UploadError, UploadedFile, type UseUploaderResult, type UseUploaderState, useUnionStack, useUploader };
package/dist/react.js ADDED
@@ -0,0 +1,133 @@
1
+ import { UnionStackClient } from './chunk-5Q2UADFS.js';
2
+ import * as React3 from 'react';
3
+
4
+ var UnionStackContext = React3.createContext(null);
5
+ function UnionStackProvider({ children, ...cfg }) {
6
+ const client = React3.useMemo(
7
+ () => new UnionStackClient(cfg),
8
+ // eslint-disable-next-line react-hooks/exhaustive-deps
9
+ [cfg.apiKey, cfg.apiBase, cfg.chunkSize]
10
+ );
11
+ return React3.createElement(UnionStackContext.Provider, { value: client }, children);
12
+ }
13
+ function useUnionStack() {
14
+ const ctx = React3.useContext(UnionStackContext);
15
+ if (!ctx) {
16
+ throw new Error(
17
+ "[union-stack] useUnionStack/useUploader must be used inside <UnionStackProvider>."
18
+ );
19
+ }
20
+ return ctx;
21
+ }
22
+ var INITIAL_STATE = {
23
+ isUploading: false,
24
+ progress: 0,
25
+ uploaded: [],
26
+ failed: [],
27
+ error: null
28
+ };
29
+ function useUploader(globalOpts = {}) {
30
+ const client = useUnionStack();
31
+ const [state, setState] = React3.useState(INITIAL_STATE);
32
+ const abortRef = React3.useRef(null);
33
+ const progressRef = React3.useRef(/* @__PURE__ */ new Map());
34
+ const updateAggregateProgress = React3.useCallback(() => {
35
+ let total = 0, loaded = 0;
36
+ progressRef.current.forEach((v) => {
37
+ total += v.totalBytes;
38
+ loaded += v.loaded;
39
+ });
40
+ const pct = total > 0 ? Math.min(100, Math.round(loaded / total * 100)) : 0;
41
+ setState((s) => ({ ...s, progress: pct }));
42
+ }, []);
43
+ const upload = React3.useCallback(async (input, opts = {}) => {
44
+ const files = Array.isArray(input) ? input : [input];
45
+ const ctrl = new AbortController();
46
+ abortRef.current = ctrl;
47
+ progressRef.current = /* @__PURE__ */ new Map();
48
+ setState({ ...INITIAL_STATE, isUploading: true });
49
+ const wrappedOpts = {
50
+ ...globalOpts,
51
+ ...opts,
52
+ signal: opts.signal ?? ctrl.signal,
53
+ onUploadStarted: (fs) => {
54
+ fs.forEach((f) => progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: 0 }));
55
+ opts.onUploadStarted?.(fs);
56
+ globalOpts.onUploadStarted?.(fs);
57
+ },
58
+ onFileUploadStarted: (f) => {
59
+ opts.onFileUploadStarted?.(f);
60
+ globalOpts.onFileUploadStarted?.(f);
61
+ },
62
+ onFileUploadProgress: (f, ev) => {
63
+ progressRef.current.set(f.uploadId, { totalBytes: ev.totalBytes, loaded: ev.loaded });
64
+ updateAggregateProgress();
65
+ opts.onFileUploadProgress?.(f, ev);
66
+ globalOpts.onFileUploadProgress?.(f, ev);
67
+ },
68
+ onFileUploadFinished: (f) => {
69
+ progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });
70
+ updateAggregateProgress();
71
+ setState((s) => ({ ...s, uploaded: [...s.uploaded, f] }));
72
+ opts.onFileUploadFinished?.(f);
73
+ globalOpts.onFileUploadFinished?.(f);
74
+ },
75
+ onFileUploadFailed: (f, error) => {
76
+ progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });
77
+ updateAggregateProgress();
78
+ setState((s) => ({ ...s, failed: [...s.failed, { file: f, error }] }));
79
+ opts.onFileUploadFailed?.(f, error);
80
+ globalOpts.onFileUploadFailed?.(f, error);
81
+ },
82
+ onUploadDone: (result) => {
83
+ setState((s) => ({ ...s, isUploading: false }));
84
+ opts.onUploadDone?.(result);
85
+ globalOpts.onUploadDone?.(result);
86
+ },
87
+ onError: (err) => {
88
+ setState((s) => ({ ...s, isUploading: false, error: err }));
89
+ opts.onError?.(err);
90
+ }
91
+ };
92
+ try {
93
+ return await client.uploadMany(files, wrappedOpts);
94
+ } catch (err) {
95
+ setState((s) => ({ ...s, isUploading: false, error: err }));
96
+ throw err;
97
+ }
98
+ }, [client, globalOpts, updateAggregateProgress]);
99
+ const cancel = React3.useCallback(() => {
100
+ abortRef.current?.abort();
101
+ setState((s) => ({ ...s, isUploading: false }));
102
+ }, []);
103
+ const reset = React3.useCallback(() => {
104
+ progressRef.current = /* @__PURE__ */ new Map();
105
+ setState(INITIAL_STATE);
106
+ }, []);
107
+ return { ...state, upload, cancel, reset };
108
+ }
109
+ function UnionStackPicker(props) {
110
+ const client = useUnionStack();
111
+ const handleRef = React3.useRef(null);
112
+ const optsRef = React3.useRef(props);
113
+ optsRef.current = props;
114
+ const open = React3.useCallback(async () => {
115
+ const { children: _children, ...opts } = optsRef.current;
116
+ handleRef.current = client.picker(opts);
117
+ await handleRef.current.open();
118
+ }, [client]);
119
+ const close = React3.useCallback(() => {
120
+ handleRef.current?.close();
121
+ }, []);
122
+ const cancel = React3.useCallback(() => {
123
+ handleRef.current?.cancel();
124
+ }, []);
125
+ React3.useEffect(() => () => {
126
+ handleRef.current?.close();
127
+ }, []);
128
+ return React3.createElement(React3.Fragment, null, props.children({ open, close, cancel }));
129
+ }
130
+
131
+ export { UnionStackPicker, UnionStackProvider, useUnionStack, useUploader };
132
+ //# sourceMappingURL=react.js.map
133
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/provider.tsx","../src/react/useUploader.ts","../src/react/UnionStackPicker.tsx"],"names":["React","React2"],"mappings":"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","file":"react.js","sourcesContent":["import * as React from 'react';\nimport { UnionStackClient } from '../client.js';\nimport type { ClientConfig } from '../types.js';\n\nconst UnionStackContext = React.createContext<UnionStackClient | null>(null);\n\nexport interface UnionStackProviderProps extends ClientConfig {\n children: React.ReactNode;\n}\n\n/**\n * Provide an SDK client to the React tree.\n *\n * <UnionStackProvider apiKey=\"unionstack_live_…\" apiBase=\"…\">\n * <App />\n * </UnionStackProvider>\n */\nexport function UnionStackProvider({ children, ...cfg }: UnionStackProviderProps) {\n // Re-create the client when apiKey or apiBase changes; otherwise keep stable.\n const client = React.useMemo(\n () => new UnionStackClient(cfg),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [cfg.apiKey, cfg.apiBase, cfg.chunkSize],\n );\n return React.createElement(UnionStackContext.Provider, { value: client }, children);\n}\n\nexport function useUnionStack(): UnionStackClient {\n const ctx = React.useContext(UnionStackContext);\n if (!ctx) {\n throw new Error(\n '[union-stack] useUnionStack/useUploader must be used inside <UnionStackProvider>.',\n );\n }\n return ctx;\n}\n","import * as React from 'react';\nimport { useUnionStack } from './provider.js';\nimport type {\n BatchUploadOptions,\n PickedFile,\n PickResponse,\n ProgressEvent as USProgressEvent,\n UploadError,\n UploadedFile,\n} from '../types.js';\n\nexport interface UseUploaderState {\n /** True between `upload()` start and its terminal callback. */\n isUploading: boolean;\n /** 0-100, averaged across files in a batch. */\n progress: number;\n /** Files that successfully uploaded — populated as each completes. */\n uploaded: UploadedFile[];\n /** Files that failed — populated as each fails. */\n failed: Array<{ file: PickedFile; error: UploadError }>;\n /** Terminal error for the *batch* (auth fail etc.). Per-file errors land in `failed`. */\n error: UploadError | null;\n}\n\nexport interface UseUploaderResult extends UseUploaderState {\n /** Upload one or more files. Returns the same PickResponse shape as the SDK. */\n upload: (files: File | Blob | (File | Blob)[], opts?: BatchUploadOptions) => Promise<PickResponse>;\n /** Cancel any in-flight uploads from this hook. */\n cancel: () => void;\n /** Reset state to initial. */\n reset: () => void;\n}\n\nconst INITIAL_STATE: UseUploaderState = {\n isUploading: false,\n progress: 0,\n uploaded: [],\n failed: [],\n error: null,\n};\n\n/**\n * React hook wrapping `client.uploadMany`. Tracks aggregate progress,\n * accumulates uploaded/failed lists, and exposes a cancel handle.\n *\n * const { upload, isUploading, progress, uploaded } = useUploader();\n * <input type=\"file\" onChange={(e) => upload(e.target.files![0])} />\n */\nexport function useUploader(globalOpts: BatchUploadOptions = {}): UseUploaderResult {\n const client = useUnionStack();\n const [state, setState] = React.useState<UseUploaderState>(INITIAL_STATE);\n const abortRef = React.useRef<AbortController | null>(null);\n\n // Track in-flight progress per file so we can compute an aggregate.\n const progressRef = React.useRef<Map<string, { totalBytes: number; loaded: number }>>(new Map());\n\n const updateAggregateProgress = React.useCallback(() => {\n let total = 0, loaded = 0;\n progressRef.current.forEach(v => { total += v.totalBytes; loaded += v.loaded; });\n const pct = total > 0 ? Math.min(100, Math.round((loaded / total) * 100)) : 0;\n setState(s => ({ ...s, progress: pct }));\n }, []);\n\n const upload = React.useCallback(async (\n input: File | Blob | (File | Blob)[],\n opts: BatchUploadOptions = {},\n ): Promise<PickResponse> => {\n const files = Array.isArray(input) ? input : [input];\n const ctrl = new AbortController();\n abortRef.current = ctrl;\n progressRef.current = new Map();\n setState({ ...INITIAL_STATE, isUploading: true });\n\n const wrappedOpts: BatchUploadOptions = {\n ...globalOpts,\n ...opts,\n signal: opts.signal ?? ctrl.signal,\n onUploadStarted: (fs) => {\n fs.forEach(f => progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: 0 }));\n opts.onUploadStarted?.(fs);\n globalOpts.onUploadStarted?.(fs);\n },\n onFileUploadStarted: (f) => {\n opts.onFileUploadStarted?.(f);\n globalOpts.onFileUploadStarted?.(f);\n },\n onFileUploadProgress: (f, ev: USProgressEvent) => {\n progressRef.current.set(f.uploadId, { totalBytes: ev.totalBytes, loaded: ev.loaded });\n updateAggregateProgress();\n opts.onFileUploadProgress?.(f, ev);\n globalOpts.onFileUploadProgress?.(f, ev);\n },\n onFileUploadFinished: (f) => {\n progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });\n updateAggregateProgress();\n setState(s => ({ ...s, uploaded: [...s.uploaded, f] }));\n opts.onFileUploadFinished?.(f);\n globalOpts.onFileUploadFinished?.(f);\n },\n onFileUploadFailed: (f, error) => {\n // Treat failed bytes as \"fully accounted for\" so aggregate progress completes.\n progressRef.current.set(f.uploadId, { totalBytes: f.size, loaded: f.size });\n updateAggregateProgress();\n setState(s => ({ ...s, failed: [...s.failed, { file: f, error }] }));\n opts.onFileUploadFailed?.(f, error);\n globalOpts.onFileUploadFailed?.(f, error);\n },\n onUploadDone: (result) => {\n setState(s => ({ ...s, isUploading: false }));\n opts.onUploadDone?.(result);\n globalOpts.onUploadDone?.(result);\n },\n onError: (err) => {\n setState(s => ({ ...s, isUploading: false, error: err }));\n opts.onError?.(err);\n },\n };\n\n try {\n return await client.uploadMany(files, wrappedOpts);\n } catch (err) {\n setState(s => ({ ...s, isUploading: false, error: err as UploadError }));\n throw err;\n }\n }, [client, globalOpts, updateAggregateProgress]);\n\n const cancel = React.useCallback(() => {\n abortRef.current?.abort();\n setState(s => ({ ...s, isUploading: false }));\n }, []);\n\n const reset = React.useCallback(() => {\n progressRef.current = new Map();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, upload, cancel, reset };\n}\n","import * as React from 'react';\nimport { useUnionStack } from './provider.js';\nimport type { PickerOptions, PickerHandle } from '../picker/types.js';\n\nexport interface UnionStackPickerProps extends PickerOptions {\n /**\n * Render-prop receives an `open()` function the caller wires to a button\n * or any other trigger.\n *\n * <UnionStackPicker onUploadDone={…}>\n * {({ open }) => <button onClick={open}>Upload</button>}\n * </UnionStackPicker>\n */\n children: (api: { open: () => Promise<void>; close: () => void; cancel: () => void }) => React.ReactNode;\n}\n\n/**\n * React wrapper around `client.picker()`. The picker bundle is lazy-loaded\n * the first time `open()` is called — the component itself is just a\n * render-prop and doesn't pull in the modal code at import time.\n */\nexport function UnionStackPicker(props: UnionStackPickerProps): React.ReactElement {\n const client = useUnionStack();\n const handleRef = React.useRef<PickerHandle | null>(null);\n\n // Stabilize the latest options without re-creating the handle on every render.\n const optsRef = React.useRef(props);\n optsRef.current = props;\n\n const open = React.useCallback(async () => {\n const { children: _children, ...opts } = optsRef.current;\n handleRef.current = client.picker(opts) as PickerHandle;\n await handleRef.current.open();\n }, [client]);\n\n const close = React.useCallback(() => { handleRef.current?.close(); }, []);\n const cancel = React.useCallback(() => { handleRef.current?.cancel(); }, []);\n\n // Make sure we tear down the modal if the component unmounts mid-upload.\n React.useEffect(() => () => { handleRef.current?.close(); }, []);\n\n return React.createElement(React.Fragment, null, props.children({ open, close, cancel }));\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@masters-union/union-stack",
3
+ "version": "0.1.2",
4
+ "description": "UnionStack — file upload SDK. Direct-to-R2 multipart uploads with a small client surface.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react.d.ts",
17
+ "import": "./dist/react.js",
18
+ "require": "./dist/react.cjs"
19
+ },
20
+ "./picker": {
21
+ "types": "./dist/picker.d.ts",
22
+ "import": "./dist/picker.js",
23
+ "require": "./dist/picker.cjs"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=17.0.0",
29
+ "react-dom": ">=17.0.0"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "react": {
33
+ "optional": true
34
+ },
35
+ "react-dom": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "sideEffects": false,
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "dev": "tsup --watch",
48
+ "typecheck": "tsc --noEmit",
49
+ "clean": "rm -rf dist",
50
+ "prepublishOnly": "npm run clean && npm run typecheck && npm run build",
51
+ "publish-cdn": "node scripts/publish-cdn.mjs",
52
+ "deploy": "npm run deploy:patch",
53
+ "deploy:patch": "npm version patch -m 'release: v%s' && git push --follow-tags",
54
+ "deploy:minor": "npm version minor -m 'release: v%s' && git push --follow-tags",
55
+ "deploy:major": "npm version major -m 'release: v%s' && git push --follow-tags"
56
+ },
57
+ "keywords": [
58
+ "upload",
59
+ "file-upload",
60
+ "multipart",
61
+ "r2",
62
+ "cloudflare",
63
+ "cdn",
64
+ "unionstack"
65
+ ],
66
+ "author": "Masters Union",
67
+ "license": "MIT",
68
+ "devDependencies": {
69
+ "@aws-sdk/client-s3": "^3.1063.0",
70
+ "@types/react": "^18.3.31",
71
+ "@types/react-dom": "^18.3.7",
72
+ "dotenv": "^17.4.2",
73
+ "react": "^18.3.1",
74
+ "react-dom": "^18.3.1",
75
+ "tsup": "^8.3.0",
76
+ "typescript": "^5.6.0"
77
+ },
78
+ "publishConfig": {
79
+ "access": "public"
80
+ },
81
+ "engines": {
82
+ "node": ">=18"
83
+ }
84
+ }