@appcorp/stellar-solutions-modules 0.1.50 → 0.1.52

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.
@@ -11,13 +11,11 @@ export type EnhancedDropzoneProps = {
11
11
  maxSize?: number;
12
12
  minSize?: number;
13
13
  disabled?: boolean;
14
- /** initial list of files or urls to show as previews */
15
- initial?: Array<File | string>;
16
- /** Called when selected File[] changes */
14
+ /** Remote image URLs to display as previews */
15
+ value?: string[];
16
+ /** Called when files are selected or removed */
17
17
  onChange?: (files: File[]) => void;
18
- /** Called when the rendered previews (string[]) change */
19
- onPreviewsChange?: (previews: string[]) => void;
20
- /** Called when a provided remote URL preview is removed (if provided) */
21
- onRemoveUrl?: (url: string) => void;
18
+ /** Called when a remote URL is removed */
19
+ onRemoveRemote?: (url: string) => void;
22
20
  };
23
21
  export declare const EnhancedDropzone: React.FC<EnhancedDropzoneProps>;
@@ -63,165 +63,107 @@ var button_1 = require("./button");
63
63
  var lucide_react_1 = require("lucide-react");
64
64
  var dropzone_1 = require("./shadcn-io/dropzone");
65
65
  var EnhancedDropzone = function (_a) {
66
- var id = _a.id, label = _a.label, info = _a.info, error = _a.error, accept = _a.accept, _b = _a.maxFiles, maxFiles = _b === void 0 ? 10 : _b, maxSize = _a.maxSize, minSize = _a.minSize, disabled = _a.disabled, _c = _a.initial, initial = _c === void 0 ? [] : _c, onChange = _a.onChange, onPreviewsChange = _a.onPreviewsChange, onRemoveUrl = _a.onRemoveUrl, className = _a.className;
67
- // Files selected locally (File objects)
68
- var _d = (0, react_1.useState)([]), files = _d[0], setFiles = _d[1];
69
- // Previews array contains strings (object URLs or provided URLs) in display order
70
- var _e = (0, react_1.useState)([]), previews = _e[0], setPreviews = _e[1];
71
- // Track which previews are remote (came from initial string URLs)
72
- var _f = (0, react_1.useState)({}), remotePreviews = _f[0], setRemotePreviews = _f[1];
73
- var createdUrlsRef = (0, react_1.useRef)([]);
74
- // Track remote URLs removed by the user so they don't reappear when
75
- // previews are rebuilt from the unchanged `initial` prop.
76
- var removedRemoteRef = (0, react_1.useRef)(new Set());
77
- var dropzoneOptions = {
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- accept: (Array.isArray(accept) ? undefined : accept) || undefined,
80
- maxFiles: maxFiles,
81
- maxSize: maxSize,
82
- minSize: minSize,
83
- disabled: disabled,
84
- onDrop: function (acceptedFiles) {
85
- // append files, respecting maxFiles
86
- var nextFiles = __spreadArray(__spreadArray([], files, true), acceptedFiles, true);
87
- if (maxFiles && nextFiles.length > maxFiles)
88
- nextFiles = nextFiles.slice(0, maxFiles);
89
- setFiles(nextFiles);
90
- onChange === null || onChange === void 0 ? void 0 : onChange(nextFiles);
91
- },
92
- };
93
- var _g = (0, react_dropzone_1.useDropzone)(dropzoneOptions), getRootProps = _g.getRootProps, getInputProps = _g.getInputProps, isDragActive = _g.isDragActive;
94
- // Build previews from initial and files
66
+ var id = _a.id, label = _a.label, info = _a.info, error = _a.error, accept = _a.accept, _b = _a.maxFiles, maxFiles = _b === void 0 ? 10 : _b, maxSize = _a.maxSize, minSize = _a.minSize, disabled = _a.disabled, _c = _a.value, value = _c === void 0 ? [] : _c, onChange = _a.onChange, onRemoveRemote = _a.onRemoveRemote, className = _a.className;
67
+ // Local files selected by user
68
+ var _d = (0, react_1.useState)([]), localFiles = _d[0], setLocalFiles = _d[1];
69
+ // Track object URLs created for local files
70
+ var localPreviewsRef = (0, react_1.useRef)(new Map());
71
+ // Create object URLs for local files
95
72
  (0, react_1.useEffect)(function () {
96
- // cleanup previous object URLs
97
- (createdUrlsRef.current || []).forEach(function (u) {
98
- try {
99
- URL.revokeObjectURL(u);
100
- }
101
- catch (_a) { }
102
- });
103
- createdUrlsRef.current = [];
104
- var next = [];
105
- var remoteMap = {};
106
- // start with initial items (may be File or string)
107
- initial.forEach(function (item) {
108
- if (typeof item === "string") {
109
- // skip any remote URL the user removed earlier
110
- if (removedRemoteRef.current.has(item))
111
- return;
112
- next.push(item);
113
- remoteMap[item] = true;
114
- }
115
- else if (item instanceof File) {
73
+ var map = localPreviewsRef.current;
74
+ // Create URLs for new files
75
+ localFiles.forEach(function (file) {
76
+ if (!map.has(file)) {
116
77
  try {
117
- var url = URL.createObjectURL(item);
118
- next.push(url);
119
- createdUrlsRef.current.push(url);
78
+ var url = URL.createObjectURL(file);
79
+ map.set(file, url);
120
80
  }
121
- catch (_a) {
122
- next.push("");
81
+ catch (error) {
82
+ console.error("Failed to create object URL:", error);
123
83
  }
124
84
  }
125
- else {
126
- next.push("");
127
- }
128
85
  });
129
- // then local files
130
- files.forEach(function (f) {
131
- try {
132
- var url = URL.createObjectURL(f);
133
- next.push(url);
134
- createdUrlsRef.current.push(url);
135
- }
136
- catch (_a) {
137
- next.push("");
86
+ // Clean up URLs for removed files
87
+ var currentFiles = new Set(localFiles);
88
+ Array.from(map.entries()).forEach(function (_a) {
89
+ var file = _a[0], url = _a[1];
90
+ if (!currentFiles.has(file)) {
91
+ URL.revokeObjectURL(url);
92
+ map.delete(file);
138
93
  }
139
94
  });
140
- setPreviews(next);
141
- setRemotePreviews(remoteMap);
142
- // notify parent about the current rendered previews (object URLs and remote URLs)
143
- onPreviewsChange === null || onPreviewsChange === void 0 ? void 0 : onPreviewsChange(next);
95
+ // Cleanup on unmount
144
96
  return function () {
145
- (createdUrlsRef.current || []).forEach(function (u) {
146
- try {
147
- URL.revokeObjectURL(u);
148
- }
149
- catch (_a) { }
97
+ Array.from(map.values()).forEach(function (url) {
98
+ URL.revokeObjectURL(url);
150
99
  });
151
- createdUrlsRef.current = [];
100
+ map.clear();
152
101
  };
153
- // files and initial are the dependencies
154
- }, [files, initial, onPreviewsChange]);
155
- // Remove a local file by index (index relative to files slice after initial length)
156
- var handleRemoveLocal = function (index) {
157
- var next = files.filter(function (_, i) { return i !== index; });
158
- setFiles(next);
159
- onChange === null || onChange === void 0 ? void 0 : onChange(next);
160
- };
161
- // Remove a preview by its absolute preview index (index within `previews`)
162
- // If the preview is remote (string from `initial`), call onRemoveUrl and
163
- // remove only that single occurrence. If it's a local file, delegate to
164
- // handleRemoveLocal with the computed files index.
165
- var handleRemoveByPreviewIndex = function (previewIndex) {
166
- var entry = previews[previewIndex];
167
- if (entry === undefined)
168
- return;
169
- var isRemote = Boolean(remotePreviews[entry]);
170
- if (isRemote) {
171
- // notify parent
172
- onRemoveUrl === null || onRemoveUrl === void 0 ? void 0 : onRemoveUrl(entry);
173
- // record removal locally so the value isn't re-added from the `initial`
174
- // prop on the next rebuild
175
- removedRemoteRef.current.add(entry);
176
- // remove only the specific preview at previewIndex
177
- setPreviews(function (p) {
178
- var next = __spreadArray([], p, true);
179
- next.splice(previewIndex, 1);
180
- return next;
181
- });
182
- setRemotePreviews(function (m) {
183
- var next = __assign({}, m);
184
- delete next[entry];
185
- return next;
102
+ }, [localFiles]);
103
+ var dropzoneOptions = {
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ accept: (Array.isArray(accept) ? undefined : accept) || undefined,
106
+ maxFiles: maxFiles,
107
+ maxSize: maxSize,
108
+ minSize: minSize,
109
+ disabled: disabled,
110
+ onDrop: (0, react_1.useCallback)(function (acceptedFiles) {
111
+ setLocalFiles(function (prev) {
112
+ var combined = __spreadArray(__spreadArray([], prev, true), acceptedFiles, true);
113
+ var limited = maxFiles ? combined.slice(0, maxFiles) : combined;
114
+ onChange === null || onChange === void 0 ? void 0 : onChange(limited);
115
+ return limited;
186
116
  });
187
- return;
188
- }
189
- // local file - compute index relative to files array and remove
190
- var fileIndex = previewIndex - initial.length;
191
- if (fileIndex >= 0 && fileIndex < files.length) {
192
- handleRemoveLocal(fileIndex);
193
- }
117
+ }, [maxFiles, onChange]),
194
118
  };
195
- // (handleRemoveRemote removed) - use handleRemoveByPreviewIndex instead so we
196
- // can remove single occurrences by index.
197
- // carousel (embla) handles scrolling via CarouselPrevious/Next
198
- return (react_1.default.createElement("div", { className: (0, utils_1.cn)("w-full border-red-300", className) },
119
+ var _e = (0, react_dropzone_1.useDropzone)(dropzoneOptions), getRootProps = _e.getRootProps, getInputProps = _e.getInputProps, isDragActive = _e.isDragActive;
120
+ // Remove remote URL
121
+ var handleRemoveRemote = (0, react_1.useCallback)(function (url) {
122
+ onRemoveRemote === null || onRemoveRemote === void 0 ? void 0 : onRemoveRemote(url);
123
+ }, [onRemoveRemote]);
124
+ // Remove local file
125
+ var handleRemoveLocal = (0, react_1.useCallback)(function (index) {
126
+ setLocalFiles(function (prev) {
127
+ var updated = prev.filter(function (_, i) { return i !== index; });
128
+ onChange === null || onChange === void 0 ? void 0 : onChange(updated);
129
+ return updated;
130
+ });
131
+ }, [onChange]);
132
+ // Get all preview URLs (remote + local)
133
+ var allPreviews = __spreadArray(__spreadArray([], value.map(function (url) { return ({ type: "remote", url: url, index: 0 }); }), true), localFiles.map(function (file, index) { return ({
134
+ type: "local",
135
+ url: localPreviewsRef.current.get(file) || "",
136
+ index: index,
137
+ }); }), true);
138
+ return (react_1.default.createElement("div", { className: (0, utils_1.cn)("w-full", className) },
199
139
  label && (react_1.default.createElement("label", { className: "mb-2 block text-sm font-medium" }, label)),
200
- react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center mx-auto", isDragActive && "ring-1 ring-ring", disabled && "opacity-60 pointer-events-none") }),
140
+ react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center", isDragActive && "ring-2 ring-ring ring-offset-2", disabled && "opacity-60 pointer-events-none") }),
201
141
  react_1.default.createElement("input", __assign({}, getInputProps(), { id: id })),
202
- previews && previews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center" },
203
- react_1.default.createElement("div", { className: "relative w-60" },
204
- react_1.default.createElement("div", { className: "relative w-60" },
205
- react_1.default.createElement(carousel_1.Carousel, { className: "w-60" },
206
- react_1.default.createElement(carousel_1.CarouselPrevious, { onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); } }),
207
- react_1.default.createElement(carousel_1.CarouselNext, { onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); } }),
208
- react_1.default.createElement(carousel_1.CarouselContent, { className: "no-scrollbar flex gap-2 px-4 py-2", onPointerDown: function (e) { return e.stopPropagation(); }, onClick: function (e) { return e.stopPropagation(); } }, previews.map(function (p, idx) {
209
- return (react_1.default.createElement(carousel_1.CarouselItem, { key: "".concat(String(p), "-").concat(idx), className: "max-w-30 max-auto" }, p ? (react_1.default.createElement("div", { className: "relative" },
210
- react_1.default.createElement("img", { src: p, alt: "preview-".concat(idx), className: "h-28 w-28 rounded-md object-cover" }),
211
- react_1.default.createElement(button_1.Button, { size: "icon", variant: "ghost", onClick: function (e) {
212
- e.stopPropagation();
213
- handleRemoveByPreviewIndex(idx);
214
- }, className: "absolute right-1 top-1 rounded bg-white/80 p-0", "aria-label": "Remove image" },
215
- react_1.default.createElement(lucide_react_1.XIcon, { size: 7 })))) : (react_1.default.createElement("div", { className: "flex h-28 w-40 items-center justify-center rounded-md bg-muted text-muted-foreground" },
216
- react_1.default.createElement("span", { className: "text-xs" }, "File")))));
217
- }))))),
218
- react_1.default.createElement("p", { className: "mt-2 w-full truncate text-sm font-medium" },
219
- previews.length,
220
- " selected"))) : (
221
- // <div className="text-sm">
222
- // Drag & drop files here, or click to select
223
- // </div>
224
- react_1.default.createElement(dropzone_1.DropzoneEmptyState, null))),
142
+ allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center" },
143
+ react_1.default.createElement("div", { className: "relative w-full max-w-md" },
144
+ react_1.default.createElement(carousel_1.Carousel, { className: "w-full" },
145
+ react_1.default.createElement(carousel_1.CarouselPrevious, { type: "button", onClick: function (e) { return e.stopPropagation(); }, onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); } }),
146
+ react_1.default.createElement(carousel_1.CarouselNext, { type: "button", onClick: function (e) { return e.stopPropagation(); }, onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); } }),
147
+ react_1.default.createElement(carousel_1.CarouselContent, { className: "ml-0" }, allPreviews.map(function (preview, idx) { return (react_1.default.createElement(carousel_1.CarouselItem, { key: "".concat(preview.type, "-").concat(preview.url, "-").concat(idx), className: "pl-4" },
148
+ react_1.default.createElement("div", { className: "relative aspect-square w-full max-w-xs mx-auto" }, preview.url ? (react_1.default.createElement(react_1.default.Fragment, null,
149
+ react_1.default.createElement("img", { src: preview.url, alt: "Preview ".concat(idx + 1), className: "h-full w-full rounded-lg object-cover", onClick: function (e) { return e.stopPropagation(); } }),
150
+ react_1.default.createElement(button_1.Button, { type: "button", size: "icon", variant: "destructive", onClick: function (e) {
151
+ e.stopPropagation();
152
+ if (preview.type === "remote") {
153
+ handleRemoveRemote(preview.url);
154
+ }
155
+ else {
156
+ handleRemoveLocal(preview.index);
157
+ }
158
+ }, className: "absolute right-2 top-2 h-8 w-8 rounded-full", "aria-label": "Remove image" },
159
+ react_1.default.createElement(lucide_react_1.XIcon, { className: "h-4 w-4" })))) : (react_1.default.createElement("div", { className: "flex h-full w-full items-center justify-center rounded-lg bg-muted text-muted-foreground" },
160
+ react_1.default.createElement("span", { className: "text-sm" }, "Loading...")))))); })))),
161
+ react_1.default.createElement("p", { className: "mt-4 text-sm font-medium text-muted-foreground" },
162
+ allPreviews.length,
163
+ " image",
164
+ allPreviews.length !== 1 ? "s" : "",
165
+ " ",
166
+ "selected"))) : (react_1.default.createElement(dropzone_1.DropzoneEmptyState, null))),
225
167
  (error || info) && (react_1.default.createElement("div", { className: "mt-2" }, error ? (react_1.default.createElement("p", { className: "text-xs text-destructive" }, error)) : info ? (react_1.default.createElement("p", { className: "text-xs text-blue-600 dark:text-blue-400" }, info)) : null))));
226
168
  };
227
169
  exports.EnhancedDropzone = EnhancedDropzone;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appcorp/stellar-solutions-modules",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "scripts": {
5
5
  "automate": "./automate.sh",
6
6
  "build": "yarn clean && yarn build:ts && cp package.json lib && cp README.md lib && cp yarn.lock lib",