@ticatec/uniface-element 0.2.0 → 0.2.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 +273 -1
- package/README_CN.md +341 -0
- package/dist/common/MouseClickHandler.d.ts +1 -1
- package/dist/dialog/CommonDialog.svelte +6 -3
- package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
- package/dist/dialog/Dialog.svelte +36 -10
- package/dist/dialog/Dialog.svelte.d.ts +4 -3
- package/dist/dialog/DialogBoard.svelte +11 -8
- package/dist/dialog/DialogWrapper.svelte +10 -0
- package/dist/dialog/DialogWrapper.svelte.d.ts +22 -0
- package/dist/dialog/Dialogs.js +1 -1
- package/dist/dialog/ModalResult.d.ts +5 -0
- package/dist/dialog/ModalResult.js +5 -0
- package/docs/buttons/cn/ActionBar.md +459 -0
- package/docs/buttons/cn/Button.md +228 -0
- package/docs/buttons/cn/IconButton.md +294 -0
- package/docs/buttons/cn/README.md +600 -0
- package/docs/buttons/cn/TextButton.md +163 -0
- package/docs/buttons/en/ActionBar.md +459 -0
- package/docs/buttons/en/Button.md +228 -0
- package/docs/buttons/en/IconButton.md +294 -0
- package/docs/buttons/en/README.md +600 -0
- package/docs/buttons/en/TextButton.md +163 -0
- package/docs/containers/cn/accordion.md +622 -0
- package/docs/containers/cn/box.md +445 -0
- package/docs/containers/cn/card.md +404 -0
- package/docs/containers/cn/page.md +576 -0
- package/docs/containers/cn/tabs.md +686 -0
- package/docs/containers/en/accordion.md +622 -0
- package/docs/containers/en/box.md +445 -0
- package/docs/containers/en/card.md +404 -0
- package/docs/containers/en/page.md +576 -0
- package/docs/containers/en/tabs.md +686 -0
- package/docs/data-display/cn/concise-data-table.md +731 -0
- package/docs/data-display/cn/listbox.md +786 -0
- package/docs/data-display/cn/treeview.md +863 -0
- package/docs/data-display/en/concise-data-table.md +732 -0
- package/docs/data-display/en/listbox.md +786 -0
- package/docs/data-display/en/treeview.md +863 -0
- package/docs/data-table/cn/README.md +596 -0
- package/docs/data-table/en/README.md +596 -0
- package/docs/dialog/cn/README.md +767 -0
- package/docs/dialog/en/README.md +767 -0
- package/docs/form/cn/README.md +847 -0
- package/docs/form/en/README.md +847 -0
- package/docs/form-components/cn/README.md +495 -0
- package/docs/form-components/cn/attachmentfilesfield.md +757 -0
- package/docs/form-components/cn/cascade-options-select.md +374 -0
- package/docs/form-components/cn/checkbox.md +58 -0
- package/docs/form-components/cn/date-picker.md +138 -0
- package/docs/form-components/cn/datetime-picker.md +228 -0
- package/docs/form-components/cn/group-checkbox.md +405 -0
- package/docs/form-components/cn/group-radiobox.md +525 -0
- package/docs/form-components/cn/imagefilesfield.md +651 -0
- package/docs/form-components/cn/input-options-select.md +250 -0
- package/docs/form-components/cn/lookup-editor.md +406 -0
- package/docs/form-components/cn/memoeditor.md +409 -0
- package/docs/form-components/cn/number-editor.md +261 -0
- package/docs/form-components/cn/options-multi-select.md +177 -0
- package/docs/form-components/cn/options-select.md +83 -0
- package/docs/form-components/cn/passwordeditor.md +538 -0
- package/docs/form-components/cn/prompts-text-editor.md +241 -0
- package/docs/form-components/cn/radiobutton.md +84 -0
- package/docs/form-components/cn/switch.md +178 -0
- package/docs/form-components/cn/text-editor.md +142 -0
- package/docs/form-components/cn/time-editor.md +159 -0
- package/docs/form-components/cn/unit-number-editor.md +164 -0
- package/docs/form-components/en/README.md +463 -0
- package/docs/form-components/en/attachmentfilesfield.md +757 -0
- package/docs/form-components/en/cascade-options-select.md +358 -0
- package/docs/form-components/en/checkbox.md +59 -0
- package/docs/form-components/en/date-picker.md +138 -0
- package/docs/form-components/en/datetime-picker.md +228 -0
- package/docs/form-components/en/group-checkbox.md +395 -0
- package/docs/form-components/en/group-radiobox.md +498 -0
- package/docs/form-components/en/imagefilesfield.md +651 -0
- package/docs/form-components/en/input-options-select.md +239 -0
- package/docs/form-components/en/lookup-editor.md +389 -0
- package/docs/form-components/en/memoeditor.md +409 -0
- package/docs/form-components/en/number-editor.md +249 -0
- package/docs/form-components/en/options-multi-select.md +177 -0
- package/docs/form-components/en/options-select.md +83 -0
- package/docs/form-components/en/passwordeditor.md +538 -0
- package/docs/form-components/en/prompts-text-editor.md +241 -0
- package/docs/form-components/en/radiobutton.md +84 -0
- package/docs/form-components/en/switch.md +180 -0
- package/docs/form-components/en/text-editor.md +142 -0
- package/docs/form-components/en/time-editor.md +159 -0
- package/docs/form-components/en/unit-number-editor.md +164 -0
- package/docs/form-layout/cn/criteria-field.md +652 -0
- package/docs/form-layout/cn/flex-form.md +454 -0
- package/docs/form-layout/cn/flex-row-form.md +542 -0
- package/docs/form-layout/cn/form-field.md +923 -0
- package/docs/form-layout/cn/grid-form.md +676 -0
- package/docs/form-layout/en/criteria-field.md +652 -0
- package/docs/form-layout/en/flex-form.md +454 -0
- package/docs/form-layout/en/flex-row-form.md +542 -0
- package/docs/form-layout/en/form-field.md +923 -0
- package/docs/form-layout/en/grid-form.md +676 -0
- package/docs/global/cn/README.md +534 -0
- package/docs/global/en/README.md +534 -0
- package/docs/layout/cn/classiclayout.md +460 -0
- package/docs/layout/cn/headerlayout.md +524 -0
- package/docs/layout/cn/sidebarlayout.md +263 -0
- package/docs/layout/en/classiclayout.md +951 -0
- package/docs/layout/en/headerlayout.md +524 -0
- package/docs/layout/en/sidebarlayout.md +262 -0
- package/docs/misc/cn/README.md +1217 -0
- package/docs/misc/en/README.md +1217 -0
- package/docs/navigation/cn/breadcrumb.md +291 -0
- package/docs/navigation/cn/navigator.md +322 -0
- package/docs/navigation/cn/navigatormenu.md +512 -0
- package/docs/navigation/cn/progressbar.md +199 -0
- package/docs/navigation/cn/progressstepbar.md +299 -0
- package/docs/navigation/en/breadcrumb.md +291 -0
- package/docs/navigation/en/navigator.md +322 -0
- package/docs/navigation/en/navigatormenu.md +512 -0
- package/docs/navigation/en/progressbar.md +199 -0
- package/docs/navigation/en/progressstepbar.md +299 -0
- package/docs/search-filters/cn/daterange.md +1156 -0
- package/docs/search-filters/cn/numberrange.md +688 -0
- package/docs/search-filters/cn/searchbox.md +1582 -0
- package/docs/search-filters/en/daterange.md +1156 -0
- package/docs/search-filters/en/numberrange.md +688 -0
- package/docs/search-filters/en/searchbox.md +1582 -0
- package/package.json +38 -5
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
# AttachmentFilesField Component
|
|
2
|
+
|
|
3
|
+
The AttachmentFilesField component provides a comprehensive file upload and management interface with support for various file types, drag-and-drop upload, progress tracking, and file removal confirmation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple File Upload**: Support for uploading multiple files simultaneously
|
|
8
|
+
- **File Type Detection**: Automatic detection and categorization of file types (image, PDF, document, etc.)
|
|
9
|
+
- **Drag & Drop Support**: Intuitive drag-and-drop file upload interface
|
|
10
|
+
- **Upload Progress**: Real-time upload progress tracking with cancel capability
|
|
11
|
+
- **File Management**: View, remove, and manage uploaded files
|
|
12
|
+
- **Display Modes**: Edit and view modes for different contexts
|
|
13
|
+
- **Variant Support**: Multiple styling variants (plain, outlined, filled)
|
|
14
|
+
- **Confirmation Dialogs**: Optional removal confirmation for file deletion
|
|
15
|
+
- **Responsive Design**: Adapts to different screen sizes and layouts
|
|
16
|
+
|
|
17
|
+
## Import
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
|
|
21
|
+
import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Basic Usage
|
|
25
|
+
|
|
26
|
+
### Simple File Upload Field
|
|
27
|
+
|
|
28
|
+
```svelte
|
|
29
|
+
<script>
|
|
30
|
+
import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
|
|
31
|
+
import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
32
|
+
|
|
33
|
+
let attachments: AttachmentFile[] = [];
|
|
34
|
+
|
|
35
|
+
const uploadFile: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
36
|
+
// Simulate file upload
|
|
37
|
+
let uploadedBytes = 0;
|
|
38
|
+
const totalBytes = file.size;
|
|
39
|
+
|
|
40
|
+
const uploadInterval = setInterval(() => {
|
|
41
|
+
uploadedBytes += Math.min(1024 * 100, totalBytes - uploadedBytes); // 100KB chunks
|
|
42
|
+
progressUpdate(uploadedBytes);
|
|
43
|
+
|
|
44
|
+
if (uploadedBytes >= totalBytes) {
|
|
45
|
+
clearInterval(uploadInterval);
|
|
46
|
+
// Simulate successful upload with a mock URL
|
|
47
|
+
const mockUrl = URL.createObjectURL(file);
|
|
48
|
+
onUploaded(mockUrl);
|
|
49
|
+
}
|
|
50
|
+
}, 100);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
cancel: async () => {
|
|
54
|
+
clearInterval(uploadInterval);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<AttachmentFilesField
|
|
62
|
+
bind:files={attachments}
|
|
63
|
+
{uploadFile}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Styled File Upload with Validation
|
|
68
|
+
|
|
69
|
+
```svelte
|
|
70
|
+
<script>
|
|
71
|
+
import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
|
|
72
|
+
import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
73
|
+
|
|
74
|
+
let documents: AttachmentFile[] = [];
|
|
75
|
+
|
|
76
|
+
const uploadDocument: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
77
|
+
// Validate file size (max 10MB)
|
|
78
|
+
if (file.size > 10 * 1024 * 1024) {
|
|
79
|
+
errorHandler(new Error('File size cannot exceed 10MB'));
|
|
80
|
+
return { cancel: async () => true };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Validate file type
|
|
84
|
+
const allowedTypes = ['pdf', 'doc', 'docx', 'txt', 'jpg', 'png'];
|
|
85
|
+
const fileExtension = file.name.split('.').pop()?.toLowerCase();
|
|
86
|
+
|
|
87
|
+
if (!fileExtension || !allowedTypes.includes(fileExtension)) {
|
|
88
|
+
errorHandler(new Error('File type not supported. Allowed: PDF, DOC, DOCX, TXT, JPG, PNG'));
|
|
89
|
+
return { cancel: async () => true };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Simulate API upload
|
|
93
|
+
const formData = new FormData();
|
|
94
|
+
formData.append('file', file);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
cancel: async () => {
|
|
98
|
+
// Implementation for canceling upload
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const confirmRemoval = async (file: AttachmentFile) => {
|
|
105
|
+
return confirm(`Are you sure you want to remove "${file.name}"?`);
|
|
106
|
+
};
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<div class="document-upload">
|
|
110
|
+
<label>Project Documents</label>
|
|
111
|
+
<AttachmentFilesField
|
|
112
|
+
bind:files={documents}
|
|
113
|
+
variant="outlined"
|
|
114
|
+
uploadFile={uploadDocument}
|
|
115
|
+
removeFileConfirm={confirmRemoval}
|
|
116
|
+
style="margin-top: 8px;"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API Reference
|
|
122
|
+
|
|
123
|
+
### Props
|
|
124
|
+
|
|
125
|
+
| Property | Type | Default | Description |
|
|
126
|
+
|----------|------|---------|-------------|
|
|
127
|
+
| `files` | `Array<AttachmentFile>` | `[]` | Array of attached files |
|
|
128
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | Visual variant |
|
|
129
|
+
| `disabled` | `boolean` | `false` | Whether the field is disabled |
|
|
130
|
+
| `readonly` | `boolean` | `false` | Whether the field is read-only |
|
|
131
|
+
| `style` | `string` | `''` | Additional CSS styles |
|
|
132
|
+
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | Display mode (Edit/View) |
|
|
133
|
+
| `removeFileConfirm` | `RemoveConfirm` | `null` | Optional confirmation function for file removal |
|
|
134
|
+
| `uploadFile` | `FileUpload` | required | Function to handle file uploads |
|
|
135
|
+
| `text` | `string \| null` | `null` | Display text (auto-generated from file names) |
|
|
136
|
+
|
|
137
|
+
### Types
|
|
138
|
+
|
|
139
|
+
#### AttachmentFile Interface
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
interface AttachmentFile {
|
|
143
|
+
name: string; // File name
|
|
144
|
+
type: FileType; // File type category
|
|
145
|
+
uri: string; // File URL or path
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### FileType Enum
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
enum FileType {
|
|
153
|
+
IMAGE = 'img',
|
|
154
|
+
PDF = 'pdf',
|
|
155
|
+
DOC = 'doc',
|
|
156
|
+
XLS = 'xml',
|
|
157
|
+
PPT = 'ppt',
|
|
158
|
+
AUDIO = 'wav',
|
|
159
|
+
VIDEO = 'mov',
|
|
160
|
+
OTHER = 'dat'
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### FileUpload Function Type
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
type FileUpload = (
|
|
168
|
+
file: File,
|
|
169
|
+
progressUpdate: ProgressUpdate,
|
|
170
|
+
onUploaded: OnUploaded,
|
|
171
|
+
errorHandler: ErrorHandler
|
|
172
|
+
) => UploadProgress;
|
|
173
|
+
|
|
174
|
+
type ProgressUpdate = (uploadBytes: number) => void;
|
|
175
|
+
type OnUploaded = (url: string, thumbnail?: string) => void;
|
|
176
|
+
type ErrorHandler = (e: Error) => void;
|
|
177
|
+
|
|
178
|
+
interface UploadProgress {
|
|
179
|
+
cancel: () => Promise<boolean>;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### RemoveConfirm Function Type
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
type RemoveConfirm = (file: AttachmentFile) => Promise<boolean>;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Advanced Examples
|
|
190
|
+
|
|
191
|
+
### Document Management System
|
|
192
|
+
|
|
193
|
+
```svelte
|
|
194
|
+
<script>
|
|
195
|
+
import AttachmentFilesField, { DisplayMode } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
196
|
+
import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
197
|
+
|
|
198
|
+
let projectFiles: AttachmentFile[] = [
|
|
199
|
+
{
|
|
200
|
+
name: "Project_Proposal.pdf",
|
|
201
|
+
type: FileType.PDF,
|
|
202
|
+
uri: "/documents/proposal.pdf"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "Requirements_Doc.docx",
|
|
206
|
+
type: FileType.DOC,
|
|
207
|
+
uri: "/documents/requirements.docx"
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
let isEditMode = true;
|
|
212
|
+
let uploadProgress = new Map<string, number>();
|
|
213
|
+
|
|
214
|
+
const uploadToServer: FileUpload = async (file, progressUpdate, onUploaded, errorHandler) => {
|
|
215
|
+
try {
|
|
216
|
+
const formData = new FormData();
|
|
217
|
+
formData.append('file', file);
|
|
218
|
+
formData.append('category', 'project-documents');
|
|
219
|
+
|
|
220
|
+
const xhr = new XMLHttpRequest();
|
|
221
|
+
|
|
222
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
223
|
+
if (e.lengthComputable) {
|
|
224
|
+
progressUpdate(e.loaded);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
xhr.addEventListener('load', () => {
|
|
229
|
+
if (xhr.status === 200) {
|
|
230
|
+
const response = JSON.parse(xhr.responseText);
|
|
231
|
+
onUploaded(response.url, response.thumbnail);
|
|
232
|
+
} else {
|
|
233
|
+
errorHandler(new Error(`Upload failed: ${xhr.statusText}`));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
xhr.addEventListener('error', () => {
|
|
238
|
+
errorHandler(new Error('Network error during upload'));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
xhr.open('POST', '/api/upload');
|
|
242
|
+
xhr.send(formData);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
cancel: async () => {
|
|
246
|
+
xhr.abort();
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
} catch (error) {
|
|
251
|
+
errorHandler(error as Error);
|
|
252
|
+
return { cancel: async () => true };
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const confirmFileRemoval = async (file: AttachmentFile) => {
|
|
257
|
+
const result = await showCustomDialog({
|
|
258
|
+
title: 'Remove File',
|
|
259
|
+
message: `Are you sure you want to remove "${file.name}"? This action cannot be undone.`,
|
|
260
|
+
buttons: ['Cancel', 'Remove'],
|
|
261
|
+
type: 'warning'
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return result === 'Remove';
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const showCustomDialog = (options: any) => {
|
|
268
|
+
return new Promise((resolve) => {
|
|
269
|
+
// Implementation of custom dialog
|
|
270
|
+
const confirmed = confirm(`${options.title}: ${options.message}`);
|
|
271
|
+
resolve(confirmed ? 'Remove' : 'Cancel');
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const toggleEditMode = () => {
|
|
276
|
+
isEditMode = !isEditMode;
|
|
277
|
+
};
|
|
278
|
+
</script>
|
|
279
|
+
|
|
280
|
+
<div class="document-manager">
|
|
281
|
+
<div class="manager-header">
|
|
282
|
+
<h3>Project Documents</h3>
|
|
283
|
+
<button on:click={toggleEditMode}>
|
|
284
|
+
{isEditMode ? 'View Mode' : 'Edit Mode'}
|
|
285
|
+
</button>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<AttachmentFilesField
|
|
289
|
+
bind:files={projectFiles}
|
|
290
|
+
variant="outlined"
|
|
291
|
+
displayMode={isEditMode ? DisplayMode.Edit : DisplayMode.View}
|
|
292
|
+
uploadFile={uploadToServer}
|
|
293
|
+
removeFileConfirm={confirmFileRemoval}
|
|
294
|
+
style="margin-top: 15px; min-height: 120px;"
|
|
295
|
+
/>
|
|
296
|
+
|
|
297
|
+
<div class="file-summary">
|
|
298
|
+
<p>Total files: {projectFiles.length}</p>
|
|
299
|
+
<p>Types: {[...new Set(projectFiles.map(f => f.type))].join(', ')}</p>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<style>
|
|
304
|
+
.document-manager {
|
|
305
|
+
max-width: 600px;
|
|
306
|
+
margin: 20px 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.manager-header {
|
|
310
|
+
display: flex;
|
|
311
|
+
justify-content: space-between;
|
|
312
|
+
align-items: center;
|
|
313
|
+
margin-bottom: 10px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.file-summary {
|
|
317
|
+
margin-top: 10px;
|
|
318
|
+
padding: 10px;
|
|
319
|
+
background: #f5f5f5;
|
|
320
|
+
border-radius: 4px;
|
|
321
|
+
font-size: 0.9em;
|
|
322
|
+
color: #666;
|
|
323
|
+
}
|
|
324
|
+
</style>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Form with File Attachments
|
|
328
|
+
|
|
329
|
+
```svelte
|
|
330
|
+
<script>
|
|
331
|
+
import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
|
|
332
|
+
import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
333
|
+
|
|
334
|
+
let formData = {
|
|
335
|
+
title: '',
|
|
336
|
+
description: '',
|
|
337
|
+
attachments: [] as AttachmentFile[]
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
let errors = {};
|
|
341
|
+
let isSubmitting = false;
|
|
342
|
+
|
|
343
|
+
const uploadFile: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
344
|
+
// File size limit: 5MB
|
|
345
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
346
|
+
errorHandler(new Error('File size must be less than 5MB'));
|
|
347
|
+
return { cancel: async () => true };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Simulate upload process
|
|
351
|
+
let uploaded = 0;
|
|
352
|
+
const total = file.size;
|
|
353
|
+
|
|
354
|
+
const interval = setInterval(() => {
|
|
355
|
+
uploaded += Math.min(1024 * 50, total - uploaded);
|
|
356
|
+
progressUpdate(uploaded);
|
|
357
|
+
|
|
358
|
+
if (uploaded >= total) {
|
|
359
|
+
clearInterval(interval);
|
|
360
|
+
setTimeout(() => {
|
|
361
|
+
const mockUrl = `/files/${Date.now()}_${file.name}`;
|
|
362
|
+
onUploaded(mockUrl);
|
|
363
|
+
}, 200);
|
|
364
|
+
}
|
|
365
|
+
}, 50);
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
cancel: async () => {
|
|
369
|
+
clearInterval(interval);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const validateForm = () => {
|
|
376
|
+
errors = {};
|
|
377
|
+
|
|
378
|
+
if (!formData.title.trim()) {
|
|
379
|
+
errors.title = 'Title is required';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!formData.description.trim()) {
|
|
383
|
+
errors.description = 'Description is required';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (formData.attachments.length === 0) {
|
|
387
|
+
errors.attachments = 'At least one attachment is required';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return Object.keys(errors).length === 0;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const submitForm = async () => {
|
|
394
|
+
if (!validateForm()) return;
|
|
395
|
+
|
|
396
|
+
isSubmitting = true;
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
// Simulate form submission
|
|
400
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
401
|
+
|
|
402
|
+
alert('Form submitted successfully!');
|
|
403
|
+
formData = { title: '', description: '', attachments: [] };
|
|
404
|
+
} catch (error) {
|
|
405
|
+
alert('Submission failed. Please try again.');
|
|
406
|
+
} finally {
|
|
407
|
+
isSubmitting = false;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const confirmRemoval = async (file: AttachmentFile) => {
|
|
412
|
+
return confirm(`Remove "${file.name}" from attachments?`);
|
|
413
|
+
};
|
|
414
|
+
</script>
|
|
415
|
+
|
|
416
|
+
<form class="attachment-form" on:submit|preventDefault={submitForm}>
|
|
417
|
+
<div class="form-group">
|
|
418
|
+
<label for="title">Title *</label>
|
|
419
|
+
<input
|
|
420
|
+
id="title"
|
|
421
|
+
type="text"
|
|
422
|
+
bind:value={formData.title}
|
|
423
|
+
disabled={isSubmitting}
|
|
424
|
+
class:error={errors.title}
|
|
425
|
+
/>
|
|
426
|
+
{#if errors.title}
|
|
427
|
+
<div class="error-message">{errors.title}</div>
|
|
428
|
+
{/if}
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div class="form-group">
|
|
432
|
+
<label for="description">Description *</label>
|
|
433
|
+
<textarea
|
|
434
|
+
id="description"
|
|
435
|
+
rows="4"
|
|
436
|
+
bind:value={formData.description}
|
|
437
|
+
disabled={isSubmitting}
|
|
438
|
+
class:error={errors.description}
|
|
439
|
+
></textarea>
|
|
440
|
+
{#if errors.description}
|
|
441
|
+
<div class="error-message">{errors.description}</div>
|
|
442
|
+
{/if}
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<div class="form-group">
|
|
446
|
+
<label>Attachments *</label>
|
|
447
|
+
<AttachmentFilesField
|
|
448
|
+
bind:files={formData.attachments}
|
|
449
|
+
variant="outlined"
|
|
450
|
+
{uploadFile}
|
|
451
|
+
removeFileConfirm={confirmRemoval}
|
|
452
|
+
disabled={isSubmitting}
|
|
453
|
+
/>
|
|
454
|
+
{#if errors.attachments}
|
|
455
|
+
<div class="error-message">{errors.attachments}</div>
|
|
456
|
+
{/if}
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<div class="form-actions">
|
|
460
|
+
<button
|
|
461
|
+
type="button"
|
|
462
|
+
on:click={() => formData = { title: '', description: '', attachments: [] }}
|
|
463
|
+
disabled={isSubmitting}
|
|
464
|
+
>
|
|
465
|
+
Clear
|
|
466
|
+
</button>
|
|
467
|
+
<button
|
|
468
|
+
type="submit"
|
|
469
|
+
disabled={isSubmitting}
|
|
470
|
+
class="primary"
|
|
471
|
+
>
|
|
472
|
+
{isSubmitting ? 'Submitting...' : 'Submit'}
|
|
473
|
+
</button>
|
|
474
|
+
</div>
|
|
475
|
+
</form>
|
|
476
|
+
|
|
477
|
+
<style>
|
|
478
|
+
.attachment-form {
|
|
479
|
+
max-width: 600px;
|
|
480
|
+
margin: 0 auto;
|
|
481
|
+
padding: 20px;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.form-group {
|
|
485
|
+
margin-bottom: 20px;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.form-group label {
|
|
489
|
+
display: block;
|
|
490
|
+
margin-bottom: 5px;
|
|
491
|
+
font-weight: bold;
|
|
492
|
+
color: #333;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.form-group input, .form-group textarea {
|
|
496
|
+
width: 100%;
|
|
497
|
+
padding: 10px;
|
|
498
|
+
border: 1px solid #ddd;
|
|
499
|
+
border-radius: 4px;
|
|
500
|
+
font-size: 14px;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.form-group input.error, .form-group textarea.error {
|
|
504
|
+
border-color: #d32f2f;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.error-message {
|
|
508
|
+
color: #d32f2f;
|
|
509
|
+
font-size: 0.875em;
|
|
510
|
+
margin-top: 4px;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.form-actions {
|
|
514
|
+
display: flex;
|
|
515
|
+
gap: 10px;
|
|
516
|
+
justify-content: flex-end;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
button {
|
|
520
|
+
padding: 10px 20px;
|
|
521
|
+
border: 1px solid #ddd;
|
|
522
|
+
border-radius: 4px;
|
|
523
|
+
cursor: pointer;
|
|
524
|
+
font-size: 14px;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
button.primary {
|
|
528
|
+
background: #1976d2;
|
|
529
|
+
color: white;
|
|
530
|
+
border-color: #1976d2;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
button:disabled {
|
|
534
|
+
opacity: 0.6;
|
|
535
|
+
cursor: not-allowed;
|
|
536
|
+
}
|
|
537
|
+
</style>
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Multi-Category File Manager
|
|
541
|
+
|
|
542
|
+
```svelte
|
|
543
|
+
<script>
|
|
544
|
+
import AttachmentFilesField, { DisplayMode } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
545
|
+
import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
|
|
546
|
+
|
|
547
|
+
let fileCategories = {
|
|
548
|
+
documents: [] as AttachmentFile[],
|
|
549
|
+
images: [] as AttachmentFile[],
|
|
550
|
+
archives: [] as AttachmentFile[]
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
let activeCategory = 'documents';
|
|
554
|
+
|
|
555
|
+
const categoryConfigs = {
|
|
556
|
+
documents: {
|
|
557
|
+
title: 'Documents',
|
|
558
|
+
accept: ['.pdf', '.doc', '.docx', '.txt', '.rtf'],
|
|
559
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
560
|
+
description: 'PDF, DOC, DOCX, TXT, RTF files up to 10MB'
|
|
561
|
+
},
|
|
562
|
+
images: {
|
|
563
|
+
title: 'Images',
|
|
564
|
+
accept: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'],
|
|
565
|
+
maxSize: 5 * 1024 * 1024, // 5MB
|
|
566
|
+
description: 'Image files up to 5MB'
|
|
567
|
+
},
|
|
568
|
+
archives: {
|
|
569
|
+
title: 'Archives',
|
|
570
|
+
accept: ['.zip', '.rar', '.7z', '.tar', '.gz'],
|
|
571
|
+
maxSize: 50 * 1024 * 1024, // 50MB
|
|
572
|
+
description: 'Archive files up to 50MB'
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const createUploadFunction = (category: string): FileUpload => {
|
|
577
|
+
return (file, progressUpdate, onUploaded, errorHandler) => {
|
|
578
|
+
const config = categoryConfigs[category];
|
|
579
|
+
|
|
580
|
+
// Validate file size
|
|
581
|
+
if (file.size > config.maxSize) {
|
|
582
|
+
const maxSizeMB = Math.round(config.maxSize / (1024 * 1024));
|
|
583
|
+
errorHandler(new Error(`File size exceeds ${maxSizeMB}MB limit`));
|
|
584
|
+
return { cancel: async () => true };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Validate file type
|
|
588
|
+
const fileExt = '.' + file.name.split('.').pop()?.toLowerCase();
|
|
589
|
+
if (!config.accept.includes(fileExt)) {
|
|
590
|
+
errorHandler(new Error(`File type not supported. Allowed: ${config.accept.join(', ')}`));
|
|
591
|
+
return { cancel: async () => true };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Simulate categorized upload
|
|
595
|
+
let uploaded = 0;
|
|
596
|
+
const total = file.size;
|
|
597
|
+
|
|
598
|
+
const interval = setInterval(() => {
|
|
599
|
+
uploaded += Math.min(1024 * 100, total - uploaded);
|
|
600
|
+
progressUpdate(uploaded);
|
|
601
|
+
|
|
602
|
+
if (uploaded >= total) {
|
|
603
|
+
clearInterval(interval);
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
const mockUrl = `/files/${category}/${Date.now()}_${file.name}`;
|
|
606
|
+
onUploaded(mockUrl);
|
|
607
|
+
}, 300);
|
|
608
|
+
}
|
|
609
|
+
}, 100);
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
cancel: async () => {
|
|
613
|
+
clearInterval(interval);
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
};
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const confirmRemoval = async (file: AttachmentFile) => {
|
|
621
|
+
return confirm(`Remove "${file.name}" from ${categoryConfigs[activeCategory].title.toLowerCase()}?`);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const getTotalFiles = () => {
|
|
625
|
+
return Object.values(fileCategories).reduce((sum, files) => sum + files.length, 0);
|
|
626
|
+
};
|
|
627
|
+
</script>
|
|
628
|
+
|
|
629
|
+
<div class="multi-category-manager">
|
|
630
|
+
<div class="category-tabs">
|
|
631
|
+
{#each Object.keys(categoryConfigs) as category}
|
|
632
|
+
<button
|
|
633
|
+
class="tab"
|
|
634
|
+
class:active={activeCategory === category}
|
|
635
|
+
on:click={() => activeCategory = category}
|
|
636
|
+
>
|
|
637
|
+
{categoryConfigs[category].title}
|
|
638
|
+
({fileCategories[category].length})
|
|
639
|
+
</button>
|
|
640
|
+
{/each}
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<div class="category-content">
|
|
644
|
+
<div class="category-header">
|
|
645
|
+
<h3>{categoryConfigs[activeCategory].title}</h3>
|
|
646
|
+
<p class="category-description">{categoryConfigs[activeCategory].description}</p>
|
|
647
|
+
</div>
|
|
648
|
+
|
|
649
|
+
<AttachmentFilesField
|
|
650
|
+
bind:files={fileCategories[activeCategory]}
|
|
651
|
+
variant="outlined"
|
|
652
|
+
uploadFile={createUploadFunction(activeCategory)}
|
|
653
|
+
removeFileConfirm={confirmRemoval}
|
|
654
|
+
style="min-height: 150px;"
|
|
655
|
+
/>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
<div class="summary">
|
|
659
|
+
<p>Total files across all categories: {getTotalFiles()}</p>
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
|
|
663
|
+
<style>
|
|
664
|
+
.multi-category-manager {
|
|
665
|
+
max-width: 800px;
|
|
666
|
+
margin: 20px auto;
|
|
667
|
+
border: 1px solid #e0e0e0;
|
|
668
|
+
border-radius: 8px;
|
|
669
|
+
overflow: hidden;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.category-tabs {
|
|
673
|
+
display: flex;
|
|
674
|
+
background: #f5f5f5;
|
|
675
|
+
border-bottom: 1px solid #e0e0e0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.tab {
|
|
679
|
+
flex: 1;
|
|
680
|
+
padding: 15px 20px;
|
|
681
|
+
border: none;
|
|
682
|
+
background: transparent;
|
|
683
|
+
cursor: pointer;
|
|
684
|
+
transition: background-color 0.2s;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.tab:hover {
|
|
688
|
+
background: #e8e8e8;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.tab.active {
|
|
692
|
+
background: white;
|
|
693
|
+
border-bottom: 2px solid #1976d2;
|
|
694
|
+
color: #1976d2;
|
|
695
|
+
font-weight: bold;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.category-content {
|
|
699
|
+
padding: 20px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.category-header {
|
|
703
|
+
margin-bottom: 15px;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.category-header h3 {
|
|
707
|
+
margin: 0 0 5px 0;
|
|
708
|
+
color: #333;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.category-description {
|
|
712
|
+
margin: 0;
|
|
713
|
+
font-size: 0.9em;
|
|
714
|
+
color: #666;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.summary {
|
|
718
|
+
padding: 15px 20px;
|
|
719
|
+
background: #f9f9f9;
|
|
720
|
+
border-top: 1px solid #e0e0e0;
|
|
721
|
+
font-size: 0.9em;
|
|
722
|
+
color: #666;
|
|
723
|
+
}
|
|
724
|
+
</style>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
## Best Practices
|
|
728
|
+
|
|
729
|
+
1. **File Validation**: Always validate file size and type before upload
|
|
730
|
+
2. **Progress Feedback**: Provide clear progress indicators during upload
|
|
731
|
+
3. **Error Handling**: Implement comprehensive error handling for network issues
|
|
732
|
+
4. **Confirmation Dialogs**: Use confirmation dialogs for destructive actions
|
|
733
|
+
5. **Accessibility**: Ensure proper ARIA labels and keyboard navigation
|
|
734
|
+
6. **Performance**: Consider lazy loading for large file lists
|
|
735
|
+
7. **Security**: Validate files on the server side as well
|
|
736
|
+
8. **User Experience**: Provide clear feedback on file limitations and requirements
|
|
737
|
+
|
|
738
|
+
## Styling Notes
|
|
739
|
+
|
|
740
|
+
- The component supports standard variant styling (plain, outlined, filled)
|
|
741
|
+
- File icons are automatically selected based on file type
|
|
742
|
+
- Upload progress is visually indicated with progress bars
|
|
743
|
+
- Custom styles can be applied via the `style` prop
|
|
744
|
+
|
|
745
|
+
## Accessibility Considerations
|
|
746
|
+
|
|
747
|
+
- Upload button is keyboard accessible
|
|
748
|
+
- File removal buttons include proper ARIA labels
|
|
749
|
+
- Screen readers can navigate through file lists
|
|
750
|
+
- Progress updates are announced to assistive technologies
|
|
751
|
+
- Color contrast meets accessibility standards
|
|
752
|
+
|
|
753
|
+
## Browser Support
|
|
754
|
+
|
|
755
|
+
- Modern browsers with File API support
|
|
756
|
+
- Drag & drop requires HTML5 support
|
|
757
|
+
- Progress tracking works in browsers supporting XMLHttpRequest Level 2
|