@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.
Files changed (126) hide show
  1. package/README.md +273 -1
  2. package/README_CN.md +341 -0
  3. package/dist/common/MouseClickHandler.d.ts +1 -1
  4. package/dist/dialog/CommonDialog.svelte +6 -3
  5. package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
  6. package/dist/dialog/Dialog.svelte +36 -10
  7. package/dist/dialog/Dialog.svelte.d.ts +4 -3
  8. package/dist/dialog/DialogBoard.svelte +11 -8
  9. package/dist/dialog/DialogWrapper.svelte +10 -0
  10. package/dist/dialog/DialogWrapper.svelte.d.ts +22 -0
  11. package/dist/dialog/Dialogs.js +1 -1
  12. package/dist/dialog/ModalResult.d.ts +5 -0
  13. package/dist/dialog/ModalResult.js +5 -0
  14. package/docs/buttons/cn/ActionBar.md +459 -0
  15. package/docs/buttons/cn/Button.md +228 -0
  16. package/docs/buttons/cn/IconButton.md +294 -0
  17. package/docs/buttons/cn/README.md +600 -0
  18. package/docs/buttons/cn/TextButton.md +163 -0
  19. package/docs/buttons/en/ActionBar.md +459 -0
  20. package/docs/buttons/en/Button.md +228 -0
  21. package/docs/buttons/en/IconButton.md +294 -0
  22. package/docs/buttons/en/README.md +600 -0
  23. package/docs/buttons/en/TextButton.md +163 -0
  24. package/docs/containers/cn/accordion.md +622 -0
  25. package/docs/containers/cn/box.md +445 -0
  26. package/docs/containers/cn/card.md +404 -0
  27. package/docs/containers/cn/page.md +576 -0
  28. package/docs/containers/cn/tabs.md +686 -0
  29. package/docs/containers/en/accordion.md +622 -0
  30. package/docs/containers/en/box.md +445 -0
  31. package/docs/containers/en/card.md +404 -0
  32. package/docs/containers/en/page.md +576 -0
  33. package/docs/containers/en/tabs.md +686 -0
  34. package/docs/data-display/cn/concise-data-table.md +731 -0
  35. package/docs/data-display/cn/listbox.md +786 -0
  36. package/docs/data-display/cn/treeview.md +863 -0
  37. package/docs/data-display/en/concise-data-table.md +732 -0
  38. package/docs/data-display/en/listbox.md +786 -0
  39. package/docs/data-display/en/treeview.md +863 -0
  40. package/docs/data-table/cn/README.md +596 -0
  41. package/docs/data-table/en/README.md +596 -0
  42. package/docs/dialog/cn/README.md +767 -0
  43. package/docs/dialog/en/README.md +767 -0
  44. package/docs/form/cn/README.md +847 -0
  45. package/docs/form/en/README.md +847 -0
  46. package/docs/form-components/cn/README.md +495 -0
  47. package/docs/form-components/cn/attachmentfilesfield.md +757 -0
  48. package/docs/form-components/cn/cascade-options-select.md +374 -0
  49. package/docs/form-components/cn/checkbox.md +58 -0
  50. package/docs/form-components/cn/date-picker.md +138 -0
  51. package/docs/form-components/cn/datetime-picker.md +228 -0
  52. package/docs/form-components/cn/group-checkbox.md +405 -0
  53. package/docs/form-components/cn/group-radiobox.md +525 -0
  54. package/docs/form-components/cn/imagefilesfield.md +651 -0
  55. package/docs/form-components/cn/input-options-select.md +250 -0
  56. package/docs/form-components/cn/lookup-editor.md +406 -0
  57. package/docs/form-components/cn/memoeditor.md +409 -0
  58. package/docs/form-components/cn/number-editor.md +261 -0
  59. package/docs/form-components/cn/options-multi-select.md +177 -0
  60. package/docs/form-components/cn/options-select.md +83 -0
  61. package/docs/form-components/cn/passwordeditor.md +538 -0
  62. package/docs/form-components/cn/prompts-text-editor.md +241 -0
  63. package/docs/form-components/cn/radiobutton.md +84 -0
  64. package/docs/form-components/cn/switch.md +178 -0
  65. package/docs/form-components/cn/text-editor.md +142 -0
  66. package/docs/form-components/cn/time-editor.md +159 -0
  67. package/docs/form-components/cn/unit-number-editor.md +164 -0
  68. package/docs/form-components/en/README.md +463 -0
  69. package/docs/form-components/en/attachmentfilesfield.md +757 -0
  70. package/docs/form-components/en/cascade-options-select.md +358 -0
  71. package/docs/form-components/en/checkbox.md +59 -0
  72. package/docs/form-components/en/date-picker.md +138 -0
  73. package/docs/form-components/en/datetime-picker.md +228 -0
  74. package/docs/form-components/en/group-checkbox.md +395 -0
  75. package/docs/form-components/en/group-radiobox.md +498 -0
  76. package/docs/form-components/en/imagefilesfield.md +651 -0
  77. package/docs/form-components/en/input-options-select.md +239 -0
  78. package/docs/form-components/en/lookup-editor.md +389 -0
  79. package/docs/form-components/en/memoeditor.md +409 -0
  80. package/docs/form-components/en/number-editor.md +249 -0
  81. package/docs/form-components/en/options-multi-select.md +177 -0
  82. package/docs/form-components/en/options-select.md +83 -0
  83. package/docs/form-components/en/passwordeditor.md +538 -0
  84. package/docs/form-components/en/prompts-text-editor.md +241 -0
  85. package/docs/form-components/en/radiobutton.md +84 -0
  86. package/docs/form-components/en/switch.md +180 -0
  87. package/docs/form-components/en/text-editor.md +142 -0
  88. package/docs/form-components/en/time-editor.md +159 -0
  89. package/docs/form-components/en/unit-number-editor.md +164 -0
  90. package/docs/form-layout/cn/criteria-field.md +652 -0
  91. package/docs/form-layout/cn/flex-form.md +454 -0
  92. package/docs/form-layout/cn/flex-row-form.md +542 -0
  93. package/docs/form-layout/cn/form-field.md +923 -0
  94. package/docs/form-layout/cn/grid-form.md +676 -0
  95. package/docs/form-layout/en/criteria-field.md +652 -0
  96. package/docs/form-layout/en/flex-form.md +454 -0
  97. package/docs/form-layout/en/flex-row-form.md +542 -0
  98. package/docs/form-layout/en/form-field.md +923 -0
  99. package/docs/form-layout/en/grid-form.md +676 -0
  100. package/docs/global/cn/README.md +534 -0
  101. package/docs/global/en/README.md +534 -0
  102. package/docs/layout/cn/classiclayout.md +460 -0
  103. package/docs/layout/cn/headerlayout.md +524 -0
  104. package/docs/layout/cn/sidebarlayout.md +263 -0
  105. package/docs/layout/en/classiclayout.md +951 -0
  106. package/docs/layout/en/headerlayout.md +524 -0
  107. package/docs/layout/en/sidebarlayout.md +262 -0
  108. package/docs/misc/cn/README.md +1217 -0
  109. package/docs/misc/en/README.md +1217 -0
  110. package/docs/navigation/cn/breadcrumb.md +291 -0
  111. package/docs/navigation/cn/navigator.md +322 -0
  112. package/docs/navigation/cn/navigatormenu.md +512 -0
  113. package/docs/navigation/cn/progressbar.md +199 -0
  114. package/docs/navigation/cn/progressstepbar.md +299 -0
  115. package/docs/navigation/en/breadcrumb.md +291 -0
  116. package/docs/navigation/en/navigator.md +322 -0
  117. package/docs/navigation/en/navigatormenu.md +512 -0
  118. package/docs/navigation/en/progressbar.md +199 -0
  119. package/docs/navigation/en/progressstepbar.md +299 -0
  120. package/docs/search-filters/cn/daterange.md +1156 -0
  121. package/docs/search-filters/cn/numberrange.md +688 -0
  122. package/docs/search-filters/cn/searchbox.md +1582 -0
  123. package/docs/search-filters/en/daterange.md +1156 -0
  124. package/docs/search-filters/en/numberrange.md +688 -0
  125. package/docs/search-filters/en/searchbox.md +1582 -0
  126. 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