osl-base-extended 2.0.36 → 3.0.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.
|
@@ -3637,11 +3637,11 @@ class OslSetup {
|
|
|
3637
3637
|
});
|
|
3638
3638
|
}
|
|
3639
3639
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3640
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardContainerRef", first: true, predicate: ["cardContainerRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }] });
|
|
3640
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardContainerRef", first: true, predicate: ["cardContainerRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }] });
|
|
3641
3641
|
}
|
|
3642
3642
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, decorators: [{
|
|
3643
3643
|
type: Component,
|
|
3644
|
-
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"] }]
|
|
3644
|
+
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"] }]
|
|
3645
3645
|
}], propDecorators: { formBodyTpl: [{
|
|
3646
3646
|
type: ViewChild,
|
|
3647
3647
|
args: ['formBodyTpl']
|
|
@@ -5371,6 +5371,217 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5371
5371
|
args: ['click', ['$event']]
|
|
5372
5372
|
}] } });
|
|
5373
5373
|
|
|
5374
|
+
class OslDocumentUploader {
|
|
5375
|
+
label = '';
|
|
5376
|
+
required = false;
|
|
5377
|
+
disabled = false;
|
|
5378
|
+
multiple = false;
|
|
5379
|
+
accept = '';
|
|
5380
|
+
maxSize = 0;
|
|
5381
|
+
minFiles = 0;
|
|
5382
|
+
maxFiles = 0;
|
|
5383
|
+
showUploadButton = false;
|
|
5384
|
+
uploadButtonLabel = 'Upload Files';
|
|
5385
|
+
savedDocuments = [];
|
|
5386
|
+
skeletonLoading = false;
|
|
5387
|
+
skeletonTheme = 'light';
|
|
5388
|
+
uploadCallback = new EventEmitter();
|
|
5389
|
+
viewCallback = new EventEmitter();
|
|
5390
|
+
deleteCallback = new EventEmitter();
|
|
5391
|
+
downloadCallback = new EventEmitter();
|
|
5392
|
+
filesChanged = new EventEmitter();
|
|
5393
|
+
pendingFiles = [];
|
|
5394
|
+
isDragOver = false;
|
|
5395
|
+
sizeErrors = [];
|
|
5396
|
+
touched = false;
|
|
5397
|
+
get maxFilesReached() {
|
|
5398
|
+
return this.multiple && this.maxFiles > 0 && this.pendingFiles.length >= this.maxFiles;
|
|
5399
|
+
}
|
|
5400
|
+
get isBelowMin() {
|
|
5401
|
+
return this.minFiles > 0 && this.pendingFiles.length > 0 && this.pendingFiles.length < this.minFiles;
|
|
5402
|
+
}
|
|
5403
|
+
get isInvalid() {
|
|
5404
|
+
if (!this.touched)
|
|
5405
|
+
return false;
|
|
5406
|
+
if (this.required && this.pendingFiles.length === 0)
|
|
5407
|
+
return true;
|
|
5408
|
+
return this.isBelowMin;
|
|
5409
|
+
}
|
|
5410
|
+
get requiredError() {
|
|
5411
|
+
return this.touched && this.required && this.pendingFiles.length === 0;
|
|
5412
|
+
}
|
|
5413
|
+
get minFilesError() {
|
|
5414
|
+
return this.touched && this.isBelowMin;
|
|
5415
|
+
}
|
|
5416
|
+
get maxSizeLabel() {
|
|
5417
|
+
if (!this.maxSize)
|
|
5418
|
+
return '';
|
|
5419
|
+
if (this.maxSize >= 1024 * 1024)
|
|
5420
|
+
return `Max ${(this.maxSize / 1024 / 1024).toFixed(1)} MB`;
|
|
5421
|
+
if (this.maxSize >= 1024)
|
|
5422
|
+
return `Max ${(this.maxSize / 1024).toFixed(0)} KB`;
|
|
5423
|
+
return `Max ${this.maxSize} B`;
|
|
5424
|
+
}
|
|
5425
|
+
getFileSize(bytes) {
|
|
5426
|
+
if (bytes >= 1024 * 1024)
|
|
5427
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
5428
|
+
if (bytes >= 1024)
|
|
5429
|
+
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
5430
|
+
return `${bytes} B`;
|
|
5431
|
+
}
|
|
5432
|
+
getFileType(name) {
|
|
5433
|
+
const ext = name.split('.').pop()?.toLowerCase() || '';
|
|
5434
|
+
if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico'].includes(ext))
|
|
5435
|
+
return 'image';
|
|
5436
|
+
if (ext === 'pdf')
|
|
5437
|
+
return 'pdf';
|
|
5438
|
+
if (['doc', 'docx'].includes(ext))
|
|
5439
|
+
return 'word';
|
|
5440
|
+
if (['xls', 'xlsx', 'csv'].includes(ext))
|
|
5441
|
+
return 'excel';
|
|
5442
|
+
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext))
|
|
5443
|
+
return 'archive';
|
|
5444
|
+
return 'generic';
|
|
5445
|
+
}
|
|
5446
|
+
formatDate(value) {
|
|
5447
|
+
if (!value)
|
|
5448
|
+
return '';
|
|
5449
|
+
const d = new Date(value);
|
|
5450
|
+
if (isNaN(d.getTime()))
|
|
5451
|
+
return String(value);
|
|
5452
|
+
return d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
|
5453
|
+
}
|
|
5454
|
+
onFileChange(event) {
|
|
5455
|
+
this.touched = true;
|
|
5456
|
+
const input = event.target;
|
|
5457
|
+
this.processFiles(input.files);
|
|
5458
|
+
input.value = '';
|
|
5459
|
+
}
|
|
5460
|
+
onDragOver(event) {
|
|
5461
|
+
event.preventDefault();
|
|
5462
|
+
if (!this.disabled && !this.maxFilesReached)
|
|
5463
|
+
this.isDragOver = true;
|
|
5464
|
+
}
|
|
5465
|
+
onDragLeave(event) {
|
|
5466
|
+
const target = event.currentTarget;
|
|
5467
|
+
const related = event.relatedTarget;
|
|
5468
|
+
if (!related || !target.contains(related))
|
|
5469
|
+
this.isDragOver = false;
|
|
5470
|
+
}
|
|
5471
|
+
onDrop(event) {
|
|
5472
|
+
event.preventDefault();
|
|
5473
|
+
this.isDragOver = false;
|
|
5474
|
+
if (this.disabled || this.maxFilesReached)
|
|
5475
|
+
return;
|
|
5476
|
+
this.touched = true;
|
|
5477
|
+
this.processFiles(event.dataTransfer?.files ?? null);
|
|
5478
|
+
}
|
|
5479
|
+
processFiles(files) {
|
|
5480
|
+
if (!files || files.length === 0)
|
|
5481
|
+
return;
|
|
5482
|
+
this.sizeErrors = [];
|
|
5483
|
+
const newFiles = [];
|
|
5484
|
+
for (let i = 0; i < files.length; i++) {
|
|
5485
|
+
const file = files[i];
|
|
5486
|
+
if (this.maxSize > 0 && file.size > this.maxSize) {
|
|
5487
|
+
this.sizeErrors.push(`"${file.name}" exceeds the ${this.maxSizeLabel} limit.`);
|
|
5488
|
+
continue;
|
|
5489
|
+
}
|
|
5490
|
+
newFiles.push(file);
|
|
5491
|
+
}
|
|
5492
|
+
if (this.multiple) {
|
|
5493
|
+
const remaining = this.maxFiles > 0 ? this.maxFiles - this.pendingFiles.length : newFiles.length;
|
|
5494
|
+
const toAdd = newFiles.slice(0, remaining);
|
|
5495
|
+
if (newFiles.length > remaining) {
|
|
5496
|
+
this.sizeErrors.push(`Only ${remaining} more file(s) can be added. ${newFiles.length - remaining} skipped.`);
|
|
5497
|
+
}
|
|
5498
|
+
this.pendingFiles = [...this.pendingFiles, ...toAdd];
|
|
5499
|
+
}
|
|
5500
|
+
else {
|
|
5501
|
+
this.pendingFiles = newFiles.slice(0, 1);
|
|
5502
|
+
}
|
|
5503
|
+
this.filesChanged.emit(this.pendingFiles);
|
|
5504
|
+
}
|
|
5505
|
+
removeFile(index) {
|
|
5506
|
+
this.pendingFiles = this.pendingFiles.filter((_, i) => i !== index);
|
|
5507
|
+
this.filesChanged.emit(this.pendingFiles);
|
|
5508
|
+
}
|
|
5509
|
+
triggerInput(fileInput) {
|
|
5510
|
+
if (!this.disabled && !this.maxFilesReached)
|
|
5511
|
+
fileInput.click();
|
|
5512
|
+
}
|
|
5513
|
+
upload() {
|
|
5514
|
+
if (this.pendingFiles.length === 0)
|
|
5515
|
+
return;
|
|
5516
|
+
const formData = new FormData();
|
|
5517
|
+
if (this.multiple) {
|
|
5518
|
+
this.pendingFiles.forEach(file => formData.append('files', file, file.name));
|
|
5519
|
+
}
|
|
5520
|
+
else {
|
|
5521
|
+
formData.append('file', this.pendingFiles[0], this.pendingFiles[0].name);
|
|
5522
|
+
}
|
|
5523
|
+
this.uploadCallback.emit(formData);
|
|
5524
|
+
}
|
|
5525
|
+
onView(doc) { this.viewCallback.emit(doc); }
|
|
5526
|
+
onDelete(doc) { this.deleteCallback.emit(doc); }
|
|
5527
|
+
onDownload(doc) { this.downloadCallback.emit(doc); }
|
|
5528
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslDocumentUploader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5529
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslDocumentUploader, isStandalone: false, selector: "osl-document-uploader", inputs: { label: "label", required: "required", disabled: "disabled", multiple: "multiple", accept: "accept", maxSize: "maxSize", minFiles: "minFiles", maxFiles: "maxFiles", showUploadButton: "showUploadButton", uploadButtonLabel: "uploadButtonLabel", savedDocuments: "savedDocuments", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme" }, outputs: { uploadCallback: "uploadCallback", viewCallback: "viewCallback", deleteCallback: "deleteCallback", downloadCallback: "downloadCallback", filesChanged: "filesChanged" }, ngImport: i0, template: "<div class=\"du-container\" [class.du--disabled]=\"disabled\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\n\n @if (label) {\n <label class=\"du-label\" [class.du-label--error]=\"isInvalid\">\n <span class=\"du-label__text\" [title]=\"label\">{{ label }}</span>\n @if (required) { <span class=\"du-label__req\">*</span> }\n </label>\n }\n\n <input type=\"file\" [accept]=\"accept\" [multiple]=\"multiple\" [disabled]=\"disabled || maxFilesReached\"\n (change)=\"onFileChange($event)\" #fileInput class=\"du-file-input\">\n\n <!-- \u2500\u2500 Drop Zone \u2500\u2500 -->\n <div class=\"du-dropzone\"\n [class.du-dropzone--over]=\"isDragOver\"\n [class.du-dropzone--error]=\"isInvalid\"\n [class.du-dropzone--disabled]=\"disabled\"\n [class.du-dropzone--maxed]=\"maxFilesReached\"\n (click)=\"triggerInput(fileInput)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n\n <div class=\"du-particles\">\n <span class=\"du-p du-p--1\"></span>\n <span class=\"du-p du-p--2\"></span>\n <span class=\"du-p du-p--3\"></span>\n <span class=\"du-p du-p--4\"></span>\n <span class=\"du-p du-p--5\"></span>\n <span class=\"du-p du-p--6\"></span>\n </div>\n\n <div class=\"du-dropzone__inner\">\n\n @if (maxFilesReached) {\n <!-- Max files reached state -->\n <div class=\"du-maxed-icon\">\n <svg viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"32\" cy=\"32\" r=\"28\" fill=\"#f0fdf4\" stroke=\"#86efac\" stroke-width=\"2\"/>\n <path d=\"M20 32l8 8 16-16\" stroke=\"#22c55e\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n <p class=\"du-dropzone__headline\">\n <span class=\"du-gradient-text du-gradient-text--green\">Limit reached</span>\n </p>\n <p class=\"du-maxed-sub\">{{ maxFiles }} / {{ maxFiles }} files added</p>\n } @else {\n\n <!-- Cloud SVG -->\n <div class=\"du-cloud-wrap\" [class.du-cloud-wrap--over]=\"isDragOver\">\n <svg class=\"du-cloud-svg\" viewBox=\"0 0 90 72\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path class=\"du-cloud-body\"\n d=\"M63 42H27C19.8 42 14 36.2 14 29C14 22.2 19.2 16.7 25.8 16.1C25.9 9.4 31.3 4 38 4C42.8 4 47 6.7 49.2 10.7C50.8 9.6 52.8 9 55 9C61 9 65.9 13.9 65.9 19.9V20.1C72.1 20.9 77 26.3 77 33C77 38.1 70.7 42 63 42Z\"\n stroke=\"#667eea\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <line class=\"du-arrow\" x1=\"45\" y1=\"64\" x2=\"45\" y2=\"46\" stroke=\"#764ba2\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <polyline class=\"du-arrow\" points=\"37,55 45,46 53,55\"\n stroke=\"#764ba2\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>\n <circle class=\"du-cloud-glint\" cx=\"30\" cy=\"26\" r=\"3\" fill=\"#a5b4fc\" opacity=\"0.5\"/>\n </svg>\n </div>\n\n @if (!isDragOver) {\n <p class=\"du-dropzone__headline\">\n <span class=\"du-gradient-text\">Click to browse</span><span class=\"du-or\"> or drag & drop</span>\n </p>\n <div class=\"du-dropzone__meta\">\n @if (multiple) {\n @if (maxFiles > 0 && minFiles > 0) {\n <span class=\"du-chip du-chip--info\">{{ minFiles }}\u2013{{ maxFiles }} files</span>\n } @else if (maxFiles > 0) {\n <span class=\"du-chip du-chip--info\">Max {{ maxFiles }} files</span>\n } @else if (minFiles > 0) {\n <span class=\"du-chip du-chip--info\">Min {{ minFiles }} files</span>\n } @else {\n <span class=\"du-chip du-chip--info\">Multiple files</span>\n }\n } @else {\n <span class=\"du-chip du-chip--info\">Single file</span>\n }\n @if (accept) { <span class=\"du-chip\">{{ accept }}</span> }\n @if (maxSize) { <span class=\"du-chip\">{{ maxSizeLabel }}</span> }\n </div>\n } @else {\n <p class=\"du-dropzone__headline du-dropzone__headline--drop\">\n <span class=\"du-gradient-text\">Release to drop!</span>\n </p>\n }\n\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Errors \u2500\u2500 -->\n @for (err of sizeErrors; track err) {\n <div class=\"du-msg du-msg--error\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"17\" r=\"1.2\" fill=\"currentColor\"/>\n </svg>\n {{ err }}\n </div>\n }\n @if (requiredError) {\n <div class=\"du-msg du-msg--error\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"17\" r=\"1.2\" fill=\"currentColor\"/>\n </svg>\n {{ label || 'File' }} is required!\n </div>\n }\n @if (minFilesError) {\n <div class=\"du-msg du-msg--error\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"17\" r=\"1.2\" fill=\"currentColor\"/>\n </svg>\n At least {{ minFiles }} files are required ({{ pendingFiles.length }} added).\n </div>\n }\n\n <!-- \u2500\u2500 Pending Files \u2500\u2500 -->\n @if (pendingFiles.length > 0) {\n <div class=\"du-section\">\n <div class=\"du-section__hdr\">\n <span class=\"du-section__title\">Queued Files</span>\n @if (multiple && maxFiles > 0) {\n <span class=\"du-count-label\" [class.du-count-label--full]=\"maxFilesReached\">\n {{ pendingFiles.length }} / {{ maxFiles }}\n </span>\n } @else {\n <span class=\"du-count du-count--purple\">{{ pendingFiles.length }}</span>\n }\n </div>\n <div class=\"du-file-list\">\n @for (file of pendingFiles; track file.name; let i = $index) {\n <div class=\"du-file-card\" [attr.data-ftype]=\"getFileType(file.name)\" [style.animation-delay]=\"i * 40 + 'ms'\">\n\n <div class=\"du-file-icon\">\n @switch (getFileType(file.name)) {\n @case ('pdf') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#FEE2E2\"/>\n <path d=\"M26 4v10h10\" fill=\"#FECACA\"/>\n <rect x=\"4\" y=\"28\" width=\"32\" height=\"12\" rx=\"2\" fill=\"#EF4444\"/>\n <text x=\"20\" y=\"38\" text-anchor=\"middle\" fill=\"white\" font-size=\"7\" font-weight=\"800\" font-family=\"sans-serif\">PDF</text>\n </svg>\n }\n @case ('word') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#DBEAFE\"/>\n <path d=\"M26 4v10h10\" fill=\"#BFDBFE\"/>\n <rect x=\"4\" y=\"28\" width=\"32\" height=\"12\" rx=\"2\" fill=\"#3B82F6\"/>\n <text x=\"20\" y=\"38\" text-anchor=\"middle\" fill=\"white\" font-size=\"7\" font-weight=\"800\" font-family=\"sans-serif\">DOC</text>\n </svg>\n }\n @case ('excel') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#DCFCE7\"/>\n <path d=\"M26 4v10h10\" fill=\"#BBF7D0\"/>\n <rect x=\"4\" y=\"28\" width=\"32\" height=\"12\" rx=\"2\" fill=\"#22C55E\"/>\n <text x=\"20\" y=\"38\" text-anchor=\"middle\" fill=\"white\" font-size=\"7\" font-weight=\"800\" font-family=\"sans-serif\">XLS</text>\n </svg>\n }\n @case ('image') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#FEF9C3\"/>\n <path d=\"M26 4v10h10\" fill=\"#FEF08A\"/>\n <rect x=\"6\" y=\"18\" width=\"28\" height=\"18\" rx=\"2\" fill=\"#FDE047\"/>\n <circle cx=\"13\" cy=\"24\" r=\"3\" fill=\"#CA8A04\"/>\n <path d=\"M6 36l9-9 5 5 4-4 10 8H6Z\" fill=\"#EAB308\" opacity=\"0.8\"/>\n </svg>\n }\n @case ('archive') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#EDE9FE\"/>\n <path d=\"M26 4v10h10\" fill=\"#DDD6FE\"/>\n <rect x=\"14\" y=\"12\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#A78BFA\"/>\n <rect x=\"14\" y=\"18\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#C4B5FD\"/>\n <rect x=\"14\" y=\"24\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#A78BFA\"/>\n <rect x=\"14\" y=\"30\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#C4B5FD\"/>\n <rect x=\"16\" y=\"26\" width=\"8\" height=\"6\" rx=\"1\" fill=\"#7C3AED\"/>\n </svg>\n }\n @default {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#F1F5F9\"/>\n <path d=\"M26 4v10h10\" fill=\"#E2E8F0\"/>\n <line x1=\"10\" y1=\"24\" x2=\"30\" y2=\"24\" stroke=\"#94A3B8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <line x1=\"10\" y1=\"30\" x2=\"26\" y2=\"30\" stroke=\"#94A3B8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <line x1=\"10\" y1=\"36\" x2=\"22\" y2=\"36\" stroke=\"#94A3B8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"du-file-info\">\n <span class=\"du-file-name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"du-file-size\">{{ getFileSize(file.size) }}</span>\n </div>\n\n <button type=\"button\" class=\"du-remove-btn\" (click)=\"removeFile(i)\" title=\"Remove\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <path d=\"M15 9L9 15M9 9l6 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Upload Button \u2500\u2500 -->\n @if (showUploadButton && pendingFiles.length > 0) {\n <button type=\"button\" class=\"du-upload-btn\" (click)=\"upload()\" [disabled]=\"disabled\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"17\" height=\"17\">\n <path d=\"M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>\n <polyline points=\"17,8 12,3 7,8\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>\n </svg>\n {{ uploadButtonLabel }}\n <span class=\"du-upload-btn__badge\">{{ pendingFiles.length }}</span>\n </button>\n }\n\n <!-- \u2500\u2500 Saved Documents \u2500\u2500 -->\n @if (savedDocuments.length > 0) {\n <div class=\"du-section\">\n <div class=\"du-section__hdr\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"du-section__icon\">\n <path d=\"M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z\" stroke=\"#10b981\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <polyline points=\"14,2 14,8 20,8\" stroke=\"#10b981\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n <span class=\"du-section__title\">Saved Documents</span>\n <span class=\"du-count du-count--green\">{{ savedDocuments.length }}</span>\n </div>\n <div class=\"du-saved-list\">\n @for (doc of savedDocuments; track doc.id; let i = $index) {\n <div class=\"du-saved-card\" [style.animation-delay]=\"i * 50 + 'ms'\">\n\n <div class=\"du-saved-icon\">\n @switch (getFileType(doc.name)) {\n @case ('pdf') {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#FEE2E2\"/>\n <path d=\"M20 3v8h8\" fill=\"#FECACA\"/>\n <rect x=\"2\" y=\"22\" width=\"28\" height=\"10\" rx=\"2\" fill=\"#EF4444\"/>\n <text x=\"16\" y=\"30\" text-anchor=\"middle\" fill=\"white\" font-size=\"6\" font-weight=\"800\" font-family=\"sans-serif\">PDF</text>\n </svg>\n }\n @case ('word') {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#DBEAFE\"/>\n <path d=\"M20 3v8h8\" fill=\"#BFDBFE\"/>\n <rect x=\"2\" y=\"22\" width=\"28\" height=\"10\" rx=\"2\" fill=\"#3B82F6\"/>\n <text x=\"16\" y=\"30\" text-anchor=\"middle\" fill=\"white\" font-size=\"6\" font-weight=\"800\" font-family=\"sans-serif\">DOC</text>\n </svg>\n }\n @case ('excel') {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#DCFCE7\"/>\n <path d=\"M20 3v8h8\" fill=\"#BBF7D0\"/>\n <rect x=\"2\" y=\"22\" width=\"28\" height=\"10\" rx=\"2\" fill=\"#22C55E\"/>\n <text x=\"16\" y=\"30\" text-anchor=\"middle\" fill=\"white\" font-size=\"6\" font-weight=\"800\" font-family=\"sans-serif\">XLS</text>\n </svg>\n }\n @case ('image') {\n <svg viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"32\" height=\"32\" rx=\"6\" fill=\"#FEF9C3\"/>\n <rect x=\"4\" y=\"7\" width=\"24\" height=\"18\" rx=\"2\" fill=\"#FDE047\"/>\n <circle cx=\"10\" cy=\"13\" r=\"2.5\" fill=\"#CA8A04\"/>\n <path d=\"M4 25l8-8 5 5 4-4 11 7H4Z\" fill=\"#EAB308\" opacity=\"0.7\"/>\n </svg>\n }\n @case ('archive') {\n <svg viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"32\" height=\"32\" rx=\"6\" fill=\"#EDE9FE\"/>\n <rect x=\"4\" y=\"8\" width=\"24\" height=\"5\" rx=\"1.5\" fill=\"#A78BFA\"/>\n <rect x=\"4\" y=\"14\" width=\"24\" height=\"14\" rx=\"2\" fill=\"#C4B5FD\"/>\n <rect x=\"11\" y=\"18\" width=\"10\" height=\"6\" rx=\"1.5\" fill=\"#7C3AED\"/>\n <line x1=\"16\" y1=\"18\" x2=\"16\" y2=\"24\" stroke=\"white\" stroke-width=\"1.5\"/>\n </svg>\n }\n @default {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#F1F5F9\"/>\n <path d=\"M20 3v8h8\" fill=\"#E2E8F0\"/>\n <line x1=\"7\" y1=\"20\" x2=\"25\" y2=\"20\" stroke=\"#94A3B8\" stroke-width=\"1.8\" stroke-linecap=\"round\"/>\n <line x1=\"7\" y1=\"25\" x2=\"21\" y2=\"25\" stroke=\"#94A3B8\" stroke-width=\"1.8\" stroke-linecap=\"round\"/>\n <line x1=\"7\" y1=\"30\" x2=\"17\" y2=\"30\" stroke=\"#94A3B8\" stroke-width=\"1.8\" stroke-linecap=\"round\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"du-saved-info\">\n <span class=\"du-saved-name\" [title]=\"doc.name\">{{ doc.name }}</span>\n @if (doc.addBy || doc.addOn) {\n <span class=\"du-saved-meta\">\n @if (doc.addBy) { <span class=\"du-saved-by\">{{ doc.addBy }}</span> }\n @if (doc.addBy && doc.addOn) { <span class=\"du-saved-sep\">\u00B7</span> }\n @if (doc.addOn) { <span class=\"du-saved-on\">{{ formatDate(doc.addOn) }}</span> }\n </span>\n }\n </div>\n\n <div class=\"du-saved-actions\">\n <button type=\"button\" class=\"du-act du-act--view\" (click)=\"onView(doc)\" title=\"View\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"du-act du-act--download\" (click)=\"onDownload(doc)\" title=\"Download\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <polyline points=\"7,10 12,15 17,10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"du-act du-act--delete\" (click)=\"onDelete(doc)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <polyline points=\"3,6 5,6 21,6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M10 11v6M14 11v6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n </div>\n\n </div>\n }\n </div>\n </div>\n }\n\n</div>\n", styles: ["@keyframes du-float{0%,to{transform:translateY(0)}50%{transform:translateY(-9px)}}@keyframes du-arrow-bounce{0%,to{transform:translateY(0)}50%{transform:translateY(-6px)}}@keyframes du-particle{0%,to{transform:translate(0) scale(1);opacity:.25}33%{transform:translate(4px,-14px) scale(1.15);opacity:.5}66%{transform:translate(-4px,-7px) scale(.88);opacity:.2}}@keyframes du-slide-in{0%{opacity:0;transform:translate(18px)}to{opacity:1;transform:translate(0)}}@keyframes du-fade-up{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes du-pop-in{0%{opacity:0;transform:scale(.8)}70%{transform:scale(1.06)}to{opacity:1;transform:scale(1)}}@keyframes du-glow-pulse{0%,to{box-shadow:0 0 #667eea59}50%{box-shadow:0 0 0 8px #667eea00}}@keyframes du-btn-pulse{0%,to{box-shadow:0 4px 20px #667eea66}50%{box-shadow:0 6px 30px #764ba299}}@keyframes du-gradient-shift{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes du-ripple{0%{transform:scale(.6);opacity:.7}to{transform:scale(2.2);opacity:0}}@keyframes du-glint{0%,80%,to{opacity:0;transform:scale(.5)}40%{opacity:.7;transform:scale(1.4)}}.du-container{display:flex;flex-direction:column;gap:10px;width:100%}.du-container.du--disabled{opacity:.55;pointer-events:none}.du-label{display:flex;align-items:center;gap:2px;font-size:var(--osl-label-font-size, 13px);font-weight:500;color:#374151;overflow:hidden}.du-label--error{color:var(--osl-error-color, #ef4444)}.du-label__text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.du-label__req{color:var(--osl-error-color, #ef4444);flex-shrink:0;font-weight:700}.du-file-input{display:none}.du-dropzone{position:relative;width:100%;min-height:158px;border-radius:18px;border:2px dashed rgba(102,126,234,.35);background:linear-gradient(145deg,#fafbff,#f5f3ff);cursor:pointer;overflow:hidden;transition:border-color .3s ease,background .3s ease,box-shadow .3s ease,transform .25s ease}.du-dropzone:hover:not(.du-dropzone--disabled){border-color:#667eeab3;background:linear-gradient(145deg,#f5f3ff,#ede9fe);transform:translateY(-2px);box-shadow:0 10px 30px #667eea1f}.du-dropzone--over{border:2px solid #667eea!important;background:linear-gradient(145deg,#ede9fe,#ddd6fe)!important;transform:scale(1.012) translateY(-3px)!important;box-shadow:0 18px 40px #667eea38,inset 0 0 0 1px #667eea4d!important}.du-dropzone--over .du-cloud-body{stroke:#764ba2}.du-dropzone--over .du-cloud-glint{animation:du-glint .6s ease infinite}.du-dropzone--error{border-color:#ef444480;background:linear-gradient(145deg,#fff5f5,#fee2e2);animation:du-glow-pulse 1.8s ease infinite}.du-dropzone--disabled{cursor:not-allowed;background:#f9fafb!important;border-color:#e5e7eb!important;box-shadow:none!important;transform:none!important}.du-particles{position:absolute;inset:0;pointer-events:none;overflow:hidden}.du-p{position:absolute;border-radius:50%;background:radial-gradient(circle,#667eea38,#764ba21f);animation:du-particle var(--dur, 4s) ease-in-out infinite}.du-p--1{width:22px;height:22px;top:12%;left:6%;--dur: 4.2s;animation-delay:0s}.du-p--2{width:14px;height:14px;top:65%;left:88%;--dur: 5.1s;animation-delay:.9s}.du-p--3{width:18px;height:18px;top:18%;left:82%;--dur: 3.8s;animation-delay:1.6s}.du-p--4{width:10px;height:10px;top:75%;left:12%;--dur: 6.3s;animation-delay:.4s}.du-p--5{width:16px;height:16px;top:50%;left:48%;--dur: 4.7s;animation-delay:2.1s}.du-p--6{width:8px;height:8px;top:30%;left:38%;--dur: 5.5s;animation-delay:1.1s}.du-dropzone__inner{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:26px 20px;min-height:158px}.du-cloud-wrap{width:80px;height:64px;animation:du-float 3.2s ease-in-out infinite;filter:drop-shadow(0 4px 10px rgba(102,126,234,.2));transition:filter .3s ease;flex-shrink:0}.du-cloud-wrap--over{animation-duration:1.2s;filter:drop-shadow(0 6px 18px rgba(102,126,234,.45))}.du-cloud-svg{width:100%;height:100%}.du-cloud-body{transition:stroke .3s ease}.du-cloud-glint{transform-origin:center}.du-arrow{animation:du-arrow-bounce 1.6s ease-in-out infinite}.du-dropzone__headline{margin:0;font-size:14px;color:#6b7280;line-height:1.5;text-align:center}.du-dropzone__headline--drop .du-gradient-text{animation:du-glow-pulse .7s ease infinite}.du-gradient-text{background:linear-gradient(135deg,#667eea,#764ba2);background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:700;animation:du-gradient-shift 3s ease infinite}.du-or{color:#9ca3af;font-size:13px}.du-dropzone__meta{display:flex;flex-wrap:wrap;justify-content:center;gap:6px}.du-chip{border-radius:20px;padding:2px 10px;font-size:11px;font-weight:500;background:#667eea14;color:#7c6fcd;border:1px solid rgba(102,126,234,.18);transition:background .2s}.du-chip--info{background:#10b98114;color:#059669;border-color:#10b98133}.du-msg{display:flex;align-items:center;gap:6px;font-size:var(--osl-hint-font-size, 11px);animation:du-fade-up .2s ease}.du-msg svg{flex-shrink:0}.du-msg--error{color:var(--osl-error-color, #ef4444)}.du-section{display:flex;flex-direction:column;gap:8px;animation:du-fade-up .3s ease}.du-section__hdr{display:flex;align-items:center;gap:7px}.du-section__icon{flex-shrink:0}.du-section__title{font-size:11px;font-weight:700;color:#9ca3af;text-transform:uppercase;letter-spacing:.07em}.du-count{width:19px;height:19px;border-radius:50%;font-size:10px;font-weight:800;color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;animation:du-pop-in .3s ease}.du-count--purple{background:linear-gradient(135deg,#667eea,#764ba2)}.du-count--green{background:linear-gradient(135deg,#10b981,#059669)}.du-file-list{display:flex;flex-direction:column;gap:7px}.du-file-card{display:flex;align-items:center;gap:11px;padding:10px 14px 10px 16px;background:#fff;border-radius:13px;border:1px solid #e9ecf1;animation:du-slide-in .28s ease both;transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease;position:relative}.du-file-card:before{content:\"\";position:absolute;left:0;top:50%;transform:translateY(-50%);width:4px;height:60%;border-radius:0 4px 4px 0;transition:height .2s ease}.du-file-card[data-ftype=pdf]:before{background:#ef4444}.du-file-card[data-ftype=word]:before{background:#3b82f6}.du-file-card[data-ftype=excel]:before{background:#22c55e}.du-file-card[data-ftype=image]:before{background:#f59e0b}.du-file-card[data-ftype=archive]:before{background:#8b5cf6}.du-file-card[data-ftype=generic]:before{background:#94a3b8}.du-file-card:hover{border-color:#c4b5fd;box-shadow:0 3px 14px #667eea1a;transform:translate(3px)}.du-file-card:hover:before{height:75%}.du-file-icon{width:38px;height:46px;flex-shrink:0}.du-file-icon svg{width:100%;height:100%}.du-file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.du-file-name{font-size:13px;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.du-file-size{font-size:11px;color:#94a3b8;font-weight:500}.du-remove-btn{width:30px;height:30px;flex-shrink:0;border-radius:50%;border:1.5px solid #fecaca;background:#fef2f2;color:#fca5a5;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:6px;transition:all .22s ease}.du-remove-btn svg{width:100%;height:100%}.du-remove-btn:hover{background:#fee2e2;border-color:#ef4444;color:#ef4444;transform:rotate(90deg) scale(1.1);box-shadow:0 3px 10px #ef444440}.du-upload-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 24px;width:100%;border:none;border-radius:13px;background:linear-gradient(135deg,#667eea,#764ba2);background-size:200% 200%;color:#fff;font-size:14px;font-weight:700;letter-spacing:.03em;cursor:pointer;transition:transform .25s ease,box-shadow .25s ease;animation:du-btn-pulse 2.5s ease-in-out infinite,du-gradient-shift 4s ease infinite}.du-upload-btn svg{flex-shrink:0;transition:transform .25s ease}.du-upload-btn:hover:not([disabled]){transform:translateY(-3px);box-shadow:0 10px 28px #667eea80;animation:du-gradient-shift 1.5s ease infinite}.du-upload-btn:hover:not([disabled]) svg{transform:translateY(-3px)}.du-upload-btn:active:not([disabled]){transform:translateY(-1px)}.du-upload-btn[disabled]{opacity:.45;cursor:not-allowed;animation:none}.du-upload-btn__badge{min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:#ffffff38;font-size:11px;font-weight:800;display:flex;align-items:center;justify-content:center;animation:du-pop-in .3s ease}.du-saved-list{display:flex;flex-direction:column;gap:7px}.du-saved-card{display:flex;align-items:center;gap:11px;padding:10px 12px;background:linear-gradient(135deg,#fff,#f8faff);border-radius:13px;border:1px solid #e9ecf1;opacity:0;animation:du-fade-up .32s ease forwards;transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease}.du-saved-card:hover{border-color:#a5b4fc;box-shadow:0 4px 18px #667eea1a;transform:translateY(-1px)}.du-saved-icon{width:32px;height:38px;flex-shrink:0}.du-saved-icon svg{width:100%;height:100%}.du-saved-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.du-saved-name{font-size:13px;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.du-saved-size{font-size:11px;color:#94a3b8;font-weight:500}.du-saved-actions{display:flex;align-items:center;gap:5px;flex-shrink:0}.du-act{width:32px;height:32px;border-radius:9px;border:1.5px solid transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:7px;transition:all .22s ease;position:relative;overflow:hidden}.du-act svg{width:100%;height:100%;transition:transform .22s ease}.du-act:after{content:\"\";position:absolute;inset:50%;border-radius:50%;transition:all 0s;pointer-events:none}.du-act:active:after{inset:0;border-radius:9px;background:#ffffff59;animation:du-ripple .4s ease}.du-act--view{background:#eff6ff;color:#3b82f6;border-color:#bfdbfe}.du-act--view:hover{background:#dbeafe;border-color:#93c5fd;box-shadow:0 3px 12px #3b82f64d}.du-act--view:hover svg{transform:scale(1.12)}.du-act--download{background:#f0fdf4;color:#22c55e;border-color:#bbf7d0}.du-act--download:hover{background:#dcfce7;border-color:#86efac;box-shadow:0 3px 12px #22c55e4d}.du-act--download:hover svg{transform:translateY(2px) scale(1.08)}.du-act--delete{background:#fef2f2;color:#ef4444;border-color:#fecaca}.du-act--delete:hover{background:#fee2e2;border-color:#fca5a5;box-shadow:0 3px 12px #ef44444d}.du-act--delete:hover svg{transform:scale(1.1) rotate(-5deg)}.du-dropzone--maxed{cursor:default;border-color:#22c55e66!important;background:linear-gradient(145deg,#f0fdf4,#dcfce7)!important;pointer-events:none}.du-dropzone--maxed .du-particles .du-p{background:radial-gradient(circle,#22c55e2e,#10b9811a)}.du-maxed-icon{width:56px;height:56px;animation:du-pop-in .4s cubic-bezier(.34,1.56,.64,1)}.du-maxed-icon svg{width:100%;height:100%}.du-gradient-text--green{background:linear-gradient(135deg,#22c55e,#059669)!important;background-size:200% 200%!important;-webkit-background-clip:text!important;-webkit-text-fill-color:transparent!important;background-clip:text!important}.du-maxed-sub{margin:0;font-size:12px;color:#6b7280;font-weight:500}.du-count-label{font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;background:#667eea1a;color:#667eea;border:1px solid rgba(102,126,234,.2);transition:all .25s ease;animation:du-pop-in .3s ease}.du-count-label--full{background:#22c55e1a;color:#16a34a;border-color:#22c55e40}.du-saved-meta{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.du-saved-by{font-size:11px;font-weight:600;color:#667eea}.du-saved-sep{font-size:11px;color:#d1d5db}.du-saved-on{font-size:11px;color:#94a3b8}\n"], dependencies: [{ kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }] });
|
|
5530
|
+
}
|
|
5531
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslDocumentUploader, decorators: [{
|
|
5532
|
+
type: Component,
|
|
5533
|
+
args: [{ selector: 'osl-document-uploader', standalone: false, template: "<div class=\"du-container\" [class.du--disabled]=\"disabled\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\n\n @if (label) {\n <label class=\"du-label\" [class.du-label--error]=\"isInvalid\">\n <span class=\"du-label__text\" [title]=\"label\">{{ label }}</span>\n @if (required) { <span class=\"du-label__req\">*</span> }\n </label>\n }\n\n <input type=\"file\" [accept]=\"accept\" [multiple]=\"multiple\" [disabled]=\"disabled || maxFilesReached\"\n (change)=\"onFileChange($event)\" #fileInput class=\"du-file-input\">\n\n <!-- \u2500\u2500 Drop Zone \u2500\u2500 -->\n <div class=\"du-dropzone\"\n [class.du-dropzone--over]=\"isDragOver\"\n [class.du-dropzone--error]=\"isInvalid\"\n [class.du-dropzone--disabled]=\"disabled\"\n [class.du-dropzone--maxed]=\"maxFilesReached\"\n (click)=\"triggerInput(fileInput)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n\n <div class=\"du-particles\">\n <span class=\"du-p du-p--1\"></span>\n <span class=\"du-p du-p--2\"></span>\n <span class=\"du-p du-p--3\"></span>\n <span class=\"du-p du-p--4\"></span>\n <span class=\"du-p du-p--5\"></span>\n <span class=\"du-p du-p--6\"></span>\n </div>\n\n <div class=\"du-dropzone__inner\">\n\n @if (maxFilesReached) {\n <!-- Max files reached state -->\n <div class=\"du-maxed-icon\">\n <svg viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"32\" cy=\"32\" r=\"28\" fill=\"#f0fdf4\" stroke=\"#86efac\" stroke-width=\"2\"/>\n <path d=\"M20 32l8 8 16-16\" stroke=\"#22c55e\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n <p class=\"du-dropzone__headline\">\n <span class=\"du-gradient-text du-gradient-text--green\">Limit reached</span>\n </p>\n <p class=\"du-maxed-sub\">{{ maxFiles }} / {{ maxFiles }} files added</p>\n } @else {\n\n <!-- Cloud SVG -->\n <div class=\"du-cloud-wrap\" [class.du-cloud-wrap--over]=\"isDragOver\">\n <svg class=\"du-cloud-svg\" viewBox=\"0 0 90 72\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path class=\"du-cloud-body\"\n d=\"M63 42H27C19.8 42 14 36.2 14 29C14 22.2 19.2 16.7 25.8 16.1C25.9 9.4 31.3 4 38 4C42.8 4 47 6.7 49.2 10.7C50.8 9.6 52.8 9 55 9C61 9 65.9 13.9 65.9 19.9V20.1C72.1 20.9 77 26.3 77 33C77 38.1 70.7 42 63 42Z\"\n stroke=\"#667eea\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <line class=\"du-arrow\" x1=\"45\" y1=\"64\" x2=\"45\" y2=\"46\" stroke=\"#764ba2\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <polyline class=\"du-arrow\" points=\"37,55 45,46 53,55\"\n stroke=\"#764ba2\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>\n <circle class=\"du-cloud-glint\" cx=\"30\" cy=\"26\" r=\"3\" fill=\"#a5b4fc\" opacity=\"0.5\"/>\n </svg>\n </div>\n\n @if (!isDragOver) {\n <p class=\"du-dropzone__headline\">\n <span class=\"du-gradient-text\">Click to browse</span><span class=\"du-or\"> or drag & drop</span>\n </p>\n <div class=\"du-dropzone__meta\">\n @if (multiple) {\n @if (maxFiles > 0 && minFiles > 0) {\n <span class=\"du-chip du-chip--info\">{{ minFiles }}\u2013{{ maxFiles }} files</span>\n } @else if (maxFiles > 0) {\n <span class=\"du-chip du-chip--info\">Max {{ maxFiles }} files</span>\n } @else if (minFiles > 0) {\n <span class=\"du-chip du-chip--info\">Min {{ minFiles }} files</span>\n } @else {\n <span class=\"du-chip du-chip--info\">Multiple files</span>\n }\n } @else {\n <span class=\"du-chip du-chip--info\">Single file</span>\n }\n @if (accept) { <span class=\"du-chip\">{{ accept }}</span> }\n @if (maxSize) { <span class=\"du-chip\">{{ maxSizeLabel }}</span> }\n </div>\n } @else {\n <p class=\"du-dropzone__headline du-dropzone__headline--drop\">\n <span class=\"du-gradient-text\">Release to drop!</span>\n </p>\n }\n\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Errors \u2500\u2500 -->\n @for (err of sizeErrors; track err) {\n <div class=\"du-msg du-msg--error\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"17\" r=\"1.2\" fill=\"currentColor\"/>\n </svg>\n {{ err }}\n </div>\n }\n @if (requiredError) {\n <div class=\"du-msg du-msg--error\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"17\" r=\"1.2\" fill=\"currentColor\"/>\n </svg>\n {{ label || 'File' }} is required!\n </div>\n }\n @if (minFilesError) {\n <div class=\"du-msg du-msg--error\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"17\" r=\"1.2\" fill=\"currentColor\"/>\n </svg>\n At least {{ minFiles }} files are required ({{ pendingFiles.length }} added).\n </div>\n }\n\n <!-- \u2500\u2500 Pending Files \u2500\u2500 -->\n @if (pendingFiles.length > 0) {\n <div class=\"du-section\">\n <div class=\"du-section__hdr\">\n <span class=\"du-section__title\">Queued Files</span>\n @if (multiple && maxFiles > 0) {\n <span class=\"du-count-label\" [class.du-count-label--full]=\"maxFilesReached\">\n {{ pendingFiles.length }} / {{ maxFiles }}\n </span>\n } @else {\n <span class=\"du-count du-count--purple\">{{ pendingFiles.length }}</span>\n }\n </div>\n <div class=\"du-file-list\">\n @for (file of pendingFiles; track file.name; let i = $index) {\n <div class=\"du-file-card\" [attr.data-ftype]=\"getFileType(file.name)\" [style.animation-delay]=\"i * 40 + 'ms'\">\n\n <div class=\"du-file-icon\">\n @switch (getFileType(file.name)) {\n @case ('pdf') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#FEE2E2\"/>\n <path d=\"M26 4v10h10\" fill=\"#FECACA\"/>\n <rect x=\"4\" y=\"28\" width=\"32\" height=\"12\" rx=\"2\" fill=\"#EF4444\"/>\n <text x=\"20\" y=\"38\" text-anchor=\"middle\" fill=\"white\" font-size=\"7\" font-weight=\"800\" font-family=\"sans-serif\">PDF</text>\n </svg>\n }\n @case ('word') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#DBEAFE\"/>\n <path d=\"M26 4v10h10\" fill=\"#BFDBFE\"/>\n <rect x=\"4\" y=\"28\" width=\"32\" height=\"12\" rx=\"2\" fill=\"#3B82F6\"/>\n <text x=\"20\" y=\"38\" text-anchor=\"middle\" fill=\"white\" font-size=\"7\" font-weight=\"800\" font-family=\"sans-serif\">DOC</text>\n </svg>\n }\n @case ('excel') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#DCFCE7\"/>\n <path d=\"M26 4v10h10\" fill=\"#BBF7D0\"/>\n <rect x=\"4\" y=\"28\" width=\"32\" height=\"12\" rx=\"2\" fill=\"#22C55E\"/>\n <text x=\"20\" y=\"38\" text-anchor=\"middle\" fill=\"white\" font-size=\"7\" font-weight=\"800\" font-family=\"sans-serif\">XLS</text>\n </svg>\n }\n @case ('image') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#FEF9C3\"/>\n <path d=\"M26 4v10h10\" fill=\"#FEF08A\"/>\n <rect x=\"6\" y=\"18\" width=\"28\" height=\"18\" rx=\"2\" fill=\"#FDE047\"/>\n <circle cx=\"13\" cy=\"24\" r=\"3\" fill=\"#CA8A04\"/>\n <path d=\"M6 36l9-9 5 5 4-4 10 8H6Z\" fill=\"#EAB308\" opacity=\"0.8\"/>\n </svg>\n }\n @case ('archive') {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#EDE9FE\"/>\n <path d=\"M26 4v10h10\" fill=\"#DDD6FE\"/>\n <rect x=\"14\" y=\"12\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#A78BFA\"/>\n <rect x=\"14\" y=\"18\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#C4B5FD\"/>\n <rect x=\"14\" y=\"24\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#A78BFA\"/>\n <rect x=\"14\" y=\"30\" width=\"12\" height=\"4\" rx=\"1\" fill=\"#C4B5FD\"/>\n <rect x=\"16\" y=\"26\" width=\"8\" height=\"6\" rx=\"1\" fill=\"#7C3AED\"/>\n </svg>\n }\n @default {\n <svg viewBox=\"0 0 40 48\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 4h20l10 10v30a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z\" fill=\"#F1F5F9\"/>\n <path d=\"M26 4v10h10\" fill=\"#E2E8F0\"/>\n <line x1=\"10\" y1=\"24\" x2=\"30\" y2=\"24\" stroke=\"#94A3B8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <line x1=\"10\" y1=\"30\" x2=\"26\" y2=\"30\" stroke=\"#94A3B8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <line x1=\"10\" y1=\"36\" x2=\"22\" y2=\"36\" stroke=\"#94A3B8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"du-file-info\">\n <span class=\"du-file-name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"du-file-size\">{{ getFileSize(file.size) }}</span>\n </div>\n\n <button type=\"button\" class=\"du-remove-btn\" (click)=\"removeFile(i)\" title=\"Remove\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <path d=\"M15 9L9 15M9 9l6 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Upload Button \u2500\u2500 -->\n @if (showUploadButton && pendingFiles.length > 0) {\n <button type=\"button\" class=\"du-upload-btn\" (click)=\"upload()\" [disabled]=\"disabled\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"17\" height=\"17\">\n <path d=\"M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>\n <polyline points=\"17,8 12,3 7,8\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>\n </svg>\n {{ uploadButtonLabel }}\n <span class=\"du-upload-btn__badge\">{{ pendingFiles.length }}</span>\n </button>\n }\n\n <!-- \u2500\u2500 Saved Documents \u2500\u2500 -->\n @if (savedDocuments.length > 0) {\n <div class=\"du-section\">\n <div class=\"du-section__hdr\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"du-section__icon\">\n <path d=\"M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z\" stroke=\"#10b981\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <polyline points=\"14,2 14,8 20,8\" stroke=\"#10b981\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n <span class=\"du-section__title\">Saved Documents</span>\n <span class=\"du-count du-count--green\">{{ savedDocuments.length }}</span>\n </div>\n <div class=\"du-saved-list\">\n @for (doc of savedDocuments; track doc.id; let i = $index) {\n <div class=\"du-saved-card\" [style.animation-delay]=\"i * 50 + 'ms'\">\n\n <div class=\"du-saved-icon\">\n @switch (getFileType(doc.name)) {\n @case ('pdf') {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#FEE2E2\"/>\n <path d=\"M20 3v8h8\" fill=\"#FECACA\"/>\n <rect x=\"2\" y=\"22\" width=\"28\" height=\"10\" rx=\"2\" fill=\"#EF4444\"/>\n <text x=\"16\" y=\"30\" text-anchor=\"middle\" fill=\"white\" font-size=\"6\" font-weight=\"800\" font-family=\"sans-serif\">PDF</text>\n </svg>\n }\n @case ('word') {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#DBEAFE\"/>\n <path d=\"M20 3v8h8\" fill=\"#BFDBFE\"/>\n <rect x=\"2\" y=\"22\" width=\"28\" height=\"10\" rx=\"2\" fill=\"#3B82F6\"/>\n <text x=\"16\" y=\"30\" text-anchor=\"middle\" fill=\"white\" font-size=\"6\" font-weight=\"800\" font-family=\"sans-serif\">DOC</text>\n </svg>\n }\n @case ('excel') {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#DCFCE7\"/>\n <path d=\"M20 3v8h8\" fill=\"#BBF7D0\"/>\n <rect x=\"2\" y=\"22\" width=\"28\" height=\"10\" rx=\"2\" fill=\"#22C55E\"/>\n <text x=\"16\" y=\"30\" text-anchor=\"middle\" fill=\"white\" font-size=\"6\" font-weight=\"800\" font-family=\"sans-serif\">XLS</text>\n </svg>\n }\n @case ('image') {\n <svg viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"32\" height=\"32\" rx=\"6\" fill=\"#FEF9C3\"/>\n <rect x=\"4\" y=\"7\" width=\"24\" height=\"18\" rx=\"2\" fill=\"#FDE047\"/>\n <circle cx=\"10\" cy=\"13\" r=\"2.5\" fill=\"#CA8A04\"/>\n <path d=\"M4 25l8-8 5 5 4-4 11 7H4Z\" fill=\"#EAB308\" opacity=\"0.7\"/>\n </svg>\n }\n @case ('archive') {\n <svg viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"32\" height=\"32\" rx=\"6\" fill=\"#EDE9FE\"/>\n <rect x=\"4\" y=\"8\" width=\"24\" height=\"5\" rx=\"1.5\" fill=\"#A78BFA\"/>\n <rect x=\"4\" y=\"14\" width=\"24\" height=\"14\" rx=\"2\" fill=\"#C4B5FD\"/>\n <rect x=\"11\" y=\"18\" width=\"10\" height=\"6\" rx=\"1.5\" fill=\"#7C3AED\"/>\n <line x1=\"16\" y1=\"18\" x2=\"16\" y2=\"24\" stroke=\"white\" stroke-width=\"1.5\"/>\n </svg>\n }\n @default {\n <svg viewBox=\"0 0 32 38\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4 3h16l8 8v24a1.5 1.5 0 01-1.5 1.5H4A1.5 1.5 0 012.5 35V4.5A1.5 1.5 0 014 3z\" fill=\"#F1F5F9\"/>\n <path d=\"M20 3v8h8\" fill=\"#E2E8F0\"/>\n <line x1=\"7\" y1=\"20\" x2=\"25\" y2=\"20\" stroke=\"#94A3B8\" stroke-width=\"1.8\" stroke-linecap=\"round\"/>\n <line x1=\"7\" y1=\"25\" x2=\"21\" y2=\"25\" stroke=\"#94A3B8\" stroke-width=\"1.8\" stroke-linecap=\"round\"/>\n <line x1=\"7\" y1=\"30\" x2=\"17\" y2=\"30\" stroke=\"#94A3B8\" stroke-width=\"1.8\" stroke-linecap=\"round\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"du-saved-info\">\n <span class=\"du-saved-name\" [title]=\"doc.name\">{{ doc.name }}</span>\n @if (doc.addBy || doc.addOn) {\n <span class=\"du-saved-meta\">\n @if (doc.addBy) { <span class=\"du-saved-by\">{{ doc.addBy }}</span> }\n @if (doc.addBy && doc.addOn) { <span class=\"du-saved-sep\">\u00B7</span> }\n @if (doc.addOn) { <span class=\"du-saved-on\">{{ formatDate(doc.addOn) }}</span> }\n </span>\n }\n </div>\n\n <div class=\"du-saved-actions\">\n <button type=\"button\" class=\"du-act du-act--view\" (click)=\"onView(doc)\" title=\"View\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"du-act du-act--download\" (click)=\"onDownload(doc)\" title=\"Download\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <polyline points=\"7,10 12,15 17,10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"du-act du-act--delete\" (click)=\"onDelete(doc)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <polyline points=\"3,6 5,6 21,6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M10 11v6M14 11v6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n </div>\n\n </div>\n }\n </div>\n </div>\n }\n\n</div>\n", styles: ["@keyframes du-float{0%,to{transform:translateY(0)}50%{transform:translateY(-9px)}}@keyframes du-arrow-bounce{0%,to{transform:translateY(0)}50%{transform:translateY(-6px)}}@keyframes du-particle{0%,to{transform:translate(0) scale(1);opacity:.25}33%{transform:translate(4px,-14px) scale(1.15);opacity:.5}66%{transform:translate(-4px,-7px) scale(.88);opacity:.2}}@keyframes du-slide-in{0%{opacity:0;transform:translate(18px)}to{opacity:1;transform:translate(0)}}@keyframes du-fade-up{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes du-pop-in{0%{opacity:0;transform:scale(.8)}70%{transform:scale(1.06)}to{opacity:1;transform:scale(1)}}@keyframes du-glow-pulse{0%,to{box-shadow:0 0 #667eea59}50%{box-shadow:0 0 0 8px #667eea00}}@keyframes du-btn-pulse{0%,to{box-shadow:0 4px 20px #667eea66}50%{box-shadow:0 6px 30px #764ba299}}@keyframes du-gradient-shift{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes du-ripple{0%{transform:scale(.6);opacity:.7}to{transform:scale(2.2);opacity:0}}@keyframes du-glint{0%,80%,to{opacity:0;transform:scale(.5)}40%{opacity:.7;transform:scale(1.4)}}.du-container{display:flex;flex-direction:column;gap:10px;width:100%}.du-container.du--disabled{opacity:.55;pointer-events:none}.du-label{display:flex;align-items:center;gap:2px;font-size:var(--osl-label-font-size, 13px);font-weight:500;color:#374151;overflow:hidden}.du-label--error{color:var(--osl-error-color, #ef4444)}.du-label__text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.du-label__req{color:var(--osl-error-color, #ef4444);flex-shrink:0;font-weight:700}.du-file-input{display:none}.du-dropzone{position:relative;width:100%;min-height:158px;border-radius:18px;border:2px dashed rgba(102,126,234,.35);background:linear-gradient(145deg,#fafbff,#f5f3ff);cursor:pointer;overflow:hidden;transition:border-color .3s ease,background .3s ease,box-shadow .3s ease,transform .25s ease}.du-dropzone:hover:not(.du-dropzone--disabled){border-color:#667eeab3;background:linear-gradient(145deg,#f5f3ff,#ede9fe);transform:translateY(-2px);box-shadow:0 10px 30px #667eea1f}.du-dropzone--over{border:2px solid #667eea!important;background:linear-gradient(145deg,#ede9fe,#ddd6fe)!important;transform:scale(1.012) translateY(-3px)!important;box-shadow:0 18px 40px #667eea38,inset 0 0 0 1px #667eea4d!important}.du-dropzone--over .du-cloud-body{stroke:#764ba2}.du-dropzone--over .du-cloud-glint{animation:du-glint .6s ease infinite}.du-dropzone--error{border-color:#ef444480;background:linear-gradient(145deg,#fff5f5,#fee2e2);animation:du-glow-pulse 1.8s ease infinite}.du-dropzone--disabled{cursor:not-allowed;background:#f9fafb!important;border-color:#e5e7eb!important;box-shadow:none!important;transform:none!important}.du-particles{position:absolute;inset:0;pointer-events:none;overflow:hidden}.du-p{position:absolute;border-radius:50%;background:radial-gradient(circle,#667eea38,#764ba21f);animation:du-particle var(--dur, 4s) ease-in-out infinite}.du-p--1{width:22px;height:22px;top:12%;left:6%;--dur: 4.2s;animation-delay:0s}.du-p--2{width:14px;height:14px;top:65%;left:88%;--dur: 5.1s;animation-delay:.9s}.du-p--3{width:18px;height:18px;top:18%;left:82%;--dur: 3.8s;animation-delay:1.6s}.du-p--4{width:10px;height:10px;top:75%;left:12%;--dur: 6.3s;animation-delay:.4s}.du-p--5{width:16px;height:16px;top:50%;left:48%;--dur: 4.7s;animation-delay:2.1s}.du-p--6{width:8px;height:8px;top:30%;left:38%;--dur: 5.5s;animation-delay:1.1s}.du-dropzone__inner{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:26px 20px;min-height:158px}.du-cloud-wrap{width:80px;height:64px;animation:du-float 3.2s ease-in-out infinite;filter:drop-shadow(0 4px 10px rgba(102,126,234,.2));transition:filter .3s ease;flex-shrink:0}.du-cloud-wrap--over{animation-duration:1.2s;filter:drop-shadow(0 6px 18px rgba(102,126,234,.45))}.du-cloud-svg{width:100%;height:100%}.du-cloud-body{transition:stroke .3s ease}.du-cloud-glint{transform-origin:center}.du-arrow{animation:du-arrow-bounce 1.6s ease-in-out infinite}.du-dropzone__headline{margin:0;font-size:14px;color:#6b7280;line-height:1.5;text-align:center}.du-dropzone__headline--drop .du-gradient-text{animation:du-glow-pulse .7s ease infinite}.du-gradient-text{background:linear-gradient(135deg,#667eea,#764ba2);background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:700;animation:du-gradient-shift 3s ease infinite}.du-or{color:#9ca3af;font-size:13px}.du-dropzone__meta{display:flex;flex-wrap:wrap;justify-content:center;gap:6px}.du-chip{border-radius:20px;padding:2px 10px;font-size:11px;font-weight:500;background:#667eea14;color:#7c6fcd;border:1px solid rgba(102,126,234,.18);transition:background .2s}.du-chip--info{background:#10b98114;color:#059669;border-color:#10b98133}.du-msg{display:flex;align-items:center;gap:6px;font-size:var(--osl-hint-font-size, 11px);animation:du-fade-up .2s ease}.du-msg svg{flex-shrink:0}.du-msg--error{color:var(--osl-error-color, #ef4444)}.du-section{display:flex;flex-direction:column;gap:8px;animation:du-fade-up .3s ease}.du-section__hdr{display:flex;align-items:center;gap:7px}.du-section__icon{flex-shrink:0}.du-section__title{font-size:11px;font-weight:700;color:#9ca3af;text-transform:uppercase;letter-spacing:.07em}.du-count{width:19px;height:19px;border-radius:50%;font-size:10px;font-weight:800;color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;animation:du-pop-in .3s ease}.du-count--purple{background:linear-gradient(135deg,#667eea,#764ba2)}.du-count--green{background:linear-gradient(135deg,#10b981,#059669)}.du-file-list{display:flex;flex-direction:column;gap:7px}.du-file-card{display:flex;align-items:center;gap:11px;padding:10px 14px 10px 16px;background:#fff;border-radius:13px;border:1px solid #e9ecf1;animation:du-slide-in .28s ease both;transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease;position:relative}.du-file-card:before{content:\"\";position:absolute;left:0;top:50%;transform:translateY(-50%);width:4px;height:60%;border-radius:0 4px 4px 0;transition:height .2s ease}.du-file-card[data-ftype=pdf]:before{background:#ef4444}.du-file-card[data-ftype=word]:before{background:#3b82f6}.du-file-card[data-ftype=excel]:before{background:#22c55e}.du-file-card[data-ftype=image]:before{background:#f59e0b}.du-file-card[data-ftype=archive]:before{background:#8b5cf6}.du-file-card[data-ftype=generic]:before{background:#94a3b8}.du-file-card:hover{border-color:#c4b5fd;box-shadow:0 3px 14px #667eea1a;transform:translate(3px)}.du-file-card:hover:before{height:75%}.du-file-icon{width:38px;height:46px;flex-shrink:0}.du-file-icon svg{width:100%;height:100%}.du-file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.du-file-name{font-size:13px;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.du-file-size{font-size:11px;color:#94a3b8;font-weight:500}.du-remove-btn{width:30px;height:30px;flex-shrink:0;border-radius:50%;border:1.5px solid #fecaca;background:#fef2f2;color:#fca5a5;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:6px;transition:all .22s ease}.du-remove-btn svg{width:100%;height:100%}.du-remove-btn:hover{background:#fee2e2;border-color:#ef4444;color:#ef4444;transform:rotate(90deg) scale(1.1);box-shadow:0 3px 10px #ef444440}.du-upload-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 24px;width:100%;border:none;border-radius:13px;background:linear-gradient(135deg,#667eea,#764ba2);background-size:200% 200%;color:#fff;font-size:14px;font-weight:700;letter-spacing:.03em;cursor:pointer;transition:transform .25s ease,box-shadow .25s ease;animation:du-btn-pulse 2.5s ease-in-out infinite,du-gradient-shift 4s ease infinite}.du-upload-btn svg{flex-shrink:0;transition:transform .25s ease}.du-upload-btn:hover:not([disabled]){transform:translateY(-3px);box-shadow:0 10px 28px #667eea80;animation:du-gradient-shift 1.5s ease infinite}.du-upload-btn:hover:not([disabled]) svg{transform:translateY(-3px)}.du-upload-btn:active:not([disabled]){transform:translateY(-1px)}.du-upload-btn[disabled]{opacity:.45;cursor:not-allowed;animation:none}.du-upload-btn__badge{min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:#ffffff38;font-size:11px;font-weight:800;display:flex;align-items:center;justify-content:center;animation:du-pop-in .3s ease}.du-saved-list{display:flex;flex-direction:column;gap:7px}.du-saved-card{display:flex;align-items:center;gap:11px;padding:10px 12px;background:linear-gradient(135deg,#fff,#f8faff);border-radius:13px;border:1px solid #e9ecf1;opacity:0;animation:du-fade-up .32s ease forwards;transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease}.du-saved-card:hover{border-color:#a5b4fc;box-shadow:0 4px 18px #667eea1a;transform:translateY(-1px)}.du-saved-icon{width:32px;height:38px;flex-shrink:0}.du-saved-icon svg{width:100%;height:100%}.du-saved-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.du-saved-name{font-size:13px;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.du-saved-size{font-size:11px;color:#94a3b8;font-weight:500}.du-saved-actions{display:flex;align-items:center;gap:5px;flex-shrink:0}.du-act{width:32px;height:32px;border-radius:9px;border:1.5px solid transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:7px;transition:all .22s ease;position:relative;overflow:hidden}.du-act svg{width:100%;height:100%;transition:transform .22s ease}.du-act:after{content:\"\";position:absolute;inset:50%;border-radius:50%;transition:all 0s;pointer-events:none}.du-act:active:after{inset:0;border-radius:9px;background:#ffffff59;animation:du-ripple .4s ease}.du-act--view{background:#eff6ff;color:#3b82f6;border-color:#bfdbfe}.du-act--view:hover{background:#dbeafe;border-color:#93c5fd;box-shadow:0 3px 12px #3b82f64d}.du-act--view:hover svg{transform:scale(1.12)}.du-act--download{background:#f0fdf4;color:#22c55e;border-color:#bbf7d0}.du-act--download:hover{background:#dcfce7;border-color:#86efac;box-shadow:0 3px 12px #22c55e4d}.du-act--download:hover svg{transform:translateY(2px) scale(1.08)}.du-act--delete{background:#fef2f2;color:#ef4444;border-color:#fecaca}.du-act--delete:hover{background:#fee2e2;border-color:#fca5a5;box-shadow:0 3px 12px #ef44444d}.du-act--delete:hover svg{transform:scale(1.1) rotate(-5deg)}.du-dropzone--maxed{cursor:default;border-color:#22c55e66!important;background:linear-gradient(145deg,#f0fdf4,#dcfce7)!important;pointer-events:none}.du-dropzone--maxed .du-particles .du-p{background:radial-gradient(circle,#22c55e2e,#10b9811a)}.du-maxed-icon{width:56px;height:56px;animation:du-pop-in .4s cubic-bezier(.34,1.56,.64,1)}.du-maxed-icon svg{width:100%;height:100%}.du-gradient-text--green{background:linear-gradient(135deg,#22c55e,#059669)!important;background-size:200% 200%!important;-webkit-background-clip:text!important;-webkit-text-fill-color:transparent!important;background-clip:text!important}.du-maxed-sub{margin:0;font-size:12px;color:#6b7280;font-weight:500}.du-count-label{font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;background:#667eea1a;color:#667eea;border:1px solid rgba(102,126,234,.2);transition:all .25s ease;animation:du-pop-in .3s ease}.du-count-label--full{background:#22c55e1a;color:#16a34a;border-color:#22c55e40}.du-saved-meta{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.du-saved-by{font-size:11px;font-weight:600;color:#667eea}.du-saved-sep{font-size:11px;color:#d1d5db}.du-saved-on{font-size:11px;color:#94a3b8}\n"] }]
|
|
5534
|
+
}], propDecorators: { label: [{
|
|
5535
|
+
type: Input,
|
|
5536
|
+
args: ['label']
|
|
5537
|
+
}], required: [{
|
|
5538
|
+
type: Input,
|
|
5539
|
+
args: ['required']
|
|
5540
|
+
}], disabled: [{
|
|
5541
|
+
type: Input,
|
|
5542
|
+
args: ['disabled']
|
|
5543
|
+
}], multiple: [{
|
|
5544
|
+
type: Input,
|
|
5545
|
+
args: ['multiple']
|
|
5546
|
+
}], accept: [{
|
|
5547
|
+
type: Input,
|
|
5548
|
+
args: ['accept']
|
|
5549
|
+
}], maxSize: [{
|
|
5550
|
+
type: Input,
|
|
5551
|
+
args: ['maxSize']
|
|
5552
|
+
}], minFiles: [{
|
|
5553
|
+
type: Input,
|
|
5554
|
+
args: ['minFiles']
|
|
5555
|
+
}], maxFiles: [{
|
|
5556
|
+
type: Input,
|
|
5557
|
+
args: ['maxFiles']
|
|
5558
|
+
}], showUploadButton: [{
|
|
5559
|
+
type: Input,
|
|
5560
|
+
args: ['showUploadButton']
|
|
5561
|
+
}], uploadButtonLabel: [{
|
|
5562
|
+
type: Input,
|
|
5563
|
+
args: ['uploadButtonLabel']
|
|
5564
|
+
}], savedDocuments: [{
|
|
5565
|
+
type: Input,
|
|
5566
|
+
args: ['savedDocuments']
|
|
5567
|
+
}], skeletonLoading: [{
|
|
5568
|
+
type: Input,
|
|
5569
|
+
args: ['skeletonLoading']
|
|
5570
|
+
}], skeletonTheme: [{
|
|
5571
|
+
type: Input,
|
|
5572
|
+
args: ['skeletonTheme']
|
|
5573
|
+
}], uploadCallback: [{
|
|
5574
|
+
type: Output
|
|
5575
|
+
}], viewCallback: [{
|
|
5576
|
+
type: Output
|
|
5577
|
+
}], deleteCallback: [{
|
|
5578
|
+
type: Output
|
|
5579
|
+
}], downloadCallback: [{
|
|
5580
|
+
type: Output
|
|
5581
|
+
}], filesChanged: [{
|
|
5582
|
+
type: Output
|
|
5583
|
+
}] } });
|
|
5584
|
+
|
|
5374
5585
|
class FormStructureModule {
|
|
5375
5586
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
5376
5587
|
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, declarations: [DynamicForm,
|
|
@@ -5394,7 +5605,8 @@ class FormStructureModule {
|
|
|
5394
5605
|
OslReportForm,
|
|
5395
5606
|
OslUserLog,
|
|
5396
5607
|
OslMenu,
|
|
5397
|
-
OslMenuTriggerFor
|
|
5608
|
+
OslMenuTriggerFor,
|
|
5609
|
+
OslDocumentUploader], imports: [NgTemplateOutlet,
|
|
5398
5610
|
NgStyle,
|
|
5399
5611
|
NgClass,
|
|
5400
5612
|
DatePipe,
|
|
@@ -5430,7 +5642,7 @@ class FormStructureModule {
|
|
|
5430
5642
|
OslButton,
|
|
5431
5643
|
OslSetup,
|
|
5432
5644
|
OslSearchbar, OslAutocompleteLister, OslReportGrid, OslReportForm,
|
|
5433
|
-
OslMenu, OslMenuTriggerFor] });
|
|
5645
|
+
OslMenu, OslMenuTriggerFor, OslDocumentUploader] });
|
|
5434
5646
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, providers: [
|
|
5435
5647
|
{ provide: AUTOCOMPLETE_LISTER_COMPONENT, useValue: OslAutocompleteLister },
|
|
5436
5648
|
], imports: [FormsModule,
|
|
@@ -5476,6 +5688,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5476
5688
|
OslUserLog,
|
|
5477
5689
|
OslMenu,
|
|
5478
5690
|
OslMenuTriggerFor,
|
|
5691
|
+
OslDocumentUploader,
|
|
5479
5692
|
],
|
|
5480
5693
|
imports: [
|
|
5481
5694
|
NgTemplateOutlet,
|
|
@@ -5516,7 +5729,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5516
5729
|
OslButton,
|
|
5517
5730
|
OslSetup,
|
|
5518
5731
|
OslSearchbar, OslAutocompleteLister, OslReportGrid, OslReportForm,
|
|
5519
|
-
OslMenu, OslMenuTriggerFor],
|
|
5732
|
+
OslMenu, OslMenuTriggerFor, OslDocumentUploader],
|
|
5520
5733
|
providers: [
|
|
5521
5734
|
{ provide: AUTOCOMPLETE_LISTER_COMPONENT, useValue: OslAutocompleteLister },
|
|
5522
5735
|
],
|
|
@@ -6781,5 +6994,5 @@ var validation_util = /*#__PURE__*/Object.freeze({
|
|
|
6781
6994
|
* Generated bundle index. Do not edit.
|
|
6782
6995
|
*/
|
|
6783
6996
|
|
|
6784
|
-
export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslDatetimepicker, OslFileUpload, OslFormGrid, OslGrid, OslMenu, OslMenuTriggerFor, OslRadio, OslReportForm, OslReportGrid, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, OslUserLog, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
|
|
6997
|
+
export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslDatetimepicker, OslDocumentUploader, OslFileUpload, OslFormGrid, OslGrid, OslMenu, OslMenuTriggerFor, OslRadio, OslReportForm, OslReportGrid, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, OslUserLog, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
|
|
6785
6998
|
//# sourceMappingURL=osl-base-extended.mjs.map
|