@ticatec/uniface-element 0.2.0 → 0.2.2

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 (209) hide show
  1. package/README.md +320 -1
  2. package/README_CN.md +392 -0
  3. package/dist/accordion/Accordion.svelte +1 -1
  4. package/dist/action-bar/ActionBar.svelte +7 -1
  5. package/dist/attachment-files/FileRender.svelte +1 -1
  6. package/dist/attachment-files/FileUploadBar.svelte +7 -6
  7. package/dist/attachment-files/FileUploadPanel.svelte +2 -2
  8. package/dist/base-calendar/DateContext.js +5 -4
  9. package/dist/base-calendar/YearPickupPanel.svelte +4 -4
  10. package/dist/card/Card.svelte +1 -1
  11. package/dist/card/Card.svelte.d.ts +1 -1
  12. package/dist/cascade-options-select/CascadeOptionSelect.svelte +2 -3
  13. package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +46 -1
  14. package/dist/cascade-options-select/CascadePanel.svelte +1 -1
  15. package/dist/checkbox/CheckBox.svelte +1 -1
  16. package/dist/color-picker/ColorPicker.svelte +9 -9
  17. package/dist/color-picker/ColorPicker.svelte.d.ts +0 -2
  18. package/dist/color-picker/ColorPickerPanel.svelte +11 -15
  19. package/dist/color-picker/ColorPickerPanel.svelte.d.ts +1 -1
  20. package/dist/common/CommonPicker.svelte +2 -2
  21. package/dist/common/CommonPicker.svelte.d.ts +1 -1
  22. package/dist/common/MouseClickHandler.d.ts +1 -1
  23. package/dist/common/Popover.svelte +28 -6
  24. package/dist/common/Popover.svelte.d.ts +9 -3
  25. package/dist/common/uniface-utils.js +6 -4
  26. package/dist/common-editor/CommonEditor.svelte +1 -1
  27. package/dist/common-editor/CommonEditor.svelte.d.ts +1 -1
  28. package/dist/common-editor/NumberInput.svelte +1 -1
  29. package/dist/common-editor/NumberInput.svelte.d.ts +1 -1
  30. package/dist/concise-data-table/ConciseListTable.svelte +7 -2
  31. package/dist/concise-data-table/ConciseListTable.svelte.d.ts +1 -0
  32. package/dist/concise-data-table/DataRow.svelte +1 -1
  33. package/dist/concise-data-table/TableOptions.d.ts +1 -1
  34. package/dist/data-table/DataTable.svelte +1 -1
  35. package/dist/data-table/parts/ActionsPanel.svelte +4 -3
  36. package/dist/data-table/parts/ActionsRow.svelte +3 -3
  37. package/dist/data-table/parts/ContentPanel.svelte +3 -2
  38. package/dist/data-table/parts/FixedColumnsPanel.svelte +0 -1
  39. package/dist/data-table/parts/FixedColumnsPanel.svelte.d.ts +1 -2
  40. package/dist/data-table/parts/FixedHeaderPanel.svelte +2 -2
  41. package/dist/data-table/parts/FixedRow.svelte +2 -1
  42. package/dist/data-table/parts/FixedRow.svelte.d.ts +1 -1
  43. package/dist/data-table/parts/HrefCell.svelte +23 -2
  44. package/dist/data-table/parts/PopupMenu.svelte +25 -2
  45. package/dist/data-table/parts/PopupMenu.svelte.d.ts +8 -1
  46. package/dist/date-picker/DatePicker.svelte +0 -1
  47. package/dist/date-picker/DateTimePicker.svelte +3 -3
  48. package/dist/date-picker/ScrollBar.svelte +4 -7
  49. package/dist/date-range/DateRangeEditor.svelte +1 -1
  50. package/dist/dialog/CommonDialog.svelte +8 -5
  51. package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
  52. package/dist/dialog/Dialog.svelte +37 -12
  53. package/dist/dialog/Dialog.svelte.d.ts +4 -3
  54. package/dist/dialog/DialogBoard.svelte +11 -8
  55. package/dist/dialog/DialogWrapper.svelte +10 -0
  56. package/dist/{common-editor/DateInput.svelte.d.ts → dialog/DialogWrapper.svelte.d.ts} +6 -13
  57. package/dist/dialog/Dialogs.js +1 -1
  58. package/dist/dialog/ModalResult.d.ts +5 -0
  59. package/dist/dialog/ModalResult.js +5 -0
  60. package/dist/drawer/Drawer.svelte +1 -1
  61. package/dist/form-field/FormField.svelte +2 -1
  62. package/dist/form-field/FormField.svelte.d.ts +1 -0
  63. package/dist/form-panel/flex-row-form/FormContainer.svelte +1 -1
  64. package/dist/form-panel/grid-form/EmptyCell.svelte +8 -7
  65. package/dist/form-panel/grid-form/EmptyCell.svelte.d.ts +2 -2
  66. package/dist/form-panel/grid-form/GridField.svelte +1 -1
  67. package/dist/i18nRes/i18nRes.d.ts +2 -0
  68. package/dist/i18nRes/i18nRes.js +44 -0
  69. package/dist/i18nRes/index.d.ts +2 -0
  70. package/dist/i18nRes/index.js +2 -0
  71. package/dist/image-files/ImageFilesField.svelte +1 -1
  72. package/dist/indicator/IndicatorBoard.svelte +1 -1
  73. package/dist/inline-cell-editor/InvalidEditor.svelte +1 -0
  74. package/dist/input-options-select/InputOptionsSelect.svelte +6 -6
  75. package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +1 -1
  76. package/dist/layout/flex/FlexBlock.svelte +1 -1
  77. package/dist/lookup-editor/LookupEditor.svelte +1 -1
  78. package/dist/memo-editor/MemoEditor.svelte +2 -2
  79. package/dist/message-box/IMessageBox.d.ts +2 -4
  80. package/dist/message-box/IMessageBox.js +4 -6
  81. package/dist/message-box/MessageBoxBoard.svelte +1 -3
  82. package/dist/options-multi-select/OptionsMultiSelect.svelte +1 -1
  83. package/dist/prompts-text-editor/PromptsTextEditor.svelte +2 -2
  84. package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +1 -1
  85. package/dist/property-editor/PropertyEditor.svelte +3 -3
  86. package/dist/text-editor/PasswordEditor.svelte +1 -1
  87. package/dist/transfer/Transfer.svelte +17 -12
  88. package/dist/transfer/Transfer.svelte.d.ts +0 -1
  89. package/dist/unit-number-editor/UnitNumberEditor.svelte +2 -2
  90. package/docs/buttons/cn/ActionBar.md +459 -0
  91. package/docs/buttons/cn/Button.md +228 -0
  92. package/docs/buttons/cn/IconButton.md +294 -0
  93. package/docs/buttons/cn/README.md +600 -0
  94. package/docs/buttons/cn/TextButton.md +163 -0
  95. package/docs/buttons/en/ActionBar.md +459 -0
  96. package/docs/buttons/en/Button.md +228 -0
  97. package/docs/buttons/en/IconButton.md +294 -0
  98. package/docs/buttons/en/README.md +600 -0
  99. package/docs/buttons/en/TextButton.md +163 -0
  100. package/docs/containers/cn/accordion.md +622 -0
  101. package/docs/containers/cn/box.md +445 -0
  102. package/docs/containers/cn/card.md +404 -0
  103. package/docs/containers/cn/page.md +576 -0
  104. package/docs/containers/cn/tabs.md +686 -0
  105. package/docs/containers/en/accordion.md +622 -0
  106. package/docs/containers/en/box.md +445 -0
  107. package/docs/containers/en/card.md +404 -0
  108. package/docs/containers/en/page.md +576 -0
  109. package/docs/containers/en/tabs.md +686 -0
  110. package/docs/data-display/cn/concise-data-table.md +731 -0
  111. package/docs/data-display/cn/listbox.md +786 -0
  112. package/docs/data-display/cn/treeview.md +863 -0
  113. package/docs/data-display/en/concise-data-table.md +732 -0
  114. package/docs/data-display/en/listbox.md +786 -0
  115. package/docs/data-display/en/treeview.md +863 -0
  116. package/docs/data-table/cn/README.md +596 -0
  117. package/docs/data-table/en/README.md +596 -0
  118. package/docs/dialog/cn/README.md +767 -0
  119. package/docs/dialog/en/README.md +767 -0
  120. package/docs/form/cn/README.md +847 -0
  121. package/docs/form/en/README.md +847 -0
  122. package/docs/form-components/cn/README.md +495 -0
  123. package/docs/form-components/cn/attachmentfilesfield.md +757 -0
  124. package/docs/form-components/cn/cascade-options-select.md +374 -0
  125. package/docs/form-components/cn/checkbox.md +58 -0
  126. package/docs/form-components/cn/date-picker.md +138 -0
  127. package/docs/form-components/cn/datetime-picker.md +228 -0
  128. package/docs/form-components/cn/group-checkbox.md +405 -0
  129. package/docs/form-components/cn/group-radiobox.md +525 -0
  130. package/docs/form-components/cn/imagefilesfield.md +651 -0
  131. package/docs/form-components/cn/input-options-select.md +250 -0
  132. package/docs/form-components/cn/lookup-editor.md +406 -0
  133. package/docs/form-components/cn/memoeditor.md +409 -0
  134. package/docs/form-components/cn/number-editor.md +261 -0
  135. package/docs/form-components/cn/options-multi-select.md +177 -0
  136. package/docs/form-components/cn/options-select.md +83 -0
  137. package/docs/form-components/cn/passwordeditor.md +538 -0
  138. package/docs/form-components/cn/prompts-text-editor.md +241 -0
  139. package/docs/form-components/cn/radiobutton.md +84 -0
  140. package/docs/form-components/cn/switch.md +178 -0
  141. package/docs/form-components/cn/text-editor.md +142 -0
  142. package/docs/form-components/cn/time-editor.md +159 -0
  143. package/docs/form-components/cn/unit-number-editor.md +164 -0
  144. package/docs/form-components/en/README.md +463 -0
  145. package/docs/form-components/en/attachmentfilesfield.md +757 -0
  146. package/docs/form-components/en/cascade-options-select.md +358 -0
  147. package/docs/form-components/en/checkbox.md +59 -0
  148. package/docs/form-components/en/date-picker.md +138 -0
  149. package/docs/form-components/en/datetime-picker.md +228 -0
  150. package/docs/form-components/en/group-checkbox.md +395 -0
  151. package/docs/form-components/en/group-radiobox.md +498 -0
  152. package/docs/form-components/en/imagefilesfield.md +651 -0
  153. package/docs/form-components/en/input-options-select.md +239 -0
  154. package/docs/form-components/en/lookup-editor.md +389 -0
  155. package/docs/form-components/en/memoeditor.md +409 -0
  156. package/docs/form-components/en/number-editor.md +249 -0
  157. package/docs/form-components/en/options-multi-select.md +177 -0
  158. package/docs/form-components/en/options-select.md +83 -0
  159. package/docs/form-components/en/passwordeditor.md +538 -0
  160. package/docs/form-components/en/prompts-text-editor.md +241 -0
  161. package/docs/form-components/en/radiobutton.md +84 -0
  162. package/docs/form-components/en/switch.md +180 -0
  163. package/docs/form-components/en/text-editor.md +142 -0
  164. package/docs/form-components/en/time-editor.md +159 -0
  165. package/docs/form-components/en/unit-number-editor.md +164 -0
  166. package/docs/form-layout/cn/criteria-field.md +652 -0
  167. package/docs/form-layout/cn/flex-form.md +454 -0
  168. package/docs/form-layout/cn/flex-row-form.md +542 -0
  169. package/docs/form-layout/cn/form-field.md +923 -0
  170. package/docs/form-layout/cn/grid-form.md +676 -0
  171. package/docs/form-layout/en/criteria-field.md +652 -0
  172. package/docs/form-layout/en/flex-form.md +454 -0
  173. package/docs/form-layout/en/flex-row-form.md +542 -0
  174. package/docs/form-layout/en/form-field.md +923 -0
  175. package/docs/form-layout/en/grid-form.md +676 -0
  176. package/docs/global/cn/README.md +534 -0
  177. package/docs/global/en/README.md +534 -0
  178. package/docs/layout/cn/classiclayout.md +460 -0
  179. package/docs/layout/cn/headerlayout.md +524 -0
  180. package/docs/layout/cn/sidebarlayout.md +263 -0
  181. package/docs/layout/en/classiclayout.md +951 -0
  182. package/docs/layout/en/headerlayout.md +524 -0
  183. package/docs/layout/en/sidebarlayout.md +262 -0
  184. package/docs/misc/cn/README.md +1217 -0
  185. package/docs/misc/en/README.md +1217 -0
  186. package/docs/navigation/cn/breadcrumb.md +291 -0
  187. package/docs/navigation/cn/navigator.md +322 -0
  188. package/docs/navigation/cn/navigatormenu.md +512 -0
  189. package/docs/navigation/cn/progressbar.md +199 -0
  190. package/docs/navigation/cn/progressstepbar.md +299 -0
  191. package/docs/navigation/en/breadcrumb.md +291 -0
  192. package/docs/navigation/en/navigator.md +322 -0
  193. package/docs/navigation/en/navigatormenu.md +512 -0
  194. package/docs/navigation/en/progressbar.md +199 -0
  195. package/docs/navigation/en/progressstepbar.md +299 -0
  196. package/docs/search-filters/cn/daterange.md +1156 -0
  197. package/docs/search-filters/cn/numberrange.md +688 -0
  198. package/docs/search-filters/cn/searchbox.md +1582 -0
  199. package/docs/search-filters/en/daterange.md +1156 -0
  200. package/docs/search-filters/en/numberrange.md +688 -0
  201. package/docs/search-filters/en/searchbox.md +1582 -0
  202. package/package.json +53 -13
  203. package/dist/common-editor/DateInput.svelte +0 -27
  204. package/dist/i18n_resources/index.d.ts +0 -3
  205. package/dist/i18n_resources/index.js +0 -3
  206. /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.d.ts +0 -0
  207. /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.js +0 -0
  208. /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.d.ts +0 -0
  209. /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.js +0 -0
@@ -0,0 +1,651 @@
1
+ # ImageFilesField Component
2
+
3
+ The ImageFilesField component provides a specialized image upload and management interface with thumbnail previews, image preview functionality, and support for multiple image formats.
4
+
5
+ ## Features
6
+
7
+ - **Image-Specific Upload**: Optimized for image file handling with automatic thumbnail generation
8
+ - **Image Preview**: Full-size image preview in modal dialog
9
+ - **Thumbnail Display**: Automatic thumbnail generation and display
10
+ - **Multiple Image Support**: Configurable maximum number of images
11
+ - **Image Format Support**: Support for common image formats (JPG, PNG, GIF, WebP, etc.)
12
+ - **Drag & Drop**: Intuitive drag-and-drop image upload
13
+ - **Progress Tracking**: Real-time upload progress with cancel capability
14
+ - **Display Modes**: Edit and view modes for different contexts
15
+ - **Variant Support**: Multiple styling variants (plain, outlined, filled)
16
+ - **Responsive Layout**: Adapts to different screen sizes
17
+
18
+ ## Import
19
+
20
+ ```typescript
21
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
22
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
23
+ ```
24
+
25
+ ## Basic Usage
26
+
27
+ ### Simple Image Upload Field
28
+
29
+ ```svelte
30
+ <script>
31
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
32
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
33
+
34
+ let images: ImageFile[] = [];
35
+
36
+ const uploadImage: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
37
+ // Validate that it's an image file
38
+ if (!file.type.startsWith('image/')) {
39
+ errorHandler(new Error('Please select an image file'));
40
+ return { cancel: async () => true };
41
+ }
42
+
43
+ // Simulate image upload with thumbnail generation
44
+ let uploadedBytes = 0;
45
+ const totalBytes = file.size;
46
+
47
+ const uploadInterval = setInterval(() => {
48
+ uploadedBytes += Math.min(1024 * 100, totalBytes - uploadedBytes);
49
+ progressUpdate(uploadedBytes);
50
+
51
+ if (uploadedBytes >= totalBytes) {
52
+ clearInterval(uploadInterval);
53
+
54
+ // Create thumbnail
55
+ const canvas = document.createElement('canvas');
56
+ const ctx = canvas.getContext('2d');
57
+ const img = new Image();
58
+
59
+ img.onload = () => {
60
+ canvas.width = 150;
61
+ canvas.height = 150;
62
+ ctx?.drawImage(img, 0, 0, 150, 150);
63
+
64
+ const thumbnailUrl = canvas.toDataURL();
65
+ const fullUrl = URL.createObjectURL(file);
66
+
67
+ onUploaded(fullUrl, thumbnailUrl);
68
+ };
69
+
70
+ img.src = URL.createObjectURL(file);
71
+ }
72
+ }, 100);
73
+
74
+ return {
75
+ cancel: async () => {
76
+ clearInterval(uploadInterval);
77
+ return true;
78
+ }
79
+ };
80
+ };
81
+ </script>
82
+
83
+ <ImageFilesField
84
+ bind:files={images}
85
+ {uploadImage}
86
+ maxFiles={5}
87
+ />
88
+ ```
89
+
90
+ ### Avatar Upload with Single Image
91
+
92
+ ```svelte
93
+ <script>
94
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
95
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
96
+
97
+ let avatar: ImageFile[] = [];
98
+
99
+ const uploadAvatar: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
100
+ // Validate image dimensions and size
101
+ const img = new Image();
102
+ img.onload = () => {
103
+ if (img.width < 100 || img.height < 100) {
104
+ errorHandler(new Error('Image must be at least 100x100 pixels'));
105
+ return;
106
+ }
107
+
108
+ if (file.size > 2 * 1024 * 1024) {
109
+ errorHandler(new Error('Image size must be less than 2MB'));
110
+ return;
111
+ }
112
+
113
+ // Create square thumbnail
114
+ const canvas = document.createElement('canvas');
115
+ const ctx = canvas.getContext('2d');
116
+ const size = Math.min(img.width, img.height);
117
+
118
+ canvas.width = 100;
119
+ canvas.height = 100;
120
+
121
+ const sx = (img.width - size) / 2;
122
+ const sy = (img.height - size) / 2;
123
+
124
+ ctx?.drawImage(img, sx, sy, size, size, 0, 0, 100, 100);
125
+
126
+ const thumbnailUrl = canvas.toDataURL();
127
+ const fullUrl = URL.createObjectURL(file);
128
+
129
+ onUploaded(fullUrl, thumbnailUrl);
130
+ };
131
+
132
+ img.src = URL.createObjectURL(file);
133
+
134
+ return {
135
+ cancel: async () => true
136
+ };
137
+ };
138
+
139
+ const confirmRemoval = async (file: ImageFile) => {
140
+ return confirm('Remove avatar image?');
141
+ };
142
+ </script>
143
+
144
+ <div class="avatar-upload">
145
+ <label>Profile Picture</label>
146
+ <ImageFilesField
147
+ bind:files={avatar}
148
+ variant="outlined"
149
+ uploadFile={uploadAvatar}
150
+ removeFileConfirm={confirmRemoval}
151
+ maxFiles={1}
152
+ style="margin-top: 8px;"
153
+ />
154
+ </div>
155
+ ```
156
+
157
+ ## API Reference
158
+
159
+ ### Props
160
+
161
+ | Property | Type | Default | Description |
162
+ |----------|------|---------|-------------|
163
+ | `files` | `Array<ImageFile>` | `[]` | Array of image files |
164
+ | `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | Visual variant |
165
+ | `disabled` | `boolean` | `false` | Whether the field is disabled |
166
+ | `readonly` | `boolean` | `false` | Whether the field is read-only |
167
+ | `style` | `string` | `''` | Additional CSS styles |
168
+ | `displayMode` | `DisplayMode` | `DisplayMode.Edit` | Display mode (Edit/View) |
169
+ | `removeFileConfirm` | `RemoveConfirm` | `null` | Optional confirmation function for file removal |
170
+ | `uploadFile` | `FileUpload` | required | Function to handle file uploads |
171
+ | `maxFiles` | `number` | `1` | Maximum number of images allowed |
172
+ | `text` | `string \| null` | `null` | Display text (auto-generated from file names) |
173
+
174
+ ### Types
175
+
176
+ #### ImageFile Interface
177
+
178
+ ```typescript
179
+ interface ImageFile {
180
+ name: string; // Image file name
181
+ uri: string; // Full-size image URL or path
182
+ thumbnail?: string; // Thumbnail URL (optional)
183
+ }
184
+ ```
185
+
186
+ #### FileUpload Function Type
187
+
188
+ ```typescript
189
+ type FileUpload = (
190
+ file: File,
191
+ progressUpdate: ProgressUpdate,
192
+ onUploaded: OnUploaded,
193
+ errorHandler: ErrorHandler
194
+ ) => UploadProgress;
195
+
196
+ type ProgressUpdate = (uploadBytes: number) => void;
197
+ type OnUploaded = (url: string, thumbnail?: string) => void;
198
+ type ErrorHandler = (e: Error) => void;
199
+
200
+ interface UploadProgress {
201
+ cancel: () => Promise<boolean>;
202
+ }
203
+ ```
204
+
205
+ #### RemoveConfirm Function Type
206
+
207
+ ```typescript
208
+ type RemoveConfirm = (file: ImageFile) => Promise<boolean>;
209
+ ```
210
+
211
+ ## Advanced Examples
212
+
213
+ ### Photo Gallery Upload
214
+
215
+ ```svelte
216
+ <script>
217
+ import ImageFilesField, { DisplayMode } from '@ticatec/uniface-element/ImageFilesField';
218
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
219
+
220
+ let galleryImages: ImageFile[] = [];
221
+ let isEditMode = true;
222
+
223
+ const uploadToGallery: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
224
+ // Validate image file
225
+ if (!file.type.startsWith('image/')) {
226
+ errorHandler(new Error('Only image files are allowed'));
227
+ return { cancel: async () => true };
228
+ }
229
+
230
+ // Check file size (max 10MB)
231
+ if (file.size > 10 * 1024 * 1024) {
232
+ errorHandler(new Error('Image size cannot exceed 10MB'));
233
+ return { cancel: async () => true };
234
+ }
235
+
236
+ const img = new Image();
237
+ img.onload = async () => {
238
+ try {
239
+ // Create optimized thumbnail
240
+ const thumbnail = await createThumbnail(img, 200, 200);
241
+
242
+ // Simulate API upload
243
+ const formData = new FormData();
244
+ formData.append('image', file);
245
+ formData.append('gallery', 'main');
246
+
247
+ let uploadedBytes = 0;
248
+ const totalBytes = file.size;
249
+
250
+ const simulateUpload = setInterval(() => {
251
+ uploadedBytes += Math.min(1024 * 200, totalBytes - uploadedBytes);
252
+ progressUpdate(uploadedBytes);
253
+
254
+ if (uploadedBytes >= totalBytes) {
255
+ clearInterval(simulateUpload);
256
+ const fullUrl = URL.createObjectURL(file);
257
+ onUploaded(fullUrl, thumbnail);
258
+ }
259
+ }, 100);
260
+
261
+ } catch (error) {
262
+ errorHandler(new Error('Failed to process image'));
263
+ }
264
+ };
265
+
266
+ img.onerror = () => {
267
+ errorHandler(new Error('Invalid image file'));
268
+ };
269
+
270
+ img.src = URL.createObjectURL(file);
271
+
272
+ return {
273
+ cancel: async () => true
274
+ };
275
+ };
276
+
277
+ const createThumbnail = (img: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<string> => {
278
+ return new Promise((resolve) => {
279
+ const canvas = document.createElement('canvas');
280
+ const ctx = canvas.getContext('2d');
281
+
282
+ // Calculate dimensions maintaining aspect ratio
283
+ let { width, height } = img;
284
+
285
+ if (width > height) {
286
+ if (width > maxWidth) {
287
+ height = (height * maxWidth) / width;
288
+ width = maxWidth;
289
+ }
290
+ } else {
291
+ if (height > maxHeight) {
292
+ width = (width * maxHeight) / height;
293
+ height = maxHeight;
294
+ }
295
+ }
296
+
297
+ canvas.width = width;
298
+ canvas.height = height;
299
+
300
+ ctx?.drawImage(img, 0, 0, width, height);
301
+ resolve(canvas.toDataURL('image/jpeg', 0.8));
302
+ });
303
+ };
304
+
305
+ const confirmImageRemoval = async (image: ImageFile) => {
306
+ const result = await showConfirmDialog({
307
+ title: 'Remove Image',
308
+ message: `Remove "${image.name}" from gallery?`,
309
+ confirmText: 'Remove',
310
+ cancelText: 'Cancel'
311
+ });
312
+
313
+ return result;
314
+ };
315
+
316
+ const showConfirmDialog = (options: any) => {
317
+ return new Promise<boolean>((resolve) => {
318
+ // Custom confirmation dialog implementation
319
+ const confirmed = confirm(`${options.title}: ${options.message}`);
320
+ resolve(confirmed);
321
+ });
322
+ };
323
+ </script>
324
+
325
+ <div class="photo-gallery">
326
+ <div class="gallery-header">
327
+ <h3>Photo Gallery</h3>
328
+ <div class="gallery-actions">
329
+ <button on:click={() => isEditMode = !isEditMode}>
330
+ {isEditMode ? 'View Mode' : 'Edit Mode'}
331
+ </button>
332
+ </div>
333
+ </div>
334
+
335
+ <ImageFilesField
336
+ bind:files={galleryImages}
337
+ variant="outlined"
338
+ displayMode={isEditMode ? DisplayMode.Edit : DisplayMode.View}
339
+ uploadFile={uploadToGallery}
340
+ removeFileConfirm={confirmImageRemoval}
341
+ maxFiles={20}
342
+ style="min-height: 200px; border-radius: 8px;"
343
+ />
344
+
345
+ <div class="gallery-stats">
346
+ <p>Images: {galleryImages.length} / 20</p>
347
+ <p>Formats supported: JPG, PNG, GIF, WebP</p>
348
+ </div>
349
+ </div>
350
+
351
+ <style>
352
+ .photo-gallery {
353
+ max-width: 800px;
354
+ margin: 20px auto;
355
+ padding: 20px;
356
+ border: 1px solid #e0e0e0;
357
+ border-radius: 12px;
358
+ background: #fafafa;
359
+ }
360
+
361
+ .gallery-header {
362
+ display: flex;
363
+ justify-content: space-between;
364
+ align-items: center;
365
+ margin-bottom: 20px;
366
+ }
367
+
368
+ .gallery-stats {
369
+ margin-top: 15px;
370
+ padding: 10px;
371
+ background: white;
372
+ border-radius: 4px;
373
+ font-size: 0.9em;
374
+ color: #666;
375
+ }
376
+
377
+ .gallery-stats p {
378
+ margin: 2px 0;
379
+ }
380
+ </style>
381
+ ```
382
+
383
+ ### Product Image Management
384
+
385
+ ```svelte
386
+ <script>
387
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
388
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
389
+
390
+ let productImages: ImageFile[] = [];
391
+ let uploadErrors: string[] = [];
392
+
393
+ const uploadProductImage: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
394
+ // Clear previous errors
395
+ uploadErrors = [];
396
+
397
+ // Validate image requirements for product
398
+ if (!file.type.startsWith('image/')) {
399
+ const error = 'Only image files are allowed for products';
400
+ uploadErrors = [...uploadErrors, error];
401
+ errorHandler(new Error(error));
402
+ return { cancel: async () => true };
403
+ }
404
+
405
+ // Check file size
406
+ if (file.size > 5 * 1024 * 1024) {
407
+ const error = 'Product images must be smaller than 5MB';
408
+ uploadErrors = [...uploadErrors, error];
409
+ errorHandler(new Error(error));
410
+ return { cancel: async () => true };
411
+ }
412
+
413
+ const img = new Image();
414
+ img.onload = async () => {
415
+ // Validate dimensions for product images
416
+ if (img.width < 500 || img.height < 500) {
417
+ const error = 'Product images must be at least 500x500 pixels';
418
+ uploadErrors = [...uploadErrors, error];
419
+ errorHandler(new Error(error));
420
+ return;
421
+ }
422
+
423
+ try {
424
+ // Create multiple thumbnail sizes for product
425
+ const thumbnailSmall = await createProductThumbnail(img, 150, 150);
426
+ const thumbnailMedium = await createProductThumbnail(img, 300, 300);
427
+
428
+ // Simulate upload with progress
429
+ let uploaded = 0;
430
+ const total = file.size;
431
+
432
+ const uploadInterval = setInterval(() => {
433
+ uploaded += Math.min(1024 * 150, total - uploaded);
434
+ progressUpdate(uploaded);
435
+
436
+ if (uploaded >= total) {
437
+ clearInterval(uploadInterval);
438
+
439
+ // Return full URL and thumbnail
440
+ const fullUrl = URL.createObjectURL(file);
441
+ onUploaded(fullUrl, thumbnailSmall);
442
+ }
443
+ }, 80);
444
+
445
+ } catch (error) {
446
+ const errorMsg = 'Failed to process product image';
447
+ uploadErrors = [...uploadErrors, errorMsg];
448
+ errorHandler(new Error(errorMsg));
449
+ }
450
+ };
451
+
452
+ img.onerror = () => {
453
+ const error = 'Invalid or corrupted image file';
454
+ uploadErrors = [...uploadErrors, error];
455
+ errorHandler(new Error(error));
456
+ };
457
+
458
+ img.src = URL.createObjectURL(file);
459
+
460
+ return {
461
+ cancel: async () => true
462
+ };
463
+ };
464
+
465
+ const createProductThumbnail = (img: HTMLImageElement, width: number, height: number): Promise<string> => {
466
+ return new Promise((resolve) => {
467
+ const canvas = document.createElement('canvas');
468
+ const ctx = canvas.getContext('2d');
469
+
470
+ canvas.width = width;
471
+ canvas.height = height;
472
+
473
+ // Center and crop the image to fill the square
474
+ const size = Math.min(img.width, img.height);
475
+ const sx = (img.width - size) / 2;
476
+ const sy = (img.height - size) / 2;
477
+
478
+ ctx?.drawImage(img, sx, sy, size, size, 0, 0, width, height);
479
+ resolve(canvas.toDataURL('image/jpeg', 0.9));
480
+ });
481
+ };
482
+
483
+ const confirmProductImageRemoval = async (image: ImageFile) => {
484
+ return confirm(`Remove product image "${image.name}"? This will affect product display.`);
485
+ };
486
+
487
+ const clearErrors = () => {
488
+ uploadErrors = [];
489
+ };
490
+ </script>
491
+
492
+ <div class="product-image-manager">
493
+ <div class="manager-header">
494
+ <h3>Product Images</h3>
495
+ <div class="requirements">
496
+ <h4>Image Requirements:</h4>
497
+ <ul>
498
+ <li>Minimum 500×500 pixels</li>
499
+ <li>Maximum 5MB file size</li>
500
+ <li>Formats: JPG, PNG, WebP</li>
501
+ <li>Up to 8 images per product</li>
502
+ </ul>
503
+ </div>
504
+ </div>
505
+
506
+ {#if uploadErrors.length > 0}
507
+ <div class="error-panel">
508
+ <h4>Upload Errors:</h4>
509
+ <ul>
510
+ {#each uploadErrors as error}
511
+ <li>{error}</li>
512
+ {/each}
513
+ </ul>
514
+ <button on:click={clearErrors}>Clear Errors</button>
515
+ </div>
516
+ {/if}
517
+
518
+ <ImageFilesField
519
+ bind:files={productImages}
520
+ variant="outlined"
521
+ uploadFile={uploadProductImage}
522
+ removeFileConfirm={confirmProductImageRemoval}
523
+ maxFiles={8}
524
+ style="margin: 20px 0; min-height: 180px;"
525
+ />
526
+
527
+ <div class="image-summary">
528
+ <p><strong>Current images:</strong> {productImages.length} / 8</p>
529
+ {#if productImages.length > 0}
530
+ <p><strong>Image names:</strong></p>
531
+ <ul class="image-list">
532
+ {#each productImages as image}
533
+ <li>{image.name}</li>
534
+ {/each}
535
+ </ul>
536
+ {/if}
537
+ </div>
538
+ </div>
539
+
540
+ <style>
541
+ .product-image-manager {
542
+ max-width: 700px;
543
+ margin: 20px auto;
544
+ padding: 25px;
545
+ border: 1px solid #ddd;
546
+ border-radius: 8px;
547
+ background: white;
548
+ }
549
+
550
+ .manager-header {
551
+ display: flex;
552
+ justify-content: space-between;
553
+ align-items: flex-start;
554
+ margin-bottom: 20px;
555
+ }
556
+
557
+ .requirements {
558
+ background: #f0f8ff;
559
+ padding: 15px;
560
+ border-radius: 6px;
561
+ max-width: 250px;
562
+ }
563
+
564
+ .requirements h4 {
565
+ margin: 0 0 10px 0;
566
+ color: #1976d2;
567
+ font-size: 0.9em;
568
+ }
569
+
570
+ .requirements ul {
571
+ margin: 0;
572
+ padding-left: 20px;
573
+ font-size: 0.85em;
574
+ color: #555;
575
+ }
576
+
577
+ .error-panel {
578
+ background: #ffebee;
579
+ border: 1px solid #f44336;
580
+ padding: 15px;
581
+ border-radius: 4px;
582
+ margin-bottom: 20px;
583
+ }
584
+
585
+ .error-panel h4 {
586
+ margin: 0 0 10px 0;
587
+ color: #d32f2f;
588
+ }
589
+
590
+ .error-panel ul {
591
+ margin: 0 0 10px 0;
592
+ padding-left: 20px;
593
+ color: #d32f2f;
594
+ }
595
+
596
+ .error-panel button {
597
+ background: #d32f2f;
598
+ color: white;
599
+ border: none;
600
+ padding: 5px 10px;
601
+ border-radius: 3px;
602
+ cursor: pointer;
603
+ }
604
+
605
+ .image-summary {
606
+ background: #f5f5f5;
607
+ padding: 15px;
608
+ border-radius: 4px;
609
+ font-size: 0.9em;
610
+ }
611
+
612
+ .image-list {
613
+ margin: 5px 0 0 0;
614
+ padding-left: 20px;
615
+ color: #666;
616
+ }
617
+ </style>
618
+ ```
619
+
620
+ ## Best Practices
621
+
622
+ 1. **Image Validation**: Always validate image dimensions, file size, and format
623
+ 2. **Thumbnail Generation**: Create thumbnails for better performance and UX
624
+ 3. **Progress Feedback**: Show upload progress for large image files
625
+ 4. **Error Handling**: Provide clear error messages for validation failures
626
+ 5. **Preview Functionality**: Allow users to preview images before and after upload
627
+ 6. **Aspect Ratio**: Consider maintaining or enforcing specific aspect ratios
628
+ 7. **Optimization**: Compress images when appropriate to reduce file size
629
+ 8. **Accessibility**: Provide alt text and proper ARIA labels for images
630
+
631
+ ## Styling Notes
632
+
633
+ - The component displays images as thumbnails in a grid layout
634
+ - Image previews open in a modal overlay
635
+ - Upload progress is shown with progress bars
636
+ - Custom styles can be applied via the `style` prop
637
+
638
+ ## Accessibility Considerations
639
+
640
+ - Images include appropriate alt attributes
641
+ - Keyboard navigation is supported for all interactive elements
642
+ - Screen readers can access image information
643
+ - Color contrast meets accessibility standards
644
+ - Focus management is handled properly in modals
645
+
646
+ ## Browser Support
647
+
648
+ - Modern browsers with Canvas API support for thumbnail generation
649
+ - File API support required for file handling
650
+ - Drag & drop requires HTML5 support
651
+ - Image preview functionality works in all modern browsers