bromcom-ui 3.0.0-alpha.4 → 3.0.0-alpha.5

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 (63) hide show
  1. package/dist/bromcom-ui/bromcom-ui.css +1 -1
  2. package/dist/bromcom-ui/bromcom-ui.esm.js +1 -1
  3. package/dist/bromcom-ui/p-01eaca9a.entry.js +5 -0
  4. package/dist/bromcom-ui/p-3ddd3c83.entry.js +5 -0
  5. package/dist/bromcom-ui/p-aa67918a.entry.js +5 -0
  6. package/dist/bromcom-ui/p-d5464577.entry.js +16 -0
  7. package/dist/cjs/bcm-accordion_69.cjs.entry.js +704 -31
  8. package/dist/cjs/bcm-file-upload.cjs.entry.js +578 -0
  9. package/dist/cjs/bcm-form-2.cjs.entry.js +1 -0
  10. package/dist/cjs/bcm-number-input.cjs.entry.js +8 -2
  11. package/dist/cjs/bromcom-ui.cjs.js +1 -1
  12. package/dist/cjs/loader.cjs.js +1 -1
  13. package/dist/collection/collection-manifest.json +1 -0
  14. package/dist/collection/components/atoms/button/button.js +1 -1
  15. package/dist/collection/components/atoms/button/types.js +7 -0
  16. package/dist/collection/components/molecules/dropdown/dropdown.js +36 -1
  17. package/dist/collection/components/molecules/linked/linked-focus-manager.js +86 -0
  18. package/dist/collection/components/molecules/linked/linked.js +43 -1
  19. package/dist/collection/components/molecules/number-input/number-input.js +8 -2
  20. package/dist/collection/components/molecules/number-input/react-input.js +1 -1
  21. package/dist/collection/components/molecules/upload/file-upload.component.js +1060 -0
  22. package/dist/collection/components/molecules/upload/file-upload.style.js +201 -0
  23. package/dist/collection/components/organism/form/form.js +1 -0
  24. package/dist/collection/components/organism/form-2/bcm-form-components.js +1 -0
  25. package/dist/collection/components/organism/list/list-focus-manager.js +344 -0
  26. package/dist/collection/components/organism/list/list-key-control.js +9 -4
  27. package/dist/collection/components/organism/list/list.css +8 -3
  28. package/dist/collection/components/organism/list/list.js +226 -26
  29. package/dist/collection/components/other_deprecated/old-tag/old-tag.js +1 -1
  30. package/dist/collection/components/other_deprecated/tag/tag.js +1 -1
  31. package/dist/collection/templates/list-item-template.js +2 -1
  32. package/dist/components/bcm-dropdown.js +35 -0
  33. package/dist/components/bcm-file-upload.d.ts +11 -0
  34. package/dist/components/bcm-file-upload.js +625 -0
  35. package/dist/components/bcm-form-2.js +1 -0
  36. package/dist/components/bcm-number-input.js +8 -2
  37. package/dist/components/button.js +7 -0
  38. package/dist/components/form.js +1 -0
  39. package/dist/components/index.d.ts +1 -0
  40. package/dist/components/index.js +1 -0
  41. package/dist/components/linked.js +105 -2
  42. package/dist/components/list.js +559 -31
  43. package/dist/esm/bcm-accordion_69.entry.js +704 -31
  44. package/dist/esm/bcm-file-upload.entry.js +574 -0
  45. package/dist/esm/bcm-form-2.entry.js +1 -0
  46. package/dist/esm/bcm-number-input.entry.js +8 -2
  47. package/dist/esm/bromcom-ui.js +1 -1
  48. package/dist/esm/loader.js +1 -1
  49. package/dist/types/components/atoms/button/types.d.ts +8 -1
  50. package/dist/types/components/molecules/dropdown/dropdown.d.ts +7 -0
  51. package/dist/types/components/molecules/linked/linked-focus-manager.d.ts +13 -0
  52. package/dist/types/components/molecules/linked/linked.d.ts +3 -0
  53. package/dist/types/components/molecules/number-input/number-input.d.ts +1 -0
  54. package/dist/types/components/molecules/upload/file-upload.component.d.ts +233 -0
  55. package/dist/types/components/molecules/upload/file-upload.style.d.ts +548 -0
  56. package/dist/types/components/organism/list/list-focus-manager.d.ts +53 -0
  57. package/dist/types/components/organism/list/list-key-control.d.ts +1 -0
  58. package/dist/types/components/organism/list/list.d.ts +18 -1
  59. package/dist/types/components.d.ts +417 -1
  60. package/package.json +1 -1
  61. package/dist/bromcom-ui/p-8e7274e4.entry.js +0 -5
  62. package/dist/bromcom-ui/p-9e0dd503.entry.js +0 -16
  63. package/dist/bromcom-ui/p-a320cde8.entry.js +0 -5
@@ -0,0 +1,574 @@
1
+ /*!
2
+ * Built with Stencil
3
+ * Copyright (c) Bromcom.
4
+ */
5
+ import { r as registerInstance, c as createEvent, h, H as Host, g as getElement } from './index-4614b7f0.js';
6
+ import { G as Generate } from './generate-56d49b70.js';
7
+ import { c as ce } from './index-3bc8c6e3.js';
8
+ import { C as CaptionTemplate } from './caption-template-16dd9444.js';
9
+ import './package-edee14d5.js';
10
+ import './colors-ed4e7579.js';
11
+ import './_commonjsHelpers-7002e5de.js';
12
+ import './index-00c8a8f7.js';
13
+
14
+ const zoneDefaultSurface = 'tw-border-dashed tw-border-color tw-bg-color-default';
15
+ const zoneHoverSurface = '!tw-border-solid !tw-border-color-primaryInfo';
16
+ const zoneDragSurface = '!tw-border-solid !tw-border-color-primaryInfo !tw-bg-primary-50';
17
+ const uploadClasses = ce({
18
+ slots: {
19
+ container: 'bcm-ui-element tw-flex tw-flex-col tw-w-full',
20
+ label: 'tw-text-3 tw-font-medium tw-text-color-label tw-leading-5',
21
+ triggerWrap: 'tw-flex tw-flex-col tw-w-full',
22
+ zone: [
23
+ 'tw-relative tw-rounded tw-border tw-transition-all tw-duration-200 tw-ease-in-out',
24
+ 'tw-select-none tw-flex tw-items-center tw-justify-center',
25
+ zoneDefaultSurface,
26
+ ],
27
+ iconContainer: 'tw-flex tw-shrink-0 tw-items-center tw-justify-center',
28
+ iconSizeClass: 'tw-block tw-text-color-default',
29
+ zoneTextWrapper: 'tw-flex tw-flex-col tw-flex-1 tw-min-w-0',
30
+ zoneTextMain: 'tw-text-3 tw-font-medium tw-text-color-caption tw-leading-tight',
31
+ zoneLink: 'tw-text-color-primary tw-font-medium hover:tw-underline tw-cursor-pointer',
32
+ zoneTextSub: 'tw-text-2 tw-text-color-caption tw-leading-4',
33
+ list: 'tw-flex tw-flex-col tw-gap-2',
34
+ fileRow: [
35
+ 'tw-flex tw-flex-col tw-rounded tw-min-w-0',
36
+ 'tw-bg-color-default tw-px-4 tw-py-2',
37
+ ],
38
+ fileRowHeader: 'tw-flex tw-items-center tw-gap-2 tw-w-full tw-min-w-0',
39
+ fileIcon: 'tw-shrink-0 tw-text-color-primary',
40
+ fileTitle: 'tw-font-medium tw-leading-5 tw-text-color-default tw-truncate tw-flex-1 tw-min-w-0',
41
+ progressWrapper: 'tw-flex tw-items-center tw-gap-2 tw-w-full',
42
+ progressTrack: 'tw-flex-1 tw-h-2 tw-bg-color-default-hover tw-rounded-full tw-overflow-hidden',
43
+ progressBar: 'tw-h-full tw-bg-primary-600 tw-rounded-full tw-transition-all tw-duration-300 tw-ease-out',
44
+ percent: 'tw-text-3 tw-font-medium tw-leading-5 tw-text-color-default tw-min-w-[3ch] tw-text-right tw-shrink-0',
45
+ actions: 'tw-flex tw-items-center tw-shrink-0',
46
+ iconBtn: [
47
+ 'tw-flex tw-items-center tw-justify-center tw-transition-colors tw-cursor-pointer',
48
+ 'tw-appearance-none tw-border-none tw-bg-transparent tw-p-0',
49
+ 'tw-text-color-default hover:tw-text-color-primary',
50
+ ],
51
+ errorArea: [
52
+ 'tw-flex tw-items-center tw-gap-3 tw-py-1.5 tw-px-3 tw-rounded',
53
+ 'tw-bg-red-50 tw-text-color-error tw-text-3 tw-font-medium tw-leading-none',
54
+ ],
55
+ errorIcon: 'tw-flex tw-items-center tw-justify-center tw-shrink-0',
56
+ errorClose: 'tw-ml-auto tw-cursor-pointer hover:tw-opacity-80',
57
+ captionWrap: '[&_.bcm-caption-area]:!tw-mt-0',
58
+ },
59
+ variants: {
60
+ size: {
61
+ medium: {
62
+ container: 'tw-gap-1',
63
+ zone: 'tw-gap-2 tw-px-4 tw-py-4',
64
+ zoneTextWrapper: 'tw-gap-1',
65
+ fileRow: 'tw-gap-1 tw-px-4 tw-py-2',
66
+ fileRowHeader: 'tw-gap-2',
67
+ fileIcon: 'tw-text-4',
68
+ fileTitle: 'tw-text-3',
69
+ iconBtn: 'tw-text-4',
70
+ progressWrapper: 'tw-gap-2',
71
+ progressTrack: 'tw-h-2',
72
+ },
73
+ small: {
74
+ container: 'tw-gap-0.5',
75
+ zone: 'tw-gap-2 tw-px-3 tw-py-3',
76
+ zoneTextWrapper: 'tw-gap-0.5',
77
+ fileRow: 'tw-gap-0.5 tw-px-3 tw-py-1',
78
+ fileRowHeader: 'tw-gap-1',
79
+ fileTitle: 'tw-text-2',
80
+ fileIcon: 'tw-text-3',
81
+ percent: 'tw-text-2',
82
+ iconBtn: 'tw-text-3',
83
+ progressWrapper: 'tw-gap-1',
84
+ progressTrack: 'tw-h-1.5',
85
+ list: 'tw-gap-1.5',
86
+ },
87
+ },
88
+ position: {
89
+ vertical: {
90
+ zone: 'tw-flex-col tw-text-center',
91
+ zoneTextWrapper: 'tw-items-center',
92
+ zoneTextSub: 'tw-text-center',
93
+ },
94
+ horizontal: {
95
+ zone: 'tw-flex-row tw-text-left',
96
+ zoneTextWrapper: 'tw-items-start',
97
+ },
98
+ button: {
99
+ container: 'tw-gap-1',
100
+ },
101
+ },
102
+ disabled: {
103
+ true: '',
104
+ false: '',
105
+ },
106
+ zoneHovered: {
107
+ true: '',
108
+ false: '',
109
+ },
110
+ dragActive: {
111
+ true: '',
112
+ false: '',
113
+ },
114
+ zoneError: {
115
+ true: '',
116
+ false: '',
117
+ },
118
+ fullWidth: {
119
+ true: {
120
+ container: 'tw-max-w-full',
121
+ },
122
+ false: {
123
+ container: 'tw-max-w-64',
124
+ },
125
+ },
126
+ },
127
+ compoundVariants: [
128
+ {
129
+ position: ['vertical', 'horizontal'],
130
+ size: 'medium',
131
+ class: {
132
+ iconContainer: 'tw-h-10',
133
+ iconSizeClass: 'tw-text-[40px] tw-leading-none',
134
+ },
135
+ },
136
+ {
137
+ position: 'vertical',
138
+ size: 'small',
139
+ class: {
140
+ iconContainer: 'tw-size-8',
141
+ iconSizeClass: 'tw-text-[32px] tw-leading-none',
142
+ },
143
+ },
144
+ {
145
+ position: 'horizontal',
146
+ size: 'small',
147
+ class: {
148
+ iconContainer: 'tw-h-8 tw-max-h-8 tw-max-w-9',
149
+ iconSizeClass: '!tw-text-[28px] !tw-leading-none',
150
+ },
151
+ },
152
+ {
153
+ position: ['vertical', 'horizontal'],
154
+ disabled: false,
155
+ class: {
156
+ zone: 'tw-cursor-pointer',
157
+ },
158
+ },
159
+ {
160
+ position: ['vertical', 'horizontal'],
161
+ disabled: false,
162
+ zoneError: false,
163
+ zoneHovered: true,
164
+ dragActive: false,
165
+ class: {
166
+ zone: zoneHoverSurface,
167
+ },
168
+ },
169
+ {
170
+ position: ['vertical', 'horizontal'],
171
+ disabled: false,
172
+ zoneError: false,
173
+ dragActive: true,
174
+ class: {
175
+ zone: zoneDragSurface,
176
+ },
177
+ },
178
+ {
179
+ position: ['vertical', 'horizontal'],
180
+ zoneError: true,
181
+ disabled: false,
182
+ class: {
183
+ zone: '!tw-border-solid tw-border-color-error !tw-bg-red-50',
184
+ },
185
+ },
186
+ {
187
+ disabled: true,
188
+ class: {
189
+ zone: [
190
+ 'tw-cursor-not-allowed',
191
+ 'tw-border-dashed tw-border-color-disabled tw-bg-color-disabled',
192
+ ],
193
+ zoneTextMain: 'tw-text-color-disabled',
194
+ zoneTextSub: 'tw-text-color-disabled',
195
+ zoneLink: 'tw-text-color-disabled tw-cursor-not-allowed tw-no-underline hover:tw-no-underline',
196
+ iconSizeClass: '!tw-text-color-disabled',
197
+ },
198
+ },
199
+ ],
200
+ defaultVariants: {
201
+ size: 'medium',
202
+ position: 'vertical',
203
+ disabled: false,
204
+ zoneHovered: false,
205
+ dragActive: false,
206
+ zoneError: false,
207
+ fullWidth: false,
208
+ },
209
+ });
210
+
211
+ const BcmFileUpload = class {
212
+ constructor(hostRef) {
213
+ registerInstance(this, hostRef);
214
+ this.bcmFileChange = createEvent(this, "bcm-file-change", 1);
215
+ this.bcmChange = createEvent(this, "bcm-change", 7);
216
+ this.bcmFileRemoved = createEvent(this, "bcm-file-removed", 1);
217
+ this.bcmUploadCanceled = createEvent(this, "bcm-upload-canceled", 1);
218
+ this.bcmFocus = createEvent(this, "bcm-focus", 1);
219
+ this.bcmBlur = createEvent(this, "bcm-blur", 1);
220
+ this.inputId = Generate.UID();
221
+ this.labelId = Generate.UID();
222
+ this.internalMaxSizeInBytes = this.maxSize * 1024 * 1024;
223
+ this.onFileInputChange = (event) => {
224
+ const input = event.target;
225
+ this.addFiles(input.files);
226
+ // Reset input so the same file can be selected again
227
+ input.value = '';
228
+ };
229
+ this.openFileDialog = (ev) => {
230
+ var _a;
231
+ ev === null || ev === void 0 ? void 0 : ev.preventDefault();
232
+ ev === null || ev === void 0 ? void 0 : ev.stopPropagation();
233
+ if (this.disabled)
234
+ return;
235
+ (_a = this.fileInput) === null || _a === void 0 ? void 0 : _a.click();
236
+ };
237
+ this.handleZoneMouseEnter = () => {
238
+ if (!this.disabled)
239
+ this.zoneHovered = true;
240
+ };
241
+ this.handleZoneMouseLeave = () => {
242
+ this.zoneHovered = false;
243
+ };
244
+ this.handleZoneKeyDown = (event) => {
245
+ if (this.disabled)
246
+ return;
247
+ if (event.key === ' ' || event.key === 'Enter') {
248
+ event.preventDefault();
249
+ this.openFileDialog();
250
+ }
251
+ };
252
+ this.handleFocus = (event) => {
253
+ this.bcmFocus.emit(event);
254
+ };
255
+ this.handleBlur = (event) => {
256
+ this.bcmBlur.emit(event);
257
+ };
258
+ this.name = 'file';
259
+ this.label = '';
260
+ this.caption = '';
261
+ this.noCaption = false;
262
+ this.multiple = false;
263
+ this.accept = '';
264
+ this.maxSize = 2;
265
+ this.required = false;
266
+ this.disabled = false;
267
+ this.size = 'medium';
268
+ this.position = 'vertical';
269
+ this.buttonLabel = undefined;
270
+ this.showFileList = true;
271
+ this.fullWidth = false;
272
+ this.uploadUrl = undefined;
273
+ this.maxFileCount = undefined;
274
+ this.customErrorMessages = undefined;
275
+ this.value = null;
276
+ this.captionError = undefined;
277
+ this.captionType = undefined;
278
+ this.files = [];
279
+ this.dragActive = false;
280
+ this.zoneHovered = false;
281
+ this.maxSizeError = false;
282
+ this.typeError = false;
283
+ this.maxFileCountError = false;
284
+ }
285
+ componentWillLoad() {
286
+ this.internalMaxSizeInBytes = this.maxSize * 1024 * 1024;
287
+ if (!this.host.hasAttribute('position') && this.size === 'small') {
288
+ this.position = 'horizontal';
289
+ }
290
+ }
291
+ get isButtonMode() {
292
+ return this.position === 'button';
293
+ }
294
+ get zoneError() {
295
+ return this.captionType === 'error' && !!this.captionError;
296
+ }
297
+ getStyleProps() {
298
+ return {
299
+ size: this.size,
300
+ position: this.position,
301
+ disabled: this.disabled,
302
+ zoneHovered: this.zoneHovered,
303
+ dragActive: this.dragActive,
304
+ zoneError: this.zoneError,
305
+ fullWidth: this.fullWidth,
306
+ };
307
+ }
308
+ disconnectedCallback() {
309
+ // Clear interval when component is removed from the DOM
310
+ if (this.uploadTimer) {
311
+ window.clearInterval(this.uploadTimer);
312
+ this.uploadTimer = undefined;
313
+ }
314
+ }
315
+ handleMaxSizeChange() {
316
+ this.internalMaxSizeInBytes = this.maxSize * 1024 * 1024;
317
+ }
318
+ /**
319
+ * Normalize and check a file against the `accept` attribute.
320
+ */
321
+ isFileAccepted(file) {
322
+ if (!this.accept)
323
+ return true;
324
+ const extension = '.' + (file.name.split('.').pop() || '').toLowerCase();
325
+ const accepted = this.accept
326
+ .toLowerCase()
327
+ .split(',')
328
+ .map(x => x.trim())
329
+ .filter(Boolean); // e.g. ['.pdf', '.jpg', '.png']
330
+ return accepted.includes(extension);
331
+ }
332
+ emitChange() {
333
+ const fileObjects = this.files.map(f => f.file);
334
+ this.value = fileObjects.length > 0 ? fileObjects : null;
335
+ this.bcmFileChange.emit(fileObjects);
336
+ this.bcmChange.emit(this.value);
337
+ }
338
+ normalizeSetDataToItems(data) {
339
+ const input = Array.isArray(data) ? data : [data];
340
+ return input
341
+ .filter(item => item instanceof File)
342
+ .map((file) => ({
343
+ id: Generate.UID(),
344
+ file,
345
+ status: this.showFileList ? 'uploading' : 'uploaded',
346
+ progress: this.showFileList ? 0 : undefined,
347
+ }));
348
+ }
349
+ async get() {
350
+ return this.value;
351
+ }
352
+ async set(data) {
353
+ if (this.uploadTimer) {
354
+ window.clearInterval(this.uploadTimer);
355
+ this.uploadTimer = undefined;
356
+ }
357
+ const items = this.normalizeSetDataToItems(data);
358
+ if (items.length > 0) {
359
+ this.files = this.multiple ? items : [items[0]];
360
+ this.maxSizeError = false;
361
+ this.typeError = false;
362
+ this.maxFileCountError = false;
363
+ this.emitChange();
364
+ if (this.showFileList) {
365
+ this.startFakeUpload();
366
+ }
367
+ return;
368
+ }
369
+ // Backward-compatible fallback for non-File payloads
370
+ this.value = data;
371
+ this.bcmChange.emit(this.value);
372
+ }
373
+ async setClear() {
374
+ this.files = [];
375
+ this.maxSizeError = false;
376
+ this.typeError = false;
377
+ this.maxFileCountError = false;
378
+ this.emitChange();
379
+ return new Promise(resolve => setTimeout(() => {
380
+ this.resetCaption();
381
+ resolve(undefined);
382
+ }, 10));
383
+ }
384
+ async resetCaption() {
385
+ return new Promise(resolve => setTimeout(() => {
386
+ this.captionError = null;
387
+ this.captionType = null;
388
+ this.maxSizeError = false;
389
+ this.typeError = false;
390
+ this.maxFileCountError = false;
391
+ resolve(undefined);
392
+ }, 10));
393
+ }
394
+ /**
395
+ * Simulate upload progress. Replace with real upload logic if needed.
396
+ */
397
+ startFakeUpload() {
398
+ if (this.uploadTimer) {
399
+ window.clearInterval(this.uploadTimer);
400
+ }
401
+ this.uploadTimer = window.setInterval(() => {
402
+ let anyUploading = false;
403
+ this.files = this.files.map(item => {
404
+ var _a;
405
+ if (item.status !== 'uploading')
406
+ return item;
407
+ anyUploading = true;
408
+ const current = (_a = item.progress) !== null && _a !== void 0 ? _a : 0;
409
+ const next = Math.min(current + 15, 100);
410
+ if (next >= 100) {
411
+ return Object.assign(Object.assign({}, item), { status: 'uploaded', progress: undefined });
412
+ }
413
+ return Object.assign(Object.assign({}, item), { progress: next });
414
+ });
415
+ if (!anyUploading && this.uploadTimer) {
416
+ window.clearInterval(this.uploadTimer);
417
+ this.uploadTimer = undefined;
418
+ this.emitChange();
419
+ }
420
+ }, 300);
421
+ }
422
+ /**
423
+ * Add files from input or drop.
424
+ */
425
+ addFiles(fileList) {
426
+ if (!fileList || this.disabled)
427
+ return;
428
+ const newItems = [];
429
+ let hasSizeError = false;
430
+ let hasTypeError = false;
431
+ Array.from(fileList).forEach(file => {
432
+ if (!this.isFileAccepted(file)) {
433
+ hasTypeError = true;
434
+ return;
435
+ }
436
+ if (file.size > this.internalMaxSizeInBytes) {
437
+ hasSizeError = true;
438
+ return;
439
+ }
440
+ newItems.push({
441
+ id: Generate.UID(),
442
+ file,
443
+ status: this.showFileList ? 'uploading' : 'uploaded',
444
+ progress: this.showFileList ? 0 : undefined,
445
+ });
446
+ });
447
+ this.maxSizeError = hasSizeError;
448
+ this.typeError = hasTypeError;
449
+ this.maxFileCountError = false;
450
+ // If there are no valid files, just update validity and exit.
451
+ if (!newItems.length) {
452
+ this.emitChange();
453
+ return;
454
+ }
455
+ let merged;
456
+ if (this.multiple) {
457
+ if (typeof this.maxFileCount === 'number') {
458
+ const remaining = this.maxFileCount - this.files.length;
459
+ if (remaining <= 0) {
460
+ this.maxFileCountError = true;
461
+ this.emitChange();
462
+ return;
463
+ }
464
+ const acceptedNew = newItems.slice(0, Math.max(0, remaining));
465
+ if (acceptedNew.length < newItems.length) {
466
+ this.maxFileCountError = true;
467
+ }
468
+ merged = [...this.files, ...acceptedNew];
469
+ }
470
+ else {
471
+ merged = [...this.files, ...newItems];
472
+ }
473
+ }
474
+ else {
475
+ // single file mode → only take first new item
476
+ merged = [newItems[0]];
477
+ }
478
+ this.files = merged;
479
+ this.emitChange();
480
+ // Only fake upload for now (skip when file list UI is hidden).
481
+ if (this.showFileList) {
482
+ this.startFakeUpload();
483
+ }
484
+ }
485
+ removeFile(item) {
486
+ this.files = this.files.filter(f => f.id !== item.id);
487
+ this.bcmFileRemoved.emit(item);
488
+ this.emitChange();
489
+ }
490
+ cancelUpload(item) {
491
+ this.files = this.files.filter(f => f.id !== item.id);
492
+ this.bcmUploadCanceled.emit(item);
493
+ this.emitChange();
494
+ }
495
+ handleDrop(ev) {
496
+ var _a;
497
+ ev.preventDefault();
498
+ this.dragActive = false;
499
+ if (this.disabled || this.isButtonMode)
500
+ return;
501
+ const files = ((_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) || null;
502
+ if (!files || files.length === 0)
503
+ return;
504
+ const first = files.item(0);
505
+ if (!first || !this.isFileAccepted(first)) {
506
+ this.typeError = true;
507
+ return;
508
+ }
509
+ this.addFiles(files);
510
+ }
511
+ handleDragOver(ev) {
512
+ ev.preventDefault();
513
+ if (this.disabled || this.isButtonMode)
514
+ return;
515
+ this.dragActive = true;
516
+ }
517
+ handleDragLeave(ev) {
518
+ ev.preventDefault();
519
+ this.dragActive = false;
520
+ }
521
+ async setFocus(options) {
522
+ var _a, _b;
523
+ if (this.isButtonMode) {
524
+ (_a = this.uploadButtonRef) === null || _a === void 0 ? void 0 : _a.focus(options);
525
+ return;
526
+ }
527
+ (_b = this.uploadZoneRef) === null || _b === void 0 ? void 0 : _b.focus(options);
528
+ }
529
+ getMaxSizeLabel() {
530
+ const maxMb = Math.round(this.internalMaxSizeInBytes / (1024 * 1024));
531
+ return `Maximum size ${maxMb}MB`;
532
+ }
533
+ renderErrors() {
534
+ var _a, _b, _c;
535
+ const { errorArea, errorIcon, errorClose } = uploadClasses(this.getStyleProps());
536
+ const maxSizeMsg = ((_a = this.customErrorMessages) === null || _a === void 0 ? void 0 : _a.maxSize) ||
537
+ `The file size exceeds the limit of ${Math.round(this.internalMaxSizeInBytes / (1024 * 1024))} MB.`;
538
+ const typeMsg = ((_b = this.customErrorMessages) === null || _b === void 0 ? void 0 : _b.type) ||
539
+ 'This file type is not supported.';
540
+ const maxFileCountMsg = ((_c = this.customErrorMessages) === null || _c === void 0 ? void 0 : _c.maxFileCount) ||
541
+ 'You have reached the maximum number of files.';
542
+ return (h("div", { class: "tw-w-full tw-space-y-2" }, this.maxSizeError && (h("div", { class: errorArea() }, h("div", { class: errorIcon() }, h("i", { class: "fa-solid fa-circle-exclamation" })), h("span", { class: "tw-flex-1" }, maxSizeMsg), h("div", { class: errorClose(), onClick: () => (this.maxSizeError = false) }, h("i", { class: "fa-solid fa-xmark" })))), this.typeError && (h("div", { class: errorArea() }, h("div", { class: errorIcon() }, h("i", { class: "fa-solid fa-circle-exclamation" })), h("span", { class: "tw-flex-1" }, typeMsg), h("div", { class: errorClose(), onClick: () => (this.typeError = false) }, h("i", { class: "fa-solid fa-xmark" })))), this.maxFileCountError && (h("div", { class: errorArea() }, h("div", { class: errorIcon() }, h("i", { class: "fa-solid fa-circle-exclamation" })), h("span", { class: "tw-flex-1" }, maxFileCountMsg), h("div", { class: errorClose(), onClick: () => (this.maxFileCountError = false) }, h("i", { class: "fa-solid fa-xmark" }))))));
543
+ }
544
+ renderUploadZone() {
545
+ const { zone, iconContainer, iconSizeClass, zoneTextWrapper, zoneTextMain, zoneTextSub, zoneLink, } = uploadClasses(this.getStyleProps());
546
+ return (h("div", { ref: el => (this.uploadZoneRef = el), tabindex: this.disabled ? undefined : 0, part: "upload-zone", class: zone(), role: "button", "aria-disabled": this.disabled ? 'true' : 'false', "aria-labelledby": this.label ? this.labelId : undefined, "aria-label": this.label || 'File upload', onClick: this.openFileDialog, onKeyDown: this.handleZoneKeyDown, onFocus: this.handleFocus, onBlur: this.handleBlur, onMouseEnter: this.handleZoneMouseEnter, onMouseLeave: this.handleZoneMouseLeave, onDragOver: ev => this.handleDragOver(ev), onDragLeave: ev => this.handleDragLeave(ev), onDrop: ev => this.handleDrop(ev) }, h("div", { class: iconContainer() }, h("i", { class: `fa-solid fa-cloud-arrow-up ${iconSizeClass()}`, "aria-hidden": "true" })), h("div", { class: zoneTextWrapper() }, h("div", { class: zoneTextMain() }, "Drag and drop your file here ", h("span", { class: "tw-inline-block" }, "or", ' ', h("span", { class: zoneLink(), role: "button", tabindex: -1, "aria-label": "Choose file", onClick: e => this.openFileDialog(e) }, "Browse files"))), h("div", { class: zoneTextSub() }, this.getMaxSizeLabel()))));
547
+ }
548
+ renderUploadButton() {
549
+ const { triggerWrap } = uploadClasses(this.getStyleProps());
550
+ const label = this.buttonLabel || 'Upload';
551
+ return (h("div", { class: triggerWrap(), part: "upload-trigger" }, h("bcm-button", { ref: el => (this.uploadButtonRef = el), variant: "upload", size: this.size, disabled: this.disabled, onFocus: this.handleFocus, onBlur: this.handleBlur, "onBcm-click": (e) => this.openFileDialog(e) }, label)));
552
+ }
553
+ renderHiddenInput() {
554
+ return (h("input", { ref: el => (this.fileInput = el), id: this.inputId, type: "file", hidden: true, accept: this.accept, multiple: this.multiple, disabled: this.disabled, onChange: this.onFileInputChange }));
555
+ }
556
+ renderFileRow(item) {
557
+ var _a, _b;
558
+ const { percent, iconBtn, fileRow, fileRowHeader, fileIcon, fileTitle, progressWrapper, progressTrack, progressBar, } = uploadClasses(this.getStyleProps());
559
+ const isUploading = (item === null || item === void 0 ? void 0 : item.status) === 'uploading';
560
+ return (h("div", { part: "file-row", class: fileRow(), title: (_a = item === null || item === void 0 ? void 0 : item.file) === null || _a === void 0 ? void 0 : _a.name }, h("div", { class: fileRowHeader() }, h("i", { class: `fa-regular fa-file ${fileIcon()}` }), h("span", { class: fileTitle() }, (_b = item === null || item === void 0 ? void 0 : item.file) === null || _b === void 0 ? void 0 : _b.name), h("button", { type: "button", part: isUploading ? 'cancel-btn' : 'remove-btn', class: iconBtn(), onClick: () => isUploading ? this.cancelUpload(item) : this.removeFile(item), title: isUploading ? 'Cancel' : 'Remove' }, h("i", { class: "fa-regular fa-trash" }))), this.showFileList && isUploading && (h("div", { class: progressWrapper() }, h("div", { class: progressTrack() }, h("div", { class: progressBar(), style: { width: `${item.progress || 0}%` } })), h("span", { class: percent() }, item.progress || 0, "%")))));
561
+ }
562
+ render() {
563
+ const { container, label, list, captionWrap } = uploadClasses(this.getStyleProps());
564
+ const hasCaptionText = typeof this.caption === 'string' ? this.caption.trim().length > 0 : !!this.caption;
565
+ const shouldShowCaption = !this.noCaption && (hasCaptionText || !!this.captionError);
566
+ return (h(Host, { tabindex: "-1" }, h("div", { class: container() }, this.label && (h("label", { id: this.labelId, htmlFor: this.inputId, part: "label", class: label() }, this.label)), this.isButtonMode ? this.renderUploadButton() : this.renderUploadZone(), this.renderHiddenInput(), this.renderErrors(), shouldShowCaption && (h("div", { class: captionWrap(), part: "caption" }, h(CaptionTemplate, { noCaption: false, captionType: this.captionType, captionError: this.captionError, caption: this.caption, size: this.size === 'small' ? 'medium' : 'large' }))), this.showFileList && this.files.length > 0 && (h("div", { part: "file-list", class: list() }, this.files.map(item => this.renderFileRow(item)))))));
567
+ }
568
+ get host() { return getElement(this); }
569
+ static get watchers() { return {
570
+ "maxSize": ["handleMaxSizeChange"]
571
+ }; }
572
+ };
573
+
574
+ export { BcmFileUpload as bcm_file_upload };
@@ -32,6 +32,7 @@ const BcmFormComponents = [
32
32
  'BCM-CHIP-GROUP',
33
33
  'BCM-INPUT-DROPDOWN',
34
34
  'BCM-NUMBER-INPUT',
35
+ 'BCM-FILE-UPLOAD',
35
36
  //... to be added to v3
36
37
  ];
37
38
 
@@ -40,10 +40,13 @@ const NumberInput = class {
40
40
  this.input.emit(e);
41
41
  });
42
42
  };
43
+ this.emitChange = () => {
44
+ this.change.emit(this.value);
45
+ };
43
46
  this.onChange = () => {
44
47
  var _a;
45
- (_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.addEventListener("change", (e) => {
46
- this.change.emit(e);
48
+ (_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.addEventListener("change", () => {
49
+ this.emitChange();
47
50
  });
48
51
  };
49
52
  this.onFocus = () => {
@@ -137,6 +140,7 @@ const NumberInput = class {
137
140
  this.value = this.max;
138
141
  }
139
142
  }
143
+ this.emitChange();
140
144
  }
141
145
  down() {
142
146
  if (this.disabled)
@@ -149,6 +153,7 @@ const NumberInput = class {
149
153
  this.value = this.min;
150
154
  }
151
155
  }
156
+ this.emitChange();
152
157
  }
153
158
  handleClear(e) {
154
159
  if (this.disabled)
@@ -158,6 +163,7 @@ const NumberInput = class {
158
163
  return;
159
164
  this.value = "";
160
165
  this.clear.emit(e);
166
+ this.emitChange();
161
167
  }
162
168
  checkLength() {
163
169
  const { min, max, value } = this;