@urbicon-ui/blocks 6.34.0 → 6.36.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/README.md +11 -1
- package/dist/components/Calendar/CalendarHeader.svelte +32 -26
- package/dist/components/Calendar/CalendarMiniMonth.svelte +10 -10
- package/dist/components/Calendar/calendar.variants.js +19 -5
- 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 +52 -125
- package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
- package/dist/components/FileUpload/index.d.ts +2 -28
- package/dist/components/Planner/PlannerHeader.svelte +16 -13
- package/dist/components/Planner/planner.variants.js +14 -3
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/i18n/index.d.ts +268 -382
- package/dist/i18n/index.js +83 -9
- package/dist/internal/core/CoreIconButton.svelte +66 -0
- package/dist/internal/core/CoreIconButton.svelte.d.ts +13 -0
- package/dist/internal/core/CoreSpinner.svelte +49 -0
- package/dist/internal/core/CoreSpinner.svelte.d.ts +8 -0
- package/dist/internal/core/spinner-geometry.d.ts +6 -0
- package/dist/internal/core/spinner-geometry.js +6 -0
- package/dist/mint/registry.d.ts +20 -2
- package/dist/mint/registry.js +20 -2
- package/dist/primitives/Badge/Badge.svelte +5 -5
- package/dist/primitives/Badge/badge.variants.js +17 -1
- package/dist/primitives/Button/Button.svelte +4 -2
- package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
- package/dist/primitives/Dialog/Dialog.svelte +7 -5
- package/dist/primitives/Dialog/dialog.variants.d.ts +7 -0
- package/dist/primitives/Dialog/dialog.variants.js +17 -0
- package/dist/primitives/Drawer/Drawer.svelte +7 -5
- package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
- package/dist/primitives/Drawer/drawer.variants.js +17 -0
- package/dist/primitives/Spinner/Spinner.svelte +3 -1
- 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/Toast/Toaster.svelte +17 -2
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -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/dist/utils/variants.js +44 -10
- package/package.json +4 -6
|
@@ -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';
|
|
@@ -17,10 +11,20 @@
|
|
|
17
11
|
import DangerCircleIconDefault from '../../icons/DangerCircleIcon.svelte';
|
|
18
12
|
import { mintRegistry } from '../../mint';
|
|
19
13
|
import { Progress } from '../../primitives/Progress';
|
|
20
|
-
import
|
|
14
|
+
// internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
|
|
15
|
+
import CoreSpinner from '../../internal/core/CoreSpinner.svelte';
|
|
21
16
|
import { fly } from 'svelte/transition';
|
|
22
17
|
import { quintOut } from 'svelte/easing';
|
|
23
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';
|
|
24
28
|
|
|
25
29
|
const bt = useBlocksI18n();
|
|
26
30
|
|
|
@@ -115,54 +119,27 @@
|
|
|
115
119
|
|
|
116
120
|
// ── Validation ─────────────────────────────────────────────────────────────
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (file.size > maxFileSize) {
|
|
137
|
-
errors.push({
|
|
138
|
-
code: 'FILE_TOO_LARGE',
|
|
139
|
-
message: bt('fileUpload.tooLarge', { size: formatFileSize(maxFileSize) })
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (file.size < minFileSize) {
|
|
144
|
-
errors.push({
|
|
145
|
-
code: 'FILE_TOO_SMALL',
|
|
146
|
-
message: bt('fileUpload.tooSmall', { size: formatFileSize(minFileSize) })
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (files.some((f) => f.file.name === file.name && f.file.size === file.size)) {
|
|
151
|
-
errors.push({ code: 'FILE_EXISTS', message: bt('fileUpload.exists') });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (validate) {
|
|
155
|
-
const custom = validate(file);
|
|
156
|
-
if (custom) errors.push(...custom);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return errors;
|
|
160
|
-
}
|
|
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
|
+
});
|
|
161
140
|
|
|
162
141
|
// ── File Processing ────────────────────────────────────────────────────────
|
|
163
142
|
|
|
164
|
-
let idCounter = 0;
|
|
165
|
-
|
|
166
143
|
// Two-way sync between `files[0]` and the convenience `file` binding.
|
|
167
144
|
// `lastSyncedFile` is the snapshot we last wrote to `file` — comparing
|
|
168
145
|
// against it lets us tell whether the change came from `files` (internal,
|
|
@@ -193,9 +170,7 @@
|
|
|
193
170
|
if (file === lastSyncedFile) return;
|
|
194
171
|
|
|
195
172
|
if (file === null) {
|
|
196
|
-
|
|
197
|
-
if (entry.preview) URL.revokeObjectURL(entry.preview);
|
|
198
|
-
}
|
|
173
|
+
revokeIntakePreviews(files);
|
|
199
174
|
files = [];
|
|
200
175
|
lastSyncedFile = null;
|
|
201
176
|
onFilesChange?.(files);
|
|
@@ -203,15 +178,8 @@
|
|
|
203
178
|
}
|
|
204
179
|
|
|
205
180
|
const old = files[0];
|
|
206
|
-
if (old
|
|
207
|
-
|
|
208
|
-
id: `file-${Date.now()}-${++idCounter}`,
|
|
209
|
-
file,
|
|
210
|
-
status: 'pending',
|
|
211
|
-
errors: [],
|
|
212
|
-
preview: isImageFile(file) ? URL.createObjectURL(file) : undefined
|
|
213
|
-
};
|
|
214
|
-
files = [replacement];
|
|
181
|
+
if (old) revokeIntakePreviews([old]);
|
|
182
|
+
files = [createIntakeEntry(file)];
|
|
215
183
|
lastSyncedFile = file;
|
|
216
184
|
onFilesChange?.(files);
|
|
217
185
|
});
|
|
@@ -219,36 +187,7 @@
|
|
|
219
187
|
function processFiles(incoming: File[]) {
|
|
220
188
|
if (disabled) return;
|
|
221
189
|
|
|
222
|
-
const accepted
|
|
223
|
-
const rejected: FileRejection[] = [];
|
|
224
|
-
|
|
225
|
-
const remaining = maxFiles - files.length;
|
|
226
|
-
const toProcess = incoming.slice(0, remaining);
|
|
227
|
-
const excess = incoming.slice(remaining);
|
|
228
|
-
|
|
229
|
-
for (const file of excess) {
|
|
230
|
-
rejected.push({
|
|
231
|
-
file,
|
|
232
|
-
errors: [
|
|
233
|
-
{ code: 'TOO_MANY_FILES', message: bt('fileUpload.tooMany', { count: String(maxFiles) }) }
|
|
234
|
-
]
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
for (const file of toProcess) {
|
|
239
|
-
const errors = validateFile(file);
|
|
240
|
-
if (errors.length > 0) {
|
|
241
|
-
rejected.push({ file, errors });
|
|
242
|
-
} else {
|
|
243
|
-
accepted.push({
|
|
244
|
-
id: `file-${Date.now()}-${++idCounter}`,
|
|
245
|
-
file,
|
|
246
|
-
status: 'pending',
|
|
247
|
-
errors: [],
|
|
248
|
-
preview: isImageFile(file) ? URL.createObjectURL(file) : undefined
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
190
|
+
const { accepted, rejected } = partitionIntake(incoming, files, constraints, messages);
|
|
252
191
|
|
|
253
192
|
if (accepted.length > 0) {
|
|
254
193
|
files = [...files, ...accepted];
|
|
@@ -262,16 +201,14 @@
|
|
|
262
201
|
}
|
|
263
202
|
|
|
264
203
|
function removeFile(entry: FileUploadFile) {
|
|
265
|
-
|
|
204
|
+
revokeIntakePreviews([entry]);
|
|
266
205
|
files = files.filter((f) => f.id !== entry.id);
|
|
267
206
|
onFileRemove?.(entry);
|
|
268
207
|
onFilesChange?.(files);
|
|
269
208
|
}
|
|
270
209
|
|
|
271
210
|
onDestroy(() => {
|
|
272
|
-
|
|
273
|
-
if (entry.preview) URL.revokeObjectURL(entry.preview);
|
|
274
|
-
}
|
|
211
|
+
revokeIntakePreviews(files);
|
|
275
212
|
});
|
|
276
213
|
|
|
277
214
|
// ── Drag Handlers ──────────────────────────────────────────────────────────
|
|
@@ -284,16 +221,7 @@
|
|
|
284
221
|
dragging = true;
|
|
285
222
|
|
|
286
223
|
if (accept && e.dataTransfer?.items?.length) {
|
|
287
|
-
|
|
288
|
-
const hasInvalid = Array.from(e.dataTransfer.items).some((item) => {
|
|
289
|
-
if (item.kind !== 'file') return true;
|
|
290
|
-
return !types.some((type) => {
|
|
291
|
-
if (type.endsWith('/*')) return item.type.startsWith(type.replace('/*', '/'));
|
|
292
|
-
if (type.startsWith('.')) return true;
|
|
293
|
-
return item.type === type;
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
dragInvalid = hasInvalid;
|
|
224
|
+
dragInvalid = !dragItemsMatchAccept(e.dataTransfer.items, accept);
|
|
297
225
|
}
|
|
298
226
|
}
|
|
299
227
|
|
|
@@ -369,21 +297,6 @@
|
|
|
369
297
|
};
|
|
370
298
|
});
|
|
371
299
|
|
|
372
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
373
|
-
|
|
374
|
-
function formatFileSize(bytes: number): string {
|
|
375
|
-
if (bytes === 0) return '0 B';
|
|
376
|
-
if (!isFinite(bytes)) return '';
|
|
377
|
-
const k = 1024;
|
|
378
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
379
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
380
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function isImageFile(file: File): boolean {
|
|
384
|
-
return file.type.startsWith('image/');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
300
|
// ── Mint ─────────────────────────────────────────────────────────────────
|
|
388
301
|
|
|
389
302
|
$effect(() => {
|
|
@@ -520,7 +433,21 @@
|
|
|
520
433
|
<!-- Status indicator -->
|
|
521
434
|
<div class={slot('fileItemStatusIcon')}>
|
|
522
435
|
{#if entry.status === 'uploading'}
|
|
523
|
-
|
|
436
|
+
<!--
|
|
437
|
+
CoreSpinner instead of the public Spinner (see internal/core/).
|
|
438
|
+
The old call was `visible` (explicitly true) with no label
|
|
439
|
+
override, and this branch already gates rendering on the
|
|
440
|
+
uploading status — so the core needs no {#if} of its own.
|
|
441
|
+
Deliberate a11y delta: the old Spinner emitted role="status" +
|
|
442
|
+
aria-live + an sr-only "Loading..." inside the file list's own
|
|
443
|
+
aria-live="polite" region (nested live regions); the core emits
|
|
444
|
+
no semantics — the list region owns announcements. `text-primary`
|
|
445
|
+
pins the old default color (the public Spinner's intent default),
|
|
446
|
+
matching the explicit text-success/text-danger on the sibling
|
|
447
|
+
status icons; it wins the duel against the core's `text-current`
|
|
448
|
+
by stylesheet order (theme colors sort after keyword colors).
|
|
449
|
+
-->
|
|
450
|
+
<CoreSpinner size="xs" class="text-primary" />
|
|
524
451
|
{:else if entry.status === 'complete'}
|
|
525
452
|
<CheckCircleIcon size={itemIconSize} class="text-success" />
|
|
526
453
|
{:else if entry.status === 'error'}
|
|
@@ -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[];
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
-->
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import { useBlocksI18n } from '../..';
|
|
9
|
-
import
|
|
9
|
+
// internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
|
|
10
|
+
import CoreIconButton from '../../internal/core/CoreIconButton.svelte';
|
|
10
11
|
import { Tooltip } from '../../primitives/Tooltip';
|
|
11
12
|
import { resolveIcon } from '../../icons';
|
|
12
13
|
import ChevronLeftIconDefault from '../../icons/ChevronLeftIcon.svelte';
|
|
@@ -37,44 +38,46 @@
|
|
|
37
38
|
);
|
|
38
39
|
</script>
|
|
39
40
|
|
|
41
|
+
<!--
|
|
42
|
+
Nav buttons render on the internal CoreIconButton (was `<Button unstyled
|
|
43
|
+
mint="none">`, which emitted only the call-site classes). The core's plumbing
|
|
44
|
+
overlaps the navButton slot's old baseline (inline-flex centring, focus-visible
|
|
45
|
+
reset, disabled opacity/cursor — now supplied by the core, stripped from the
|
|
46
|
+
slot); the deliberate deltas it introduces are documented on the slot in
|
|
47
|
+
planner.variants.ts.
|
|
48
|
+
-->
|
|
40
49
|
<div class={ctx.slot('header')}>
|
|
41
50
|
<div class={ctx.slot('nav')}>
|
|
42
|
-
<
|
|
43
|
-
unstyled
|
|
44
|
-
mint="none"
|
|
51
|
+
<CoreIconButton
|
|
45
52
|
class={ctx.slot('navButton')}
|
|
46
53
|
onclick={() => ctx.navigate(-1)}
|
|
47
54
|
disabled={!ctx.canGoBack || ctx.disabled}
|
|
48
55
|
aria-label={prevLabel}
|
|
49
56
|
>
|
|
50
57
|
<ChevronLeftIcon size={16} />
|
|
51
|
-
</
|
|
58
|
+
</CoreIconButton>
|
|
52
59
|
</div>
|
|
53
60
|
|
|
54
61
|
<span class={ctx.slot('headerTitle')}>{ctx.title}</span>
|
|
55
62
|
|
|
56
63
|
<div class={ctx.slot('nav')}>
|
|
57
64
|
<Tooltip label={bt('planner.today')}>
|
|
58
|
-
<
|
|
59
|
-
unstyled
|
|
60
|
-
mint="none"
|
|
65
|
+
<CoreIconButton
|
|
61
66
|
class={ctx.slot('navButton')}
|
|
62
67
|
onclick={() => ctx.goToToday()}
|
|
63
68
|
disabled={!ctx.canGoToToday || ctx.disabled}
|
|
64
69
|
aria-label={bt('planner.today')}
|
|
65
70
|
>
|
|
66
71
|
<CalendarDaysIcon size={16} />
|
|
67
|
-
</
|
|
72
|
+
</CoreIconButton>
|
|
68
73
|
</Tooltip>
|
|
69
|
-
<
|
|
70
|
-
unstyled
|
|
71
|
-
mint="none"
|
|
74
|
+
<CoreIconButton
|
|
72
75
|
class={ctx.slot('navButton')}
|
|
73
76
|
onclick={() => ctx.navigate(1)}
|
|
74
77
|
disabled={!ctx.canGoForward || ctx.disabled}
|
|
75
78
|
aria-label={nextLabel}
|
|
76
79
|
>
|
|
77
80
|
<ChevronRightIcon size={16} />
|
|
78
|
-
</
|
|
81
|
+
</CoreIconButton>
|
|
79
82
|
</div>
|
|
80
83
|
</div>
|
|
@@ -14,12 +14,23 @@ export const plannerVariants = tv({
|
|
|
14
14
|
header: ['flex items-center justify-between gap-2', 'border-b border-border-hairline'],
|
|
15
15
|
headerTitle: 'font-semibold text-text-primary select-none tabular-nums',
|
|
16
16
|
nav: 'flex items-center gap-1',
|
|
17
|
+
// Rendered on the internal CoreIconButton (behaviour-only base: inline-flex
|
|
18
|
+
// centring, cursor/select affordance, focus-visible reset, disabled
|
|
19
|
+
// opacity/cursor/inertness), so this slot carries only the visual identity
|
|
20
|
+
// on top — the classes the core already supplies (inline-flex items-center
|
|
21
|
+
// justify-center, focus-visible:outline-none, disabled:opacity-50,
|
|
22
|
+
// disabled:cursor-not-allowed) are not repeated here. Deliberate deltas vs.
|
|
23
|
+
// the old `<Button unstyled mint="none">` render (which had NO plumbing):
|
|
24
|
+
// `cursor-pointer` (was the UA arrow — Tailwind 4 preflight doesn't set it;
|
|
25
|
+
// now consistent with every styled Button) and `disabled:pointer-events-none`
|
|
26
|
+
// (a disabled nav button is fully inert: no more hover-bg feedback or
|
|
27
|
+
// not-allowed cursor while disabled — matching the styled Button base).
|
|
28
|
+
// Mirrors calendar.variants navButton. See internal/core/.
|
|
17
29
|
navButton: [
|
|
18
|
-
'
|
|
30
|
+
'rounded-md',
|
|
19
31
|
'text-text-secondary hover:bg-surface-hover hover:text-text-primary',
|
|
20
32
|
'transition-colors duration-[var(--blocks-duration-fast)]',
|
|
21
|
-
'focus-visible:
|
|
22
|
-
'disabled:opacity-50 disabled:cursor-not-allowed'
|
|
33
|
+
'focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2'
|
|
23
34
|
],
|
|
24
35
|
// Grid scaffolding
|
|
25
36
|
grid: 'w-full',
|
|
@@ -6,6 +6,7 @@ export * from './BarChart/index.js';
|
|
|
6
6
|
export type { CalendarContext, CalendarEvent, CalendarEventCategory, CalendarHeaderProps, CalendarProps, CalendarSelection, CalendarSlotName, CalendarViewMode, DateRange, DayCellContext, EventItemContext, HeaderContext } from './Calendar/index.js';
|
|
7
7
|
export * from './Calendar/index.js';
|
|
8
8
|
export * from './ChartFrame/index.js';
|
|
9
|
+
export * from './Chat/index.js';
|
|
9
10
|
export * from './CommandPalette/index.js';
|
|
10
11
|
export type { CompositionBarIntent, CompositionBarProps, CompositionItem } from './CompositionBar/index.js';
|
|
11
12
|
export * from './CompositionBar/index.js';
|
package/dist/components/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from './AreaChart/index.js';
|
|
|
5
5
|
export * from './BarChart/index.js';
|
|
6
6
|
export * from './Calendar/index.js';
|
|
7
7
|
export * from './ChartFrame/index.js';
|
|
8
|
+
export * from './Chat/index.js';
|
|
8
9
|
export * from './CommandPalette/index.js';
|
|
9
10
|
export * from './CompositionBar/index.js';
|
|
10
11
|
export * from './CurrencyInput/index.js';
|