dn-react-router-toolkit 0.1.4 → 0.1.5

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,18 +11,21 @@ type FileInputProps = Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTM
11
11
  };
12
12
  type FileItem<T> = {
13
13
  key: string;
14
+ width?: number;
15
+ height?: number;
14
16
  item?: T;
15
17
  };
16
18
  type Props<T> = {
17
19
  defaultValue?: T | T[];
18
20
  options?: FileUploaderOptions;
19
- uploadFile?: (file: File, options?: FileUploaderOptions) => Promise<T>;
20
21
  onFileInput?: (files: File[]) => void;
21
22
  onFileUploaded?: (file: T) => Promise<void>;
22
- onChange?: (files: FileItem<T>[]) => void;
23
+ onChange?: (files: T[]) => void;
23
24
  limit?: number;
24
25
  };
25
- declare function useDropFileInput<T>({ defaultValue, options, uploadFile, onFileInput, onFileUploaded, limit, }?: Props<T>): {
26
+ declare function createUseDropFileInput<T>({ uploadFile, }: {
27
+ uploadFile: (file: File, options?: FileUploaderOptions) => Promise<T>;
28
+ }): ({ defaultValue, options, onChange, onFileInput, onFileUploaded, limit, }?: Props<T>) => {
26
29
  fileIds: string[];
27
30
  files: FileItem<T>[];
28
31
  setFiles: React.Dispatch<React.SetStateAction<FileItem<T>[]>>;
@@ -30,4 +33,4 @@ declare function useDropFileInput<T>({ defaultValue, options, uploadFile, onFile
30
33
  };
31
34
  declare function DropFileMessageBox(): React.JSX.Element;
32
35
 
33
- export { DropFileMessageBox, type FileInputProps, type FileItem, useDropFileInput as default, useDropFileInput };
36
+ export { DropFileMessageBox, type FileInputProps, type FileItem, createUseDropFileInput as default };
@@ -11,18 +11,21 @@ type FileInputProps = Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTM
11
11
  };
12
12
  type FileItem<T> = {
13
13
  key: string;
14
+ width?: number;
15
+ height?: number;
14
16
  item?: T;
15
17
  };
16
18
  type Props<T> = {
17
19
  defaultValue?: T | T[];
18
20
  options?: FileUploaderOptions;
19
- uploadFile?: (file: File, options?: FileUploaderOptions) => Promise<T>;
20
21
  onFileInput?: (files: File[]) => void;
21
22
  onFileUploaded?: (file: T) => Promise<void>;
22
- onChange?: (files: FileItem<T>[]) => void;
23
+ onChange?: (files: T[]) => void;
23
24
  limit?: number;
24
25
  };
25
- declare function useDropFileInput<T>({ defaultValue, options, uploadFile, onFileInput, onFileUploaded, limit, }?: Props<T>): {
26
+ declare function createUseDropFileInput<T>({ uploadFile, }: {
27
+ uploadFile: (file: File, options?: FileUploaderOptions) => Promise<T>;
28
+ }): ({ defaultValue, options, onChange, onFileInput, onFileUploaded, limit, }?: Props<T>) => {
26
29
  fileIds: string[];
27
30
  files: FileItem<T>[];
28
31
  setFiles: React.Dispatch<React.SetStateAction<FileItem<T>[]>>;
@@ -30,4 +33,4 @@ declare function useDropFileInput<T>({ defaultValue, options, uploadFile, onFile
30
33
  };
31
34
  declare function DropFileMessageBox(): React.JSX.Element;
32
35
 
33
- export { DropFileMessageBox, type FileInputProps, type FileItem, useDropFileInput as default, useDropFileInput };
36
+ export { DropFileMessageBox, type FileInputProps, type FileItem, createUseDropFileInput as default };
@@ -31,8 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var drop_file_input_exports = {};
32
32
  __export(drop_file_input_exports, {
33
33
  DropFileMessageBox: () => DropFileMessageBox,
34
- default: () => drop_file_input_default,
35
- useDropFileInput: () => useDropFileInput
34
+ default: () => createUseDropFileInput
36
35
  });
37
36
  module.exports = __toCommonJS(drop_file_input_exports);
38
37
  var import_react = __toESM(require("react"));
@@ -43,193 +42,256 @@ function cn(...classes) {
43
42
  return classes.filter(Boolean).join(" ").trim();
44
43
  }
45
44
 
46
- // src/file-kit/client/drop_file_input.tsx
47
- function useDropFileInput({
48
- defaultValue,
49
- options,
50
- uploadFile,
51
- onFileInput,
52
- onFileUploaded,
53
- limit
54
- } = {}) {
55
- const [files, setFiles] = (0, import_react.useState)(
56
- defaultValue ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue]).map((v) => {
57
- return {
58
- key: (0, import_uuid.v4)(),
59
- item: v
45
+ // src/file-kit/client/metadata.ts
46
+ function generateMetadata(blob) {
47
+ return new Promise((resolve, reject) => {
48
+ if (blob.type.startsWith("image/")) {
49
+ const img = new Image();
50
+ img.src = URL.createObjectURL(blob);
51
+ img.onload = () => {
52
+ resolve({
53
+ width: img.width,
54
+ height: img.height
55
+ });
60
56
  };
61
- }).slice(0, limit ? limit : Infinity) : []
62
- );
63
- const fileRef = (0, import_react.useRef)([]);
64
- (0, import_react.useEffect)(() => {
65
- fileRef.current = files;
66
- }, [files]);
67
- const Component = (0, import_react.useCallback)(
68
- function Component2({
69
- className,
70
- container = "border border-dashed border-neutral-300 rounded flex items-center justify-center",
71
- draggingClassName,
72
- name,
73
- hideMessage = false,
74
- children,
75
- ...props
76
- }) {
77
- const [isDragging, setIsDragging] = (0, import_react.useState)(false);
78
- const handleDragEnter = (0, import_react.useCallback)((e) => {
79
- e.preventDefault();
80
- e.stopPropagation();
81
- setIsDragging(true);
82
- }, []);
83
- const handleDragLeave = (0, import_react.useCallback)((e) => {
84
- e.preventDefault();
85
- e.stopPropagation();
86
- setIsDragging(false);
87
- }, []);
88
- const handleDragOver = (0, import_react.useCallback)((e) => {
89
- e.preventDefault();
90
- e.stopPropagation();
91
- }, []);
92
- const handleFiles = (0, import_react.useCallback)(
93
- async (files2) => {
94
- if (limit && fileRef.current.length >= limit) {
95
- alert(`\uD30C\uC77C\uC740 \uCD5C\uB300 ${limit}\uAC1C \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
96
- return;
97
- }
98
- const filteredFiles = files2.filter((file) => {
99
- return true;
100
- });
101
- if (files2.length !== filteredFiles.length) {
102
- alert(`${props.accept} \uD615\uC2DD\uC758 \uD30C\uC77C\uB9CC \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
103
- }
104
- const limitedFiles = filteredFiles.slice(
105
- 0,
106
- limit ? limit - fileRef.current.length : Infinity
107
- );
108
- if (limitedFiles.length === 0) {
109
- return;
110
- }
111
- if (onFileInput) {
112
- onFileInput(limitedFiles);
113
- }
114
- for (const file of limitedFiles) {
115
- const fileItem = {
116
- key: (0, import_uuid.v4)()
117
- };
118
- setFiles((prevFiles) => [...prevFiles, fileItem]);
119
- uploadFile?.(file, options).then(async (item) => {
120
- await onFileUploaded?.(item);
121
- setFiles(
122
- (prevFiles) => prevFiles.map((f) => {
123
- if (f.key === fileItem.key) {
124
- return {
125
- ...f,
126
- item
127
- };
128
- }
129
- return f;
130
- })
131
- );
132
- });
133
- }
134
- },
135
- [props.accept]
136
- );
137
- const handleDrop = (0, import_react.useCallback)(
138
- (e) => {
57
+ img.onerror = reject;
58
+ return;
59
+ }
60
+ if (blob.type.startsWith("video/")) {
61
+ const video = document.createElement("video");
62
+ video.src = URL.createObjectURL(blob);
63
+ video.onloadedmetadata = () => {
64
+ resolve({
65
+ width: video.videoWidth,
66
+ height: video.videoHeight,
67
+ duration: video.duration
68
+ });
69
+ };
70
+ video.onerror = reject;
71
+ return;
72
+ }
73
+ resolve({});
74
+ });
75
+ }
76
+
77
+ // src/file-kit/client/drop_file_input.tsx
78
+ function createUseDropFileInput({
79
+ uploadFile
80
+ }) {
81
+ return function useDropFileInput({
82
+ defaultValue,
83
+ options,
84
+ onChange,
85
+ onFileInput,
86
+ onFileUploaded,
87
+ limit
88
+ } = {}) {
89
+ const [files, setFiles] = (0, import_react.useState)(
90
+ defaultValue ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue]).map((v) => {
91
+ return {
92
+ key: (0, import_uuid.v4)(),
93
+ item: v
94
+ };
95
+ }).slice(0, limit ? limit : Infinity) : []
96
+ );
97
+ const fileRef = (0, import_react.useRef)([]);
98
+ (0, import_react.useEffect)(() => {
99
+ fileRef.current = files;
100
+ onChange?.(files.map((f) => f.item).filter(Boolean));
101
+ }, [files]);
102
+ const Component = (0, import_react.useCallback)(
103
+ function Component2({
104
+ className,
105
+ container = "border border-dashed border-neutral-300 rounded flex items-center justify-center",
106
+ draggingClassName,
107
+ name,
108
+ hideMessage = false,
109
+ children,
110
+ ...props
111
+ }) {
112
+ const [isDragging, setIsDragging] = (0, import_react.useState)(false);
113
+ const handleDragEnter = (0, import_react.useCallback)((e) => {
114
+ e.preventDefault();
115
+ e.stopPropagation();
116
+ setIsDragging(true);
117
+ }, []);
118
+ const handleDragLeave = (0, import_react.useCallback)((e) => {
139
119
  e.preventDefault();
140
120
  e.stopPropagation();
141
121
  setIsDragging(false);
142
- if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
143
- handleFiles(Array.from(e.dataTransfer.files));
144
- e.dataTransfer.clearData();
145
- }
146
- },
147
- [handleFiles]
148
- );
149
- const inputRef = (0, import_react.useRef)(null);
150
- const handleClick = (0, import_react.useCallback)(() => {
151
- inputRef.current?.click();
152
- }, []);
153
- const handleKeyDown = (0, import_react.useCallback)(
154
- (e) => {
155
- if (e.key === "Enter" || e.key === " ") {
156
- handleClick();
157
- }
158
- },
159
- [handleClick]
160
- );
161
- const handleChange = (0, import_react.useCallback)(
162
- (e) => {
163
- if (e.target.files && e.target.files.length > 0) {
164
- handleFiles(Array.from(e.target.files));
165
- e.target.value = "";
166
- }
167
- },
168
- [handleFiles]
169
- );
170
- return /* @__PURE__ */ import_react.default.createElement(
171
- "div",
172
- {
173
- className: cn(
174
- className,
175
- container,
176
- draggingClassName?.(isDragging) || (isDragging ? "bg-neutral-300/25" : "hover:bg-neutral-300/25"),
177
- "transition-colors cursor-pointer"
178
- ),
179
- onDragEnter: handleDragEnter,
180
- onDragLeave: handleDragLeave,
181
- onDragOver: handleDragOver,
182
- onDrop: handleDrop,
183
- onClick: handleClick,
184
- onChange: handleChange,
185
- onKeyDown: handleKeyDown,
186
- tabIndex: 0,
187
- role: "button"
188
- },
189
- /* @__PURE__ */ import_react.default.createElement("input", { ...props, defaultValue: "", type: "file", hidden: true, ref: inputRef }),
190
- /* @__PURE__ */ import_react.default.createElement(
191
- "input",
192
- {
193
- name,
194
- hidden: true,
195
- readOnly: true,
196
- value: files.map((file) => {
197
- if (file.item && typeof file.item === "object" && "id" in file.item) {
198
- return file.item.id;
122
+ }, []);
123
+ const handleDragOver = (0, import_react.useCallback)((e) => {
124
+ e.preventDefault();
125
+ e.stopPropagation();
126
+ }, []);
127
+ const handleFiles = (0, import_react.useCallback)(
128
+ async (files2) => {
129
+ if (limit && fileRef.current.length >= limit) {
130
+ alert(`\uD30C\uC77C\uC740 \uCD5C\uB300 ${limit}\uAC1C \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
131
+ return;
132
+ }
133
+ const filteredFiles = files2.filter((file) => {
134
+ if (!props.accept) {
135
+ return true;
199
136
  }
200
- return null;
201
- }).filter(Boolean).join(",")
202
- }
203
- ),
204
- children || !(hideMessage && !isDragging) && /* @__PURE__ */ import_react.default.createElement(DropFileMessageBox, null)
205
- );
206
- },
207
- [limit, fileRef, files, options, uploadFile, onFileInput, onFileUploaded]
208
- );
209
- const loadedFileIds = files.map((file) => {
210
- if (file.item && typeof file.item === "object" && "id" in file.item) {
211
- return file.item.id;
212
- }
213
- return null;
214
- }).filter(Boolean);
215
- const loadedFileIdsString = loadedFileIds.join(",");
216
- const fileIds = (0, import_react.useMemo)(
217
- () => loadedFileIdsString.split(",").filter(Boolean),
218
- [loadedFileIdsString]
219
- );
220
- return {
221
- fileIds,
222
- files,
223
- setFiles,
224
- Component
137
+ const accepts = props.accept.split(",");
138
+ for (const accept of accepts) {
139
+ const trimmedAccept = accept.trim();
140
+ if (file.type === trimmedAccept || file.type.endsWith(trimmedAccept) || file.name.endsWith(trimmedAccept)) {
141
+ return true;
142
+ }
143
+ if (trimmedAccept.endsWith("/*")) {
144
+ const baseType = trimmedAccept.replace("/*", "");
145
+ if (file.type.startsWith(baseType + "/")) {
146
+ return true;
147
+ }
148
+ }
149
+ }
150
+ return false;
151
+ });
152
+ if (files2.length !== filteredFiles.length) {
153
+ alert(`${props.accept} \uD615\uC2DD\uC758 \uD30C\uC77C\uB9CC \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
154
+ }
155
+ const limitedFiles = filteredFiles.slice(
156
+ 0,
157
+ limit ? limit - fileRef.current.length : Infinity
158
+ );
159
+ if (limitedFiles.length === 0) {
160
+ return;
161
+ }
162
+ if (onFileInput) {
163
+ onFileInput(limitedFiles);
164
+ }
165
+ for (const file of limitedFiles) {
166
+ const { width, height } = await generateMetadata(file);
167
+ const fileItem = {
168
+ key: (0, import_uuid.v4)(),
169
+ width: Number(width) || void 0,
170
+ height: Number(height) || void 0
171
+ };
172
+ setFiles((prevFiles) => [...prevFiles, fileItem]);
173
+ uploadFile?.(file, options).then(async (item) => {
174
+ await onFileUploaded?.(item);
175
+ setFiles(
176
+ (prevFiles) => prevFiles.map((f) => {
177
+ if (f.key === fileItem.key) {
178
+ return {
179
+ ...f,
180
+ item
181
+ };
182
+ }
183
+ return f;
184
+ })
185
+ );
186
+ });
187
+ }
188
+ },
189
+ [props.accept]
190
+ );
191
+ const handleDrop = (0, import_react.useCallback)(
192
+ (e) => {
193
+ e.preventDefault();
194
+ e.stopPropagation();
195
+ setIsDragging(false);
196
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
197
+ handleFiles(Array.from(e.dataTransfer.files));
198
+ e.dataTransfer.clearData();
199
+ }
200
+ },
201
+ [handleFiles]
202
+ );
203
+ const inputRef = (0, import_react.useRef)(null);
204
+ const handleClick = (0, import_react.useCallback)(() => {
205
+ inputRef.current?.click();
206
+ }, []);
207
+ const handleKeyDown = (0, import_react.useCallback)(
208
+ (e) => {
209
+ if (e.key === "Enter" || e.key === " ") {
210
+ handleClick();
211
+ }
212
+ },
213
+ [handleClick]
214
+ );
215
+ const handleChange = (0, import_react.useCallback)(
216
+ (e) => {
217
+ if (e.target.files && e.target.files.length > 0) {
218
+ handleFiles(Array.from(e.target.files));
219
+ e.target.value = "";
220
+ }
221
+ },
222
+ [handleFiles]
223
+ );
224
+ return /* @__PURE__ */ import_react.default.createElement(
225
+ "div",
226
+ {
227
+ className: cn(
228
+ className,
229
+ container,
230
+ draggingClassName?.(isDragging) || (isDragging ? "bg-neutral-300/25" : "hover:bg-neutral-300/25"),
231
+ "transition-colors cursor-pointer"
232
+ ),
233
+ onDragEnter: handleDragEnter,
234
+ onDragLeave: handleDragLeave,
235
+ onDragOver: handleDragOver,
236
+ onDrop: handleDrop,
237
+ onClick: handleClick,
238
+ onChange: handleChange,
239
+ onKeyDown: handleKeyDown,
240
+ tabIndex: 0,
241
+ role: "button"
242
+ },
243
+ /* @__PURE__ */ import_react.default.createElement(
244
+ "input",
245
+ {
246
+ ...props,
247
+ defaultValue: "",
248
+ type: "file",
249
+ hidden: true,
250
+ ref: inputRef
251
+ }
252
+ ),
253
+ /* @__PURE__ */ import_react.default.createElement(
254
+ "input",
255
+ {
256
+ name,
257
+ hidden: true,
258
+ readOnly: true,
259
+ value: files.map((file) => {
260
+ if (file.item && typeof file.item === "object" && "id" in file.item) {
261
+ return file.item.id;
262
+ }
263
+ return null;
264
+ }).filter(Boolean).join(",")
265
+ }
266
+ ),
267
+ children || !(hideMessage && !isDragging) && /* @__PURE__ */ import_react.default.createElement(DropFileMessageBox, null)
268
+ );
269
+ },
270
+ [limit, fileRef, files, options, uploadFile, onFileInput, onFileUploaded]
271
+ );
272
+ const loadedFileIds = files.map((file) => {
273
+ if (file.item && typeof file.item === "object" && "id" in file.item) {
274
+ return file.item.id;
275
+ }
276
+ return null;
277
+ }).filter(Boolean);
278
+ const loadedFileIdsString = loadedFileIds.join(",");
279
+ const fileIds = (0, import_react.useMemo)(
280
+ () => loadedFileIdsString.split(",").filter(Boolean),
281
+ [loadedFileIdsString]
282
+ );
283
+ return {
284
+ fileIds,
285
+ files,
286
+ setFiles,
287
+ Component
288
+ };
225
289
  };
226
290
  }
227
291
  function DropFileMessageBox() {
228
292
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "text-sm pointer-events-none flex justify-center items-center" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ import_react.default.createElement("span", null, "\uD30C\uC77C\uC744 \uC5EC\uAE30\uB85C \uB04C\uC5B4\uB2E4 \uB193\uAC70\uB098 \uD074\uB9AD\uD574\uC11C \uC120\uD0DD\uD574 \uC8FC\uC138\uC694")));
229
293
  }
230
- var drop_file_input_default = useDropFileInput;
231
294
  // Annotate the CommonJS export names for ESM import in node:
232
295
  0 && (module.exports = {
233
- DropFileMessageBox,
234
- useDropFileInput
296
+ DropFileMessageBox
235
297
  });
@@ -13,193 +13,256 @@ function cn(...classes) {
13
13
  return classes.filter(Boolean).join(" ").trim();
14
14
  }
15
15
 
16
- // src/file-kit/client/drop_file_input.tsx
17
- function useDropFileInput({
18
- defaultValue,
19
- options,
20
- uploadFile,
21
- onFileInput,
22
- onFileUploaded,
23
- limit
24
- } = {}) {
25
- const [files, setFiles] = useState(
26
- defaultValue ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue]).map((v) => {
27
- return {
28
- key: v4(),
29
- item: v
16
+ // src/file-kit/client/metadata.ts
17
+ function generateMetadata(blob) {
18
+ return new Promise((resolve, reject) => {
19
+ if (blob.type.startsWith("image/")) {
20
+ const img = new Image();
21
+ img.src = URL.createObjectURL(blob);
22
+ img.onload = () => {
23
+ resolve({
24
+ width: img.width,
25
+ height: img.height
26
+ });
30
27
  };
31
- }).slice(0, limit ? limit : Infinity) : []
32
- );
33
- const fileRef = useRef([]);
34
- useEffect(() => {
35
- fileRef.current = files;
36
- }, [files]);
37
- const Component = useCallback(
38
- function Component2({
39
- className,
40
- container = "border border-dashed border-neutral-300 rounded flex items-center justify-center",
41
- draggingClassName,
42
- name,
43
- hideMessage = false,
44
- children,
45
- ...props
46
- }) {
47
- const [isDragging, setIsDragging] = useState(false);
48
- const handleDragEnter = useCallback((e) => {
49
- e.preventDefault();
50
- e.stopPropagation();
51
- setIsDragging(true);
52
- }, []);
53
- const handleDragLeave = useCallback((e) => {
54
- e.preventDefault();
55
- e.stopPropagation();
56
- setIsDragging(false);
57
- }, []);
58
- const handleDragOver = useCallback((e) => {
59
- e.preventDefault();
60
- e.stopPropagation();
61
- }, []);
62
- const handleFiles = useCallback(
63
- async (files2) => {
64
- if (limit && fileRef.current.length >= limit) {
65
- alert(`\uD30C\uC77C\uC740 \uCD5C\uB300 ${limit}\uAC1C \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
66
- return;
67
- }
68
- const filteredFiles = files2.filter((file) => {
69
- return true;
70
- });
71
- if (files2.length !== filteredFiles.length) {
72
- alert(`${props.accept} \uD615\uC2DD\uC758 \uD30C\uC77C\uB9CC \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
73
- }
74
- const limitedFiles = filteredFiles.slice(
75
- 0,
76
- limit ? limit - fileRef.current.length : Infinity
77
- );
78
- if (limitedFiles.length === 0) {
79
- return;
80
- }
81
- if (onFileInput) {
82
- onFileInput(limitedFiles);
83
- }
84
- for (const file of limitedFiles) {
85
- const fileItem = {
86
- key: v4()
87
- };
88
- setFiles((prevFiles) => [...prevFiles, fileItem]);
89
- uploadFile?.(file, options).then(async (item) => {
90
- await onFileUploaded?.(item);
91
- setFiles(
92
- (prevFiles) => prevFiles.map((f) => {
93
- if (f.key === fileItem.key) {
94
- return {
95
- ...f,
96
- item
97
- };
98
- }
99
- return f;
100
- })
101
- );
102
- });
103
- }
104
- },
105
- [props.accept]
106
- );
107
- const handleDrop = useCallback(
108
- (e) => {
28
+ img.onerror = reject;
29
+ return;
30
+ }
31
+ if (blob.type.startsWith("video/")) {
32
+ const video = document.createElement("video");
33
+ video.src = URL.createObjectURL(blob);
34
+ video.onloadedmetadata = () => {
35
+ resolve({
36
+ width: video.videoWidth,
37
+ height: video.videoHeight,
38
+ duration: video.duration
39
+ });
40
+ };
41
+ video.onerror = reject;
42
+ return;
43
+ }
44
+ resolve({});
45
+ });
46
+ }
47
+
48
+ // src/file-kit/client/drop_file_input.tsx
49
+ function createUseDropFileInput({
50
+ uploadFile
51
+ }) {
52
+ return function useDropFileInput({
53
+ defaultValue,
54
+ options,
55
+ onChange,
56
+ onFileInput,
57
+ onFileUploaded,
58
+ limit
59
+ } = {}) {
60
+ const [files, setFiles] = useState(
61
+ defaultValue ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue]).map((v) => {
62
+ return {
63
+ key: v4(),
64
+ item: v
65
+ };
66
+ }).slice(0, limit ? limit : Infinity) : []
67
+ );
68
+ const fileRef = useRef([]);
69
+ useEffect(() => {
70
+ fileRef.current = files;
71
+ onChange?.(files.map((f) => f.item).filter(Boolean));
72
+ }, [files]);
73
+ const Component = useCallback(
74
+ function Component2({
75
+ className,
76
+ container = "border border-dashed border-neutral-300 rounded flex items-center justify-center",
77
+ draggingClassName,
78
+ name,
79
+ hideMessage = false,
80
+ children,
81
+ ...props
82
+ }) {
83
+ const [isDragging, setIsDragging] = useState(false);
84
+ const handleDragEnter = useCallback((e) => {
85
+ e.preventDefault();
86
+ e.stopPropagation();
87
+ setIsDragging(true);
88
+ }, []);
89
+ const handleDragLeave = useCallback((e) => {
109
90
  e.preventDefault();
110
91
  e.stopPropagation();
111
92
  setIsDragging(false);
112
- if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
113
- handleFiles(Array.from(e.dataTransfer.files));
114
- e.dataTransfer.clearData();
115
- }
116
- },
117
- [handleFiles]
118
- );
119
- const inputRef = useRef(null);
120
- const handleClick = useCallback(() => {
121
- inputRef.current?.click();
122
- }, []);
123
- const handleKeyDown = useCallback(
124
- (e) => {
125
- if (e.key === "Enter" || e.key === " ") {
126
- handleClick();
127
- }
128
- },
129
- [handleClick]
130
- );
131
- const handleChange = useCallback(
132
- (e) => {
133
- if (e.target.files && e.target.files.length > 0) {
134
- handleFiles(Array.from(e.target.files));
135
- e.target.value = "";
136
- }
137
- },
138
- [handleFiles]
139
- );
140
- return /* @__PURE__ */ React.createElement(
141
- "div",
142
- {
143
- className: cn(
144
- className,
145
- container,
146
- draggingClassName?.(isDragging) || (isDragging ? "bg-neutral-300/25" : "hover:bg-neutral-300/25"),
147
- "transition-colors cursor-pointer"
148
- ),
149
- onDragEnter: handleDragEnter,
150
- onDragLeave: handleDragLeave,
151
- onDragOver: handleDragOver,
152
- onDrop: handleDrop,
153
- onClick: handleClick,
154
- onChange: handleChange,
155
- onKeyDown: handleKeyDown,
156
- tabIndex: 0,
157
- role: "button"
158
- },
159
- /* @__PURE__ */ React.createElement("input", { ...props, defaultValue: "", type: "file", hidden: true, ref: inputRef }),
160
- /* @__PURE__ */ React.createElement(
161
- "input",
162
- {
163
- name,
164
- hidden: true,
165
- readOnly: true,
166
- value: files.map((file) => {
167
- if (file.item && typeof file.item === "object" && "id" in file.item) {
168
- return file.item.id;
93
+ }, []);
94
+ const handleDragOver = useCallback((e) => {
95
+ e.preventDefault();
96
+ e.stopPropagation();
97
+ }, []);
98
+ const handleFiles = useCallback(
99
+ async (files2) => {
100
+ if (limit && fileRef.current.length >= limit) {
101
+ alert(`\uD30C\uC77C\uC740 \uCD5C\uB300 ${limit}\uAC1C \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
102
+ return;
103
+ }
104
+ const filteredFiles = files2.filter((file) => {
105
+ if (!props.accept) {
106
+ return true;
169
107
  }
170
- return null;
171
- }).filter(Boolean).join(",")
172
- }
173
- ),
174
- children || !(hideMessage && !isDragging) && /* @__PURE__ */ React.createElement(DropFileMessageBox, null)
175
- );
176
- },
177
- [limit, fileRef, files, options, uploadFile, onFileInput, onFileUploaded]
178
- );
179
- const loadedFileIds = files.map((file) => {
180
- if (file.item && typeof file.item === "object" && "id" in file.item) {
181
- return file.item.id;
182
- }
183
- return null;
184
- }).filter(Boolean);
185
- const loadedFileIdsString = loadedFileIds.join(",");
186
- const fileIds = useMemo(
187
- () => loadedFileIdsString.split(",").filter(Boolean),
188
- [loadedFileIdsString]
189
- );
190
- return {
191
- fileIds,
192
- files,
193
- setFiles,
194
- Component
108
+ const accepts = props.accept.split(",");
109
+ for (const accept of accepts) {
110
+ const trimmedAccept = accept.trim();
111
+ if (file.type === trimmedAccept || file.type.endsWith(trimmedAccept) || file.name.endsWith(trimmedAccept)) {
112
+ return true;
113
+ }
114
+ if (trimmedAccept.endsWith("/*")) {
115
+ const baseType = trimmedAccept.replace("/*", "");
116
+ if (file.type.startsWith(baseType + "/")) {
117
+ return true;
118
+ }
119
+ }
120
+ }
121
+ return false;
122
+ });
123
+ if (files2.length !== filteredFiles.length) {
124
+ alert(`${props.accept} \uD615\uC2DD\uC758 \uD30C\uC77C\uB9CC \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
125
+ }
126
+ const limitedFiles = filteredFiles.slice(
127
+ 0,
128
+ limit ? limit - fileRef.current.length : Infinity
129
+ );
130
+ if (limitedFiles.length === 0) {
131
+ return;
132
+ }
133
+ if (onFileInput) {
134
+ onFileInput(limitedFiles);
135
+ }
136
+ for (const file of limitedFiles) {
137
+ const { width, height } = await generateMetadata(file);
138
+ const fileItem = {
139
+ key: v4(),
140
+ width: Number(width) || void 0,
141
+ height: Number(height) || void 0
142
+ };
143
+ setFiles((prevFiles) => [...prevFiles, fileItem]);
144
+ uploadFile?.(file, options).then(async (item) => {
145
+ await onFileUploaded?.(item);
146
+ setFiles(
147
+ (prevFiles) => prevFiles.map((f) => {
148
+ if (f.key === fileItem.key) {
149
+ return {
150
+ ...f,
151
+ item
152
+ };
153
+ }
154
+ return f;
155
+ })
156
+ );
157
+ });
158
+ }
159
+ },
160
+ [props.accept]
161
+ );
162
+ const handleDrop = useCallback(
163
+ (e) => {
164
+ e.preventDefault();
165
+ e.stopPropagation();
166
+ setIsDragging(false);
167
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
168
+ handleFiles(Array.from(e.dataTransfer.files));
169
+ e.dataTransfer.clearData();
170
+ }
171
+ },
172
+ [handleFiles]
173
+ );
174
+ const inputRef = useRef(null);
175
+ const handleClick = useCallback(() => {
176
+ inputRef.current?.click();
177
+ }, []);
178
+ const handleKeyDown = useCallback(
179
+ (e) => {
180
+ if (e.key === "Enter" || e.key === " ") {
181
+ handleClick();
182
+ }
183
+ },
184
+ [handleClick]
185
+ );
186
+ const handleChange = useCallback(
187
+ (e) => {
188
+ if (e.target.files && e.target.files.length > 0) {
189
+ handleFiles(Array.from(e.target.files));
190
+ e.target.value = "";
191
+ }
192
+ },
193
+ [handleFiles]
194
+ );
195
+ return /* @__PURE__ */ React.createElement(
196
+ "div",
197
+ {
198
+ className: cn(
199
+ className,
200
+ container,
201
+ draggingClassName?.(isDragging) || (isDragging ? "bg-neutral-300/25" : "hover:bg-neutral-300/25"),
202
+ "transition-colors cursor-pointer"
203
+ ),
204
+ onDragEnter: handleDragEnter,
205
+ onDragLeave: handleDragLeave,
206
+ onDragOver: handleDragOver,
207
+ onDrop: handleDrop,
208
+ onClick: handleClick,
209
+ onChange: handleChange,
210
+ onKeyDown: handleKeyDown,
211
+ tabIndex: 0,
212
+ role: "button"
213
+ },
214
+ /* @__PURE__ */ React.createElement(
215
+ "input",
216
+ {
217
+ ...props,
218
+ defaultValue: "",
219
+ type: "file",
220
+ hidden: true,
221
+ ref: inputRef
222
+ }
223
+ ),
224
+ /* @__PURE__ */ React.createElement(
225
+ "input",
226
+ {
227
+ name,
228
+ hidden: true,
229
+ readOnly: true,
230
+ value: files.map((file) => {
231
+ if (file.item && typeof file.item === "object" && "id" in file.item) {
232
+ return file.item.id;
233
+ }
234
+ return null;
235
+ }).filter(Boolean).join(",")
236
+ }
237
+ ),
238
+ children || !(hideMessage && !isDragging) && /* @__PURE__ */ React.createElement(DropFileMessageBox, null)
239
+ );
240
+ },
241
+ [limit, fileRef, files, options, uploadFile, onFileInput, onFileUploaded]
242
+ );
243
+ const loadedFileIds = files.map((file) => {
244
+ if (file.item && typeof file.item === "object" && "id" in file.item) {
245
+ return file.item.id;
246
+ }
247
+ return null;
248
+ }).filter(Boolean);
249
+ const loadedFileIdsString = loadedFileIds.join(",");
250
+ const fileIds = useMemo(
251
+ () => loadedFileIdsString.split(",").filter(Boolean),
252
+ [loadedFileIdsString]
253
+ );
254
+ return {
255
+ fileIds,
256
+ files,
257
+ setFiles,
258
+ Component
259
+ };
195
260
  };
196
261
  }
197
262
  function DropFileMessageBox() {
198
263
  return /* @__PURE__ */ React.createElement("div", { className: "text-sm pointer-events-none flex justify-center items-center" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React.createElement("span", null, "\uD30C\uC77C\uC744 \uC5EC\uAE30\uB85C \uB04C\uC5B4\uB2E4 \uB193\uAC70\uB098 \uD074\uB9AD\uD574\uC11C \uC120\uD0DD\uD574 \uC8FC\uC138\uC694")));
199
264
  }
200
- var drop_file_input_default = useDropFileInput;
201
265
  export {
202
266
  DropFileMessageBox,
203
- drop_file_input_default as default,
204
- useDropFileInput
267
+ createUseDropFileInput as default
205
268
  };
@@ -1,5 +1,6 @@
1
1
  type FileUploaderOptions = {
2
2
  metadata?: Record<string, unknown>;
3
+ convertToWebp?: boolean;
3
4
  };
4
5
  declare class FileUploader {
5
6
  endpoint: string;
@@ -1,5 +1,6 @@
1
1
  type FileUploaderOptions = {
2
2
  metadata?: Record<string, unknown>;
3
+ convertToWebp?: boolean;
3
4
  };
4
5
  declare class FileUploader {
5
6
  endpoint: string;
@@ -23,6 +23,40 @@ __export(file_uploader_exports, {
23
23
  FileUploader: () => FileUploader
24
24
  });
25
25
  module.exports = __toCommonJS(file_uploader_exports);
26
+
27
+ // src/file-kit/client/metadata.ts
28
+ function generateMetadata(blob) {
29
+ return new Promise((resolve, reject) => {
30
+ if (blob.type.startsWith("image/")) {
31
+ const img = new Image();
32
+ img.src = URL.createObjectURL(blob);
33
+ img.onload = () => {
34
+ resolve({
35
+ width: img.width,
36
+ height: img.height
37
+ });
38
+ };
39
+ img.onerror = reject;
40
+ return;
41
+ }
42
+ if (blob.type.startsWith("video/")) {
43
+ const video = document.createElement("video");
44
+ video.src = URL.createObjectURL(blob);
45
+ video.onloadedmetadata = () => {
46
+ resolve({
47
+ width: video.videoWidth,
48
+ height: video.videoHeight,
49
+ duration: video.duration
50
+ });
51
+ };
52
+ video.onerror = reject;
53
+ return;
54
+ }
55
+ resolve({});
56
+ });
57
+ }
58
+
59
+ // src/file-kit/client/file_uploader.ts
26
60
  var FileUploader = class {
27
61
  endpoint;
28
62
  constructor(endpoint = "/api/files") {
@@ -42,45 +76,18 @@ var FileUploader = class {
42
76
  }
43
77
  async uploadBlob(blob, name = "blob", options = {}) {
44
78
  const { type, size } = blob;
45
- const metadataForMedia = await new Promise(
46
- (resolve, reject) => {
47
- if (blob.type.startsWith("image/")) {
48
- const img = new Image();
49
- img.src = URL.createObjectURL(blob);
50
- img.onload = () => {
51
- resolve({
52
- ...options.metadata,
53
- width: img.width,
54
- height: img.height
55
- });
56
- };
57
- img.onerror = reject;
58
- return;
59
- }
60
- if (blob.type.startsWith("video/")) {
61
- const video = document.createElement("video");
62
- video.src = URL.createObjectURL(blob);
63
- video.onloadedmetadata = () => {
64
- resolve({
65
- ...options.metadata,
66
- width: video.videoWidth,
67
- height: video.videoHeight,
68
- duration: video.duration
69
- });
70
- };
71
- video.onerror = reject;
72
- return;
73
- }
74
- resolve(options.metadata || {});
75
- }
76
- );
79
+ const metadataForMedia = await generateMetadata(blob);
80
+ const metadata = {
81
+ ...metadataForMedia,
82
+ ...options.metadata
83
+ };
77
84
  const res1 = await fetch(this.endpoint, {
78
85
  method: "POST",
79
86
  body: JSON.stringify({
80
87
  name: name.replace(/ /g, "_"),
81
88
  type,
82
89
  size,
83
- metadata: metadataForMedia
90
+ metadata
84
91
  })
85
92
  });
86
93
  if (!res1.ok) {
@@ -1,3 +1,35 @@
1
+ // src/file-kit/client/metadata.ts
2
+ function generateMetadata(blob) {
3
+ return new Promise((resolve, reject) => {
4
+ if (blob.type.startsWith("image/")) {
5
+ const img = new Image();
6
+ img.src = URL.createObjectURL(blob);
7
+ img.onload = () => {
8
+ resolve({
9
+ width: img.width,
10
+ height: img.height
11
+ });
12
+ };
13
+ img.onerror = reject;
14
+ return;
15
+ }
16
+ if (blob.type.startsWith("video/")) {
17
+ const video = document.createElement("video");
18
+ video.src = URL.createObjectURL(blob);
19
+ video.onloadedmetadata = () => {
20
+ resolve({
21
+ width: video.videoWidth,
22
+ height: video.videoHeight,
23
+ duration: video.duration
24
+ });
25
+ };
26
+ video.onerror = reject;
27
+ return;
28
+ }
29
+ resolve({});
30
+ });
31
+ }
32
+
1
33
  // src/file-kit/client/file_uploader.ts
2
34
  var FileUploader = class {
3
35
  endpoint;
@@ -18,45 +50,18 @@ var FileUploader = class {
18
50
  }
19
51
  async uploadBlob(blob, name = "blob", options = {}) {
20
52
  const { type, size } = blob;
21
- const metadataForMedia = await new Promise(
22
- (resolve, reject) => {
23
- if (blob.type.startsWith("image/")) {
24
- const img = new Image();
25
- img.src = URL.createObjectURL(blob);
26
- img.onload = () => {
27
- resolve({
28
- ...options.metadata,
29
- width: img.width,
30
- height: img.height
31
- });
32
- };
33
- img.onerror = reject;
34
- return;
35
- }
36
- if (blob.type.startsWith("video/")) {
37
- const video = document.createElement("video");
38
- video.src = URL.createObjectURL(blob);
39
- video.onloadedmetadata = () => {
40
- resolve({
41
- ...options.metadata,
42
- width: video.videoWidth,
43
- height: video.videoHeight,
44
- duration: video.duration
45
- });
46
- };
47
- video.onerror = reject;
48
- return;
49
- }
50
- resolve(options.metadata || {});
51
- }
52
- );
53
+ const metadataForMedia = await generateMetadata(blob);
54
+ const metadata = {
55
+ ...metadataForMedia,
56
+ ...options.metadata
57
+ };
53
58
  const res1 = await fetch(this.endpoint, {
54
59
  method: "POST",
55
60
  body: JSON.stringify({
56
61
  name: name.replace(/ /g, "_"),
57
62
  type,
58
63
  size,
59
- metadata: metadataForMedia
64
+ metadata
60
65
  })
61
66
  });
62
67
  if (!res1.ok) {
@@ -0,0 +1,3 @@
1
+ declare function generateMetadata(blob: Blob | File): Promise<Record<string, unknown>>;
2
+
3
+ export { generateMetadata };
@@ -0,0 +1,3 @@
1
+ declare function generateMetadata(blob: Blob | File): Promise<Record<string, unknown>>;
2
+
3
+ export { generateMetadata };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/file-kit/client/metadata.ts
21
+ var metadata_exports = {};
22
+ __export(metadata_exports, {
23
+ generateMetadata: () => generateMetadata
24
+ });
25
+ module.exports = __toCommonJS(metadata_exports);
26
+ function generateMetadata(blob) {
27
+ return new Promise((resolve, reject) => {
28
+ if (blob.type.startsWith("image/")) {
29
+ const img = new Image();
30
+ img.src = URL.createObjectURL(blob);
31
+ img.onload = () => {
32
+ resolve({
33
+ width: img.width,
34
+ height: img.height
35
+ });
36
+ };
37
+ img.onerror = reject;
38
+ return;
39
+ }
40
+ if (blob.type.startsWith("video/")) {
41
+ const video = document.createElement("video");
42
+ video.src = URL.createObjectURL(blob);
43
+ video.onloadedmetadata = () => {
44
+ resolve({
45
+ width: video.videoWidth,
46
+ height: video.videoHeight,
47
+ duration: video.duration
48
+ });
49
+ };
50
+ video.onerror = reject;
51
+ return;
52
+ }
53
+ resolve({});
54
+ });
55
+ }
56
+ // Annotate the CommonJS export names for ESM import in node:
57
+ 0 && (module.exports = {
58
+ generateMetadata
59
+ });
@@ -0,0 +1,34 @@
1
+ // src/file-kit/client/metadata.ts
2
+ function generateMetadata(blob) {
3
+ return new Promise((resolve, reject) => {
4
+ if (blob.type.startsWith("image/")) {
5
+ const img = new Image();
6
+ img.src = URL.createObjectURL(blob);
7
+ img.onload = () => {
8
+ resolve({
9
+ width: img.width,
10
+ height: img.height
11
+ });
12
+ };
13
+ img.onerror = reject;
14
+ return;
15
+ }
16
+ if (blob.type.startsWith("video/")) {
17
+ const video = document.createElement("video");
18
+ video.src = URL.createObjectURL(blob);
19
+ video.onloadedmetadata = () => {
20
+ resolve({
21
+ width: video.videoWidth,
22
+ height: video.videoHeight,
23
+ duration: video.duration
24
+ });
25
+ };
26
+ video.onerror = reject;
27
+ return;
28
+ }
29
+ resolve({});
30
+ });
31
+ }
32
+ export {
33
+ generateMetadata
34
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dn-react-router-toolkit",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "types": "./dist/index.d.ts",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.js",