@octane-xplat/files 0.10.0 → 0.11.1
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/README.md +37 -0
- package/dist/linux/FileInput.js +2 -0
- package/dist/linux/FileInput.web.js +263 -0
- package/dist/linux/file-bytes.js +45 -0
- package/dist/linux/file-input-utils.js +52 -0
- package/dist/linux/files.js +2 -0
- package/dist/linux/files.web.js +144 -0
- package/dist/linux/index.js +5 -0
- package/dist/linux/octane-client-build.json +8 -0
- package/dist/linux/props.js +0 -0
- package/dist/linux/types.js +0 -0
- package/dist/native/FileInput.js +434 -0
- package/dist/native/file-bytes.js +45 -0
- package/dist/native/file-input-utils.js +52 -0
- package/dist/native/files.js +283 -0
- package/dist/native/index.js +3 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/native/props.js +0 -0
- package/dist/native/types.js +0 -0
- package/dist/web/FileInput.js +263 -0
- package/dist/web/file-bytes.js +45 -0
- package/dist/web/file-input-utils.js +52 -0
- package/dist/web/files.js +144 -0
- package/dist/web/index.js +3 -0
- package/dist/web/octane-client-build.json +8 -0
- package/dist/web/props.js +0 -0
- package/dist/web/types.js +0 -0
- package/package.json +50 -8
- package/src/file-bytes.test.ts +53 -0
- package/src/file-bytes.ts +68 -0
- package/src/files.bridge.test.ts +342 -0
- package/src/files.macos.ts +23 -2
- package/src/files.ts +329 -6
- package/src/files.web.test.ts +117 -0
- package/src/files.web.ts +133 -4
- package/src/types.ts +65 -2
- package/types/index.d.ts +8 -1
- package/types/index.macos.d.ts +8 -1
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { files } from "./files.js";
|
|
2
|
+
import { cx, formatFileSize, normalizePicked, validateFiles } from "./file-input-utils.js";
|
|
3
|
+
import { defineUniversalComponent, hookSlots, universalChildren, universalComponent, universalPlan, universalProps, universalValue, useId, useImperativeHandle, useRef, useState, withSlot } from "@nativescript-community/octane";
|
|
4
|
+
import { Field, Icon, Pressable, useFieldControl } from "@octane-xplat/ui";
|
|
5
|
+
//#region src/FileInput.tsrx
|
|
6
|
+
var _hs$ = /* @__PURE__ */ hookSlots(8);
|
|
7
|
+
var _h$0 = /* @__PURE__ */ Symbol(_hs$);
|
|
8
|
+
var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
|
|
9
|
+
var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
|
|
10
|
+
var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
|
|
11
|
+
var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
|
|
12
|
+
var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
|
|
13
|
+
var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
|
|
14
|
+
var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
|
|
15
|
+
var __octaneUniversalPlan0 = universalPlan("nativescript", {
|
|
16
|
+
"kind": "host",
|
|
17
|
+
"type": "label",
|
|
18
|
+
"props": { "className": "vx-fileinput-itemsize" },
|
|
19
|
+
"bindings": [["text", 0]]
|
|
20
|
+
});
|
|
21
|
+
var __octaneUniversalPlan1 = universalPlan("nativescript", {
|
|
22
|
+
"kind": "host",
|
|
23
|
+
"type": "label",
|
|
24
|
+
"props": { "text": "×" }
|
|
25
|
+
});
|
|
26
|
+
var __octaneUniversalPlan2 = universalPlan("nativescript", {
|
|
27
|
+
"kind": "slot",
|
|
28
|
+
"slot": 0
|
|
29
|
+
});
|
|
30
|
+
var __octaneUniversalPlan3 = universalPlan("nativescript", {
|
|
31
|
+
"kind": "host",
|
|
32
|
+
"type": "flexboxlayout",
|
|
33
|
+
"propsSlot": 0,
|
|
34
|
+
"children": [
|
|
35
|
+
{
|
|
36
|
+
"kind": "slot",
|
|
37
|
+
"slot": 1
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"kind": "host",
|
|
41
|
+
"type": "label",
|
|
42
|
+
"props": { "className": "vx-fileinput-itemname" },
|
|
43
|
+
"bindings": [["text", 2]]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"kind": "slot",
|
|
47
|
+
"slot": 3
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"kind": "slot",
|
|
51
|
+
"slot": 4
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
var __octaneUniversalPlan4 = universalPlan("nativescript", {
|
|
56
|
+
"kind": "host",
|
|
57
|
+
"type": "flexboxlayout",
|
|
58
|
+
"props": {
|
|
59
|
+
"flexDirection": "column",
|
|
60
|
+
"className": "vx-fileinput-list"
|
|
61
|
+
},
|
|
62
|
+
"children": [{
|
|
63
|
+
"kind": "slot",
|
|
64
|
+
"slot": 0
|
|
65
|
+
}]
|
|
66
|
+
});
|
|
67
|
+
var __octaneUniversalPlan5 = universalPlan("nativescript", {
|
|
68
|
+
"kind": "host",
|
|
69
|
+
"type": "flexboxlayout",
|
|
70
|
+
"props": {
|
|
71
|
+
"flexDirection": "row",
|
|
72
|
+
"alignItems": "center"
|
|
73
|
+
},
|
|
74
|
+
"children": [{
|
|
75
|
+
"kind": "slot",
|
|
76
|
+
"slot": 0
|
|
77
|
+
}, {
|
|
78
|
+
"kind": "host",
|
|
79
|
+
"type": "label",
|
|
80
|
+
"bindings": [["text", 1]]
|
|
81
|
+
}]
|
|
82
|
+
});
|
|
83
|
+
var __octaneUniversalPlan6 = universalPlan("nativescript", {
|
|
84
|
+
"kind": "host",
|
|
85
|
+
"type": "label",
|
|
86
|
+
"props": { "text": "×" }
|
|
87
|
+
});
|
|
88
|
+
var __octaneUniversalPlan7 = universalPlan("nativescript", {
|
|
89
|
+
"kind": "slot",
|
|
90
|
+
"slot": 0
|
|
91
|
+
});
|
|
92
|
+
var __octaneUniversalPlan8 = universalPlan("nativescript", {
|
|
93
|
+
"kind": "host",
|
|
94
|
+
"type": "label",
|
|
95
|
+
"bindings": [
|
|
96
|
+
["className", 0],
|
|
97
|
+
["text", 1],
|
|
98
|
+
["accessibilityLabel", 2]
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
var __octaneUniversalPlan9 = universalPlan("nativescript", {
|
|
102
|
+
"kind": "host",
|
|
103
|
+
"type": "label",
|
|
104
|
+
"bindings": [["className", 0], ["text", 1]]
|
|
105
|
+
});
|
|
106
|
+
var __octaneUniversalPlan10 = universalPlan("nativescript", {
|
|
107
|
+
"kind": "host",
|
|
108
|
+
"type": "flexboxlayout",
|
|
109
|
+
"props": { "flexDirection": "column" },
|
|
110
|
+
"bindings": [["className", 0], ["style", 1]],
|
|
111
|
+
"children": [
|
|
112
|
+
{
|
|
113
|
+
"kind": "slot",
|
|
114
|
+
"slot": 2
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"kind": "slot",
|
|
118
|
+
"slot": 3
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"kind": "slot",
|
|
122
|
+
"slot": 4
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"kind": "slot",
|
|
126
|
+
"slot": 5
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"kind": "slot",
|
|
130
|
+
"slot": 6
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
var __octaneUniversalPlan11 = universalPlan("nativescript", {
|
|
135
|
+
"kind": "slot",
|
|
136
|
+
"slot": 0
|
|
137
|
+
});
|
|
138
|
+
var __octaneUniversalPlan12 = universalPlan("nativescript", {
|
|
139
|
+
"kind": "slot",
|
|
140
|
+
"slot": 0
|
|
141
|
+
});
|
|
142
|
+
var __octaneUniversalPlan13 = universalPlan("nativescript", {
|
|
143
|
+
"kind": "range",
|
|
144
|
+
"children": [{
|
|
145
|
+
"kind": "slot",
|
|
146
|
+
"slot": 0
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
var STATUS_TEXT = {
|
|
150
|
+
error: "⚠",
|
|
151
|
+
warning: "⚠",
|
|
152
|
+
success: "✓"
|
|
153
|
+
};
|
|
154
|
+
var FileInputControl = defineUniversalComponent("nativescript", function FileInputControl(inputProps) {
|
|
155
|
+
const hostRef = useRef(null, _h$0);
|
|
156
|
+
const { props } = withSlot(_h$1, useFieldControl, inputProps);
|
|
157
|
+
const generatedId = useId(_h$2);
|
|
158
|
+
const inputId = props.id ?? generatedId;
|
|
159
|
+
const [busy, setBusy] = useState(false, _h$3);
|
|
160
|
+
const [validationErrors, setValidationErrors] = useState([], _h$4);
|
|
161
|
+
const files$1 = props.value == null ? [] : Array.isArray(props.value) ? props.value : [props.value];
|
|
162
|
+
const isDisabled = !!props.isDisabled || !!props.isReadOnly;
|
|
163
|
+
const isBusy = !!props.isLoading || busy;
|
|
164
|
+
const statusType = validationErrors.length ? "error" : props.status?.type;
|
|
165
|
+
const statusMessage = validationErrors.length ? validationErrors.join(" ") : props.status?.message;
|
|
166
|
+
const emitValue = (next) => props.isMultiple ? next : next[0] ?? null;
|
|
167
|
+
const fireChange = (next) => {
|
|
168
|
+
const emitted = emitValue(next);
|
|
169
|
+
props.onChange?.(emitted);
|
|
170
|
+
if (props.changeAction) {
|
|
171
|
+
setBusy(true);
|
|
172
|
+
Promise.resolve(props.changeAction(emitted)).finally(() => setBusy(false));
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const acceptRefs = (picked) => {
|
|
176
|
+
if (isDisabled || isBusy || !picked.length) return;
|
|
177
|
+
const merged = props.isMultiple ? [...files$1, ...picked] : picked;
|
|
178
|
+
const { valid, errors } = validateFiles(merged, {
|
|
179
|
+
accept: props.accept,
|
|
180
|
+
maxSize: props.maxSize,
|
|
181
|
+
maxFiles: props.maxFiles
|
|
182
|
+
});
|
|
183
|
+
setValidationErrors(errors);
|
|
184
|
+
const next = !props.isMultiple && errors.length ? files$1 : valid;
|
|
185
|
+
if (next.length !== files$1.length || next.some((file, index) => file.uri !== files$1[index]?.uri)) fireChange(next);
|
|
186
|
+
};
|
|
187
|
+
const removeAt = (index) => {
|
|
188
|
+
if (isDisabled || isBusy) return;
|
|
189
|
+
setValidationErrors([]);
|
|
190
|
+
fireChange(files$1.filter((_, i) => i !== index));
|
|
191
|
+
};
|
|
192
|
+
const pick = () => {
|
|
193
|
+
if (isDisabled || isBusy) return;
|
|
194
|
+
const options = {
|
|
195
|
+
accept: props.accept,
|
|
196
|
+
multiple: props.isMultiple
|
|
197
|
+
};
|
|
198
|
+
const picked = props.pick ? props.pick(options) : props.isMultiple ? files.pickMultiple(props.accept) : files.pick(props.accept);
|
|
199
|
+
Promise.resolve(picked).then((result) => acceptRefs(normalizePicked(result))).catch((error) => setValidationErrors([String(error?.message ?? error)]));
|
|
200
|
+
};
|
|
201
|
+
const dropzone = props.mode === "dropzone";
|
|
202
|
+
const statusText = statusType ? STATUS_TEXT[statusType] : null;
|
|
203
|
+
useImperativeHandle(props.ref, () => ({
|
|
204
|
+
open: pick,
|
|
205
|
+
native: hostRef.current
|
|
206
|
+
}), void 0, _h$5);
|
|
207
|
+
return universalValue(__octaneUniversalPlan10, [
|
|
208
|
+
cx("vx-fileinput", dropzone && "vx-fileinput--dropzone", isDisabled && "vx-fileinput--disabled", isBusy && "vx-fileinput--loading", statusType && `vx-fileinput--status-${statusType}`, props.size && `vx-fileinput--size-${props.size}`, props.className),
|
|
209
|
+
props.style,
|
|
210
|
+
files$1.length ? universalValue(__octaneUniversalPlan4, [files$1.map((f, i) => universalValue(__octaneUniversalPlan3, [
|
|
211
|
+
universalProps([
|
|
212
|
+
[
|
|
213
|
+
"set",
|
|
214
|
+
"key",
|
|
215
|
+
`${f.uri}-${i}`
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
"set",
|
|
219
|
+
"flexDirection",
|
|
220
|
+
"row"
|
|
221
|
+
],
|
|
222
|
+
[
|
|
223
|
+
"set",
|
|
224
|
+
"alignItems",
|
|
225
|
+
"center"
|
|
226
|
+
],
|
|
227
|
+
[
|
|
228
|
+
"set",
|
|
229
|
+
"className",
|
|
230
|
+
"vx-fileinput-item"
|
|
231
|
+
]
|
|
232
|
+
]),
|
|
233
|
+
universalComponent("nativescript", Icon, universalProps([[
|
|
234
|
+
"set",
|
|
235
|
+
"name",
|
|
236
|
+
"xplat-upload"
|
|
237
|
+
], [
|
|
238
|
+
"set",
|
|
239
|
+
"size",
|
|
240
|
+
14
|
|
241
|
+
]])),
|
|
242
|
+
f.name,
|
|
243
|
+
f.size != null ? universalValue(__octaneUniversalPlan0, [formatFileSize(f.size)]) : null,
|
|
244
|
+
!isDisabled && !isBusy ? universalValue(__octaneUniversalPlan2, [universalComponent("nativescript", Pressable, universalProps([
|
|
245
|
+
[
|
|
246
|
+
"set",
|
|
247
|
+
"className",
|
|
248
|
+
"vx-fileinput-itemremove"
|
|
249
|
+
],
|
|
250
|
+
[
|
|
251
|
+
"set",
|
|
252
|
+
"accessibilityLabel",
|
|
253
|
+
`Remove ${f.name}`
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
"set",
|
|
257
|
+
"onPress",
|
|
258
|
+
() => removeAt(i)
|
|
259
|
+
]
|
|
260
|
+
], universalChildren("nativescript", () => {
|
|
261
|
+
return universalValue(__octaneUniversalPlan1, []);
|
|
262
|
+
})))]) : null
|
|
263
|
+
]))]) : null,
|
|
264
|
+
universalComponent("nativescript", Pressable, universalProps([
|
|
265
|
+
[
|
|
266
|
+
"set",
|
|
267
|
+
"ref",
|
|
268
|
+
(v) => {
|
|
269
|
+
hostRef.current = v;
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
"set",
|
|
274
|
+
"id",
|
|
275
|
+
inputId
|
|
276
|
+
],
|
|
277
|
+
[
|
|
278
|
+
"set",
|
|
279
|
+
"className",
|
|
280
|
+
"vx-fileinput-trigger"
|
|
281
|
+
],
|
|
282
|
+
[
|
|
283
|
+
"set",
|
|
284
|
+
"disabled",
|
|
285
|
+
isDisabled || isBusy
|
|
286
|
+
],
|
|
287
|
+
[
|
|
288
|
+
"set",
|
|
289
|
+
"accessibilityLabel",
|
|
290
|
+
props.accessibilityLabel ?? props.ariaLabel ?? props.fieldLabel ?? props.label
|
|
291
|
+
],
|
|
292
|
+
[
|
|
293
|
+
"set",
|
|
294
|
+
"accessibilityHint",
|
|
295
|
+
props.accessibilityHint
|
|
296
|
+
],
|
|
297
|
+
[
|
|
298
|
+
"set",
|
|
299
|
+
"accessibilityState",
|
|
300
|
+
{
|
|
301
|
+
disabled: isDisabled,
|
|
302
|
+
busy: isBusy
|
|
303
|
+
}
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
"set",
|
|
307
|
+
"onPress",
|
|
308
|
+
pick
|
|
309
|
+
]
|
|
310
|
+
], universalChildren("nativescript", () => {
|
|
311
|
+
return universalValue(__octaneUniversalPlan5, [universalComponent("nativescript", Icon, universalProps([[
|
|
312
|
+
"set",
|
|
313
|
+
"name",
|
|
314
|
+
"xplat-upload"
|
|
315
|
+
], [
|
|
316
|
+
"set",
|
|
317
|
+
"size",
|
|
318
|
+
14
|
|
319
|
+
]])), files$1.length ? "Choose more files" : "Choose files"]);
|
|
320
|
+
}))),
|
|
321
|
+
files$1.length > 0 && !isDisabled && !isBusy ? universalValue(__octaneUniversalPlan7, [universalComponent("nativescript", Pressable, universalProps([
|
|
322
|
+
[
|
|
323
|
+
"set",
|
|
324
|
+
"className",
|
|
325
|
+
"vx-fileinput-clear"
|
|
326
|
+
],
|
|
327
|
+
[
|
|
328
|
+
"set",
|
|
329
|
+
"accessibilityLabel",
|
|
330
|
+
`Clear ${props.fieldLabel ?? props.label ?? "files"}`
|
|
331
|
+
],
|
|
332
|
+
[
|
|
333
|
+
"set",
|
|
334
|
+
"onPress",
|
|
335
|
+
() => {
|
|
336
|
+
setValidationErrors([]);
|
|
337
|
+
fireChange([]);
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
], universalChildren("nativescript", () => {
|
|
341
|
+
return universalValue(__octaneUniversalPlan6, []);
|
|
342
|
+
})))]) : null,
|
|
343
|
+
statusText != null ? universalValue(__octaneUniversalPlan8, [
|
|
344
|
+
cx("vx-field-icon", `vx-field-icon--${statusType}`),
|
|
345
|
+
statusText,
|
|
346
|
+
props.statusVariant === "tooltip" ? statusMessage : void 0
|
|
347
|
+
]) : null,
|
|
348
|
+
statusMessage && props.statusVariant !== "tooltip" ? universalValue(__octaneUniversalPlan9, [cx("vx-field-status", `vx-field-status--${statusType}`), statusMessage]) : null
|
|
349
|
+
]);
|
|
350
|
+
}, { "module": "@nativescript-community/octane" });
|
|
351
|
+
var FileInput = defineUniversalComponent("nativescript", function FileInput(props) {
|
|
352
|
+
const { inField } = withSlot(_h$6, useFieldControl, props);
|
|
353
|
+
const cid = useId(_h$7);
|
|
354
|
+
const control = universalValue(__octaneUniversalPlan11, [universalComponent("nativescript", FileInputControl, universalProps([["spread", props]]))]);
|
|
355
|
+
return universalValue(__octaneUniversalPlan13, [inField ? control : universalValue(__octaneUniversalPlan12, [universalComponent("nativescript", Field, universalProps([
|
|
356
|
+
[
|
|
357
|
+
"set",
|
|
358
|
+
"inputID",
|
|
359
|
+
props.id ?? cid
|
|
360
|
+
],
|
|
361
|
+
[
|
|
362
|
+
"set",
|
|
363
|
+
"label",
|
|
364
|
+
props.label
|
|
365
|
+
],
|
|
366
|
+
[
|
|
367
|
+
"set",
|
|
368
|
+
"isLabelHidden",
|
|
369
|
+
props.isLabelHidden
|
|
370
|
+
],
|
|
371
|
+
[
|
|
372
|
+
"set",
|
|
373
|
+
"description",
|
|
374
|
+
props.description
|
|
375
|
+
],
|
|
376
|
+
[
|
|
377
|
+
"set",
|
|
378
|
+
"isDisabled",
|
|
379
|
+
props.isDisabled
|
|
380
|
+
],
|
|
381
|
+
[
|
|
382
|
+
"set",
|
|
383
|
+
"disabledMessage",
|
|
384
|
+
props.disabledMessage
|
|
385
|
+
],
|
|
386
|
+
[
|
|
387
|
+
"set",
|
|
388
|
+
"isReadOnly",
|
|
389
|
+
props.isReadOnly
|
|
390
|
+
],
|
|
391
|
+
[
|
|
392
|
+
"set",
|
|
393
|
+
"isLoading",
|
|
394
|
+
props.isLoading
|
|
395
|
+
],
|
|
396
|
+
[
|
|
397
|
+
"set",
|
|
398
|
+
"isRequired",
|
|
399
|
+
props.isRequired
|
|
400
|
+
],
|
|
401
|
+
[
|
|
402
|
+
"set",
|
|
403
|
+
"isOptional",
|
|
404
|
+
props.isOptional
|
|
405
|
+
],
|
|
406
|
+
[
|
|
407
|
+
"set",
|
|
408
|
+
"size",
|
|
409
|
+
props.size
|
|
410
|
+
],
|
|
411
|
+
[
|
|
412
|
+
"set",
|
|
413
|
+
"status",
|
|
414
|
+
props.status
|
|
415
|
+
],
|
|
416
|
+
[
|
|
417
|
+
"set",
|
|
418
|
+
"statusVariant",
|
|
419
|
+
props.statusVariant
|
|
420
|
+
],
|
|
421
|
+
[
|
|
422
|
+
"set",
|
|
423
|
+
"labelTooltip",
|
|
424
|
+
props.labelTooltip
|
|
425
|
+
],
|
|
426
|
+
[
|
|
427
|
+
"set",
|
|
428
|
+
"width",
|
|
429
|
+
props.width
|
|
430
|
+
]
|
|
431
|
+
], control))])]);
|
|
432
|
+
}, { "module": "@nativescript-community/octane" });
|
|
433
|
+
//#endregion
|
|
434
|
+
export { FileInput };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/file-bytes.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared helpers for the binary file primitives (readBytes/writeBytes/export).
|
|
4
|
+
* DOM-free so the native leaf can use it too.
|
|
5
|
+
*/
|
|
6
|
+
/** File names used for sandbox writes and export temp files must be bare
|
|
7
|
+
* names, not paths — a `/`, `\`, or `..` segment escapes the sandbox. */
|
|
8
|
+
function assertSafeFileName(name) {
|
|
9
|
+
if (typeof name !== "string" || name.length === 0 || name === "." || name === ".." || name.includes("/") || name.includes("\\") || name.includes("\0")) throw new Error(`Unsafe file name: ${JSON.stringify(name)}`);
|
|
10
|
+
}
|
|
11
|
+
function fileTooLarge(name, maxBytes) {
|
|
12
|
+
return /* @__PURE__ */ new Error(`${name}: file exceeds the ${maxBytes}-byte limit`);
|
|
13
|
+
}
|
|
14
|
+
/** Named rejection for a second `files.export` while one is in flight. */
|
|
15
|
+
function exportBusy() {
|
|
16
|
+
const error = /* @__PURE__ */ new Error("files.export: an export is already in progress");
|
|
17
|
+
error.name = "ExportBusyError";
|
|
18
|
+
return error;
|
|
19
|
+
}
|
|
20
|
+
function throwIfAborted(signal) {
|
|
21
|
+
if (!signal?.aborted) return;
|
|
22
|
+
const thrower = signal.throwIfAborted;
|
|
23
|
+
if (typeof thrower === "function") thrower.call(signal);
|
|
24
|
+
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
25
|
+
error.name = "AbortError";
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
/** Concatenate Uint8Array chunks into one exact-length array. */
|
|
29
|
+
function concatBytes(chunks, total) {
|
|
30
|
+
const out = new Uint8Array(total);
|
|
31
|
+
let offset = 0;
|
|
32
|
+
for (const chunk of chunks) {
|
|
33
|
+
out.set(chunk, offset);
|
|
34
|
+
offset += chunk.byteLength;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** Return an ArrayBuffer covering exactly this view's byte range — native
|
|
39
|
+
* marshallers treat the argument as the whole buffer. */
|
|
40
|
+
function exactBuffer(bytes) {
|
|
41
|
+
if (bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength) return bytes.buffer;
|
|
42
|
+
return bytes.slice().buffer;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { assertSafeFileName, concatBytes, exactBuffer, exportBusy, fileTooLarge, throwIfAborted };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/file-input-utils.ts
|
|
2
|
+
function cx(...values) {
|
|
3
|
+
return values.filter(Boolean).join(" ");
|
|
4
|
+
}
|
|
5
|
+
function normalizePicked(result) {
|
|
6
|
+
if (result == null) return [];
|
|
7
|
+
return Array.isArray(result) ? result : [result];
|
|
8
|
+
}
|
|
9
|
+
function fileAccepts(file, accept) {
|
|
10
|
+
if (!accept) return true;
|
|
11
|
+
const tokens = accept.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
12
|
+
if (!tokens.length) return true;
|
|
13
|
+
const name = file.name.toLowerCase();
|
|
14
|
+
const mime = (file.mimeType ?? "").toLowerCase();
|
|
15
|
+
return tokens.some((token) => {
|
|
16
|
+
if (token === "*/*") return true;
|
|
17
|
+
if (token.startsWith(".")) return name.endsWith(token);
|
|
18
|
+
if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
|
|
19
|
+
return mime === token;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function formatFileSize(bytes) {
|
|
23
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
24
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
25
|
+
if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
26
|
+
return `${(bytes / 1073741824).toFixed(1)} GB`;
|
|
27
|
+
}
|
|
28
|
+
function validateFiles(files, opts) {
|
|
29
|
+
const errors = [];
|
|
30
|
+
const valid = [];
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
if (!fileAccepts(file, opts.accept)) {
|
|
33
|
+
errors.push(`${file.name}: file type not accepted`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (opts.maxSize != null && file.size != null && file.size > opts.maxSize) {
|
|
37
|
+
errors.push(`${file.name}: file exceeds ${formatFileSize(opts.maxSize)}`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
valid.push(file);
|
|
41
|
+
}
|
|
42
|
+
if (opts.maxFiles != null && valid.length > opts.maxFiles) {
|
|
43
|
+
const extra = valid.splice(opts.maxFiles);
|
|
44
|
+
for (const file of extra) errors.push(`${file.name}: exceeds the ${opts.maxFiles}-file limit`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
valid,
|
|
48
|
+
errors
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { cx, fileAccepts, formatFileSize, normalizePicked, validateFiles };
|