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