@urbicon-ui/blocks 6.35.0 → 6.37.0
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/components/Chat/Chat/Chat.svelte +53 -0
- package/dist/components/Chat/Chat/Chat.svelte.d.ts +4 -0
- package/dist/components/Chat/Chat/chat.variants.d.ts +20 -0
- package/dist/components/Chat/Chat/chat.variants.js +15 -0
- package/dist/components/Chat/Chat/index.d.ts +47 -0
- package/dist/components/Chat/Chat/index.js +2 -0
- package/dist/components/Chat/ChatMessage/ChatMessage.svelte +342 -0
- package/dist/components/Chat/ChatMessage/ChatMessage.svelte.d.ts +4 -0
- package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +138 -0
- package/dist/components/Chat/ChatMessage/chat-message.variants.js +118 -0
- package/dist/components/Chat/ChatMessage/index.d.ts +102 -0
- package/dist/components/Chat/ChatMessage/index.js +2 -0
- package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte +301 -0
- package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte.d.ts +6 -0
- package/dist/components/Chat/ChatMessageList/chat-message-list.variants.d.ts +23 -0
- package/dist/components/Chat/ChatMessageList/chat-message-list.variants.js +32 -0
- package/dist/components/Chat/ChatMessageList/chat-scroll.d.ts +45 -0
- package/dist/components/Chat/ChatMessageList/chat-scroll.js +52 -0
- package/dist/components/Chat/ChatMessageList/index.d.ts +93 -0
- package/dist/components/Chat/ChatMessageList/index.js +3 -0
- package/dist/components/Chat/CitationChip/CitationChip.svelte +107 -0
- package/dist/components/Chat/CitationChip/CitationChip.svelte.d.ts +4 -0
- package/dist/components/Chat/CitationChip/citation-chip.variants.d.ts +40 -0
- package/dist/components/Chat/CitationChip/citation-chip.variants.js +42 -0
- package/dist/components/Chat/CitationChip/index.d.ts +69 -0
- package/dist/components/Chat/CitationChip/index.js +2 -0
- package/dist/components/Chat/CodeBlock/CodeBlock.svelte +120 -0
- package/dist/components/Chat/CodeBlock/CodeBlock.svelte.d.ts +4 -0
- package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +40 -0
- package/dist/components/Chat/CodeBlock/code-block.variants.js +51 -0
- package/dist/components/Chat/CodeBlock/index.d.ts +55 -0
- package/dist/components/Chat/CodeBlock/index.js +2 -0
- package/dist/components/Chat/PromptInput/PromptInput.svelte +432 -0
- package/dist/components/Chat/PromptInput/PromptInput.svelte.d.ts +4 -0
- package/dist/components/Chat/PromptInput/index.d.ts +165 -0
- package/dist/components/Chat/PromptInput/index.js +2 -0
- package/dist/components/Chat/PromptInput/prompt-input.variants.d.ts +90 -0
- package/dist/components/Chat/PromptInput/prompt-input.variants.js +102 -0
- package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte +107 -0
- package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte.d.ts +4 -0
- package/dist/components/Chat/ReasoningDisclosure/index.d.ts +85 -0
- package/dist/components/Chat/ReasoningDisclosure/index.js +2 -0
- package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.d.ts +20 -0
- package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.js +31 -0
- package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte +99 -0
- package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte.d.ts +9 -0
- package/dist/components/Chat/StreamingMarkdown/MdInline.svelte +82 -0
- package/dist/components/Chat/StreamingMarkdown/MdInline.svelte.d.ts +9 -0
- package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte +118 -0
- package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte.d.ts +4 -0
- package/dist/components/Chat/StreamingMarkdown/index.d.ts +86 -0
- package/dist/components/Chat/StreamingMarkdown/index.js +2 -0
- package/dist/components/Chat/StreamingMarkdown/md-context.d.ts +58 -0
- package/dist/components/Chat/StreamingMarkdown/md-context.js +1 -0
- package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.d.ts +145 -0
- package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.js +73 -0
- package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +174 -0
- package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte.d.ts +4 -0
- package/dist/components/Chat/ToolCallCard/index.d.ts +84 -0
- package/dist/components/Chat/ToolCallCard/index.js +2 -0
- package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +38 -0
- package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +38 -0
- package/dist/components/Chat/chat.types.d.ts +82 -0
- package/dist/components/Chat/chat.types.js +7 -0
- package/dist/components/Chat/index.d.ts +22 -0
- package/dist/components/Chat/index.js +14 -0
- package/dist/components/Chat/markdown/blocks.d.ts +54 -0
- package/dist/components/Chat/markdown/blocks.js +462 -0
- package/dist/components/Chat/markdown/inline.d.ts +31 -0
- package/dist/components/Chat/markdown/inline.js +446 -0
- package/dist/components/Chat/markdown/repair.d.ts +23 -0
- package/dist/components/Chat/markdown/repair.js +337 -0
- package/dist/components/Chat/markdown/streaming-harness.d.ts +23 -0
- package/dist/components/Chat/markdown/streaming-harness.js +62 -0
- package/dist/components/Chat/markdown/types.d.ts +139 -0
- package/dist/components/Chat/markdown/types.js +13 -0
- package/dist/components/Chat/markdown/url-policy.d.ts +33 -0
- package/dist/components/Chat/markdown/url-policy.js +109 -0
- package/dist/components/FileUpload/FileUpload.svelte +35 -123
- package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
- package/dist/components/FileUpload/index.d.ts +2 -28
- package/dist/components/PinInput/PinInput.svelte +321 -0
- package/dist/components/PinInput/PinInput.svelte.d.ts +4 -0
- package/dist/components/PinInput/index.d.ts +98 -0
- package/dist/components/PinInput/index.js +2 -0
- package/dist/components/PinInput/pin-input.variants.d.ts +96 -0
- package/dist/components/PinInput/pin-input.variants.js +111 -0
- package/dist/components/QRCode/QRCode.svelte +107 -0
- package/dist/components/QRCode/QRCode.svelte.d.ts +4 -0
- package/dist/components/QRCode/index.d.ts +74 -0
- package/dist/components/QRCode/index.js +3 -0
- package/dist/components/QRCode/qr-code.variants.d.ts +25 -0
- package/dist/components/QRCode/qr-code.variants.js +24 -0
- package/dist/components/QRCode/qr-encode.d.ts +22 -0
- package/dist/components/QRCode/qr-encode.js +528 -0
- package/dist/components/TimeInput/TimeInput.svelte +521 -0
- package/dist/components/TimeInput/TimeInput.svelte.d.ts +4 -0
- package/dist/components/TimeInput/index.d.ts +93 -0
- package/dist/components/TimeInput/index.js +2 -0
- package/dist/components/TimeInput/time-input.variants.d.ts +131 -0
- package/dist/components/TimeInput/time-input.variants.js +120 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.js +4 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
- package/dist/primitives/SplitPane/SplitPane.svelte +293 -0
- package/dist/primitives/SplitPane/SplitPane.svelte.d.ts +4 -0
- package/dist/primitives/SplitPane/index.d.ts +88 -0
- package/dist/primitives/SplitPane/index.js +2 -0
- package/dist/primitives/SplitPane/split-pane.utils.d.ts +73 -0
- package/dist/primitives/SplitPane/split-pane.utils.js +94 -0
- package/dist/primitives/SplitPane/split-pane.variants.d.ts +40 -0
- package/dist/primitives/SplitPane/split-pane.variants.js +56 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/translations/de.d.ts +6 -0
- package/dist/translations/de.js +6 -0
- package/dist/translations/en.d.ts +6 -0
- package/dist/translations/en.js +6 -0
- package/dist/utils/file-intake.d.ts +78 -0
- package/dist/utils/file-intake.js +144 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL policy enforcement (decision A4 — strict by default).
|
|
3
|
+
*
|
|
4
|
+
* LLM output is untrusted input: prompt-injected markdown links/images are a
|
|
5
|
+
* proven data-exfiltration channel (query-param payloads to attacker hosts)
|
|
6
|
+
* even without any script execution. Policy therefore runs on every URL the
|
|
7
|
+
* parser emits, and blocked URLs never reach the produced node tree — a
|
|
8
|
+
* blocked link/image carries `blocked: true` and an empty href/src.
|
|
9
|
+
*
|
|
10
|
+
* Checks run against the *normalized* absolute URL (WHATWG `URL`), so scheme
|
|
11
|
+
* tricks (`JaVaScRiPt:`, embedded tabs/newlines, `https:\\host`, `/../`
|
|
12
|
+
* path escapes against a prefix) are neutralized before matching.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_LINK_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:'];
|
|
15
|
+
/** Placeholder base — only used to detect whether a URL is relative. */
|
|
16
|
+
const RELATIVE_PROBE_BASE = 'https://relative-probe.invalid/';
|
|
17
|
+
/**
|
|
18
|
+
* Strip ASCII tab/newline/CR and C0 controls anywhere in the string (the URL
|
|
19
|
+
* standard removes tab/NL/CR during parsing, so leaving them in would let
|
|
20
|
+
* `java\tscript:` sneak past a naive scheme check while browsers still
|
|
21
|
+
* execute it) and trim outer whitespace.
|
|
22
|
+
*/
|
|
23
|
+
function cleanRawUrl(raw) {
|
|
24
|
+
let out = '';
|
|
25
|
+
for (const ch of raw.trim()) {
|
|
26
|
+
const code = ch.charCodeAt(0);
|
|
27
|
+
if (code > 0x1f)
|
|
28
|
+
out += ch;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function resolve(raw) {
|
|
33
|
+
const cleaned = cleanRawUrl(raw);
|
|
34
|
+
if (cleaned.length === 0)
|
|
35
|
+
return { invalid: true };
|
|
36
|
+
// Protocol-relative URLs (`//host/…`) are absolute in effect; resolving
|
|
37
|
+
// them against the probe base would otherwise mislabel them as relative.
|
|
38
|
+
const protocolRelative = cleaned.startsWith('//');
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(protocolRelative ? `https:${cleaned}` : cleaned);
|
|
41
|
+
return { absolute: true, url };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Not parseable standalone → candidate for a relative reference.
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
new URL(cleaned, RELATIVE_PROBE_BASE);
|
|
48
|
+
return { absolute: false, cleaned };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { invalid: true };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const BLOCKED = { ok: false };
|
|
55
|
+
/**
|
|
56
|
+
* Check a link destination. Relative references are always allowed; absolute
|
|
57
|
+
* URLs must carry an allowed protocol.
|
|
58
|
+
*/
|
|
59
|
+
export function checkLinkUrl(raw, policy) {
|
|
60
|
+
const resolved = resolve(raw);
|
|
61
|
+
if ('invalid' in resolved && resolved.invalid)
|
|
62
|
+
return blocked('link', raw, policy);
|
|
63
|
+
if (!resolved.absolute)
|
|
64
|
+
return { ok: true, href: resolved.cleaned };
|
|
65
|
+
const allowed = (policy?.allowedLinkProtocols ?? DEFAULT_LINK_PROTOCOLS).map((p) => p.toLowerCase());
|
|
66
|
+
if (allowed.includes(resolved.url.protocol))
|
|
67
|
+
return { ok: true, href: resolved.url.href };
|
|
68
|
+
return blocked('link', raw, policy);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check an image source. Relative references are allowed (same-origin is not
|
|
72
|
+
* an exfiltration sink); absolute URLs must match an allowlisted prefix on
|
|
73
|
+
* the normalized href. The default empty allowlist blocks every external
|
|
74
|
+
* image — the CamoLeak lesson, and the posture ChatGPT/Copilot converged on.
|
|
75
|
+
*/
|
|
76
|
+
export function checkImageUrl(raw, policy) {
|
|
77
|
+
const resolved = resolve(raw);
|
|
78
|
+
if ('invalid' in resolved && resolved.invalid)
|
|
79
|
+
return blocked('image', raw, policy);
|
|
80
|
+
if (!resolved.absolute)
|
|
81
|
+
return { ok: true, href: resolved.cleaned };
|
|
82
|
+
const prefixes = policy?.allowedImagePrefixes ?? [];
|
|
83
|
+
const href = resolved.url.href;
|
|
84
|
+
for (const prefix of prefixes) {
|
|
85
|
+
if (href.startsWith(normalizePrefix(prefix)))
|
|
86
|
+
return { ok: true, href };
|
|
87
|
+
}
|
|
88
|
+
return blocked('image', raw, policy);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Normalize an allowlist prefix the same way hrefs are normalized (lowercased
|
|
92
|
+
* scheme/host, resolved path) so `HTTPS://Example.com/img/` still matches.
|
|
93
|
+
* A bare-origin prefix gains its trailing slash (`https://cdn.example.com` →
|
|
94
|
+
* `…com/`), which pins it to that origin — without it, `startsWith` would
|
|
95
|
+
* also match `https://cdn.example.com.evil.com/…`. Prefixes that are not
|
|
96
|
+
* full URLs (e.g. `data:`) are kept verbatim.
|
|
97
|
+
*/
|
|
98
|
+
function normalizePrefix(prefix) {
|
|
99
|
+
try {
|
|
100
|
+
return new URL(prefix).href;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return prefix;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function blocked(kind, raw, policy) {
|
|
107
|
+
policy?.onBlocked?.(kind, raw);
|
|
108
|
+
return BLOCKED;
|
|
109
|
+
}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useBlocksI18n } from '../..';
|
|
3
|
-
import type {
|
|
4
|
-
FileUploadProps,
|
|
5
|
-
FileUploadFile,
|
|
6
|
-
FileRejection,
|
|
7
|
-
FileUploadError,
|
|
8
|
-
FileUploadSlotName
|
|
9
|
-
} from './index';
|
|
3
|
+
import type { FileUploadProps, FileUploadFile, FileUploadSlotName } from './index';
|
|
10
4
|
import { fileUploadVariants, type FileUploadVariants } from './fileUpload.variants';
|
|
11
5
|
import { getBlocksConfig, resolveSlotClasses } from '../../provider';
|
|
12
6
|
import { resolveIcon } from '../../icons';
|
|
@@ -22,6 +16,15 @@
|
|
|
22
16
|
import { fly } from 'svelte/transition';
|
|
23
17
|
import { quintOut } from 'svelte/easing';
|
|
24
18
|
import { onDestroy } from 'svelte';
|
|
19
|
+
import {
|
|
20
|
+
createIntakeEntry,
|
|
21
|
+
dragItemsMatchAccept,
|
|
22
|
+
formatFileSize,
|
|
23
|
+
partitionIntake,
|
|
24
|
+
revokeIntakePreviews,
|
|
25
|
+
type FileIntakeConstraints,
|
|
26
|
+
type FileIntakeMessages
|
|
27
|
+
} from '../../utils/file-intake';
|
|
25
28
|
|
|
26
29
|
const bt = useBlocksI18n();
|
|
27
30
|
|
|
@@ -116,54 +119,27 @@
|
|
|
116
119
|
|
|
117
120
|
// ── Validation ─────────────────────────────────────────────────────────────
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (file.size > maxFileSize) {
|
|
138
|
-
errors.push({
|
|
139
|
-
code: 'FILE_TOO_LARGE',
|
|
140
|
-
message: bt('fileUpload.tooLarge', { size: formatFileSize(maxFileSize) })
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (file.size < minFileSize) {
|
|
145
|
-
errors.push({
|
|
146
|
-
code: 'FILE_TOO_SMALL',
|
|
147
|
-
message: bt('fileUpload.tooSmall', { size: formatFileSize(minFileSize) })
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (files.some((f) => f.file.name === file.name && f.file.size === file.size)) {
|
|
152
|
-
errors.push({ code: 'FILE_EXISTS', message: bt('fileUpload.exists') });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (validate) {
|
|
156
|
-
const custom = validate(file);
|
|
157
|
-
if (custom) errors.push(...custom);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return errors;
|
|
161
|
-
}
|
|
122
|
+
// Error texts wired from the component's i18n source; injected into the
|
|
123
|
+
// shared file-intake core so its pure functions stay label-agnostic. Texts
|
|
124
|
+
// are identical to the pre-refactor inline `bt('fileUpload.*')` calls.
|
|
125
|
+
const messages: FileIntakeMessages = {
|
|
126
|
+
invalidType: (type) => bt('fileUpload.invalidType', { type }),
|
|
127
|
+
tooLarge: (size) => bt('fileUpload.tooLarge', { size }),
|
|
128
|
+
tooSmall: (size) => bt('fileUpload.tooSmall', { size }),
|
|
129
|
+
exists: () => bt('fileUpload.exists'),
|
|
130
|
+
tooMany: (count) => bt('fileUpload.tooMany', { count: String(count) })
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const constraints = $derived<FileIntakeConstraints>({
|
|
134
|
+
accept,
|
|
135
|
+
maxFiles,
|
|
136
|
+
maxFileSize,
|
|
137
|
+
minFileSize,
|
|
138
|
+
validate
|
|
139
|
+
});
|
|
162
140
|
|
|
163
141
|
// ── File Processing ────────────────────────────────────────────────────────
|
|
164
142
|
|
|
165
|
-
let idCounter = 0;
|
|
166
|
-
|
|
167
143
|
// Two-way sync between `files[0]` and the convenience `file` binding.
|
|
168
144
|
// `lastSyncedFile` is the snapshot we last wrote to `file` — comparing
|
|
169
145
|
// against it lets us tell whether the change came from `files` (internal,
|
|
@@ -194,9 +170,7 @@
|
|
|
194
170
|
if (file === lastSyncedFile) return;
|
|
195
171
|
|
|
196
172
|
if (file === null) {
|
|
197
|
-
|
|
198
|
-
if (entry.preview) URL.revokeObjectURL(entry.preview);
|
|
199
|
-
}
|
|
173
|
+
revokeIntakePreviews(files);
|
|
200
174
|
files = [];
|
|
201
175
|
lastSyncedFile = null;
|
|
202
176
|
onFilesChange?.(files);
|
|
@@ -204,15 +178,8 @@
|
|
|
204
178
|
}
|
|
205
179
|
|
|
206
180
|
const old = files[0];
|
|
207
|
-
if (old
|
|
208
|
-
|
|
209
|
-
id: `file-${Date.now()}-${++idCounter}`,
|
|
210
|
-
file,
|
|
211
|
-
status: 'pending',
|
|
212
|
-
errors: [],
|
|
213
|
-
preview: isImageFile(file) ? URL.createObjectURL(file) : undefined
|
|
214
|
-
};
|
|
215
|
-
files = [replacement];
|
|
181
|
+
if (old) revokeIntakePreviews([old]);
|
|
182
|
+
files = [createIntakeEntry(file)];
|
|
216
183
|
lastSyncedFile = file;
|
|
217
184
|
onFilesChange?.(files);
|
|
218
185
|
});
|
|
@@ -220,36 +187,7 @@
|
|
|
220
187
|
function processFiles(incoming: File[]) {
|
|
221
188
|
if (disabled) return;
|
|
222
189
|
|
|
223
|
-
const accepted
|
|
224
|
-
const rejected: FileRejection[] = [];
|
|
225
|
-
|
|
226
|
-
const remaining = maxFiles - files.length;
|
|
227
|
-
const toProcess = incoming.slice(0, remaining);
|
|
228
|
-
const excess = incoming.slice(remaining);
|
|
229
|
-
|
|
230
|
-
for (const file of excess) {
|
|
231
|
-
rejected.push({
|
|
232
|
-
file,
|
|
233
|
-
errors: [
|
|
234
|
-
{ code: 'TOO_MANY_FILES', message: bt('fileUpload.tooMany', { count: String(maxFiles) }) }
|
|
235
|
-
]
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for (const file of toProcess) {
|
|
240
|
-
const errors = validateFile(file);
|
|
241
|
-
if (errors.length > 0) {
|
|
242
|
-
rejected.push({ file, errors });
|
|
243
|
-
} else {
|
|
244
|
-
accepted.push({
|
|
245
|
-
id: `file-${Date.now()}-${++idCounter}`,
|
|
246
|
-
file,
|
|
247
|
-
status: 'pending',
|
|
248
|
-
errors: [],
|
|
249
|
-
preview: isImageFile(file) ? URL.createObjectURL(file) : undefined
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
190
|
+
const { accepted, rejected } = partitionIntake(incoming, files, constraints, messages);
|
|
253
191
|
|
|
254
192
|
if (accepted.length > 0) {
|
|
255
193
|
files = [...files, ...accepted];
|
|
@@ -263,16 +201,14 @@
|
|
|
263
201
|
}
|
|
264
202
|
|
|
265
203
|
function removeFile(entry: FileUploadFile) {
|
|
266
|
-
|
|
204
|
+
revokeIntakePreviews([entry]);
|
|
267
205
|
files = files.filter((f) => f.id !== entry.id);
|
|
268
206
|
onFileRemove?.(entry);
|
|
269
207
|
onFilesChange?.(files);
|
|
270
208
|
}
|
|
271
209
|
|
|
272
210
|
onDestroy(() => {
|
|
273
|
-
|
|
274
|
-
if (entry.preview) URL.revokeObjectURL(entry.preview);
|
|
275
|
-
}
|
|
211
|
+
revokeIntakePreviews(files);
|
|
276
212
|
});
|
|
277
213
|
|
|
278
214
|
// ── Drag Handlers ──────────────────────────────────────────────────────────
|
|
@@ -285,16 +221,7 @@
|
|
|
285
221
|
dragging = true;
|
|
286
222
|
|
|
287
223
|
if (accept && e.dataTransfer?.items?.length) {
|
|
288
|
-
|
|
289
|
-
const hasInvalid = Array.from(e.dataTransfer.items).some((item) => {
|
|
290
|
-
if (item.kind !== 'file') return true;
|
|
291
|
-
return !types.some((type) => {
|
|
292
|
-
if (type.endsWith('/*')) return item.type.startsWith(type.replace('/*', '/'));
|
|
293
|
-
if (type.startsWith('.')) return true;
|
|
294
|
-
return item.type === type;
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
dragInvalid = hasInvalid;
|
|
224
|
+
dragInvalid = !dragItemsMatchAccept(e.dataTransfer.items, accept);
|
|
298
225
|
}
|
|
299
226
|
}
|
|
300
227
|
|
|
@@ -370,21 +297,6 @@
|
|
|
370
297
|
};
|
|
371
298
|
});
|
|
372
299
|
|
|
373
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
374
|
-
|
|
375
|
-
function formatFileSize(bytes: number): string {
|
|
376
|
-
if (bytes === 0) return '0 B';
|
|
377
|
-
if (!isFinite(bytes)) return '';
|
|
378
|
-
const k = 1024;
|
|
379
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
380
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
381
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function isImageFile(file: File): boolean {
|
|
385
|
-
return file.type.startsWith('image/');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
300
|
// ── Mint ─────────────────────────────────────────────────────────────────
|
|
389
301
|
|
|
390
302
|
$effect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { FileUploadProps } from './index.js';
|
|
2
|
-
declare const FileUpload: import("svelte").Component<FileUploadProps, {}, "
|
|
2
|
+
declare const FileUpload: import("svelte").Component<FileUploadProps, {}, "file" | "files">;
|
|
3
3
|
type FileUpload = ReturnType<typeof FileUpload>;
|
|
4
4
|
export default FileUpload;
|
|
@@ -2,34 +2,8 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
import type { MintProp } from '../../mint/index.js';
|
|
4
4
|
import type { FileUploadSlots, FileUploadVariants } from './fileUpload.variants.js';
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
/** Machine-readable error code. */
|
|
8
|
-
code: FileUploadErrorCode;
|
|
9
|
-
/** Human-readable error message. */
|
|
10
|
-
message: string;
|
|
11
|
-
}
|
|
12
|
-
export type FileUploadStatus = 'pending' | 'uploading' | 'complete' | 'error';
|
|
13
|
-
export interface FileUploadFile {
|
|
14
|
-
/** Unique identifier for this file entry. */
|
|
15
|
-
id: string;
|
|
16
|
-
/** The native File object. */
|
|
17
|
-
file: File;
|
|
18
|
-
/** Object URL for image previews (auto-generated, auto-revoked). */
|
|
19
|
-
preview?: string;
|
|
20
|
-
/** Upload progress 0–100. Undefined when not tracking. */
|
|
21
|
-
progress?: number;
|
|
22
|
-
/** Current lifecycle status. */
|
|
23
|
-
status: FileUploadStatus;
|
|
24
|
-
/** Validation or upload errors. */
|
|
25
|
-
errors: FileUploadError[];
|
|
26
|
-
}
|
|
27
|
-
export interface FileRejection {
|
|
28
|
-
/** The rejected file. */
|
|
29
|
-
file: File;
|
|
30
|
-
/** Why it was rejected. */
|
|
31
|
-
errors: FileUploadError[];
|
|
32
|
-
}
|
|
5
|
+
import type { FileIntakeRejection as FileRejection, FileIntakeError as FileUploadError, FileIntakeErrorCode as FileUploadErrorCode, FileIntakeEntry as FileUploadFile, FileIntakeStatus as FileUploadStatus } from '../../utils/file-intake.js';
|
|
6
|
+
export type { FileRejection, FileUploadError, FileUploadErrorCode, FileUploadFile, FileUploadStatus };
|
|
33
7
|
export declare const IMAGE_MIME_TYPES: string[];
|
|
34
8
|
export declare const PDF_MIME_TYPE: string[];
|
|
35
9
|
export declare const VIDEO_MIME_TYPES: string[];
|