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