@raintonic/formaui 0.2.0
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.
- package/CHANGELOG.md +7 -0
- package/README.md +145 -0
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +806 -0
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs +86 -0
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs +1757 -0
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +287 -0
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-accordion.mjs +217 -0
- package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-alert.mjs +161 -0
- package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs +726 -0
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-avatar.mjs +92 -0
- package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-badge.mjs +107 -0
- package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-big-menu.mjs +68 -0
- package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +55 -0
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-button-group.mjs +103 -0
- package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-button.mjs +241 -0
- package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-card.mjs +270 -0
- package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-checkbox.mjs +295 -0
- package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs +631 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-date-picker.mjs +1331 -0
- package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-divider.mjs +41 -0
- package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-drawer.mjs +190 -0
- package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-dynamic-form.mjs +266 -0
- package/fesm2022/raintonic-formaui-components-dynamic-form.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs +33 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-file-upload.mjs +246 -0
- package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-form-field.mjs +482 -0
- package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-icon.mjs +117 -0
- package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-input.mjs +327 -0
- package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-list.mjs +149 -0
- package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-menu.mjs +896 -0
- package/fesm2022/raintonic-formaui-components-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-number-input.mjs +345 -0
- package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-paginator.mjs +139 -0
- package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-password-input.mjs +306 -0
- package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-popover.mjs +451 -0
- package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-progressbar.mjs +148 -0
- package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-radio.mjs +260 -0
- package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-select.mjs +1011 -0
- package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-side-panel.mjs +150 -0
- package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-sidebar.mjs +257 -0
- package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs +50 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-slider.mjs +347 -0
- package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-spinner.mjs +63 -0
- package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-stepper.mjs +317 -0
- package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs +197 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tag.mjs +78 -0
- package/fesm2022/raintonic-formaui-components-tag.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-time-picker.mjs +644 -0
- package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs +171 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toolbar.mjs +140 -0
- package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tooltip.mjs +555 -0
- package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs +314 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-table.mjs +103 -0
- package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree.mjs +430 -0
- package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-core.mjs +62 -0
- package/fesm2022/raintonic-formaui-core.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs +798 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-notification.mjs +391 -0
- package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-theme.mjs +248 -0
- package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-test-utils.mjs +66 -0
- package/fesm2022/raintonic-formaui-test-utils.mjs.map +1 -0
- package/fesm2022/raintonic-formaui.mjs +15 -0
- package/fesm2022/raintonic-formaui.mjs.map +1 -0
- package/llms-full.txt +1627 -0
- package/llms.txt +60 -0
- package/package.json +251 -0
- package/styles/_fonts-entry.scss +3 -0
- package/styles/fonts/dm-mono-400-latin.woff2 +0 -0
- package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
- package/styles/fonts/inter-tight-latin.woff2 +0 -0
- package/styles/index.scss +127 -0
- package/styles/partials/_constants.scss +29 -0
- package/styles/partials/_fonts.scss +36 -0
- package/styles/partials/_grid.scss +171 -0
- package/styles/partials/_mixins.scss +145 -0
- package/styles/partials/_motion.scss +252 -0
- package/styles/partials/_theme.scss +275 -0
- package/styles/partials/_typography.scss +112 -0
- package/styles/partials/_utilities.scss +480 -0
- package/styles/partials/themes/_dark.scss +254 -0
- package/styles/partials/themes/_light.scss +254 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts +196 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-form-field.d.ts +62 -0
- package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts +843 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +112 -0
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -0
- package/types/raintonic-formaui-components-accordion.d.ts +124 -0
- package/types/raintonic-formaui-components-accordion.d.ts.map +1 -0
- package/types/raintonic-formaui-components-alert.d.ts +143 -0
- package/types/raintonic-formaui-components-alert.d.ts.map +1 -0
- package/types/raintonic-formaui-components-autocomplete.d.ts +193 -0
- package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -0
- package/types/raintonic-formaui-components-avatar.d.ts +52 -0
- package/types/raintonic-formaui-components-avatar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-badge.d.ts +47 -0
- package/types/raintonic-formaui-components-badge.d.ts.map +1 -0
- package/types/raintonic-formaui-components-big-menu.d.ts +62 -0
- package/types/raintonic-formaui-components-big-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-breadcrumb.d.ts +26 -0
- package/types/raintonic-formaui-components-breadcrumb.d.ts.map +1 -0
- package/types/raintonic-formaui-components-button-group.d.ts +61 -0
- package/types/raintonic-formaui-components-button-group.d.ts.map +1 -0
- package/types/raintonic-formaui-components-button.d.ts +116 -0
- package/types/raintonic-formaui-components-button.d.ts.map +1 -0
- package/types/raintonic-formaui-components-card.d.ts +191 -0
- package/types/raintonic-formaui-components-card.d.ts.map +1 -0
- package/types/raintonic-formaui-components-checkbox.d.ts +132 -0
- package/types/raintonic-formaui-components-checkbox.d.ts.map +1 -0
- package/types/raintonic-formaui-components-data-table.d.ts +368 -0
- package/types/raintonic-formaui-components-data-table.d.ts.map +1 -0
- package/types/raintonic-formaui-components-date-picker.d.ts +341 -0
- package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -0
- package/types/raintonic-formaui-components-divider.d.ts +21 -0
- package/types/raintonic-formaui-components-divider.d.ts.map +1 -0
- package/types/raintonic-formaui-components-drawer.d.ts +48 -0
- package/types/raintonic-formaui-components-drawer.d.ts.map +1 -0
- package/types/raintonic-formaui-components-dynamic-form.d.ts +412 -0
- package/types/raintonic-formaui-components-dynamic-form.d.ts.map +1 -0
- package/types/raintonic-formaui-components-empty-state.d.ts +14 -0
- package/types/raintonic-formaui-components-empty-state.d.ts.map +1 -0
- package/types/raintonic-formaui-components-file-upload.d.ts +77 -0
- package/types/raintonic-formaui-components-file-upload.d.ts.map +1 -0
- package/types/raintonic-formaui-components-form-field.d.ts +271 -0
- package/types/raintonic-formaui-components-form-field.d.ts.map +1 -0
- package/types/raintonic-formaui-components-icon.d.ts +61 -0
- package/types/raintonic-formaui-components-icon.d.ts.map +1 -0
- package/types/raintonic-formaui-components-input.d.ts +149 -0
- package/types/raintonic-formaui-components-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-list.d.ts +48 -0
- package/types/raintonic-formaui-components-list.d.ts.map +1 -0
- package/types/raintonic-formaui-components-menu.d.ts +403 -0
- package/types/raintonic-formaui-components-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-number-input.d.ts +127 -0
- package/types/raintonic-formaui-components-number-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-paginator.d.ts +37 -0
- package/types/raintonic-formaui-components-paginator.d.ts.map +1 -0
- package/types/raintonic-formaui-components-password-input.d.ts +111 -0
- package/types/raintonic-formaui-components-password-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-popover.d.ts +131 -0
- package/types/raintonic-formaui-components-popover.d.ts.map +1 -0
- package/types/raintonic-formaui-components-progressbar.d.ts +111 -0
- package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-radio.d.ts +95 -0
- package/types/raintonic-formaui-components-radio.d.ts.map +1 -0
- package/types/raintonic-formaui-components-select.d.ts +307 -0
- package/types/raintonic-formaui-components-select.d.ts.map +1 -0
- package/types/raintonic-formaui-components-side-panel.d.ts +51 -0
- package/types/raintonic-formaui-components-side-panel.d.ts.map +1 -0
- package/types/raintonic-formaui-components-sidebar.d.ts +174 -0
- package/types/raintonic-formaui-components-sidebar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-skeleton.d.ts +20 -0
- package/types/raintonic-formaui-components-skeleton.d.ts.map +1 -0
- package/types/raintonic-formaui-components-slider.d.ts +108 -0
- package/types/raintonic-formaui-components-slider.d.ts.map +1 -0
- package/types/raintonic-formaui-components-spinner.d.ts +42 -0
- package/types/raintonic-formaui-components-spinner.d.ts.map +1 -0
- package/types/raintonic-formaui-components-stepper.d.ts +126 -0
- package/types/raintonic-formaui-components-stepper.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tab.d.ts +96 -0
- package/types/raintonic-formaui-components-tab.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tag.d.ts +34 -0
- package/types/raintonic-formaui-components-tag.d.ts.map +1 -0
- package/types/raintonic-formaui-components-time-picker.d.ts +172 -0
- package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -0
- package/types/raintonic-formaui-components-toggle.d.ts +70 -0
- package/types/raintonic-formaui-components-toggle.d.ts.map +1 -0
- package/types/raintonic-formaui-components-toolbar.d.ts +128 -0
- package/types/raintonic-formaui-components-toolbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tooltip.d.ts +268 -0
- package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-select.d.ts +80 -0
- package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-table.d.ts +90 -0
- package/types/raintonic-formaui-components-tree-table.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree.d.ts +104 -0
- package/types/raintonic-formaui-components-tree.d.ts.map +1 -0
- package/types/raintonic-formaui-core.d.ts +115 -0
- package/types/raintonic-formaui-core.d.ts.map +1 -0
- package/types/raintonic-formaui-services-dialog.d.ts +451 -0
- package/types/raintonic-formaui-services-dialog.d.ts.map +1 -0
- package/types/raintonic-formaui-services-notification.d.ts +221 -0
- package/types/raintonic-formaui-services-notification.d.ts.map +1 -0
- package/types/raintonic-formaui-services-theme.d.ts +126 -0
- package/types/raintonic-formaui-services-theme.d.ts.map +1 -0
- package/types/raintonic-formaui-test-utils.d.ts +24 -0
- package/types/raintonic-formaui-test-utils.d.ts.map +1 -0
- package/types/raintonic-formaui.d.ts +4 -0
- package/types/raintonic-formaui.d.ts.map +1 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, booleanAttribute, output, signal, viewChild, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { FuiIconComponent } from '@raintonic/formaui/components/icon';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* # FuiFileUploadComponent
|
|
7
|
+
*
|
|
8
|
+
* A file upload component with drag-and-drop support, file previews, and validation.
|
|
9
|
+
*
|
|
10
|
+
* ## Features
|
|
11
|
+
* - Drag & drop file upload
|
|
12
|
+
* - Click to browse
|
|
13
|
+
* - File type, size, and count validation
|
|
14
|
+
* - Image preview thumbnails
|
|
15
|
+
* - Accessible (keyboard, ARIA)
|
|
16
|
+
*
|
|
17
|
+
* ## Usage
|
|
18
|
+
*
|
|
19
|
+
* ### Basic
|
|
20
|
+
* ```html
|
|
21
|
+
* <fui-file-upload
|
|
22
|
+
* accept="image/*,.pdf"
|
|
23
|
+
* [multiple]="true"
|
|
24
|
+
* [maxFileSize]="5242880"
|
|
25
|
+
* (filesSelected)="onFiles($event)"
|
|
26
|
+
* ></fui-file-upload>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
class FuiFileUploadComponent {
|
|
30
|
+
// Inputs
|
|
31
|
+
accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
32
|
+
multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
33
|
+
maxFileSize = input(0, ...(ngDevMode ? [{ debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
|
|
34
|
+
maxFiles = input(0, ...(ngDevMode ? [{ debugName: "maxFiles" }] : /* istanbul ignore next */ []));
|
|
35
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
36
|
+
showPreview = input(true, { ...(ngDevMode ? { debugName: "showPreview" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
37
|
+
// Outputs
|
|
38
|
+
filesSelected = output();
|
|
39
|
+
fileRemoved = output();
|
|
40
|
+
filesDropped = output();
|
|
41
|
+
validationErrors = output();
|
|
42
|
+
// Internal state
|
|
43
|
+
_files = signal([], ...(ngDevMode ? [{ debugName: "_files" }] : /* istanbul ignore next */ []));
|
|
44
|
+
_isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "_isDragOver" }] : /* istanbul ignore next */ []));
|
|
45
|
+
// ViewChild
|
|
46
|
+
fileInput = viewChild('fileInput', ...(ngDevMode ? [{ debugName: "fileInput" }] : /* istanbul ignore next */ []));
|
|
47
|
+
// Public methods
|
|
48
|
+
browse() {
|
|
49
|
+
if (this.disabled())
|
|
50
|
+
return;
|
|
51
|
+
this.fileInput()?.nativeElement.click();
|
|
52
|
+
}
|
|
53
|
+
removeFile(index) {
|
|
54
|
+
if (this.disabled())
|
|
55
|
+
return;
|
|
56
|
+
const files = [...this._files()];
|
|
57
|
+
const removed = files.splice(index, 1)[0];
|
|
58
|
+
if (removed) {
|
|
59
|
+
if (removed.previewUrl) {
|
|
60
|
+
URL.revokeObjectURL(removed.previewUrl);
|
|
61
|
+
}
|
|
62
|
+
this._files.set(files);
|
|
63
|
+
this.fileRemoved.emit(removed);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
clearAll() {
|
|
67
|
+
const files = this._files();
|
|
68
|
+
for (const f of files) {
|
|
69
|
+
if (f.previewUrl) {
|
|
70
|
+
URL.revokeObjectURL(f.previewUrl);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
this._files.set([]);
|
|
74
|
+
}
|
|
75
|
+
getFiles() {
|
|
76
|
+
return this._files();
|
|
77
|
+
}
|
|
78
|
+
// Template event handlers
|
|
79
|
+
_onFileInputChange(event) {
|
|
80
|
+
const input = event.target;
|
|
81
|
+
if (input.files && input.files.length > 0) {
|
|
82
|
+
this._processFiles(input.files);
|
|
83
|
+
}
|
|
84
|
+
// Reset input so the same file can be selected again
|
|
85
|
+
input.value = '';
|
|
86
|
+
}
|
|
87
|
+
_onDragEnter(event) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
event.stopPropagation();
|
|
90
|
+
if (!this.disabled()) {
|
|
91
|
+
this._isDragOver.set(true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
_onDragOver(event) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
if (!this.disabled()) {
|
|
98
|
+
this._isDragOver.set(true);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
_onDragLeave(event) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
event.stopPropagation();
|
|
104
|
+
this._isDragOver.set(false);
|
|
105
|
+
}
|
|
106
|
+
_onDrop(event) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
this._isDragOver.set(false);
|
|
110
|
+
if (this.disabled())
|
|
111
|
+
return;
|
|
112
|
+
const files = event.dataTransfer?.files;
|
|
113
|
+
if (files && files.length > 0) {
|
|
114
|
+
this._processFiles(files);
|
|
115
|
+
this.filesDropped.emit(this._files());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
_formatFileSize(bytes) {
|
|
119
|
+
if (bytes === 0)
|
|
120
|
+
return '0 B';
|
|
121
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
122
|
+
const k = 1024;
|
|
123
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
124
|
+
const size = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
|
|
125
|
+
return `${size} ${units[i]}`;
|
|
126
|
+
}
|
|
127
|
+
// Private methods
|
|
128
|
+
_processFiles(fileList) {
|
|
129
|
+
const newFiles = [];
|
|
130
|
+
const errors = [];
|
|
131
|
+
const currentFiles = this._files();
|
|
132
|
+
const filesToProcess = Array.from(fileList);
|
|
133
|
+
for (const file of filesToProcess) {
|
|
134
|
+
// Max files check
|
|
135
|
+
const maxFiles = this.maxFiles();
|
|
136
|
+
if (maxFiles > 0 && currentFiles.length + newFiles.length >= maxFiles) {
|
|
137
|
+
errors.push({
|
|
138
|
+
file,
|
|
139
|
+
error: 'maxFiles',
|
|
140
|
+
message: `Maximum ${maxFiles} file(s) allowed`,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const validationError = this._validateFile(file);
|
|
145
|
+
if (validationError) {
|
|
146
|
+
errors.push(validationError);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const uploadFile = {
|
|
150
|
+
file,
|
|
151
|
+
name: file.name,
|
|
152
|
+
size: file.size,
|
|
153
|
+
type: file.type,
|
|
154
|
+
};
|
|
155
|
+
if (this.showPreview() && this._isImage(file)) {
|
|
156
|
+
uploadFile.previewUrl = this._generatePreview(file);
|
|
157
|
+
}
|
|
158
|
+
newFiles.push(uploadFile);
|
|
159
|
+
}
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
this.validationErrors.emit(errors);
|
|
162
|
+
}
|
|
163
|
+
if (newFiles.length > 0) {
|
|
164
|
+
if (this.multiple()) {
|
|
165
|
+
this._files.set([...currentFiles, ...newFiles]);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Single mode: replace existing
|
|
169
|
+
for (const f of currentFiles) {
|
|
170
|
+
if (f.previewUrl)
|
|
171
|
+
URL.revokeObjectURL(f.previewUrl);
|
|
172
|
+
}
|
|
173
|
+
this._files.set([newFiles[0]]);
|
|
174
|
+
}
|
|
175
|
+
this.filesSelected.emit(this._files());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
_validateFile(file) {
|
|
179
|
+
// Type check
|
|
180
|
+
const accept = this.accept();
|
|
181
|
+
if (accept && !this._matchesAccept(file, accept)) {
|
|
182
|
+
return {
|
|
183
|
+
file,
|
|
184
|
+
error: 'type',
|
|
185
|
+
message: `File type "${file.type || file.name.split('.').pop()}" is not allowed`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// Size check
|
|
189
|
+
const maxSize = this.maxFileSize();
|
|
190
|
+
if (maxSize > 0 && file.size > maxSize) {
|
|
191
|
+
return {
|
|
192
|
+
file,
|
|
193
|
+
error: 'size',
|
|
194
|
+
message: `File size ${this._formatFileSize(file.size)} exceeds maximum ${this._formatFileSize(maxSize)}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
_matchesAccept(file, accept) {
|
|
200
|
+
const acceptTypes = accept.split(',').map((t) => t.trim().toLowerCase());
|
|
201
|
+
for (const acceptType of acceptTypes) {
|
|
202
|
+
// Wildcard MIME type (e.g. "image/*")
|
|
203
|
+
if (acceptType.endsWith('/*')) {
|
|
204
|
+
const category = acceptType.slice(0, acceptType.indexOf('/'));
|
|
205
|
+
if (file.type.toLowerCase().startsWith(category + '/')) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Extension match (e.g. ".pdf")
|
|
210
|
+
else if (acceptType.startsWith('.')) {
|
|
211
|
+
if (file.name.toLowerCase().endsWith(acceptType)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Exact MIME match
|
|
216
|
+
else if (file.type.toLowerCase() === acceptType) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
_isImage(file) {
|
|
223
|
+
return file.type.startsWith('image/');
|
|
224
|
+
}
|
|
225
|
+
_generatePreview(file) {
|
|
226
|
+
return URL.createObjectURL(file);
|
|
227
|
+
}
|
|
228
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiFileUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
229
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiFileUploadComponent, isStandalone: true, selector: "fui-file-upload", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesSelected: "filesSelected", fileRemoved: "fileRemoved", filesDropped: "filesDropped", validationErrors: "validationErrors" }, host: { properties: { "class.fui-file-upload--disabled": "disabled()", "class.fui-file-upload--drag-over": "_isDragOver()", "class.fui-file-upload--has-files": "_files().length > 0" }, classAttribute: "fui-file-upload" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<input\n #fileInput\n type=\"file\"\n class=\"fui-file-upload__native-input\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [attr.disabled]=\"disabled() ? '' : null\"\n (change)=\"_onFileInputChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n/>\n\n<div\n class=\"fui-file-upload__dropzone\"\n (click)=\"browse()\"\n (dragenter)=\"_onDragEnter($event)\"\n (dragover)=\"_onDragOver($event)\"\n (dragleave)=\"_onDragLeave($event)\"\n (drop)=\"_onDrop($event)\"\n (keydown.enter)=\"browse()\"\n (keydown.space)=\"browse(); $event.preventDefault()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n role=\"button\"\n [attr.aria-label]=\"'Drop files here or click to browse'\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <fui-icon name=\"upload-simple\" size=\"lg\" class=\"fui-file-upload__icon\"></fui-icon>\n <span class=\"fui-file-upload__text\"> Drag & drop files here or <strong>browse</strong> </span>\n @if (accept()) {\n <span class=\"fui-file-upload__hint\">Accepted: {{ accept() }}</span>\n }\n @if (maxFileSize() > 0) {\n <span class=\"fui-file-upload__hint\">Max size: {{ _formatFileSize(maxFileSize()) }}</span>\n }\n</div>\n\n@if (_files().length > 0) {\n <ul class=\"fui-file-upload__file-list\" role=\"list\">\n @for (fileItem of _files(); track fileItem.name; let i = $index) {\n <li class=\"fui-file-upload__file-item\" [class.fui-file-upload__file-item--error]=\"fileItem.error\">\n @if (showPreview() && fileItem.previewUrl) {\n <img [src]=\"fileItem.previewUrl\" class=\"fui-file-upload__preview\" [alt]=\"fileItem.name\" />\n } @else {\n <fui-icon name=\"file\" size=\"sm\" class=\"fui-file-upload__file-icon\"></fui-icon>\n }\n <div class=\"fui-file-upload__file-info\">\n <span class=\"fui-file-upload__file-name\">{{ fileItem.name }}</span>\n <span class=\"fui-file-upload__file-size\">{{ _formatFileSize(fileItem.size) }}</span>\n @if (fileItem.error) {\n <span class=\"fui-file-upload__file-error\">{{ fileItem.error }}</span>\n }\n </div>\n <button\n type=\"button\"\n class=\"fui-file-upload__remove-btn\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"'Remove ' + fileItem.name\"\n [disabled]=\"disabled()\"\n >\n <fui-icon name=\"x\" size=\"sm\"></fui-icon>\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-file-upload{display:block;width:100%}.fui-file-upload__native-input{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.fui-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--fui-gap-8);padding:var(--fui-padding-32, 2rem);border:2px dashed var(--fui-border-color);border-radius:var(--fui-border-radius-md);background-color:var(--fui-surface-bg);cursor:pointer;text-align:center;transition:border-color,background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-file-upload__dropzone:hover{border-color:var(--fui-primary-60, var(--fui-primary));background-color:var(--fui-primary-10, rgba(124, 58, 237, .04))}.fui-file-upload__dropzone:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-file-upload__icon{color:var(--fui-text-secondary)}.fui-file-upload__text{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);color:var(--fui-text-secondary)}.fui-file-upload__text strong{color:var(--fui-primary);font-weight:var(--fui-font-weight-semibold, 600)}.fui-file-upload__hint{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-01);color:var(--fui-text-disabled)}.fui-file-upload__file-list{list-style:none;margin:var(--fui-gap-12, .75rem) 0 0;padding:0;display:flex;flex-direction:column;gap:var(--fui-gap-4)}.fui-file-upload__file-item{display:flex;align-items:center;gap:var(--fui-gap-12, .75rem);padding:var(--fui-padding-8, .5rem) var(--fui-padding-12, .75rem);border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-sm);background-color:var(--fui-surface-card, var(--fui-surface-01));transition:border-color,background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-file-upload__file-item--error{border-color:var(--fui-state-error);background-color:var(--fui-danger-10, rgba(239, 68, 68, .04))}.fui-file-upload__preview{width:48px;height:48px;object-fit:cover;border-radius:var(--fui-border-radius-sm);flex-shrink:0}.fui-file-upload__file-icon{flex-shrink:0;color:var(--fui-text-secondary)}.fui-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fui-file-upload__file-name{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-medium, 500);color:var(--fui-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-file-upload__file-size{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-01);color:var(--fui-text-secondary)}.fui-file-upload__file-error{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-01);color:var(--fui-state-error)}.fui-file-upload__remove-btn{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-file-upload__remove-btn{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-secondary);opacity:.7;transition:opacity,color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-file-upload__remove-btn:hover:not(:disabled){opacity:1;color:var(--fui-state-error)}.fui-file-upload__remove-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-file-upload__remove-btn:disabled{opacity:.4;cursor:not-allowed}.fui-file-upload--drag-over .fui-file-upload__dropzone{border-color:var(--fui-primary);border-style:solid;background-color:var(--fui-primary-10, rgba(124, 58, 237, .08))}.fui-file-upload--disabled{opacity:var(--fui-opacity-disabled, .5)}.fui-file-upload--disabled .fui-file-upload__dropzone{cursor:not-allowed;pointer-events:none}.fui-file-upload--disabled .fui-file-upload__remove-btn{cursor:not-allowed}@media(prefers-reduced-motion:reduce){.fui-file-upload__dropzone,.fui-file-upload__file-item,.fui-file-upload__remove-btn{transition:none}}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
230
|
+
}
|
|
231
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiFileUploadComponent, decorators: [{
|
|
232
|
+
type: Component,
|
|
233
|
+
args: [{ selector: 'fui-file-upload', standalone: true, imports: [FuiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
234
|
+
class: 'fui-file-upload',
|
|
235
|
+
'[class.fui-file-upload--disabled]': 'disabled()',
|
|
236
|
+
'[class.fui-file-upload--drag-over]': '_isDragOver()',
|
|
237
|
+
'[class.fui-file-upload--has-files]': '_files().length > 0',
|
|
238
|
+
}, template: "<input\n #fileInput\n type=\"file\"\n class=\"fui-file-upload__native-input\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [attr.disabled]=\"disabled() ? '' : null\"\n (change)=\"_onFileInputChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n/>\n\n<div\n class=\"fui-file-upload__dropzone\"\n (click)=\"browse()\"\n (dragenter)=\"_onDragEnter($event)\"\n (dragover)=\"_onDragOver($event)\"\n (dragleave)=\"_onDragLeave($event)\"\n (drop)=\"_onDrop($event)\"\n (keydown.enter)=\"browse()\"\n (keydown.space)=\"browse(); $event.preventDefault()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n role=\"button\"\n [attr.aria-label]=\"'Drop files here or click to browse'\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <fui-icon name=\"upload-simple\" size=\"lg\" class=\"fui-file-upload__icon\"></fui-icon>\n <span class=\"fui-file-upload__text\"> Drag & drop files here or <strong>browse</strong> </span>\n @if (accept()) {\n <span class=\"fui-file-upload__hint\">Accepted: {{ accept() }}</span>\n }\n @if (maxFileSize() > 0) {\n <span class=\"fui-file-upload__hint\">Max size: {{ _formatFileSize(maxFileSize()) }}</span>\n }\n</div>\n\n@if (_files().length > 0) {\n <ul class=\"fui-file-upload__file-list\" role=\"list\">\n @for (fileItem of _files(); track fileItem.name; let i = $index) {\n <li class=\"fui-file-upload__file-item\" [class.fui-file-upload__file-item--error]=\"fileItem.error\">\n @if (showPreview() && fileItem.previewUrl) {\n <img [src]=\"fileItem.previewUrl\" class=\"fui-file-upload__preview\" [alt]=\"fileItem.name\" />\n } @else {\n <fui-icon name=\"file\" size=\"sm\" class=\"fui-file-upload__file-icon\"></fui-icon>\n }\n <div class=\"fui-file-upload__file-info\">\n <span class=\"fui-file-upload__file-name\">{{ fileItem.name }}</span>\n <span class=\"fui-file-upload__file-size\">{{ _formatFileSize(fileItem.size) }}</span>\n @if (fileItem.error) {\n <span class=\"fui-file-upload__file-error\">{{ fileItem.error }}</span>\n }\n </div>\n <button\n type=\"button\"\n class=\"fui-file-upload__remove-btn\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"'Remove ' + fileItem.name\"\n [disabled]=\"disabled()\"\n >\n <fui-icon name=\"x\" size=\"sm\"></fui-icon>\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-file-upload{display:block;width:100%}.fui-file-upload__native-input{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.fui-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--fui-gap-8);padding:var(--fui-padding-32, 2rem);border:2px dashed var(--fui-border-color);border-radius:var(--fui-border-radius-md);background-color:var(--fui-surface-bg);cursor:pointer;text-align:center;transition:border-color,background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-file-upload__dropzone:hover{border-color:var(--fui-primary-60, var(--fui-primary));background-color:var(--fui-primary-10, rgba(124, 58, 237, .04))}.fui-file-upload__dropzone:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-file-upload__icon{color:var(--fui-text-secondary)}.fui-file-upload__text{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);color:var(--fui-text-secondary)}.fui-file-upload__text strong{color:var(--fui-primary);font-weight:var(--fui-font-weight-semibold, 600)}.fui-file-upload__hint{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-01);color:var(--fui-text-disabled)}.fui-file-upload__file-list{list-style:none;margin:var(--fui-gap-12, .75rem) 0 0;padding:0;display:flex;flex-direction:column;gap:var(--fui-gap-4)}.fui-file-upload__file-item{display:flex;align-items:center;gap:var(--fui-gap-12, .75rem);padding:var(--fui-padding-8, .5rem) var(--fui-padding-12, .75rem);border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-sm);background-color:var(--fui-surface-card, var(--fui-surface-01));transition:border-color,background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-file-upload__file-item--error{border-color:var(--fui-state-error);background-color:var(--fui-danger-10, rgba(239, 68, 68, .04))}.fui-file-upload__preview{width:48px;height:48px;object-fit:cover;border-radius:var(--fui-border-radius-sm);flex-shrink:0}.fui-file-upload__file-icon{flex-shrink:0;color:var(--fui-text-secondary)}.fui-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fui-file-upload__file-name{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-medium, 500);color:var(--fui-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-file-upload__file-size{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-01);color:var(--fui-text-secondary)}.fui-file-upload__file-error{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-01);color:var(--fui-state-error)}.fui-file-upload__remove-btn{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-file-upload__remove-btn{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-secondary);opacity:.7;transition:opacity,color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-file-upload__remove-btn:hover:not(:disabled){opacity:1;color:var(--fui-state-error)}.fui-file-upload__remove-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-file-upload__remove-btn:disabled{opacity:.4;cursor:not-allowed}.fui-file-upload--drag-over .fui-file-upload__dropzone{border-color:var(--fui-primary);border-style:solid;background-color:var(--fui-primary-10, rgba(124, 58, 237, .08))}.fui-file-upload--disabled{opacity:var(--fui-opacity-disabled, .5)}.fui-file-upload--disabled .fui-file-upload__dropzone{cursor:not-allowed;pointer-events:none}.fui-file-upload--disabled .fui-file-upload__remove-btn{cursor:not-allowed}@media(prefers-reduced-motion:reduce){.fui-file-upload__dropzone,.fui-file-upload__file-item,.fui-file-upload__remove-btn{transition:none}}\n"] }]
|
|
239
|
+
}], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreview", required: false }] }], filesSelected: [{ type: i0.Output, args: ["filesSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }], filesDropped: [{ type: i0.Output, args: ["filesDropped"] }], validationErrors: [{ type: i0.Output, args: ["validationErrors"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Generated bundle index. Do not edit.
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
export { FuiFileUploadComponent };
|
|
246
|
+
//# sourceMappingURL=raintonic-formaui-components-file-upload.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raintonic-formaui-components-file-upload.mjs","sources":["../../../lib/components/file-upload/file-upload.component.ts","../../../lib/components/file-upload/file-upload.component.html","../../../lib/components/file-upload/raintonic-formaui-components-file-upload.ts"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ViewEncapsulation,\n input,\n output,\n signal,\n viewChild,\n ElementRef,\n WritableSignal,\n booleanAttribute,\n} from '@angular/core';\n\nimport { FuiIconComponent } from '@raintonic/formaui/components/icon';\nimport { FileUploadFile, FileUploadValidationError } from './file-upload.types';\n\n/**\n * # FuiFileUploadComponent\n *\n * A file upload component with drag-and-drop support, file previews, and validation.\n *\n * ## Features\n * - Drag & drop file upload\n * - Click to browse\n * - File type, size, and count validation\n * - Image preview thumbnails\n * - Accessible (keyboard, ARIA)\n *\n * ## Usage\n *\n * ### Basic\n * ```html\n * <fui-file-upload\n * accept=\"image/*,.pdf\"\n * [multiple]=\"true\"\n * [maxFileSize]=\"5242880\"\n * (filesSelected)=\"onFiles($event)\"\n * ></fui-file-upload>\n * ```\n */\n@Component({\n selector: 'fui-file-upload',\n standalone: true,\n imports: [FuiIconComponent],\n templateUrl: './file-upload.component.html',\n styleUrls: ['./file-upload.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'fui-file-upload',\n '[class.fui-file-upload--disabled]': 'disabled()',\n '[class.fui-file-upload--drag-over]': '_isDragOver()',\n '[class.fui-file-upload--has-files]': '_files().length > 0',\n },\n})\nexport class FuiFileUploadComponent {\n // Inputs\n readonly accept = input('');\n readonly multiple = input<boolean, unknown>(false, { transform: booleanAttribute });\n readonly maxFileSize = input(0);\n readonly maxFiles = input(0);\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n readonly showPreview = input<boolean, unknown>(true, { transform: booleanAttribute });\n\n // Outputs\n readonly filesSelected = output<FileUploadFile[]>();\n readonly fileRemoved = output<FileUploadFile>();\n readonly filesDropped = output<FileUploadFile[]>();\n readonly validationErrors = output<FileUploadValidationError[]>();\n\n // Internal state\n readonly _files: WritableSignal<FileUploadFile[]> = signal([]);\n readonly _isDragOver: WritableSignal<boolean> = signal(false);\n\n // ViewChild\n readonly fileInput = viewChild<ElementRef<HTMLInputElement>>('fileInput');\n\n // Public methods\n browse(): void {\n if (this.disabled()) return;\n this.fileInput()?.nativeElement.click();\n }\n\n removeFile(index: number): void {\n if (this.disabled()) return;\n const files = [...this._files()];\n const removed = files.splice(index, 1)[0];\n if (removed) {\n if (removed.previewUrl) {\n URL.revokeObjectURL(removed.previewUrl);\n }\n this._files.set(files);\n this.fileRemoved.emit(removed);\n }\n }\n\n clearAll(): void {\n const files = this._files();\n for (const f of files) {\n if (f.previewUrl) {\n URL.revokeObjectURL(f.previewUrl);\n }\n }\n this._files.set([]);\n }\n\n getFiles(): FileUploadFile[] {\n return this._files();\n }\n\n // Template event handlers\n _onFileInputChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n this._processFiles(input.files);\n }\n // Reset input so the same file can be selected again\n input.value = '';\n }\n\n _onDragEnter(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.disabled()) {\n this._isDragOver.set(true);\n }\n }\n\n _onDragOver(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.disabled()) {\n this._isDragOver.set(true);\n }\n }\n\n _onDragLeave(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this._isDragOver.set(false);\n }\n\n _onDrop(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this._isDragOver.set(false);\n\n if (this.disabled()) return;\n\n const files = event.dataTransfer?.files;\n if (files && files.length > 0) {\n this._processFiles(files);\n this.filesDropped.emit(this._files());\n }\n }\n\n _formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 B';\n const units = ['B', 'KB', 'MB', 'GB'];\n const k = 1024;\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n const size = parseFloat((bytes / Math.pow(k, i)).toFixed(1));\n return `${size} ${units[i]}`;\n }\n\n // Private methods\n private _processFiles(fileList: FileList): void {\n const newFiles: FileUploadFile[] = [];\n const errors: FileUploadValidationError[] = [];\n const currentFiles = this._files();\n\n const filesToProcess = Array.from(fileList);\n\n for (const file of filesToProcess) {\n // Max files check\n const maxFiles = this.maxFiles();\n if (maxFiles > 0 && currentFiles.length + newFiles.length >= maxFiles) {\n errors.push({\n file,\n error: 'maxFiles',\n message: `Maximum ${maxFiles} file(s) allowed`,\n });\n continue;\n }\n\n const validationError = this._validateFile(file);\n if (validationError) {\n errors.push(validationError);\n continue;\n }\n\n const uploadFile: FileUploadFile = {\n file,\n name: file.name,\n size: file.size,\n type: file.type,\n };\n\n if (this.showPreview() && this._isImage(file)) {\n uploadFile.previewUrl = this._generatePreview(file);\n }\n\n newFiles.push(uploadFile);\n }\n\n if (errors.length > 0) {\n this.validationErrors.emit(errors);\n }\n\n if (newFiles.length > 0) {\n if (this.multiple()) {\n this._files.set([...currentFiles, ...newFiles]);\n } else {\n // Single mode: replace existing\n for (const f of currentFiles) {\n if (f.previewUrl) URL.revokeObjectURL(f.previewUrl);\n }\n this._files.set([newFiles[0]]);\n }\n this.filesSelected.emit(this._files());\n }\n }\n\n private _validateFile(file: File): FileUploadValidationError | null {\n // Type check\n const accept = this.accept();\n if (accept && !this._matchesAccept(file, accept)) {\n return {\n file,\n error: 'type',\n message: `File type \"${file.type || file.name.split('.').pop()}\" is not allowed`,\n };\n }\n\n // Size check\n const maxSize = this.maxFileSize();\n if (maxSize > 0 && file.size > maxSize) {\n return {\n file,\n error: 'size',\n message: `File size ${this._formatFileSize(file.size)} exceeds maximum ${this._formatFileSize(maxSize)}`,\n };\n }\n\n return null;\n }\n\n private _matchesAccept(file: File, accept: string): boolean {\n const acceptTypes = accept.split(',').map((t) => t.trim().toLowerCase());\n\n for (const acceptType of acceptTypes) {\n // Wildcard MIME type (e.g. \"image/*\")\n if (acceptType.endsWith('/*')) {\n const category = acceptType.slice(0, acceptType.indexOf('/'));\n if (file.type.toLowerCase().startsWith(category + '/')) {\n return true;\n }\n }\n // Extension match (e.g. \".pdf\")\n else if (acceptType.startsWith('.')) {\n if (file.name.toLowerCase().endsWith(acceptType)) {\n return true;\n }\n }\n // Exact MIME match\n else if (file.type.toLowerCase() === acceptType) {\n return true;\n }\n }\n\n return false;\n }\n\n private _isImage(file: File): boolean {\n return file.type.startsWith('image/');\n }\n\n private _generatePreview(file: File): string {\n return URL.createObjectURL(file);\n }\n}\n","<input\n #fileInput\n type=\"file\"\n class=\"fui-file-upload__native-input\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [attr.disabled]=\"disabled() ? '' : null\"\n (change)=\"_onFileInputChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n/>\n\n<div\n class=\"fui-file-upload__dropzone\"\n (click)=\"browse()\"\n (dragenter)=\"_onDragEnter($event)\"\n (dragover)=\"_onDragOver($event)\"\n (dragleave)=\"_onDragLeave($event)\"\n (drop)=\"_onDrop($event)\"\n (keydown.enter)=\"browse()\"\n (keydown.space)=\"browse(); $event.preventDefault()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n role=\"button\"\n [attr.aria-label]=\"'Drop files here or click to browse'\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <fui-icon name=\"upload-simple\" size=\"lg\" class=\"fui-file-upload__icon\"></fui-icon>\n <span class=\"fui-file-upload__text\"> Drag & drop files here or <strong>browse</strong> </span>\n @if (accept()) {\n <span class=\"fui-file-upload__hint\">Accepted: {{ accept() }}</span>\n }\n @if (maxFileSize() > 0) {\n <span class=\"fui-file-upload__hint\">Max size: {{ _formatFileSize(maxFileSize()) }}</span>\n }\n</div>\n\n@if (_files().length > 0) {\n <ul class=\"fui-file-upload__file-list\" role=\"list\">\n @for (fileItem of _files(); track fileItem.name; let i = $index) {\n <li class=\"fui-file-upload__file-item\" [class.fui-file-upload__file-item--error]=\"fileItem.error\">\n @if (showPreview() && fileItem.previewUrl) {\n <img [src]=\"fileItem.previewUrl\" class=\"fui-file-upload__preview\" [alt]=\"fileItem.name\" />\n } @else {\n <fui-icon name=\"file\" size=\"sm\" class=\"fui-file-upload__file-icon\"></fui-icon>\n }\n <div class=\"fui-file-upload__file-info\">\n <span class=\"fui-file-upload__file-name\">{{ fileItem.name }}</span>\n <span class=\"fui-file-upload__file-size\">{{ _formatFileSize(fileItem.size) }}</span>\n @if (fileItem.error) {\n <span class=\"fui-file-upload__file-error\">{{ fileItem.error }}</span>\n }\n </div>\n <button\n type=\"button\"\n class=\"fui-file-upload__remove-btn\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"'Remove ' + fileItem.name\"\n [disabled]=\"disabled()\"\n >\n <fui-icon name=\"x\" size=\"sm\"></fui-icon>\n </button>\n </li>\n }\n </ul>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|