@dso-design-system/ui 0.0.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/esm2022/lib/alert/alert.component.mjs +54 -0
  2. package/esm2022/lib/badge/badge.component.mjs +22 -0
  3. package/esm2022/lib/badge/badge.directive.mjs +68 -0
  4. package/esm2022/lib/breadcrumb/breadcrumb.component.mjs +29 -0
  5. package/esm2022/lib/checkbox/checkbox.component.mjs +82 -0
  6. package/esm2022/lib/datepicker/datepicker.component.mjs +161 -0
  7. package/esm2022/lib/dialog/dialog.component.mjs +25 -0
  8. package/esm2022/lib/dropdown-list/dropdown-list.component.mjs +89 -0
  9. package/esm2022/lib/file-upload-items/file-upload-items.component.mjs +65 -0
  10. package/esm2022/lib/file-upload-multiple/file-upload-multiple.component.mjs +232 -0
  11. package/esm2022/lib/file-upload-multiple/upload-item.model.mjs +2 -0
  12. package/esm2022/lib/file-upload-multiple/upload-simulator.service.mjs +76 -0
  13. package/esm2022/lib/file-upload-single/file-upload-single.component.mjs +100 -0
  14. package/esm2022/lib/input-text/input-text.component.mjs +93 -0
  15. package/esm2022/lib/pagination/pagination.component.mjs +115 -0
  16. package/esm2022/lib/progress-bar/progress-bar.component.mjs +25 -0
  17. package/esm2022/lib/radio/radio.component.mjs +41 -0
  18. package/esm2022/lib/select-dropdown/select-dropdown.component.mjs +228 -0
  19. package/esm2022/lib/service/toast.service.mjs +20 -0
  20. package/esm2022/lib/side-navigation-bar/side-navigation-bar.component.mjs +113 -0
  21. package/esm2022/lib/spinner/spinner.component.mjs +60 -0
  22. package/esm2022/lib/table/table.component.mjs +136 -0
  23. package/esm2022/lib/tabs/tab.component.mjs +20 -0
  24. package/esm2022/lib/tabs/tabs.component.mjs +40 -0
  25. package/esm2022/lib/tag/tag.component.mjs +27 -0
  26. package/esm2022/lib/text-area/text-area.component.mjs +74 -0
  27. package/esm2022/lib/toast/toast.component.mjs +36 -0
  28. package/esm2022/lib/tooltip/tooltip.component.mjs +38 -0
  29. package/esm2022/lib/tooltip/tooltip.directive.mjs +105 -0
  30. package/esm2022/lib/top-navigation-bar/top-navigation-bar.component.mjs +24 -0
  31. package/esm2022/public-api.mjs +29 -2
  32. package/fesm2022/dso-design-system-ui.mjs +2053 -3
  33. package/fesm2022/dso-design-system-ui.mjs.map +1 -1
  34. package/lib/alert/alert.component.d.ts +20 -0
  35. package/lib/badge/badge.component.d.ts +8 -0
  36. package/lib/badge/badge.directive.d.ts +15 -0
  37. package/lib/breadcrumb/breadcrumb.component.d.ts +15 -0
  38. package/lib/checkbox/checkbox.component.d.ts +42 -0
  39. package/lib/datepicker/datepicker.component.d.ts +48 -0
  40. package/lib/dialog/dialog.component.d.ts +10 -0
  41. package/lib/dropdown-list/dropdown-list.component.d.ts +33 -0
  42. package/lib/file-upload-items/file-upload-items.component.d.ts +27 -0
  43. package/lib/file-upload-multiple/file-upload-multiple.component.d.ts +44 -0
  44. package/lib/file-upload-multiple/upload-item.model.d.ts +7 -0
  45. package/lib/file-upload-multiple/upload-simulator.service.d.ts +34 -0
  46. package/lib/file-upload-single/file-upload-single.component.d.ts +28 -0
  47. package/lib/input-text/input-text.component.d.ts +24 -0
  48. package/lib/pagination/pagination.component.d.ts +31 -0
  49. package/lib/progress-bar/progress-bar.component.d.ts +11 -0
  50. package/lib/radio/radio.component.d.ts +14 -0
  51. package/lib/select-dropdown/select-dropdown.component.d.ts +78 -0
  52. package/lib/service/toast.service.d.ts +16 -0
  53. package/lib/side-navigation-bar/side-navigation-bar.component.d.ts +74 -0
  54. package/lib/spinner/spinner.component.d.ts +23 -0
  55. package/lib/table/table.component.d.ts +43 -0
  56. package/lib/tabs/tab.component.d.ts +9 -0
  57. package/lib/tabs/tabs.component.d.ts +15 -0
  58. package/lib/tag/tag.component.d.ts +10 -0
  59. package/lib/text-area/text-area.component.d.ts +21 -0
  60. package/lib/toast/toast.component.d.ts +13 -0
  61. package/lib/tooltip/tooltip.component.d.ts +15 -0
  62. package/lib/tooltip/tooltip.directive.d.ts +19 -0
  63. package/lib/top-navigation-bar/top-navigation-bar.component.d.ts +16 -0
  64. package/package.json +1 -1
  65. package/public-api.d.ts +27 -0
@@ -1,7 +1,13 @@
1
1
  import * as i1 from '@angular/common';
2
- import { CommonModule } from '@angular/common';
2
+ import { CommonModule, DatePipe } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { Component, Input, EventEmitter, Output } from '@angular/core';
4
+ import { Component, Input, EventEmitter, Output, Directive, ViewChild, HostListener, Injectable, ContentChildren } from '@angular/core';
5
+ import * as i2$1 from '@angular/router';
6
+ import { RouterLink, RouterModule } from '@angular/router';
7
+ import * as i2 from '@angular/forms';
8
+ import { FormsModule } from '@angular/forms';
9
+ import lottie from 'lottie-web';
10
+ import { ReplaySubject } from 'rxjs';
5
11
 
6
12
  class IconComponent {
7
13
  /** Name of the icon to display (matches sprite ID) */
@@ -122,9 +128,2053 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
122
128
  type: Output
123
129
  }] } });
124
130
 
131
+ class AlertComponent {
132
+ type = 'info';
133
+ message = '';
134
+ showActions = false;
135
+ isDismissable = true;
136
+ actions = [];
137
+ isVisible = true;
138
+ isHiding = false;
139
+ close() {
140
+ this.isHiding = true;
141
+ // Wait for fadeOut animation to finish before removing the alert
142
+ setTimeout(() => {
143
+ this.isVisible = false;
144
+ }, 300); // match duration in CSS
145
+ }
146
+ // Pick the icon name based on alert type
147
+ get iconName() {
148
+ switch (this.type) {
149
+ case 'success': return 'icon-slanted-check';
150
+ case 'error': return 'icon-left-align';
151
+ case 'warning': return 'icon-van';
152
+ default: return 'icon-notification';
153
+ }
154
+ }
155
+ /** Handle when a button inside the alert is clicked */
156
+ handleAction(action) {
157
+ if (action.onClick) {
158
+ action.onClick();
159
+ }
160
+ }
161
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AlertComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
162
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: AlertComponent, isStandalone: true, selector: "dso-alert", inputs: { type: "type", message: "message", showActions: "showActions", isDismissable: "isDismissable", actions: "actions" }, ngImport: i0, template: "\r\n<div\r\n*ngIf=\"isVisible\"\r\nclass=\"alert\"\r\n[ngClass]=\"[type, isHiding ? 'hide' : '']\"> \r\n <div class=\"positioning\">\r\n <dso-icon [iconName]=\"iconName\" [size]=\"'medium'\"></dso-icon>\r\n </div>\r\n <span [innerHTML]=\"message\"></span>\r\n\r\n <div class=\"positioning\" *ngIf=\"actions && actions.length > 0 || isDismissable\">\r\n <div class=\"button-wrapper\">\r\n <dso-button\r\n *ngFor=\"let action of actions\"\r\n [btnLabel]=\"action.btnLabel\"\r\n [btnType]=\"action.btnType || 'contrast'\" \r\n [btnSize]=\"action.btnSize || 'mdBtn'\"\r\n [btnIconName]=\"action.btnIconName ?? null\"\r\n [isDisabled]=\"action.isDisabled || false\"\r\n (click)=\"handleAction(action)\">\r\n </dso-button>\r\n </div>\r\n <button (click)=\"close()\" *ngIf=\"isDismissable\" class=\"close-btn\">&times;</button>\r\n </div>\r\n \r\n</div>", styles: ["@charset \"UTF-8\";@keyframes fadeIn{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.alert{padding:12px 16px;border-radius:5px;gap:16px;display:flex;align-items:stretch;font-size:16px;animation:fadeIn .4s ease-in}.alert.hide{animation:fadeOut .4s ease-out forwards}.success{background-color:#77c98a;color:#fff}.error{background-color:#dc3545;color:#fff}.info{background-color:#17a2b8;color:#fff}.warning{background-color:#ffc107;color:#000}.close-btn{width:24px;height:24px;padding:0;margin:0;display:flex;align-items:center;justify-content:center;background:none;border:none;font-size:24px;line-height:1;color:inherit;cursor:pointer}.close-btn:hover{opacity:.7}.content-wrapper{display:flex;align-items:center;flex:1;gap:12px}.positioning{display:flex;align-items:flex-start;align-self:stretch}span{flex-grow:1;line-height:1.4;max-height:7em;overflow-y:auto}.button-wrapper{display:flex;flex-direction:column;gap:8px;padding-right:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }] });
163
+ }
164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AlertComponent, decorators: [{
165
+ type: Component,
166
+ args: [{ selector: 'dso-alert', standalone: true, imports: [CommonModule, ButtonComponent, IconComponent], template: "\r\n<div\r\n*ngIf=\"isVisible\"\r\nclass=\"alert\"\r\n[ngClass]=\"[type, isHiding ? 'hide' : '']\"> \r\n <div class=\"positioning\">\r\n <dso-icon [iconName]=\"iconName\" [size]=\"'medium'\"></dso-icon>\r\n </div>\r\n <span [innerHTML]=\"message\"></span>\r\n\r\n <div class=\"positioning\" *ngIf=\"actions && actions.length > 0 || isDismissable\">\r\n <div class=\"button-wrapper\">\r\n <dso-button\r\n *ngFor=\"let action of actions\"\r\n [btnLabel]=\"action.btnLabel\"\r\n [btnType]=\"action.btnType || 'contrast'\" \r\n [btnSize]=\"action.btnSize || 'mdBtn'\"\r\n [btnIconName]=\"action.btnIconName ?? null\"\r\n [isDisabled]=\"action.isDisabled || false\"\r\n (click)=\"handleAction(action)\">\r\n </dso-button>\r\n </div>\r\n <button (click)=\"close()\" *ngIf=\"isDismissable\" class=\"close-btn\">&times;</button>\r\n </div>\r\n \r\n</div>", styles: ["@charset \"UTF-8\";@keyframes fadeIn{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.alert{padding:12px 16px;border-radius:5px;gap:16px;display:flex;align-items:stretch;font-size:16px;animation:fadeIn .4s ease-in}.alert.hide{animation:fadeOut .4s ease-out forwards}.success{background-color:#77c98a;color:#fff}.error{background-color:#dc3545;color:#fff}.info{background-color:#17a2b8;color:#fff}.warning{background-color:#ffc107;color:#000}.close-btn{width:24px;height:24px;padding:0;margin:0;display:flex;align-items:center;justify-content:center;background:none;border:none;font-size:24px;line-height:1;color:inherit;cursor:pointer}.close-btn:hover{opacity:.7}.content-wrapper{display:flex;align-items:center;flex:1;gap:12px}.positioning{display:flex;align-items:flex-start;align-self:stretch}span{flex-grow:1;line-height:1.4;max-height:7em;overflow-y:auto}.button-wrapper{display:flex;flex-direction:column;gap:8px;padding-right:8px}\n"] }]
167
+ }], propDecorators: { type: [{
168
+ type: Input
169
+ }], message: [{
170
+ type: Input
171
+ }], showActions: [{
172
+ type: Input
173
+ }], isDismissable: [{
174
+ type: Input
175
+ }], actions: [{
176
+ type: Input
177
+ }] } });
178
+
179
+ class BadgeComponent {
180
+ label = null;
181
+ color = 'primary';
182
+ get isDot() {
183
+ return this.label === null || this.label === '' || this.label === undefined;
184
+ }
185
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
186
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: BadgeComponent, isStandalone: true, selector: "dso-badge", inputs: { label: "label", color: "color" }, ngImport: i0, template: "<span class=\"badge\" [ngClass]=\"[color, isDot ? 'dot' : '']\">\r\n <ng-container *ngIf=\"!isDot\">{{ label }}</ng-container>\r\n</span>", styles: [".badge{display:inline-flex;align-items:center;justify-content:center;padding:.25rem .5rem;border-radius:1rem;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#fff;line-height:1;min-width:1.25rem}.primary{background-color:#007bff}.success{background-color:#28a745}.warning{background-color:#ffc107;color:#000}.danger{background-color:#dc3545}.badge.dot{padding:0;width:.5rem;height:.5rem;min-width:0;border-radius:50%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
187
+ }
188
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BadgeComponent, decorators: [{
189
+ type: Component,
190
+ args: [{ selector: 'dso-badge', standalone: true, imports: [CommonModule], template: "<span class=\"badge\" [ngClass]=\"[color, isDot ? 'dot' : '']\">\r\n <ng-container *ngIf=\"!isDot\">{{ label }}</ng-container>\r\n</span>", styles: [".badge{display:inline-flex;align-items:center;justify-content:center;padding:.25rem .5rem;border-radius:1rem;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#fff;line-height:1;min-width:1.25rem}.primary{background-color:#007bff}.success{background-color:#28a745}.warning{background-color:#ffc107;color:#000}.danger{background-color:#dc3545}.badge.dot{padding:0;width:.5rem;height:.5rem;min-width:0;border-radius:50%}\n"] }]
191
+ }], propDecorators: { label: [{
192
+ type: Input
193
+ }], color: [{
194
+ type: Input
195
+ }] } });
196
+
197
+ class BadgeDirective {
198
+ el;
199
+ renderer;
200
+ // The text or number displayed inside the badge (e.g. "5" or empty for a dot)
201
+ label = null;
202
+ // Optional color input, defaulting to 'primary'
203
+ badgeColor = 'info';
204
+ // We'll store a reference to the created badge element here
205
+ badgeEl = null;
206
+ constructor(el, // Reference to the host element (the one with [dsoDirectiveBadge])
207
+ renderer // Used for safe DOM manipulation in Angular
208
+ ) {
209
+ this.el = el;
210
+ this.renderer = renderer;
211
+ // Ensure the host element is relatively positioned so the badge can be placed absolutely
212
+ this.renderer.setStyle(this.el.nativeElement, 'position', 'relative');
213
+ }
214
+ // Called automatically whenever @Input values change (label or badgeColor)
215
+ ngOnChanges(changes) {
216
+ this.updateBadge();
217
+ }
218
+ // Create or update the badge element
219
+ updateBadge() {
220
+ // console.log('BadgeDirective running on', this.el.nativeElement);
221
+ // ✅ STEP 1: If we don’t already have a badge element, create one
222
+ if (!this.badgeEl) {
223
+ this.badgeEl = this.renderer.createElement('span'); // create <span>
224
+ this.renderer.addClass(this.badgeEl, 'badge-directive'); // add base class
225
+ // Append the badge <span> inside the host element (the icon, button, avatar, etc.)
226
+ this.renderer.appendChild(this.el.nativeElement, this.badgeEl);
227
+ }
228
+ // At this point, badgeEl definitely exists, but TypeScript doesn’t know that.
229
+ // So, we use a non-null assertion (!) to tell TypeScript “we’re sure it’s not null”.
230
+ const badge = this.badgeEl;
231
+ // ✅ STEP 2: Update label and style
232
+ // If the label is empty/null, it becomes a dot badge
233
+ if (this.label === null || this.label === '' || this.label === undefined) {
234
+ this.renderer.addClass(badge, 'dot');
235
+ badge.textContent = ''; // no text, just a dot
236
+ }
237
+ else {
238
+ this.renderer.removeClass(badge, 'dot');
239
+ badge.textContent = this.label.toString();
240
+ }
241
+ // ✅ STEP 3: Update color classes
242
+ // First, remove all possible color classes
243
+ badge.classList.remove('info', 'success', 'warning', 'danger');
244
+ // Then, add the one we want
245
+ badge.classList.add(this.badgeColor);
246
+ }
247
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BadgeDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
248
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: BadgeDirective, isStandalone: true, selector: "[dsoDirectiveBadge]", inputs: { label: ["dsoDirectiveBadge", "label"], badgeColor: "badgeColor" }, usesOnChanges: true, ngImport: i0 });
249
+ }
250
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BadgeDirective, decorators: [{
251
+ type: Directive,
252
+ args: [{
253
+ selector: '[dsoDirectiveBadge]', // This directive is used like: <app-icon dsoDirectiveBadge="5"></app-icon>
254
+ standalone: true
255
+ }]
256
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { label: [{
257
+ type: Input,
258
+ args: ['dsoDirectiveBadge']
259
+ }], badgeColor: [{
260
+ type: Input
261
+ }] } });
262
+
263
+ class BreadcrumbComponent {
264
+ items = [];
265
+ itemSelected = new EventEmitter();
266
+ // Default separator → you can allow this to be set via @Input() later
267
+ separator = '/';
268
+ onItemClicked(item) {
269
+ this.itemSelected.emit(item);
270
+ console.log('breadcrumb emitted -> ', item);
271
+ }
272
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
273
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: BreadcrumbComponent, isStandalone: true, selector: "dso-breadcrumb", inputs: { items: "items", separator: "separator" }, outputs: { itemSelected: "itemSelected" }, ngImport: i0, template: "<nav aria-label=\"Breadcrumb\" class=\"breadcrumb\">\r\n <ol>\r\n <li *ngFor=\"let item of items; let last = last\" class=\"crumb\">\r\n\r\n <!-- Normal breadcrumb link -->\r\n <ng-container *ngIf=\"!last && item.url; else lastItem\">\r\n\r\n <dso-button\r\n btnType=\"text\"\r\n btnSize=\"smBtn\"\r\n [btnLabel]=\"item.label\"\r\n [isDisabled]=\"false\"\r\n (onClick)=\"onItemClicked(item)\"\r\n [routerLink]=\"item.url\">\r\n </dso-button>\r\n\r\n </ng-container>\r\n\r\n <!-- Last breadcrumb item (non-clickable) -->\r\n <ng-template #lastItem>\r\n <span class=\"current\" aria-current=\"page\">\r\n {{ item.label }}\r\n </span>\r\n </ng-template>\r\n\r\n <!-- Separator -->\r\n <span class=\"separator\" *ngIf=\"!last\">{{ separator }}</span>\r\n\r\n </li>\r\n </ol>\r\n <!-- <dso-button btnType=\"text\" btnLabel=\"Go Home\" [routerLink]=\"['/page1']\"></dso-button> -->\r\n\r\n</nav>\r\n\r\n\r\n", styles: [".breadcrumb{font-size:14px;color:var(--ui-text-muted, #666)}.breadcrumb ol{list-style:none;padding:0;margin:0;display:flex;align-items:center;flex-wrap:wrap}.breadcrumb .crumb{display:flex;align-items:center}.breadcrumb .crumb a{color:var(--ui-primary, #4a5cff);text-decoration:none;font-weight:500}.breadcrumb .crumb a:hover{text-decoration:underline}.breadcrumb .crumb .current{color:var(--ui-text, #222);font-weight:600;padding:0 8px}.breadcrumb .crumb .separator{margin:0 8px;color:var(--ui-text-muted, #999);font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }] });
274
+ }
275
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BreadcrumbComponent, decorators: [{
276
+ type: Component,
277
+ args: [{ selector: 'dso-breadcrumb', standalone: true, imports: [CommonModule, RouterLink, ButtonComponent], template: "<nav aria-label=\"Breadcrumb\" class=\"breadcrumb\">\r\n <ol>\r\n <li *ngFor=\"let item of items; let last = last\" class=\"crumb\">\r\n\r\n <!-- Normal breadcrumb link -->\r\n <ng-container *ngIf=\"!last && item.url; else lastItem\">\r\n\r\n <dso-button\r\n btnType=\"text\"\r\n btnSize=\"smBtn\"\r\n [btnLabel]=\"item.label\"\r\n [isDisabled]=\"false\"\r\n (onClick)=\"onItemClicked(item)\"\r\n [routerLink]=\"item.url\">\r\n </dso-button>\r\n\r\n </ng-container>\r\n\r\n <!-- Last breadcrumb item (non-clickable) -->\r\n <ng-template #lastItem>\r\n <span class=\"current\" aria-current=\"page\">\r\n {{ item.label }}\r\n </span>\r\n </ng-template>\r\n\r\n <!-- Separator -->\r\n <span class=\"separator\" *ngIf=\"!last\">{{ separator }}</span>\r\n\r\n </li>\r\n </ol>\r\n <!-- <dso-button btnType=\"text\" btnLabel=\"Go Home\" [routerLink]=\"['/page1']\"></dso-button> -->\r\n\r\n</nav>\r\n\r\n\r\n", styles: [".breadcrumb{font-size:14px;color:var(--ui-text-muted, #666)}.breadcrumb ol{list-style:none;padding:0;margin:0;display:flex;align-items:center;flex-wrap:wrap}.breadcrumb .crumb{display:flex;align-items:center}.breadcrumb .crumb a{color:var(--ui-primary, #4a5cff);text-decoration:none;font-weight:500}.breadcrumb .crumb a:hover{text-decoration:underline}.breadcrumb .crumb .current{color:var(--ui-text, #222);font-weight:600;padding:0 8px}.breadcrumb .crumb .separator{margin:0 8px;color:var(--ui-text-muted, #999);font-size:12px}\n"] }]
278
+ }], propDecorators: { items: [{
279
+ type: Input
280
+ }], itemSelected: [{
281
+ type: Output
282
+ }], separator: [{
283
+ type: Input
284
+ }] } });
285
+
286
+ /**
287
+ * Reusable Checkbox Component
288
+ *
289
+ * Features:
290
+ * - Optional label
291
+ * - Configurable size, error state, and disabled state
292
+ * - Emits changes to parent component
293
+ * - Supports custom icons
294
+ */
295
+ class CheckboxComponent {
296
+ /** Text label displayed next to the checkbox */
297
+ label;
298
+ /** Whether the checkbox is checked */
299
+ isChecked = true;
300
+ /** Whether the checkbox is disabled */
301
+ disabled = false;
302
+ /** Size of the checkbox: small, medium, or large */
303
+ size = 'medium';
304
+ /** Error state for styling purposes */
305
+ error = false;
306
+ /** Whether the checkbox is required */
307
+ required = false;
308
+ /** Error message to display when error is true */
309
+ errorMessage = '';
310
+ /** Icon name for the checkbox (default is a checkmark) */
311
+ iconName = 'icon-slanted-check';
312
+ /** Emits the updated checked state whenever the checkbox is toggled */
313
+ change = new EventEmitter();
314
+ /**
315
+ * Toggles the checked state of the checkbox
316
+ * and emits the new value to the parent component
317
+ */
318
+ toggleCheckbox() {
319
+ if (!this.disabled) {
320
+ this.isChecked = !this.isChecked;
321
+ this.change.emit(this.isChecked);
322
+ }
323
+ }
324
+ /**
325
+ * Maps the checkbox size to icon size
326
+ */
327
+ getIconSize() {
328
+ switch (this.size) {
329
+ case 'large': return 'large';
330
+ case 'medium': return 'medium';
331
+ case 'small': return 'small';
332
+ default: return 'medium'; // fallback to medium
333
+ }
334
+ }
335
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
336
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CheckboxComponent, isStandalone: true, selector: "dso-checkbox", inputs: { label: "label", isChecked: "isChecked", disabled: "disabled", size: "size", error: "error", required: "required", errorMessage: "errorMessage", iconName: "iconName" }, outputs: { change: "change" }, ngImport: i0, template: "<!--\r\n Checkbox Component Template\r\n --------------------------\r\n Features:\r\n - Clickable wrapper to toggle checkbox (disabled-safe)\r\n - Custom checkbox with icon\r\n - Label support\r\n - Error state styling\r\n - Size-based styling: small, medium, large\r\n-->\r\n\r\n<div \r\n class=\"checkbox-wrapper\"\r\n (click)=\"!disabled && toggleCheckbox()\"\r\n [ngClass]=\"{\r\n 'small': size === 'small', \r\n 'medium': size === 'medium', \r\n 'large': size === 'large',\r\n 'disabled': disabled\r\n }\">\r\n\r\n <!-- Hidden native checkbox for accessibility and form integration -->\r\n <input\r\n class=\"checkbox-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"isChecked\"\r\n [disabled]=\"disabled\"/>\r\n\r\n <!-- Custom checkbox UI -->\r\n <span \r\n class=\"custom-checkbox\" \r\n [class.checked]=\"isChecked\" \r\n [class.error]=\"error\">\r\n \r\n <!-- Display check icon only when checked -->\r\n <dso-icon \r\n *ngIf=\"isChecked\" \r\n [iconName]=\"iconName\" \r\n [size]=\"getIconSize()\">\r\n </dso-icon>\r\n\r\n </span> \r\n\r\n <!-- Optional label -->\r\n <span class=\"checkbox-label\">\r\n {{ label }}\r\n </span>\r\n\r\n</div>\r\n", styles: [".checkbox-wrapper{display:inline-flex;align-items:center;gap:8px;cursor:pointer;vertical-align:middle}.checkbox-wrapper.error .custom-checkbox{border-color:red}.checkbox-wrapper.disabled{cursor:not-allowed}.checkbox-wrapper.disabled .custom-checkbox{pointer-events:none;background-color:#f0f0f0;border-color:#d0d0d0}.checkbox-wrapper.disabled .custom-checkbox.checked{background-color:#00f;border-color:#00f;opacity:.3}.checkbox-input{width:1px;height:1px;margin:-1px;border:0;padding:0;clip:rect(0 0 0 0);clip-path:inset(100%);overflow:hidden;white-space:nowrap}.custom-checkbox{display:inline-flex;justify-content:center;border:1px solid #ccc;border-radius:4px;cursor:pointer;width:24px;height:24px}.custom-checkbox.checked{border-color:#00f;background-color:#00f;color:#fff}.checkbox-wrapper:not(.disabled):hover .custom-checkbox{background-color:#eeeff9;border-color:#0000ffc0;cursor:pointer}.checkbox-wrapper:not(.disabled):hover .custom-checkbox.checked{background-color:#0000ffa9;border-color:#0000ffc0}.custom-checkbox.small .checkbox-custom{width:12px;height:12px}.custom-checkbox.medium .checkbox-custom{width:16px;height:16px}.custom-checkbox.large .checkbox-custom{width:24px;height:24px}.checkbox-label{font-size:14px;font-weight:400}.error-message{color:red;font-size:12px;margin-top:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
337
+ }
338
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CheckboxComponent, decorators: [{
339
+ type: Component,
340
+ args: [{ selector: 'dso-checkbox', standalone: true, imports: [CommonModule, IconComponent, FormsModule], template: "<!--\r\n Checkbox Component Template\r\n --------------------------\r\n Features:\r\n - Clickable wrapper to toggle checkbox (disabled-safe)\r\n - Custom checkbox with icon\r\n - Label support\r\n - Error state styling\r\n - Size-based styling: small, medium, large\r\n-->\r\n\r\n<div \r\n class=\"checkbox-wrapper\"\r\n (click)=\"!disabled && toggleCheckbox()\"\r\n [ngClass]=\"{\r\n 'small': size === 'small', \r\n 'medium': size === 'medium', \r\n 'large': size === 'large',\r\n 'disabled': disabled\r\n }\">\r\n\r\n <!-- Hidden native checkbox for accessibility and form integration -->\r\n <input\r\n class=\"checkbox-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"isChecked\"\r\n [disabled]=\"disabled\"/>\r\n\r\n <!-- Custom checkbox UI -->\r\n <span \r\n class=\"custom-checkbox\" \r\n [class.checked]=\"isChecked\" \r\n [class.error]=\"error\">\r\n \r\n <!-- Display check icon only when checked -->\r\n <dso-icon \r\n *ngIf=\"isChecked\" \r\n [iconName]=\"iconName\" \r\n [size]=\"getIconSize()\">\r\n </dso-icon>\r\n\r\n </span> \r\n\r\n <!-- Optional label -->\r\n <span class=\"checkbox-label\">\r\n {{ label }}\r\n </span>\r\n\r\n</div>\r\n", styles: [".checkbox-wrapper{display:inline-flex;align-items:center;gap:8px;cursor:pointer;vertical-align:middle}.checkbox-wrapper.error .custom-checkbox{border-color:red}.checkbox-wrapper.disabled{cursor:not-allowed}.checkbox-wrapper.disabled .custom-checkbox{pointer-events:none;background-color:#f0f0f0;border-color:#d0d0d0}.checkbox-wrapper.disabled .custom-checkbox.checked{background-color:#00f;border-color:#00f;opacity:.3}.checkbox-input{width:1px;height:1px;margin:-1px;border:0;padding:0;clip:rect(0 0 0 0);clip-path:inset(100%);overflow:hidden;white-space:nowrap}.custom-checkbox{display:inline-flex;justify-content:center;border:1px solid #ccc;border-radius:4px;cursor:pointer;width:24px;height:24px}.custom-checkbox.checked{border-color:#00f;background-color:#00f;color:#fff}.checkbox-wrapper:not(.disabled):hover .custom-checkbox{background-color:#eeeff9;border-color:#0000ffc0;cursor:pointer}.checkbox-wrapper:not(.disabled):hover .custom-checkbox.checked{background-color:#0000ffa9;border-color:#0000ffc0}.custom-checkbox.small .checkbox-custom{width:12px;height:12px}.custom-checkbox.medium .checkbox-custom{width:16px;height:16px}.custom-checkbox.large .checkbox-custom{width:24px;height:24px}.checkbox-label{font-size:14px;font-weight:400}.error-message{color:red;font-size:12px;margin-top:4px}\n"] }]
341
+ }], propDecorators: { label: [{
342
+ type: Input
343
+ }], isChecked: [{
344
+ type: Input
345
+ }], disabled: [{
346
+ type: Input
347
+ }], size: [{
348
+ type: Input
349
+ }], error: [{
350
+ type: Input
351
+ }], required: [{
352
+ type: Input
353
+ }], errorMessage: [{
354
+ type: Input
355
+ }], iconName: [{
356
+ type: Input
357
+ }], change: [{
358
+ type: Output
359
+ }] } });
360
+
361
+ class DatepickerComponent {
362
+ /** Label displayed above the input */
363
+ inputTextLabel = 'Datepicker Title';
364
+ /** Disable the datepicker input */
365
+ isDisabled = false;
366
+ /** Currently selected date */
367
+ selectedDate = null;
368
+ /** Whether the calendar popup is visible */
369
+ calendarVisible = false;
370
+ /** Current month displayed in the calendar */
371
+ currentMonth = new Date();
372
+ /** View mode: day, month, or year */
373
+ viewMode = 'day';
374
+ /** Minimum and maximum year range */
375
+ minYear = 1990;
376
+ maxYear = 2099;
377
+ /** Month names for display */
378
+ months = [
379
+ 'January', 'February', 'March', 'April', 'May', 'June',
380
+ 'July', 'August', 'September', 'October', 'November', 'December'
381
+ ];
382
+ yearListRef;
383
+ calendarWrapper;
384
+ // --------------------------
385
+ // Calendar Visibility & Toggle
386
+ // --------------------------
387
+ /** Toggle the calendar popup */
388
+ toggleCalendar() {
389
+ this.calendarVisible = !this.calendarVisible;
390
+ this.viewMode = 'day';
391
+ }
392
+ /** Close calendar if clicking outside */
393
+ handleClickOutside(event) {
394
+ if (!this.calendarWrapper)
395
+ return;
396
+ const target = event.target;
397
+ if (this.calendarVisible && !this.calendarWrapper.nativeElement.contains(target)) {
398
+ this.calendarVisible = false;
399
+ }
400
+ }
401
+ // --------------------------
402
+ // View Switching
403
+ // --------------------------
404
+ switchToMonthView() {
405
+ this.viewMode = 'month';
406
+ }
407
+ switchToYearView() {
408
+ this.viewMode = 'year';
409
+ setTimeout(() => this.scrollToCurrentYear(), 50);
410
+ }
411
+ // --------------------------
412
+ // Navigation (Month / Year)
413
+ // --------------------------
414
+ changeMonth(direction) {
415
+ this.currentMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + direction, 1);
416
+ }
417
+ changeYear(direction) {
418
+ const newYear = this.currentMonth.getFullYear() + direction;
419
+ if (newYear >= this.minYear && newYear <= this.maxYear) {
420
+ this.currentMonth = new Date(newYear, this.currentMonth.getMonth(), 1);
421
+ setTimeout(() => this.scrollToCurrentYear(), 0);
422
+ }
423
+ }
424
+ // --------------------------
425
+ // Helpers
426
+ // --------------------------
427
+ /** Returns array of days in the current month, with leading nulls for empty grid cells */
428
+ getDaysInMonth() {
429
+ const month = this.currentMonth.getMonth();
430
+ const year = this.currentMonth.getFullYear();
431
+ const firstDay = new Date(year, month, 1).getDay();
432
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
433
+ const daysArray = Array.from({ length: daysInMonth }, (_, i) => i + 1);
434
+ return Array(firstDay).fill(null).concat(daysArray);
435
+ }
436
+ /** Returns array of years in the defined range */
437
+ getYearsRange() {
438
+ const years = [];
439
+ for (let y = this.minYear; y <= this.maxYear; y++)
440
+ years.push(y);
441
+ return years;
442
+ }
443
+ /** Scroll the year list to show the current year */
444
+ scrollToCurrentYear() {
445
+ if (!this.yearListRef)
446
+ return;
447
+ const container = this.yearListRef.nativeElement;
448
+ const currentYear = this.currentMonth.getFullYear();
449
+ const yearIndex = currentYear - this.minYear;
450
+ const columns = 4; // 4 columns grid layout
451
+ const rowIndex = Math.floor(yearIndex / columns);
452
+ const yearCell = container.querySelector('.year-cell');
453
+ if (!yearCell)
454
+ return;
455
+ const scrollTop = rowIndex * yearCell.offsetHeight;
456
+ container.scrollTop = scrollTop;
457
+ }
458
+ // --------------------------
459
+ // Selection Handlers
460
+ // --------------------------
461
+ selectDate(day) {
462
+ if (day === null)
463
+ return;
464
+ this.selectedDate = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth(), day);
465
+ this.calendarVisible = false;
466
+ }
467
+ selectMonth(monthIndex) {
468
+ this.currentMonth = new Date(this.currentMonth.getFullYear(), monthIndex, 1);
469
+ this.viewMode = 'day';
470
+ }
471
+ selectYear(year) {
472
+ this.currentMonth = new Date(year, this.currentMonth.getMonth(), 1);
473
+ this.viewMode = 'month';
474
+ }
475
+ /** Check if a day is currently selected */
476
+ isSelected(day) {
477
+ if (!this.selectedDate || day === null)
478
+ return false;
479
+ return (this.selectedDate.getDate() === day &&
480
+ this.selectedDate.getMonth() === this.currentMonth.getMonth() &&
481
+ this.selectedDate.getFullYear() === this.currentMonth.getFullYear());
482
+ }
483
+ // --------------------------
484
+ // Footer Actions
485
+ // --------------------------
486
+ /** Set today’s date */
487
+ setToday() {
488
+ const today = new Date();
489
+ this.selectedDate = today;
490
+ this.currentMonth = new Date(today.getFullYear(), today.getMonth(), 1);
491
+ this.calendarVisible = false;
492
+ }
493
+ /** Clear the selected date */
494
+ clearDate() {
495
+ this.selectedDate = null;
496
+ }
497
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatepickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
498
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatepickerComponent, isStandalone: true, selector: "dso-datepicker", inputs: { inputTextLabel: "inputTextLabel", isDisabled: "isDisabled" }, host: { listeners: { "document:click": "handleClickOutside($event)" } }, viewQueries: [{ propertyName: "yearListRef", first: true, predicate: ["yearList"], descendants: true }, { propertyName: "calendarWrapper", first: true, predicate: ["calendarWrapper"], descendants: true }], ngImport: i0, template: "<div class=\"datepicker-wrapper\" #calendarWrapper>\r\n \r\n <!-- Label above the input -->\r\n <div *ngIf=\"inputTextLabel\" class=\"datepicker-label\">{{ inputTextLabel }}</div>\r\n\r\n <!-- Input field (readonly) -->\r\n <input\r\n type=\"text\"\r\n class=\"datepicker-input\"\r\n [value]=\"selectedDate ? (selectedDate | date:'MMM d, y') : ''\"\r\n placeholder=\"Select a date\"\r\n readonly\r\n (click)=\"toggleCalendar()\"\r\n [disabled]=\"isDisabled\"\r\n [class.disabled]=\"isDisabled\"\r\n />\r\n\r\n <!-- Calendar popup -->\r\n <div class=\"calendar\" *ngIf=\"calendarVisible\">\r\n\r\n <!-- Header -->\r\n <div class=\"calendar-header\">\r\n <div class=\"header-row\">\r\n\r\n <!-- Year & Month Navigation Buttons -->\r\n <button *ngIf=\"viewMode === 'day' || viewMode === 'month'\" \r\n (click)=\"changeYear(-1)\" class=\"nav-btn\">\u00AB</button>\r\n <button *ngIf=\"viewMode === 'day'\" \r\n (click)=\"changeMonth(-1)\" class=\"nav-btn\">\u2039</button>\r\n\r\n <!-- Calendar Title (Month / Year display) -->\r\n <span class=\"calendar-title\">\r\n\r\n <!-- Day view: show Month + Year -->\r\n <ng-container *ngIf=\"viewMode === 'day'\">\r\n <span class=\"month-label\" (click)=\"switchToMonthView()\">\r\n {{ currentMonth | date:'MMMM' }}\r\n </span>\r\n <span class=\"year-label\" (click)=\"switchToYearView()\">\r\n {{ currentMonth | date:'yyyy' }}\r\n </span>\r\n </ng-container>\r\n\r\n <!-- Month view: show Year -->\r\n <ng-container *ngIf=\"viewMode === 'month'\">\r\n <span class=\"year-label\" (click)=\"switchToYearView()\">\r\n {{ currentMonth | date:'yyyy' }}\r\n </span>\r\n </ng-container>\r\n\r\n <!-- Year view: show Year only -->\r\n <ng-container *ngIf=\"viewMode === 'year'\">\r\n {{ currentMonth | date:'yyyy' }}\r\n </ng-container>\r\n\r\n </span>\r\n\r\n <!-- Forward navigation buttons -->\r\n <button *ngIf=\"viewMode === 'day'\" (click)=\"changeMonth(1)\" class=\"nav-btn\">\u203A</button>\r\n <button *ngIf=\"viewMode === 'day' || viewMode === 'month'\" \r\n (click)=\"changeYear(1)\" class=\"nav-btn\">\u00BB</button>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n DAY VIEW\r\n ---------------------- -->\r\n <div *ngIf=\"viewMode === 'day'\">\r\n\r\n <!-- Weekday Labels -->\r\n <div class=\"calendar-weekdays\">\r\n <div *ngFor=\"let day of ['S','M','T','W','T','F','S']\">{{ day }}</div>\r\n </div>\r\n\r\n <!-- Days Grid -->\r\n <div class=\"calendar-grid days-grid\">\r\n <div\r\n *ngFor=\"let day of getDaysInMonth()\"\r\n [class.empty]=\"day === null\"\r\n [class.selected]=\"isSelected(day)\"\r\n (click)=\"selectDate(day)\"\r\n >\r\n {{ day }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n MONTH VIEW\r\n ---------------------- -->\r\n <div *ngIf=\"viewMode === 'month'\" class=\"calendar-grid months-grid\">\r\n <div *ngFor=\"let m of months; let i = index\" \r\n (click)=\"selectMonth(i)\" \r\n class=\"month-cell\">\r\n {{ m }}\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n YEAR VIEW\r\n ---------------------- -->\r\n <div *ngIf=\"viewMode === 'year'\" \r\n class=\"calendar-grid years-grid scrollable\" \r\n #yearList>\r\n <div\r\n *ngFor=\"let y of getYearsRange()\"\r\n (click)=\"selectYear(y)\"\r\n class=\"year-cell\"\r\n [class.current-year]=\"y === currentMonth.getFullYear()\"\r\n >\r\n {{ y }}\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n Footer Buttons\r\n ---------------------- -->\r\n <div class=\"calendar-footer\">\r\n <button class=\"footer-btn clear-btn\" (click)=\"clearDate()\">Clear</button>\r\n <button class=\"footer-btn today-btn\" (click)=\"setToday()\">Today</button>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [".datepicker-wrapper{position:relative;display:flex;flex-direction:column;gap:8px}.datepicker-input{padding:10px;font-size:15px;width:190px;border:1px solid #ccc;border-radius:4px;cursor:pointer;background-color:#fff}.datepicker-input:hover{border:1px solid #071d35}.datepicker-input.disabled{background-color:#f5f5f5;border-color:#ccc;cursor:not-allowed}.datepicker-input.disabled .selected-option{pointer-events:none;color:#aaa}.calendar{position:absolute;top:0;left:100px;width:280px;background:#fff;border:1px solid #ddd;border-radius:8px;box-shadow:0 4px 10px #0000001a;padding:10px;z-index:100;animation:fadeIn .2s ease-in-out}.calendar-header{margin-bottom:8px}.header-row{display:flex;justify-content:center;align-items:center;gap:4px}.nav-btn{background:none;border:none;font-size:16px;cursor:pointer;padding:4px 6px;color:#333;border-radius:4px;transition:background-color .2s}.nav-btn:hover{background-color:#f0f0f0}.calendar-title{font-weight:600;font-size:16px;min-width:90px;text-align:center;display:flex;flex:1;gap:4px;align-items:center;justify-content:center}.month-label,.year-label{cursor:pointer;transition:color .2s}.month-label:hover,.year-label:hover{color:#007bff}.calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;font-weight:600;font-size:13px;margin-bottom:4px;color:#666}.calendar-grid{display:grid;gap:4px;text-align:center;min-height:200px}.days-grid{grid-template-columns:repeat(7,1fr)}.months-grid{grid-template-columns:repeat(3,1fr)}.years-grid{grid-template-columns:repeat(4,1fr)}.scrollable{max-height:160px;overflow-y:auto;padding-right:5px}.scrollable::-webkit-scrollbar{width:6px}.scrollable::-webkit-scrollbar-thumb{background-color:#ccc;border-radius:3px}.scrollable::-webkit-scrollbar-thumb:hover{background-color:#999}.calendar-grid div{padding:8px 0;border-radius:4px;cursor:pointer;transition:background-color .2s}.calendar-grid div:hover{background-color:#f0f0f0}.selected{background-color:#007bff;color:#fff!important}.current-year{background-color:#007bff1a;font-weight:700}.empty{visibility:hidden}.calendar-footer{display:flex;justify-content:space-between;margin-top:8px;border-top:1px solid #eee;padding-top:8px}.footer-btn{flex:1;padding:6px 0;font-size:14px;border-radius:4px;cursor:pointer;transition:background .2s;border:none}.clear-btn{background-color:#f8f9fa;color:#333;margin-right:5px}.clear-btn:hover{background-color:#e9ecef}.today-btn{background-color:#007bff;color:#fff}.today-btn:hover{background-color:#0069d9}@keyframes fadeIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }] });
499
+ }
500
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatepickerComponent, decorators: [{
501
+ type: Component,
502
+ args: [{ selector: 'dso-datepicker', standalone: true, imports: [CommonModule, DatePipe], template: "<div class=\"datepicker-wrapper\" #calendarWrapper>\r\n \r\n <!-- Label above the input -->\r\n <div *ngIf=\"inputTextLabel\" class=\"datepicker-label\">{{ inputTextLabel }}</div>\r\n\r\n <!-- Input field (readonly) -->\r\n <input\r\n type=\"text\"\r\n class=\"datepicker-input\"\r\n [value]=\"selectedDate ? (selectedDate | date:'MMM d, y') : ''\"\r\n placeholder=\"Select a date\"\r\n readonly\r\n (click)=\"toggleCalendar()\"\r\n [disabled]=\"isDisabled\"\r\n [class.disabled]=\"isDisabled\"\r\n />\r\n\r\n <!-- Calendar popup -->\r\n <div class=\"calendar\" *ngIf=\"calendarVisible\">\r\n\r\n <!-- Header -->\r\n <div class=\"calendar-header\">\r\n <div class=\"header-row\">\r\n\r\n <!-- Year & Month Navigation Buttons -->\r\n <button *ngIf=\"viewMode === 'day' || viewMode === 'month'\" \r\n (click)=\"changeYear(-1)\" class=\"nav-btn\">\u00AB</button>\r\n <button *ngIf=\"viewMode === 'day'\" \r\n (click)=\"changeMonth(-1)\" class=\"nav-btn\">\u2039</button>\r\n\r\n <!-- Calendar Title (Month / Year display) -->\r\n <span class=\"calendar-title\">\r\n\r\n <!-- Day view: show Month + Year -->\r\n <ng-container *ngIf=\"viewMode === 'day'\">\r\n <span class=\"month-label\" (click)=\"switchToMonthView()\">\r\n {{ currentMonth | date:'MMMM' }}\r\n </span>\r\n <span class=\"year-label\" (click)=\"switchToYearView()\">\r\n {{ currentMonth | date:'yyyy' }}\r\n </span>\r\n </ng-container>\r\n\r\n <!-- Month view: show Year -->\r\n <ng-container *ngIf=\"viewMode === 'month'\">\r\n <span class=\"year-label\" (click)=\"switchToYearView()\">\r\n {{ currentMonth | date:'yyyy' }}\r\n </span>\r\n </ng-container>\r\n\r\n <!-- Year view: show Year only -->\r\n <ng-container *ngIf=\"viewMode === 'year'\">\r\n {{ currentMonth | date:'yyyy' }}\r\n </ng-container>\r\n\r\n </span>\r\n\r\n <!-- Forward navigation buttons -->\r\n <button *ngIf=\"viewMode === 'day'\" (click)=\"changeMonth(1)\" class=\"nav-btn\">\u203A</button>\r\n <button *ngIf=\"viewMode === 'day' || viewMode === 'month'\" \r\n (click)=\"changeYear(1)\" class=\"nav-btn\">\u00BB</button>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n DAY VIEW\r\n ---------------------- -->\r\n <div *ngIf=\"viewMode === 'day'\">\r\n\r\n <!-- Weekday Labels -->\r\n <div class=\"calendar-weekdays\">\r\n <div *ngFor=\"let day of ['S','M','T','W','T','F','S']\">{{ day }}</div>\r\n </div>\r\n\r\n <!-- Days Grid -->\r\n <div class=\"calendar-grid days-grid\">\r\n <div\r\n *ngFor=\"let day of getDaysInMonth()\"\r\n [class.empty]=\"day === null\"\r\n [class.selected]=\"isSelected(day)\"\r\n (click)=\"selectDate(day)\"\r\n >\r\n {{ day }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n MONTH VIEW\r\n ---------------------- -->\r\n <div *ngIf=\"viewMode === 'month'\" class=\"calendar-grid months-grid\">\r\n <div *ngFor=\"let m of months; let i = index\" \r\n (click)=\"selectMonth(i)\" \r\n class=\"month-cell\">\r\n {{ m }}\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n YEAR VIEW\r\n ---------------------- -->\r\n <div *ngIf=\"viewMode === 'year'\" \r\n class=\"calendar-grid years-grid scrollable\" \r\n #yearList>\r\n <div\r\n *ngFor=\"let y of getYearsRange()\"\r\n (click)=\"selectYear(y)\"\r\n class=\"year-cell\"\r\n [class.current-year]=\"y === currentMonth.getFullYear()\"\r\n >\r\n {{ y }}\r\n </div>\r\n </div>\r\n\r\n <!-- ----------------------\r\n Footer Buttons\r\n ---------------------- -->\r\n <div class=\"calendar-footer\">\r\n <button class=\"footer-btn clear-btn\" (click)=\"clearDate()\">Clear</button>\r\n <button class=\"footer-btn today-btn\" (click)=\"setToday()\">Today</button>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [".datepicker-wrapper{position:relative;display:flex;flex-direction:column;gap:8px}.datepicker-input{padding:10px;font-size:15px;width:190px;border:1px solid #ccc;border-radius:4px;cursor:pointer;background-color:#fff}.datepicker-input:hover{border:1px solid #071d35}.datepicker-input.disabled{background-color:#f5f5f5;border-color:#ccc;cursor:not-allowed}.datepicker-input.disabled .selected-option{pointer-events:none;color:#aaa}.calendar{position:absolute;top:0;left:100px;width:280px;background:#fff;border:1px solid #ddd;border-radius:8px;box-shadow:0 4px 10px #0000001a;padding:10px;z-index:100;animation:fadeIn .2s ease-in-out}.calendar-header{margin-bottom:8px}.header-row{display:flex;justify-content:center;align-items:center;gap:4px}.nav-btn{background:none;border:none;font-size:16px;cursor:pointer;padding:4px 6px;color:#333;border-radius:4px;transition:background-color .2s}.nav-btn:hover{background-color:#f0f0f0}.calendar-title{font-weight:600;font-size:16px;min-width:90px;text-align:center;display:flex;flex:1;gap:4px;align-items:center;justify-content:center}.month-label,.year-label{cursor:pointer;transition:color .2s}.month-label:hover,.year-label:hover{color:#007bff}.calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;font-weight:600;font-size:13px;margin-bottom:4px;color:#666}.calendar-grid{display:grid;gap:4px;text-align:center;min-height:200px}.days-grid{grid-template-columns:repeat(7,1fr)}.months-grid{grid-template-columns:repeat(3,1fr)}.years-grid{grid-template-columns:repeat(4,1fr)}.scrollable{max-height:160px;overflow-y:auto;padding-right:5px}.scrollable::-webkit-scrollbar{width:6px}.scrollable::-webkit-scrollbar-thumb{background-color:#ccc;border-radius:3px}.scrollable::-webkit-scrollbar-thumb:hover{background-color:#999}.calendar-grid div{padding:8px 0;border-radius:4px;cursor:pointer;transition:background-color .2s}.calendar-grid div:hover{background-color:#f0f0f0}.selected{background-color:#007bff;color:#fff!important}.current-year{background-color:#007bff1a;font-weight:700}.empty{visibility:hidden}.calendar-footer{display:flex;justify-content:space-between;margin-top:8px;border-top:1px solid #eee;padding-top:8px}.footer-btn{flex:1;padding:6px 0;font-size:14px;border-radius:4px;cursor:pointer;transition:background .2s;border:none}.clear-btn{background-color:#f8f9fa;color:#333;margin-right:5px}.clear-btn:hover{background-color:#e9ecef}.today-btn{background-color:#007bff;color:#fff}.today-btn:hover{background-color:#0069d9}@keyframes fadeIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}\n"] }]
503
+ }], propDecorators: { inputTextLabel: [{
504
+ type: Input
505
+ }], isDisabled: [{
506
+ type: Input
507
+ }], yearListRef: [{
508
+ type: ViewChild,
509
+ args: ['yearList']
510
+ }], calendarWrapper: [{
511
+ type: ViewChild,
512
+ args: ['calendarWrapper', { static: false }]
513
+ }], handleClickOutside: [{
514
+ type: HostListener,
515
+ args: ['document:click', ['$event']]
516
+ }] } });
517
+
518
+ class DialogComponent {
519
+ visible = false;
520
+ title = 'Dialog Title';
521
+ close = new EventEmitter();
522
+ onClose() {
523
+ this.close.emit();
524
+ }
525
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
526
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DialogComponent, isStandalone: true, selector: "dso-dialog", inputs: { visible: "visible", title: "title" }, outputs: { close: "close" }, ngImport: i0, template: "<div class=\"dialog-overlay\" *ngIf=\"visible\" (click)=\"onClose()\">\r\n <div class=\"dialog\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"dialog-header\">{{ title }}</div>\r\n <div class=\"dialog-body\">\r\n <ng-content></ng-content> <!-- Projected content -->\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".dialog-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;padding:24px;border-radius:8px;width:400px;max-width:90%;box-shadow:0 2px 8px #00000040;position:relative;display:flex;flex-direction:column;gap:12px}.dialog-header{font-size:20px;font-weight:700}.dialog-close{background:none;border:none;font-size:1.5rem;line-height:1;cursor:pointer}.dialog-body{display:flex;flex-direction:column;gap:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
527
+ }
528
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DialogComponent, decorators: [{
529
+ type: Component,
530
+ args: [{ selector: 'dso-dialog', standalone: true, imports: [CommonModule], template: "<div class=\"dialog-overlay\" *ngIf=\"visible\" (click)=\"onClose()\">\r\n <div class=\"dialog\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"dialog-header\">{{ title }}</div>\r\n <div class=\"dialog-body\">\r\n <ng-content></ng-content> <!-- Projected content -->\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".dialog-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;padding:24px;border-radius:8px;width:400px;max-width:90%;box-shadow:0 2px 8px #00000040;position:relative;display:flex;flex-direction:column;gap:12px}.dialog-header{font-size:20px;font-weight:700}.dialog-close{background:none;border:none;font-size:1.5rem;line-height:1;cursor:pointer}.dialog-body{display:flex;flex-direction:column;gap:16px}\n"] }]
531
+ }], propDecorators: { visible: [{
532
+ type: Input
533
+ }], title: [{
534
+ type: Input
535
+ }], close: [{
536
+ type: Output
537
+ }] } });
538
+
539
+ class DropdownListComponent {
540
+ // -------------------------------
541
+ // ELEMENT REFERENCE
542
+ // -------------------------------
543
+ /** Reference to the root <div> to detect clicks inside/outside dropdown */
544
+ root;
545
+ // -------------------------------
546
+ // INPUT PROPERTIES
547
+ // -------------------------------
548
+ /** Options to display in the dropdown. Each option has a label and a value */
549
+ options = [];
550
+ /** Placeholder text when no option is selected */
551
+ placeholder = 'Select';
552
+ /** Currently selected value */
553
+ value = null;
554
+ /** Disable dropdown interaction */
555
+ disabled = false;
556
+ // -------------------------------
557
+ // OUTPUT EVENTS
558
+ // -------------------------------
559
+ /** Emits the selected value when user clicks an option */
560
+ selectionChange = new EventEmitter();
561
+ // -------------------------------
562
+ // INTERNAL STATE
563
+ // -------------------------------
564
+ /** Tracks whether the dropdown list is open or closed */
565
+ isOpen = false;
566
+ // -------------------------------
567
+ // METHODS
568
+ // -------------------------------
569
+ /** Toggle dropdown open/close when trigger is clicked */
570
+ toggle() {
571
+ if (!this.disabled) {
572
+ this.isOpen = !this.isOpen;
573
+ }
574
+ }
575
+ /** Handle option selection */
576
+ select(option) {
577
+ this.value = option.value; // Update internal state
578
+ this.selectionChange.emit(this.value); // Notify parent component
579
+ this.isOpen = false; // Close dropdown
580
+ }
581
+ // -------------------------------
582
+ // HANDLE CLICKS OUTSIDE DROPDOWN
583
+ // -------------------------------
584
+ handleOutsideClick(event) {
585
+ if (!this.isOpen)
586
+ return;
587
+ const target = event.target;
588
+ if (!this.root.nativeElement.contains(target)) {
589
+ this.isOpen = false;
590
+ }
591
+ }
592
+ // -------------------------------
593
+ // HELPER GETTER
594
+ // -------------------------------
595
+ /** Returns label of selected value or placeholder if none selected */
596
+ get selectedLabel() {
597
+ const found = this.options.find(o => o.value === this.value);
598
+ return found ? found.label : this.placeholder;
599
+ }
600
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DropdownListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
601
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DropdownListComponent, isStandalone: true, selector: "dso-dropdown-list", inputs: { options: "options", placeholder: "placeholder", value: "value", disabled: "disabled" }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "document:click": "handleOutsideClick($event)" } }, viewQueries: [{ propertyName: "root", first: true, predicate: ["root"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"dropdown\" #root [class.disabled]=\"disabled\">\r\n <!--\r\n #root: Linked to ViewChild in TS for detecting outside clicks\r\n [class.disabled]: Adds CSS class when dropdown is disabled\r\n -->\r\n\r\n <!-- Trigger area: clicking toggles dropdown open/close -->\r\n <div class=\"dropdown-trigger\" (click)=\"toggle()\">\r\n <!-- Display selected label or placeholder -->\r\n <span>{{ selectedLabel }}</span>\r\n <!-- Arrow icon that rotates when dropdown is open -->\r\n <span class=\"icon\" [class.open]=\"isOpen\">\u25BE</span>\r\n </div>\r\n\r\n <!-- Dropdown menu: visible only when isOpen is true -->\r\n <ul class=\"dropdown-menu\" *ngIf=\"isOpen\">\r\n <!-- Render each option -->\r\n <li \r\n *ngFor=\"let option of options\"\r\n (click)=\"select(option)\"\r\n [class.selected]=\"option.value === value\"> <!-- Highlight selected option -->\r\n {{ option.label }}\r\n </li>\r\n </ul>\r\n</div>\r\n", styles: [".dropdown{position:relative;display:inline-flex;font-family:inherit}.dropdown.disabled{opacity:.5;pointer-events:none}.dropdown-trigger{border:1px solid #ccc;padding:8px 12px;background:#fff;cursor:pointer;display:inline-flex;gap:8px;align-items:center;border-radius:4px}.dropdown-trigger:hover{border-color:#888}.dropdown-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;background:#fff;border:1px solid #ccc;border-radius:4px;list-style:none;padding:4px 0;margin:0;max-height:200px;overflow-y:auto;z-index:100}.dropdown-menu li{padding:8px 12px;cursor:pointer}.dropdown-menu li:hover{background:#f2f2f2}.dropdown-menu li.selected{background:#e6e6e6;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
602
+ }
603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DropdownListComponent, decorators: [{
604
+ type: Component,
605
+ args: [{ selector: 'dso-dropdown-list', standalone: true, imports: [CommonModule], template: "<div class=\"dropdown\" #root [class.disabled]=\"disabled\">\r\n <!--\r\n #root: Linked to ViewChild in TS for detecting outside clicks\r\n [class.disabled]: Adds CSS class when dropdown is disabled\r\n -->\r\n\r\n <!-- Trigger area: clicking toggles dropdown open/close -->\r\n <div class=\"dropdown-trigger\" (click)=\"toggle()\">\r\n <!-- Display selected label or placeholder -->\r\n <span>{{ selectedLabel }}</span>\r\n <!-- Arrow icon that rotates when dropdown is open -->\r\n <span class=\"icon\" [class.open]=\"isOpen\">\u25BE</span>\r\n </div>\r\n\r\n <!-- Dropdown menu: visible only when isOpen is true -->\r\n <ul class=\"dropdown-menu\" *ngIf=\"isOpen\">\r\n <!-- Render each option -->\r\n <li \r\n *ngFor=\"let option of options\"\r\n (click)=\"select(option)\"\r\n [class.selected]=\"option.value === value\"> <!-- Highlight selected option -->\r\n {{ option.label }}\r\n </li>\r\n </ul>\r\n</div>\r\n", styles: [".dropdown{position:relative;display:inline-flex;font-family:inherit}.dropdown.disabled{opacity:.5;pointer-events:none}.dropdown-trigger{border:1px solid #ccc;padding:8px 12px;background:#fff;cursor:pointer;display:inline-flex;gap:8px;align-items:center;border-radius:4px}.dropdown-trigger:hover{border-color:#888}.dropdown-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;background:#fff;border:1px solid #ccc;border-radius:4px;list-style:none;padding:4px 0;margin:0;max-height:200px;overflow-y:auto;z-index:100}.dropdown-menu li{padding:8px 12px;cursor:pointer}.dropdown-menu li:hover{background:#f2f2f2}.dropdown-menu li.selected{background:#e6e6e6;font-weight:500}\n"] }]
606
+ }], propDecorators: { root: [{
607
+ type: ViewChild,
608
+ args: ['root', { static: true }]
609
+ }], options: [{
610
+ type: Input
611
+ }], placeholder: [{
612
+ type: Input
613
+ }], value: [{
614
+ type: Input
615
+ }], disabled: [{
616
+ type: Input
617
+ }], selectionChange: [{
618
+ type: Output
619
+ }], handleOutsideClick: [{
620
+ type: HostListener,
621
+ args: ['document:click', ['$event']]
622
+ }] } });
623
+
624
+ class ProgressBarComponent {
625
+ /** Progress value (0–100). If null, shows indeterminate animation. */
626
+ value = null;
627
+ /** Color variants */
628
+ color = 'primary';
629
+ /** Optional animation toggle */
630
+ animated = true;
631
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProgressBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
632
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ProgressBarComponent, isStandalone: true, selector: "dso-progress-bar", inputs: { value: "value", color: "color", animated: "animated" }, ngImport: i0, template: "<div class=\"progress-bar-container\">\r\n <div\r\n class=\"progress-bar-fill\"\r\n [ngClass]=\"[\r\n color,\r\n animated ? 'animated' : '',\r\n value === null ? 'indeterminate' : ''\r\n ]\"\r\n [style.width.%]=\"value !== null ? value : 50\"\r\n ></div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";.progress-bar-container{width:100%;height:8px;background-color:#e9ecef;border-radius:4px;overflow:hidden;position:relative;min-width:240px}.progress-bar-fill{position:absolute;height:100%;border-radius:4px;transition:width .4s ease}.progress-bar-fill.primary{background-color:#007bff}.progress-bar-fill.success{background-color:#28a745}.progress-bar-fill.warning{background-color:#ffc107}.progress-bar-fill.danger{background-color:#dc3545}.progress-bar-fill.animated{transition:width .4s ease-in-out}.progress-bar-fill.indeterminate{width:30%;animation:scrollLoop 1.5s linear infinite}@keyframes scrollLoop{0%{transform:translate(-100%)}to{transform:translate(400%)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
633
+ }
634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProgressBarComponent, decorators: [{
635
+ type: Component,
636
+ args: [{ selector: 'dso-progress-bar', standalone: true, imports: [CommonModule], template: "<div class=\"progress-bar-container\">\r\n <div\r\n class=\"progress-bar-fill\"\r\n [ngClass]=\"[\r\n color,\r\n animated ? 'animated' : '',\r\n value === null ? 'indeterminate' : ''\r\n ]\"\r\n [style.width.%]=\"value !== null ? value : 50\"\r\n ></div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";.progress-bar-container{width:100%;height:8px;background-color:#e9ecef;border-radius:4px;overflow:hidden;position:relative;min-width:240px}.progress-bar-fill{position:absolute;height:100%;border-radius:4px;transition:width .4s ease}.progress-bar-fill.primary{background-color:#007bff}.progress-bar-fill.success{background-color:#28a745}.progress-bar-fill.warning{background-color:#ffc107}.progress-bar-fill.danger{background-color:#dc3545}.progress-bar-fill.animated{transition:width .4s ease-in-out}.progress-bar-fill.indeterminate{width:30%;animation:scrollLoop 1.5s linear infinite}@keyframes scrollLoop{0%{transform:translate(-100%)}to{transform:translate(400%)}}\n"] }]
637
+ }], propDecorators: { value: [{
638
+ type: Input
639
+ }], color: [{
640
+ type: Input
641
+ }], animated: [{
642
+ type: Input
643
+ }] } });
644
+
645
+ class FileUploadItemComponent {
646
+ /** The file upload item to display */
647
+ item;
648
+ /** Emitted when the user retries a failed upload */
649
+ retry = new EventEmitter();
650
+ /** Emitted when the user removes the file from the list */
651
+ remove = new EventEmitter();
652
+ /** Emitted when the user cancels an ongoing upload */
653
+ cancel = new EventEmitter();
654
+ /**
655
+ * Format the file size into a human-readable string
656
+ * @param size - size in bytes
657
+ * @returns formatted string like '12.3 KB', '1.5 MB'
658
+ */
659
+ formatSize(size) {
660
+ if (size < 1024)
661
+ return size + ' B';
662
+ if (size < 1024 * 1024)
663
+ return (size / 1024).toFixed(1) + ' KB';
664
+ return (size / (1024 * 1024)).toFixed(1) + ' MB';
665
+ }
666
+ /**
667
+ * Get an icon name for the file based on its type or extension
668
+ * @param item - the UploadItem
669
+ * @returns icon name string
670
+ */
671
+ getFileIcon(item) {
672
+ const file = item.file;
673
+ const type = file.type.toLowerCase();
674
+ const name = file.name.toLowerCase();
675
+ // Image MIME types
676
+ if (type.startsWith('image/')) {
677
+ return 'icon-gallery';
678
+ }
679
+ // Image file extensions fallback
680
+ const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'];
681
+ if (imageExtensions.some(ext => name.endsWith(ext))) {
682
+ return 'icon-gallery';
683
+ }
684
+ // Default icon for non-image files
685
+ return 'icon-van';
686
+ }
687
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileUploadItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
688
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FileUploadItemComponent, isStandalone: true, selector: "dso-file-item", inputs: { item: "item" }, outputs: { retry: "retry", remove: "remove", cancel: "cancel" }, ngImport: i0, template: "<div class=\"file-row\">\r\n\r\n <!-- LEFT SECTION: Thumbnail + filename -->\r\n <div class=\"thumbnail-filename-wrapper\">\r\n <div class=\"file-thumb\">\r\n <dso-icon \r\n [iconName]=\"getFileIcon(item)\"\r\n size=\"small\"\r\n class=\"thumb-icon\">\r\n </dso-icon>\r\n </div>\r\n\r\n <div class=\"file-info\">\r\n <div class=\"file-name\">{{ item.file.name }}</div>\r\n <div class=\"file-size\">{{ formatSize(item.file.size) }}</div>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- MIDDLE SECTION: Progress or status -->\r\n <div class=\"filecontent-loader-wrapper\">\r\n \r\n <!-- Progress bar -->\r\n <div class=\"file-progress\" *ngIf=\"!item.hideProgressBar\">\r\n <dso-progress-bar\r\n [value]=\"\r\n item.status === 'uploading' ? item.progress :\r\n item.status === 'completed' ? 100 :\r\n null\r\n \"\r\n [color]=\"item.status === 'completed' ? 'success' : 'primary'\"\r\n ></dso-progress-bar>\r\n </div>\r\n\r\n <!-- Status text -->\r\n <div class=\"file-status\" *ngIf=\"item.hideProgressBar\">\r\n\r\n <!-- Success -->\r\n <div *ngIf=\"item.status === 'completed'\" class=\"status success\">\r\n <dso-icon iconName=\"icon-van\" size=\"small\"></dso-icon>\r\n <span>Uploaded</span>\r\n </div>\r\n\r\n <!-- Failed -->\r\n <div *ngIf=\"item.status === 'failed'\" class=\"status error\">\r\n <dso-icon iconName=\"icon-center-align\" size=\"small\"></dso-icon>\r\n <span>Upload failed</span>\r\n </div>\r\n\r\n </div>\r\n\r\n <!-- RIGHT SECTION: Action buttons -->\r\n <div class=\"file-actions\">\r\n\r\n <!-- Uploading -->\r\n <ng-container *ngIf=\"item.status === 'uploading'\">\r\n <dso-button\r\n btnLabel=\"Cancel\"\r\n btnType=\"text\"\r\n (onClick)=\"cancel.emit()\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n <!-- Failed -->\r\n <ng-container *ngIf=\"item.status === 'failed'\">\r\n <dso-button\r\n btnLabel=\"Retry\"\r\n btnType=\"text\"\r\n (onClick)=\"retry.emit()\">\r\n </dso-button>\r\n\r\n <dso-button\r\n btnLabel=\"Remove\"\r\n btnType=\"text\"\r\n (onClick)=\"remove.emit()\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n <!-- Completed -->\r\n <ng-container *ngIf=\"item.status === 'completed'\">\r\n <dso-button\r\n btnLabel=\"Remove\"\r\n btnType=\"text\"\r\n (onClick)=\"remove.emit()\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n", styles: [".file-row{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:#fafafa;border-radius:4px}.file-info{display:flex;flex-direction:column}.file-info .file-name{font-weight:300}.file-info .file-size{font-size:.85rem;color:#666}.file-progress{padding-right:1rem;flex:1 0 0;display:flex;justify-content:center;align-items:center;min-width:260px;max-width:320px}.file-actions{display:flex;width:160px;gap:.5rem;justify-content:flex-end}.file-status{display:flex;align-items:center;font-size:14px;flex:1 0 0;min-width:260px;max-width:320px}.file-status .status{display:flex;align-items:center;gap:6px;font-weight:500}.file-status .status.success{color:#16a34a}.file-status .status.error{color:#dc2626}.file-status .status .status-icon{width:16px;height:16px}.file-thumb{width:40px;height:40px;flex-shrink:0;border-radius:6px;overflow:hidden;background:#f3f4f6;display:flex;align-items:center;justify-content:center}.file-thumb img{width:100%;height:100%;object-fit:cover}.thumbnail-filename-wrapper{display:flex;gap:24px}.filecontent-loader-wrapper{display:flex;gap:20px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }, { kind: "component", type: ProgressBarComponent, selector: "dso-progress-bar", inputs: ["value", "color", "animated"] }] });
689
+ }
690
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileUploadItemComponent, decorators: [{
691
+ type: Component,
692
+ args: [{ selector: 'dso-file-item', standalone: true, imports: [CommonModule, ButtonComponent, IconComponent, ProgressBarComponent], template: "<div class=\"file-row\">\r\n\r\n <!-- LEFT SECTION: Thumbnail + filename -->\r\n <div class=\"thumbnail-filename-wrapper\">\r\n <div class=\"file-thumb\">\r\n <dso-icon \r\n [iconName]=\"getFileIcon(item)\"\r\n size=\"small\"\r\n class=\"thumb-icon\">\r\n </dso-icon>\r\n </div>\r\n\r\n <div class=\"file-info\">\r\n <div class=\"file-name\">{{ item.file.name }}</div>\r\n <div class=\"file-size\">{{ formatSize(item.file.size) }}</div>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- MIDDLE SECTION: Progress or status -->\r\n <div class=\"filecontent-loader-wrapper\">\r\n \r\n <!-- Progress bar -->\r\n <div class=\"file-progress\" *ngIf=\"!item.hideProgressBar\">\r\n <dso-progress-bar\r\n [value]=\"\r\n item.status === 'uploading' ? item.progress :\r\n item.status === 'completed' ? 100 :\r\n null\r\n \"\r\n [color]=\"item.status === 'completed' ? 'success' : 'primary'\"\r\n ></dso-progress-bar>\r\n </div>\r\n\r\n <!-- Status text -->\r\n <div class=\"file-status\" *ngIf=\"item.hideProgressBar\">\r\n\r\n <!-- Success -->\r\n <div *ngIf=\"item.status === 'completed'\" class=\"status success\">\r\n <dso-icon iconName=\"icon-van\" size=\"small\"></dso-icon>\r\n <span>Uploaded</span>\r\n </div>\r\n\r\n <!-- Failed -->\r\n <div *ngIf=\"item.status === 'failed'\" class=\"status error\">\r\n <dso-icon iconName=\"icon-center-align\" size=\"small\"></dso-icon>\r\n <span>Upload failed</span>\r\n </div>\r\n\r\n </div>\r\n\r\n <!-- RIGHT SECTION: Action buttons -->\r\n <div class=\"file-actions\">\r\n\r\n <!-- Uploading -->\r\n <ng-container *ngIf=\"item.status === 'uploading'\">\r\n <dso-button\r\n btnLabel=\"Cancel\"\r\n btnType=\"text\"\r\n (onClick)=\"cancel.emit()\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n <!-- Failed -->\r\n <ng-container *ngIf=\"item.status === 'failed'\">\r\n <dso-button\r\n btnLabel=\"Retry\"\r\n btnType=\"text\"\r\n (onClick)=\"retry.emit()\">\r\n </dso-button>\r\n\r\n <dso-button\r\n btnLabel=\"Remove\"\r\n btnType=\"text\"\r\n (onClick)=\"remove.emit()\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n <!-- Completed -->\r\n <ng-container *ngIf=\"item.status === 'completed'\">\r\n <dso-button\r\n btnLabel=\"Remove\"\r\n btnType=\"text\"\r\n (onClick)=\"remove.emit()\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n", styles: [".file-row{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:#fafafa;border-radius:4px}.file-info{display:flex;flex-direction:column}.file-info .file-name{font-weight:300}.file-info .file-size{font-size:.85rem;color:#666}.file-progress{padding-right:1rem;flex:1 0 0;display:flex;justify-content:center;align-items:center;min-width:260px;max-width:320px}.file-actions{display:flex;width:160px;gap:.5rem;justify-content:flex-end}.file-status{display:flex;align-items:center;font-size:14px;flex:1 0 0;min-width:260px;max-width:320px}.file-status .status{display:flex;align-items:center;gap:6px;font-weight:500}.file-status .status.success{color:#16a34a}.file-status .status.error{color:#dc2626}.file-status .status .status-icon{width:16px;height:16px}.file-thumb{width:40px;height:40px;flex-shrink:0;border-radius:6px;overflow:hidden;background:#f3f4f6;display:flex;align-items:center;justify-content:center}.file-thumb img{width:100%;height:100%;object-fit:cover}.thumbnail-filename-wrapper{display:flex;gap:24px}.filecontent-loader-wrapper{display:flex;gap:20px}\n"] }]
693
+ }], propDecorators: { item: [{
694
+ type: Input
695
+ }], retry: [{
696
+ type: Output
697
+ }], remove: [{
698
+ type: Output
699
+ }], cancel: [{
700
+ type: Output
701
+ }] } });
702
+
703
+ // upload-simulator.service.ts
704
+ /**
705
+ * UploadSimulatorService
706
+ * -----------------------
707
+ * This service simulates an upload process.
708
+ * It is ONLY for demo and UI development. Later, it can
709
+ * be replaced with a real HTTP upload service without
710
+ * changing the UI components.
711
+ *
712
+ * How it works:
713
+ * - Every 300ms, a timer increases the upload progress.
714
+ * - Progress increases by a random amount (to look realistic).
715
+ * - When it reaches 100%, the upload is marked complete.
716
+ * - There is a 10% chance the upload "fails" to simulate errors.
717
+ * - If an upload is cancelled, the interval stops immediately.
718
+ *
719
+ * This pattern mimics:
720
+ * - progress events (like HttpClient upload events)
721
+ * - cancellation (AbortController / unsubscribe)
722
+ */
723
+ class UploadSimulatorService {
724
+ /**
725
+ * Simulates uploading a single file.
726
+ *
727
+ * @param item The UploadItem being uploaded
728
+ * @param onProgress Callback fired whenever progress changes
729
+ * @param onComplete Callback fired when upload reaches 100%
730
+ * @param onError Callback fired when upload "fails"
731
+ */
732
+ simulateUpload(item, onProgress, onComplete, onError) {
733
+ console.log('%c[SIMULATOR] Starting upload:', 'color: green;', item.file.name);
734
+ let progress = 0;
735
+ // This interval simulates a real upload stream
736
+ const interval = setInterval(() => {
737
+ // If cancelled externally, we stop updating
738
+ if (item.status === 'cancelled') {
739
+ console.log('%c[SIMULATOR] Upload CANCELLED:', 'color: orange;', item.file.name);
740
+ clearInterval(interval);
741
+ return;
742
+ }
743
+ // 🔼 Simulate uploading by adding random progress increments
744
+ const increment = Math.random() * 15 + 5;
745
+ progress += increment;
746
+ console.log(`%c[SIMULATOR] ${item.file.name} +${increment.toFixed(1)}% → ${progress.toFixed(1)}%`, 'color: #1e90ff;');
747
+ // 🟦 If progress reaches 100%, finalize upload
748
+ if (progress >= 100) {
749
+ progress = 100;
750
+ onProgress(progress);
751
+ clearInterval(interval);
752
+ console.log('%c[SIMULATOR] Upload reached 100% for ' + item.file.name, 'color: green; font-weight: bold;');
753
+ // 🔥 50% chance to simulate failure
754
+ const randomFail = Math.random() < 0.5;
755
+ if (randomFail) {
756
+ console.log('%c[SIMULATOR] Simulated FAILURE for ' + item.file.name, 'color: red; font-weight: bold;');
757
+ onError();
758
+ }
759
+ else {
760
+ console.log('%c[SIMULATOR] Upload SUCCESS for ' + item.file.name, 'color: limegreen; font-weight: bold;');
761
+ onComplete();
762
+ }
763
+ return;
764
+ }
765
+ // Emit progress update
766
+ onProgress(progress);
767
+ }, 300); // runs every 300ms like a real upload tick
768
+ }
769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UploadSimulatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
770
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UploadSimulatorService, providedIn: 'root' });
771
+ }
772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UploadSimulatorService, decorators: [{
773
+ type: Injectable,
774
+ args: [{ providedIn: 'root' }]
775
+ }] });
776
+
777
+ // file-upload-multi.component.ts
778
+ /**
779
+ * FileUploadMultiComponent
780
+ * -------------------------
781
+ * This component handles:
782
+ * - Drag & drop file uploads
783
+ * - Selecting multiple files through <input type="file">
784
+ * - Upload progress per file
785
+ * - Cancelling an upload
786
+ * - Retrying failed uploads
787
+ * - Removing items from the queue
788
+ *
789
+ * UploadSimulatorService is used to mimic real upload behavior.
790
+ * Later, you can replace it with a real HTTP upload service.
791
+ */
792
+ class FileUploadMultiComponent {
793
+ sim;
794
+ // ngOnInit() {
795
+ // // Fake completed file
796
+ // this.uploadQueue.push({
797
+ // file: new File([""], "example-image.jpg", { type: "image/jpeg" }),
798
+ // progress: 100,
799
+ // status: "completed",
800
+ // hideProgressBar: true
801
+ // });
802
+ // // Fake failed file
803
+ // this.uploadQueue.push({
804
+ // file: new File([""], "broken-file.pdf", { type: "application/pdf" }),
805
+ // progress: null,
806
+ // status: "failed",
807
+ // hideProgressBar: true
808
+ // });
809
+ // // Fake uploading file
810
+ // this.uploadQueue.push({
811
+ // file: new File([""], "uploading.png", { type: "image/png" }),
812
+ // progress: 40,
813
+ // status: "uploading",
814
+ // hideProgressBar: false
815
+ // });
816
+ // }
817
+ /** All files being managed (uploaded, uploading, failed, completed). */
818
+ uploadQueue = [];
819
+ /** True while user drags a file over the drop zone. */
820
+ isDragOver = false;
821
+ constructor(sim) {
822
+ this.sim = sim;
823
+ // console.log('%c[COMPONENT] FileUploadMultiComponent initialized', 'color: purple;');
824
+ }
825
+ /* ===========================================================
826
+ * DRAG & DROP EVENTS
827
+ * =========================================================== */
828
+ onDragOver(event) {
829
+ event.preventDefault();
830
+ this.isDragOver = true;
831
+ // console.log('%c[DRAG] Dragging over drop zone...', 'color: #00aaff;');
832
+ }
833
+ onDragLeave(event) {
834
+ this.isDragOver = false;
835
+ // console.log('%c[DRAG] Drag left drop zone', 'color: #888;');
836
+ }
837
+ onDrop(event) {
838
+ event.preventDefault();
839
+ this.isDragOver = false;
840
+ // console.log('%c[DRAG] Files dropped', 'color: #00cc66;');
841
+ if (event.dataTransfer?.files) {
842
+ // console.log('[DRAG] Dropped files:', event.dataTransfer.files);
843
+ this.handleFiles(event.dataTransfer.files);
844
+ }
845
+ }
846
+ /* ===========================================================
847
+ * FILE INPUT SELECT
848
+ * =========================================================== */
849
+ onFileSelected(event) {
850
+ const input = event.target;
851
+ if (input.files) {
852
+ // console.log('%c[INPUT] Files selected:', 'color: #0099ff;', input.files);
853
+ this.handleFiles(input.files);
854
+ // Clears <input> so user can re-select same file
855
+ input.value = '';
856
+ }
857
+ }
858
+ /* ===========================================================
859
+ * PROCESSING NEWLY ADDED FILES
860
+ * =========================================================== */
861
+ handleFiles(fileList) {
862
+ // console.log('%c[FILES] Processing new files...', 'color: #ffaa00;');
863
+ Array.from(fileList).forEach(file => {
864
+ // console.log(`%c[FILES] Added: ${file.name} (${file.size} bytes)`, 'color: #ffaa00;');
865
+ const item = {
866
+ file,
867
+ progress: 0,
868
+ status: 'queued'
869
+ };
870
+ this.uploadQueue.push(item);
871
+ this.startUpload(item);
872
+ });
873
+ }
874
+ /* ===========================================================
875
+ * UPLOAD LOGIC
876
+ * =========================================================== */
877
+ startUpload(item) {
878
+ // console.log(`%c[UPLOAD] Starting upload for ${item.file.name}`, 'color: #00cc99;');
879
+ item.status = 'uploading';
880
+ item.progress = 0;
881
+ this.sim.simulateUpload(item,
882
+ // ---- onProgress callback ----
883
+ (progress) => {
884
+ item.progress = progress;
885
+ // console.log(`%c[UPLOAD] ${item.file.name}: ${progress.toFixed(1)}%`, 'color: #0099ff;');
886
+ },
887
+ // ---- onComplete callback ----
888
+ () => {
889
+ item.status = 'completed';
890
+ item.progress = 100;
891
+ // Hide progress bar after a very small delay (100–300ms)
892
+ setTimeout(() => item.hideProgressBar = true, 300);
893
+ // console.log(`%c[UPLOAD] COMPLETED: ${item.file.name}`, 'color: green; font-weight: bold;');
894
+ },
895
+ // ---- onError callback ----
896
+ () => {
897
+ item.status = 'failed';
898
+ item.progress = null;
899
+ // Hide progress bar so status text can show
900
+ setTimeout(() => item.hideProgressBar = true, 300);
901
+ // console.log(`%c[UPLOAD] FAILED: ${item.file.name}`, 'color: red; font-weight: bold;');
902
+ });
903
+ }
904
+ onDropZoneClick(fileInput) {
905
+ // Prevent click if user is dragging files over the drop-zone
906
+ if (this.isDragOver)
907
+ return;
908
+ console.log('%c[CLICK] Drop zone clicked → opening file dialog', 'color: #0077ff;');
909
+ fileInput.click();
910
+ }
911
+ onBrowseButtonClick(event, fileInput) {
912
+ event.stopPropagation(); // stops click from bubbling to drop-zone
913
+ // console.log('%c[BUTTON] Browse clicked → opening file dialog', 'color: #8844ff;');
914
+ fileInput.click();
915
+ }
916
+ /* ===========================================================
917
+ * BUTTON ACTIONS (CANCEL / RETRY / REMOVE)
918
+ * =========================================================== */
919
+ cancelUpload(item) {
920
+ // console.log(`%c[CANCEL] Cancelling ${item.file.name}`, 'color: orange; font-weight: bold;');
921
+ // item.status = 'cancelled';
922
+ // item.progress = null;
923
+ this.removeItem(item);
924
+ }
925
+ retryUpload(item) {
926
+ // console.log('[RETRY] Restarting upload for', item.file.name);
927
+ // Reset state
928
+ item.status = 'queued';
929
+ item.progress = 0;
930
+ item.hideProgressBar = false; // <-- IMPORTANT
931
+ // Start upload again
932
+ this.startUpload(item);
933
+ }
934
+ removeItem(item) {
935
+ // console.log(`%c[REMOVE] Removing ${item.file.name}`, 'color: gray;');
936
+ this.uploadQueue = this.uploadQueue.filter(i => i !== item);
937
+ }
938
+ /* ===========================================================
939
+ * UTILITY FUNCTIONS
940
+ * =========================================================== */
941
+ /**
942
+ * Converts bytes → human readable sizes.
943
+ */
944
+ formatSize(size) {
945
+ if (size < 1024)
946
+ return size + ' B';
947
+ if (size < 1024 * 1024)
948
+ return (size / 1024).toFixed(1) + ' KB';
949
+ return (size / (1024 * 1024)).toFixed(1) + ' MB';
950
+ }
951
+ // getThumbnail(item: UploadItem): string | null {
952
+ // const file = item.file;
953
+ // // If actual image → return preview URL
954
+ // if (file.type.startsWith('image/')) {
955
+ // return URL.createObjectURL(file);
956
+ // }
957
+ // // Otherwise return an icon path depending on type
958
+ // if (file.type === 'application/pdf') return 'assets/icons/pdf.svg';
959
+ // if (file.type.includes('word')) return 'assets/icons/doc.svg';
960
+ // if (file.type.includes('spreadsheet')) return 'assets/icons/xlsx.svg';
961
+ // // default icon
962
+ // return 'assets/icons/file.svg';
963
+ // }
964
+ getThumbnail(item) {
965
+ const file = item.file;
966
+ // Image preview for image file types
967
+ if (file.type.startsWith('image/')) {
968
+ return URL.createObjectURL(file);
969
+ }
970
+ // Not an image → no thumbnail
971
+ return null;
972
+ }
973
+ getFileIcon(item) {
974
+ const file = item.file;
975
+ const type = file.type.toLowerCase();
976
+ const name = file.name.toLowerCase();
977
+ console.log('%c[DEBUG] File name:', 'color: blue;', file.name);
978
+ console.log('%c[DEBUG] File type:', 'color: green;', file.type);
979
+ // Check MIME type first
980
+ if (type.startsWith('image/')) {
981
+ console.log('%c[DEBUG] Detected as image by MIME type', 'color: purple;');
982
+ return 'icon-gallery';
983
+ }
984
+ // Fallback: check extension for image types including SVG
985
+ const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'];
986
+ if (imageExtensions.some(ext => name.endsWith(ext))) {
987
+ console.log('%c[DEBUG] Detected as image by extension', 'color: orange;');
988
+ return 'icon-gallery';
989
+ }
990
+ console.log('%c[DEBUG] Defaulting to document icon', 'color: red;');
991
+ return 'icon-van';
992
+ }
993
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileUploadMultiComponent, deps: [{ token: UploadSimulatorService }], target: i0.ɵɵFactoryTarget.Component });
994
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FileUploadMultiComponent, isStandalone: true, selector: "dso-file-upload-multi", ngImport: i0, template: "<div class=\"upload-container\">\r\n\r\n <!-- DRAG & DROP AREA -->\r\n <div\r\n class=\"drop-zone\"\r\n [class.drag-over]=\"isDragOver\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n (click)=\"onDropZoneClick(fileInput)\"\r\n >\r\n\r\n <dso-button \r\n style=\"padding: 0; \"\r\n btnLabel=\"Select files\"\r\n btnType=\"link\"\r\n (onClick)=\"onBrowseButtonClick($event, fileInput)\">\r\n </dso-button>\r\n or drag & drop files \r\n <input \r\n type=\"file\" \r\n multiple \r\n hidden \r\n #fileInput \r\n (change)=\"onFileSelected($event)\"\r\n />\r\n </div>\r\n\r\n <!-- FILE LIST -->\r\n <div class=\"file-list\" *ngIf=\"uploadQueue.length > 0\">\r\n\r\n <dso-file-item\r\n *ngFor=\"let item of uploadQueue\"\r\n [item]=\"item\"\r\n (retry)=\"retryUpload(item)\"\r\n (remove)=\"removeItem(item)\"\r\n (cancel)=\"cancelUpload(item)\"\r\n ></dso-file-item>\r\n\r\n\r\n </div>\r\n</div>\r\n", styles: [".upload-container{display:flex;flex-direction:column;gap:8px}.drop-zone{border:2px dashed #ccc;padding:2rem;text-align:center;border-radius:6px;transition:border-color .2s,background-color .2s;cursor:pointer}.drop-zone.drag-over{border-color:#007bff;background-color:#f0f7ff}.file-list{display:flex;flex-direction:column;gap:.75rem;max-height:360px;overflow-y:auto;padding-right:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "component", type: FileUploadItemComponent, selector: "dso-file-item", inputs: ["item"], outputs: ["retry", "remove", "cancel"] }] });
995
+ }
996
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileUploadMultiComponent, decorators: [{
997
+ type: Component,
998
+ args: [{ selector: 'dso-file-upload-multi', standalone: true, imports: [CommonModule, ButtonComponent, IconComponent, FileUploadItemComponent], template: "<div class=\"upload-container\">\r\n\r\n <!-- DRAG & DROP AREA -->\r\n <div\r\n class=\"drop-zone\"\r\n [class.drag-over]=\"isDragOver\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n (click)=\"onDropZoneClick(fileInput)\"\r\n >\r\n\r\n <dso-button \r\n style=\"padding: 0; \"\r\n btnLabel=\"Select files\"\r\n btnType=\"link\"\r\n (onClick)=\"onBrowseButtonClick($event, fileInput)\">\r\n </dso-button>\r\n or drag & drop files \r\n <input \r\n type=\"file\" \r\n multiple \r\n hidden \r\n #fileInput \r\n (change)=\"onFileSelected($event)\"\r\n />\r\n </div>\r\n\r\n <!-- FILE LIST -->\r\n <div class=\"file-list\" *ngIf=\"uploadQueue.length > 0\">\r\n\r\n <dso-file-item\r\n *ngFor=\"let item of uploadQueue\"\r\n [item]=\"item\"\r\n (retry)=\"retryUpload(item)\"\r\n (remove)=\"removeItem(item)\"\r\n (cancel)=\"cancelUpload(item)\"\r\n ></dso-file-item>\r\n\r\n\r\n </div>\r\n</div>\r\n", styles: [".upload-container{display:flex;flex-direction:column;gap:8px}.drop-zone{border:2px dashed #ccc;padding:2rem;text-align:center;border-radius:6px;transition:border-color .2s,background-color .2s;cursor:pointer}.drop-zone.drag-over{border-color:#007bff;background-color:#f0f7ff}.file-list{display:flex;flex-direction:column;gap:.75rem;max-height:360px;overflow-y:auto;padding-right:4px}\n"] }]
999
+ }], ctorParameters: () => [{ type: UploadSimulatorService }] });
1000
+
1001
+ class FileUploadSingleComponent {
1002
+ sim;
1003
+ /** Button label text */
1004
+ btnLabel = 'Choose File';
1005
+ /** Allowed file types, e.g. ".png,.jpg,.pdf" */
1006
+ accept = '';
1007
+ /** Emit the selected file to parent component */
1008
+ fileSelected = new EventEmitter();
1009
+ /** The currently uploaded file shown in the UI */
1010
+ item = null;
1011
+ constructor(sim) {
1012
+ this.sim = sim;
1013
+ }
1014
+ /** Trigger the hidden file input click */
1015
+ onButtonClick(input) {
1016
+ input.click();
1017
+ }
1018
+ /** Handle file selection from input */
1019
+ onFileChange(event) {
1020
+ const input = event.target;
1021
+ if (!input.files || input.files.length === 0)
1022
+ return;
1023
+ const file = input.files[0];
1024
+ // Wrap file in UploadItem structure (same as multi-upload)
1025
+ this.item = {
1026
+ file,
1027
+ progress: 0,
1028
+ status: 'queued',
1029
+ hideProgressBar: false
1030
+ };
1031
+ // Emit file to parent
1032
+ this.fileSelected.emit(file);
1033
+ // Start simulated upload
1034
+ this.startUpload();
1035
+ // Reset input so user can pick same file again
1036
+ input.value = '';
1037
+ }
1038
+ /** Start upload simulation */
1039
+ startUpload() {
1040
+ if (!this.item)
1041
+ return;
1042
+ const item = this.item;
1043
+ item.status = 'uploading';
1044
+ item.progress = 0;
1045
+ this.sim.simulateUpload(item,
1046
+ // onProgress callback
1047
+ (progress) => {
1048
+ item.progress = progress;
1049
+ },
1050
+ // onComplete callback
1051
+ () => {
1052
+ item.status = 'completed';
1053
+ item.progress = 100;
1054
+ setTimeout(() => item.hideProgressBar = true, 300);
1055
+ },
1056
+ // onError callback
1057
+ () => {
1058
+ item.status = 'failed';
1059
+ item.progress = null;
1060
+ setTimeout(() => item.hideProgressBar = true, 300);
1061
+ });
1062
+ }
1063
+ /** Retry failed upload */
1064
+ retry() {
1065
+ if (!this.item)
1066
+ return;
1067
+ this.item.status = 'queued';
1068
+ this.item.progress = 0;
1069
+ this.item.hideProgressBar = false;
1070
+ this.startUpload();
1071
+ }
1072
+ /** Cancel or remove the file from UI */
1073
+ clearFile() {
1074
+ this.item = null;
1075
+ }
1076
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileUploadSingleComponent, deps: [{ token: UploadSimulatorService }], target: i0.ɵɵFactoryTarget.Component });
1077
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FileUploadSingleComponent, isStandalone: true, selector: "dso-file-upload-single", inputs: { btnLabel: "btnLabel", accept: "accept" }, outputs: { fileSelected: "fileSelected" }, ngImport: i0, template: "<div class=\"file-upload-single\">\r\n\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n #fileInput\r\n [accept]=\"accept\"\r\n (change)=\"onFileChange($event)\"\r\n hidden\r\n />\r\n\r\n <!-- Main button -->\r\n <dso-button\r\n [btnLabel]=\"btnLabel\"\r\n btnType=\"filled\"\r\n (onClick)=\"onButtonClick(fileInput)\"\r\n ></dso-button>\r\n\r\n <!-- Render file info using shared reusable component -->\r\n <dso-file-item\r\n *ngIf=\"item\"\r\n [item]=\"item\"\r\n (cancel)=\"clearFile()\"\r\n (remove)=\"clearFile()\"\r\n (retry)=\"retry()\"\r\n ></dso-file-item>\r\n\r\n</div>\r\n", styles: [".file-upload-single{display:flex;flex-direction:column;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "component", type: FileUploadItemComponent, selector: "dso-file-item", inputs: ["item"], outputs: ["retry", "remove", "cancel"] }] });
1078
+ }
1079
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileUploadSingleComponent, decorators: [{
1080
+ type: Component,
1081
+ args: [{ selector: 'dso-file-upload-single', standalone: true, imports: [
1082
+ CommonModule,
1083
+ ButtonComponent,
1084
+ FileUploadItemComponent
1085
+ ], template: "<div class=\"file-upload-single\">\r\n\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n #fileInput\r\n [accept]=\"accept\"\r\n (change)=\"onFileChange($event)\"\r\n hidden\r\n />\r\n\r\n <!-- Main button -->\r\n <dso-button\r\n [btnLabel]=\"btnLabel\"\r\n btnType=\"filled\"\r\n (onClick)=\"onButtonClick(fileInput)\"\r\n ></dso-button>\r\n\r\n <!-- Render file info using shared reusable component -->\r\n <dso-file-item\r\n *ngIf=\"item\"\r\n [item]=\"item\"\r\n (cancel)=\"clearFile()\"\r\n (remove)=\"clearFile()\"\r\n (retry)=\"retry()\"\r\n ></dso-file-item>\r\n\r\n</div>\r\n", styles: [".file-upload-single{display:flex;flex-direction:column;gap:8px}\n"] }]
1086
+ }], ctorParameters: () => [{ type: UploadSimulatorService }], propDecorators: { btnLabel: [{
1087
+ type: Input
1088
+ }], accept: [{
1089
+ type: Input
1090
+ }], fileSelected: [{
1091
+ type: Output
1092
+ }] } });
1093
+
1094
+ class TextInputComponent {
1095
+ // -------------------------------
1096
+ // INPUT PROPERTIES
1097
+ // -------------------------------
1098
+ inputLabel = ''; // Optional label displayed above input
1099
+ value = ''; // Two-way bound input value
1100
+ placeholder = 'Input value'; // Placeholder text
1101
+ type = ''; // Input type (text, email, etc.)
1102
+ isDisabled = false; // Disable the input
1103
+ helperText = ''; // Optional helper text below input
1104
+ errorText = ''; // Optional error message
1105
+ enableValidation = true; // Flag to enable/disable validation
1106
+ maxLength = 80; // Maximum characters allowed
1107
+ // -------------------------------
1108
+ // OUTPUT EVENTS
1109
+ // -------------------------------
1110
+ valueChange = new EventEmitter(); // Emit input changes to parent
1111
+ // -------------------------------
1112
+ // INTERNAL STATE
1113
+ // -------------------------------
1114
+ isTouched = false; // Tracks if user has interacted with input
1115
+ isInvalid = false; // True if validation fails
1116
+ charCount = 0; // Tracks current character count
1117
+ isExceeded = false; // True if charCount exceeds maxLength
1118
+ // -------------------------------
1119
+ // EVENT HANDLERS
1120
+ // -------------------------------
1121
+ onInputChange(event) {
1122
+ this.value = event.target.value; // Update value
1123
+ const inputValue = event.target.value;
1124
+ const charCount = inputValue.length;
1125
+ // Track if max length exceeded
1126
+ this.isExceeded = charCount > this.maxLength;
1127
+ this.isTouched = true; // Mark input as touched
1128
+ this.valueChange.emit(this.value); // Notify parent
1129
+ this.checkValidity(); // Validate input
1130
+ }
1131
+ onBlur() {
1132
+ this.isTouched = true; // Input lost focus → mark as touched
1133
+ this.checkValidity(); // Validate when blurred
1134
+ }
1135
+ // -------------------------------
1136
+ // HELPER METHODS
1137
+ // -------------------------------
1138
+ getCharCountStyle() {
1139
+ const charCount = this.value.length;
1140
+ if (this.isExceeded) {
1141
+ return 'char-count-error'; // Apply error style if limit exceeded
1142
+ }
1143
+ // Optional: style differently if more than 50% of maxLength used
1144
+ if (charCount > this.maxLength / 2) {
1145
+ return '';
1146
+ }
1147
+ return ''; // Default style
1148
+ }
1149
+ checkValidity() {
1150
+ // If touched and value is empty, mark as invalid
1151
+ this.isInvalid = this.isTouched && !this.value.trim();
1152
+ }
1153
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1154
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TextInputComponent, isStandalone: true, selector: "dso-input-text", inputs: { inputLabel: "inputLabel", value: "value", placeholder: "placeholder", type: "type", isDisabled: "isDisabled", helperText: "helperText", errorText: "errorText", enableValidation: "enableValidation", maxLength: "maxLength" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"input-container\">\r\n <!-- Optional label -->\r\n <div class=\"text-wrapper\">\r\n <!-- Optional label -->\r\n <label *ngIf=\"inputLabel\">\r\n {{ inputLabel }}\r\n </label> \r\n <!-- Character count inside the container -->\r\n <div class=\"char-count\" [ngClass]=\"getCharCountStyle()\" *ngIf=\"maxLength\">\r\n <span>\r\n {{ value.length }} \r\n </span>\r\n / {{ maxLength }}\r\n </div>\r\n </div> \r\n <input\r\n [type]=\"type\" \r\n [placeholder]=\"placeholder\"\r\n [(ngModel)]=\"value\"\r\n (input)=\"onInputChange($event)\" \r\n (blur)=\"onBlur()\"\r\n [disabled]=\"isDisabled\"\r\n [class.disabled]=\"isDisabled\"\r\n [class.invalid]=\"(isInvalid && isTouched && enableValidation) || isExceeded\" \r\n />\r\n\r\n <!-- Helper Text \r\n This condition checks if the helper text should be displayed:\r\n - If the character limit is **not exceeded** and validation is **not enabled**, show the helper text.\r\n - If validation **is enabled**, show the helper text only if the input is **not invalid** and `helperText` is provided.\r\n - If the character limit is exceeded, the helper text will be replaced with an error message. -->\r\n <div *ngIf=\"!isExceeded && !enableValidation || ((!isInvalid && helperText) && !isExceeded)\" class=\"helper-text\">\r\n {{ helperText }}\r\n </div>\r\n\r\n <!-- Specified Error Text -->\r\n <div *ngIf=\"isInvalid && isTouched && enableValidation\" class=\"error-message\">\r\n {{ errorText }}\r\n </div>\r\n\r\n <!-- Error message if character limit exceeded -->\r\n <div *ngIf=\"isExceeded\" class=\"error-message\">\r\n Character limit exceeded.\r\n </div>\r\n</div>\r\n\r\n", styles: [":host{display:inline-block}.input-container{width:500px;display:flex;flex-direction:column;gap:8px}.text-wrapper{display:flex;justify-content:space-between}input{width:100%;height:48px;padding:12px 8px;font-size:16px;border:1px solid #ccc;border-radius:4px}input:hover{border:1px solid #071d35}input:focus{border-color:#007bff;outline:none}input.invalid{border-color:red}.error-message{color:red;font-size:16px}input.disabled{cursor:not-allowed;background-color:#f0f0f0;border:1px solid #ddd;color:#000}.helper-text{font-size:16px;color:#6c757d}.error-message{font-size:16px;color:red}.char-count{font-size:12px;color:#666;align-self:flex-end}.char-count span,.char-count-error{font-size:12px;color:#666}.char-count-error span{font-size:12px;color:red}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1155
+ }
1156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextInputComponent, decorators: [{
1157
+ type: Component,
1158
+ args: [{ selector: 'dso-input-text', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"input-container\">\r\n <!-- Optional label -->\r\n <div class=\"text-wrapper\">\r\n <!-- Optional label -->\r\n <label *ngIf=\"inputLabel\">\r\n {{ inputLabel }}\r\n </label> \r\n <!-- Character count inside the container -->\r\n <div class=\"char-count\" [ngClass]=\"getCharCountStyle()\" *ngIf=\"maxLength\">\r\n <span>\r\n {{ value.length }} \r\n </span>\r\n / {{ maxLength }}\r\n </div>\r\n </div> \r\n <input\r\n [type]=\"type\" \r\n [placeholder]=\"placeholder\"\r\n [(ngModel)]=\"value\"\r\n (input)=\"onInputChange($event)\" \r\n (blur)=\"onBlur()\"\r\n [disabled]=\"isDisabled\"\r\n [class.disabled]=\"isDisabled\"\r\n [class.invalid]=\"(isInvalid && isTouched && enableValidation) || isExceeded\" \r\n />\r\n\r\n <!-- Helper Text \r\n This condition checks if the helper text should be displayed:\r\n - If the character limit is **not exceeded** and validation is **not enabled**, show the helper text.\r\n - If validation **is enabled**, show the helper text only if the input is **not invalid** and `helperText` is provided.\r\n - If the character limit is exceeded, the helper text will be replaced with an error message. -->\r\n <div *ngIf=\"!isExceeded && !enableValidation || ((!isInvalid && helperText) && !isExceeded)\" class=\"helper-text\">\r\n {{ helperText }}\r\n </div>\r\n\r\n <!-- Specified Error Text -->\r\n <div *ngIf=\"isInvalid && isTouched && enableValidation\" class=\"error-message\">\r\n {{ errorText }}\r\n </div>\r\n\r\n <!-- Error message if character limit exceeded -->\r\n <div *ngIf=\"isExceeded\" class=\"error-message\">\r\n Character limit exceeded.\r\n </div>\r\n</div>\r\n\r\n", styles: [":host{display:inline-block}.input-container{width:500px;display:flex;flex-direction:column;gap:8px}.text-wrapper{display:flex;justify-content:space-between}input{width:100%;height:48px;padding:12px 8px;font-size:16px;border:1px solid #ccc;border-radius:4px}input:hover{border:1px solid #071d35}input:focus{border-color:#007bff;outline:none}input.invalid{border-color:red}.error-message{color:red;font-size:16px}input.disabled{cursor:not-allowed;background-color:#f0f0f0;border:1px solid #ddd;color:#000}.helper-text{font-size:16px;color:#6c757d}.error-message{font-size:16px;color:red}.char-count{font-size:12px;color:#666;align-self:flex-end}.char-count span,.char-count-error{font-size:12px;color:#666}.char-count-error span{font-size:12px;color:red}\n"] }]
1159
+ }], propDecorators: { inputLabel: [{
1160
+ type: Input
1161
+ }], value: [{
1162
+ type: Input
1163
+ }], placeholder: [{
1164
+ type: Input
1165
+ }], type: [{
1166
+ type: Input
1167
+ }], isDisabled: [{
1168
+ type: Input
1169
+ }], helperText: [{
1170
+ type: Input
1171
+ }], errorText: [{
1172
+ type: Input
1173
+ }], enableValidation: [{
1174
+ type: Input
1175
+ }], maxLength: [{
1176
+ type: Input
1177
+ }], valueChange: [{
1178
+ type: Output
1179
+ }] } });
1180
+
1181
+ class PaginationComponent {
1182
+ /* =======================
1183
+ Inputs (controlled state)
1184
+ ======================= */
1185
+ page = 1;
1186
+ pageSize = 10;
1187
+ totalItems = 0;
1188
+ pageSizes = [10, 20, 50, 100];
1189
+ /* =======================
1190
+ Outputs (user intent)
1191
+ ======================= */
1192
+ pageChange = new EventEmitter();
1193
+ pageSizeChange = new EventEmitter();
1194
+ /* =======================
1195
+ Internal UI state
1196
+ ======================= */
1197
+ pageNumbers = [];
1198
+ jumpPageNumber = null;
1199
+ pageSizeOptions = [];
1200
+ /* =======================
1201
+ Lifecycle
1202
+ ======================= */
1203
+ ngOnChanges() {
1204
+ this.buildPageSizeOptions();
1205
+ this.generatePageNumbers();
1206
+ }
1207
+ /* =======================
1208
+ Derived values
1209
+ ======================= */
1210
+ get totalPages() {
1211
+ return Math.max(Math.ceil(this.totalItems / this.pageSize), 1);
1212
+ }
1213
+ get startItem() {
1214
+ return this.totalItems === 0 ? 0 : (this.page - 1) * this.pageSize + 1;
1215
+ }
1216
+ get endItem() {
1217
+ return Math.min(this.page * this.pageSize, this.totalItems);
1218
+ }
1219
+ /* =======================
1220
+ UI helpers
1221
+ ======================= */
1222
+ buildPageSizeOptions() {
1223
+ this.pageSizeOptions = this.pageSizes.map(size => ({
1224
+ label: `${size} per page`,
1225
+ value: size
1226
+ }));
1227
+ }
1228
+ generatePageNumbers() {
1229
+ const total = this.totalPages;
1230
+ let start = Math.max(this.page - 2, 1);
1231
+ let end = Math.min(start + 4, total);
1232
+ start = Math.max(end - 4, 1);
1233
+ this.pageNumbers = [];
1234
+ for (let i = start; i <= end; i++) {
1235
+ this.pageNumbers.push(i);
1236
+ }
1237
+ }
1238
+ /* =======================
1239
+ Navigation (emit only)
1240
+ ======================= */
1241
+ goToPage(page) {
1242
+ if (page >= 1 && page <= this.totalPages && page !== this.page) {
1243
+ this.pageChange.emit(page);
1244
+ }
1245
+ }
1246
+ goFirst() {
1247
+ this.goToPage(1);
1248
+ }
1249
+ goPrevious() {
1250
+ this.goToPage(this.page - 1);
1251
+ }
1252
+ goNext() {
1253
+ this.goToPage(this.page + 1);
1254
+ }
1255
+ goLast() {
1256
+ this.goToPage(this.totalPages);
1257
+ }
1258
+ onPageSizeChange(newSize) {
1259
+ this.pageSizeChange.emit(newSize);
1260
+ }
1261
+ jumpToPage() {
1262
+ if (!this.jumpPageNumber)
1263
+ return;
1264
+ const page = Math.max(1, Math.min(this.jumpPageNumber, this.totalPages));
1265
+ this.pageChange.emit(page);
1266
+ this.jumpPageNumber = null;
1267
+ }
1268
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1269
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: PaginationComponent, isStandalone: true, selector: "dso-pagination", inputs: { page: "page", pageSize: "pageSize", totalItems: "totalItems", pageSizes: "pageSizes" }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"pagination-container\">\r\n\r\n <!-- First / Previous -->\r\n <dso-button\r\n btnLabel=\"\u00AB\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === 1\"\r\n (onClick)=\"goFirst()\">\r\n </dso-button>\r\n\r\n <dso-button\r\n btnLabel=\"\u2039\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === 1\"\r\n (onClick)=\"goPrevious()\">\r\n </dso-button>\r\n\r\n <!-- Page numbers -->\r\n <div class=\"page-buttons\">\r\n <ng-container *ngFor=\"let p of pageNumbers\">\r\n <dso-button\r\n [btnLabel]=\"p.toString()\"\r\n btnType=\"ghost\"\r\n btnSize=\"mdBtn\"\r\n [isDisabled]=\"page === p\"\r\n [isActive]=\"page === p\"\r\n (onClick)=\"goToPage(p)\">\r\n </dso-button>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Next / Last -->\r\n <dso-button\r\n btnLabel=\"\u203A\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === totalPages\"\r\n (onClick)=\"goNext()\">\r\n </dso-button>\r\n\r\n <dso-button\r\n btnLabel=\"\u00BB\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === totalPages\"\r\n (onClick)=\"goLast()\">\r\n </dso-button>\r\n\r\n <!-- Page size -->\r\n <dso-dropdown-list\r\n [options]=\"pageSizeOptions\"\r\n [value]=\"pageSize\"\r\n (selectionChange)=\"onPageSizeChange($event)\">\r\n </dso-dropdown-list>\r\n\r\n <!-- Range info -->\r\n <span class=\"range-info\">\r\n Showing {{ startItem }}\u2013{{ endItem }} of {{ totalItems }} results\r\n </span>\r\n\r\n <!-- Jump to page -->\r\n <div class=\"jump-to-page\">\r\n <input\r\n type=\"number\"\r\n min=\"1\"\r\n [(ngModel)]=\"jumpPageNumber\"\r\n (keydown.enter)=\"jumpToPage()\" />\r\n\r\n <dso-button\r\n btnLabel=\"Go\"\r\n btnType=\"ghost\"\r\n btnSize=\"mdBtn\"\r\n [isDisabled]=\"!jumpPageNumber\"\r\n (onClick)=\"jumpToPage()\">\r\n </dso-button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".pagination-container{display:flex;align-items:center;gap:8px;font-size:14px}.range-info{margin-right:12px}button{padding:6px 10px;border-radius:4px;border:1px solid #ccc;background:#f0f0f0;cursor:pointer}button:disabled{opacity:.4;cursor:not-allowed}button.page-number.active{background-color:#007bff;color:#fff;font-weight:700}.page-buttons{display:flex;gap:4px}.page-buttons dso-button{flex:0 0 44px;text-align:center}.jump-to-page{display:flex;align-items:center;gap:4px}.jump-to-page input{width:48px;padding:6px 8px;border-radius:4px;border:1px solid #ccc;text-align:center}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: DropdownListComponent, selector: "dso-dropdown-list", inputs: ["options", "placeholder", "value", "disabled"], outputs: ["selectionChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1270
+ }
1271
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PaginationComponent, decorators: [{
1272
+ type: Component,
1273
+ args: [{ selector: 'dso-pagination', standalone: true, imports: [ButtonComponent, CommonModule, DropdownListComponent, FormsModule], template: "<div class=\"pagination-container\">\r\n\r\n <!-- First / Previous -->\r\n <dso-button\r\n btnLabel=\"\u00AB\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === 1\"\r\n (onClick)=\"goFirst()\">\r\n </dso-button>\r\n\r\n <dso-button\r\n btnLabel=\"\u2039\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === 1\"\r\n (onClick)=\"goPrevious()\">\r\n </dso-button>\r\n\r\n <!-- Page numbers -->\r\n <div class=\"page-buttons\">\r\n <ng-container *ngFor=\"let p of pageNumbers\">\r\n <dso-button\r\n [btnLabel]=\"p.toString()\"\r\n btnType=\"ghost\"\r\n btnSize=\"mdBtn\"\r\n [isDisabled]=\"page === p\"\r\n [isActive]=\"page === p\"\r\n (onClick)=\"goToPage(p)\">\r\n </dso-button>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Next / Last -->\r\n <dso-button\r\n btnLabel=\"\u203A\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === totalPages\"\r\n (onClick)=\"goNext()\">\r\n </dso-button>\r\n\r\n <dso-button\r\n btnLabel=\"\u00BB\"\r\n btnType=\"outlined\"\r\n [isDisabled]=\"page === totalPages\"\r\n (onClick)=\"goLast()\">\r\n </dso-button>\r\n\r\n <!-- Page size -->\r\n <dso-dropdown-list\r\n [options]=\"pageSizeOptions\"\r\n [value]=\"pageSize\"\r\n (selectionChange)=\"onPageSizeChange($event)\">\r\n </dso-dropdown-list>\r\n\r\n <!-- Range info -->\r\n <span class=\"range-info\">\r\n Showing {{ startItem }}\u2013{{ endItem }} of {{ totalItems }} results\r\n </span>\r\n\r\n <!-- Jump to page -->\r\n <div class=\"jump-to-page\">\r\n <input\r\n type=\"number\"\r\n min=\"1\"\r\n [(ngModel)]=\"jumpPageNumber\"\r\n (keydown.enter)=\"jumpToPage()\" />\r\n\r\n <dso-button\r\n btnLabel=\"Go\"\r\n btnType=\"ghost\"\r\n btnSize=\"mdBtn\"\r\n [isDisabled]=\"!jumpPageNumber\"\r\n (onClick)=\"jumpToPage()\">\r\n </dso-button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".pagination-container{display:flex;align-items:center;gap:8px;font-size:14px}.range-info{margin-right:12px}button{padding:6px 10px;border-radius:4px;border:1px solid #ccc;background:#f0f0f0;cursor:pointer}button:disabled{opacity:.4;cursor:not-allowed}button.page-number.active{background-color:#007bff;color:#fff;font-weight:700}.page-buttons{display:flex;gap:4px}.page-buttons dso-button{flex:0 0 44px;text-align:center}.jump-to-page{display:flex;align-items:center;gap:4px}.jump-to-page input{width:48px;padding:6px 8px;border-radius:4px;border:1px solid #ccc;text-align:center}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
1274
+ }], propDecorators: { page: [{
1275
+ type: Input
1276
+ }], pageSize: [{
1277
+ type: Input
1278
+ }], totalItems: [{
1279
+ type: Input
1280
+ }], pageSizes: [{
1281
+ type: Input
1282
+ }], pageChange: [{
1283
+ type: Output
1284
+ }], pageSizeChange: [{
1285
+ type: Output
1286
+ }] } });
1287
+
1288
+ class RadioComponent {
1289
+ label = 'Radio label';
1290
+ value;
1291
+ name; // for grouping radios
1292
+ disabled = false;
1293
+ error = false;
1294
+ isChecked = false;
1295
+ change = new EventEmitter();
1296
+ // Method to handle change in radio state
1297
+ toggleRadio(event) {
1298
+ // console.log(this.value);
1299
+ event.stopPropagation();
1300
+ this.change.emit(this.value); // Emit the updated value to the parent
1301
+ }
1302
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1303
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: RadioComponent, isStandalone: true, selector: "dso-radio", inputs: { label: "label", value: "value", name: "name", disabled: "disabled", error: "error", isChecked: "isChecked" }, outputs: { change: "change" }, ngImport: i0, template: "<label class=\"radio-wrapper\" \r\n[class.disabled]=\"disabled\" \r\n[class.error]=\"error\">\r\n <input\r\n class=\"radio-input\"\r\n type=\"radio\"\r\n [attr.name]=\"name\"\r\n [value]=\"value\"\r\n [checked]=\"isChecked\"\r\n [disabled]=\"disabled\"\r\n (change)=\"toggleRadio($event)\"\r\n />\r\n <span class=\"custom-radio\"></span>\r\n <span *ngIf=\"label\" class=\"radio-label\">{{ label }}</span>\r\n</label>\r\n", styles: [".radio-wrapper{display:flex;align-items:center;gap:8px;cursor:pointer}.radio-wrapper.disabled{cursor:not-allowed}.radio-wrapper.disabled .custom-radio{background-color:#f0f0f0;border-color:#d0d0d0}.radio-wrapper.disabled .radio-input:checked~.custom-radio{border:1px solid rgba(0,0,255,.5)}.radio-wrapper.disabled .custom-radio:after{opacity:.5}.radio-input{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;clip:rect(0 0 0 0);clip-path:inset(100%);overflow:hidden;white-space:nowrap}.custom-radio{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid #666666;border-radius:50%}.radio-input:checked~.custom-radio{border:1px solid blue}input[type=radio]:checked+.custom-radio:after{display:block;content:\"\";width:100%;height:100%;background:#00f;border:5px solid blue;background:transparent;box-sizing:border-box;border-radius:50%}.radio-label{font-size:14px}.error .custom-radio{border-color:red}.radio-wrapper:not(.disabled):hover .custom-radio{background-color:#eef5f9;border-color:#0000ffc0;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1304
+ }
1305
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RadioComponent, decorators: [{
1306
+ type: Component,
1307
+ args: [{ selector: 'dso-radio', standalone: true, imports: [FormsModule, CommonModule], template: "<label class=\"radio-wrapper\" \r\n[class.disabled]=\"disabled\" \r\n[class.error]=\"error\">\r\n <input\r\n class=\"radio-input\"\r\n type=\"radio\"\r\n [attr.name]=\"name\"\r\n [value]=\"value\"\r\n [checked]=\"isChecked\"\r\n [disabled]=\"disabled\"\r\n (change)=\"toggleRadio($event)\"\r\n />\r\n <span class=\"custom-radio\"></span>\r\n <span *ngIf=\"label\" class=\"radio-label\">{{ label }}</span>\r\n</label>\r\n", styles: [".radio-wrapper{display:flex;align-items:center;gap:8px;cursor:pointer}.radio-wrapper.disabled{cursor:not-allowed}.radio-wrapper.disabled .custom-radio{background-color:#f0f0f0;border-color:#d0d0d0}.radio-wrapper.disabled .radio-input:checked~.custom-radio{border:1px solid rgba(0,0,255,.5)}.radio-wrapper.disabled .custom-radio:after{opacity:.5}.radio-input{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;clip:rect(0 0 0 0);clip-path:inset(100%);overflow:hidden;white-space:nowrap}.custom-radio{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid #666666;border-radius:50%}.radio-input:checked~.custom-radio{border:1px solid blue}input[type=radio]:checked+.custom-radio:after{display:block;content:\"\";width:100%;height:100%;background:#00f;border:5px solid blue;background:transparent;box-sizing:border-box;border-radius:50%}.radio-label{font-size:14px}.error .custom-radio{border-color:red}.radio-wrapper:not(.disabled):hover .custom-radio{background-color:#eef5f9;border-color:#0000ffc0;cursor:pointer}\n"] }]
1308
+ }], propDecorators: { label: [{
1309
+ type: Input
1310
+ }], value: [{
1311
+ type: Input
1312
+ }], name: [{
1313
+ type: Input
1314
+ }], disabled: [{
1315
+ type: Input
1316
+ }], error: [{
1317
+ type: Input
1318
+ }], isChecked: [{
1319
+ type: Input
1320
+ }], change: [{
1321
+ type: Output
1322
+ }] } });
1323
+
1324
+ class SingleSelectComponent {
1325
+ elementRef;
1326
+ // Reference to the dropdown <ul> for scroll management
1327
+ dropdownListRef;
1328
+ // Inputs for configuration & data binding
1329
+ options = [];
1330
+ dropdownPosition = 'bottom';
1331
+ placeholder = 'Select an option';
1332
+ inputTextLabel = 'Dropdown Title'; // Optional input label
1333
+ isDisabled = false; // Controls disabled state
1334
+ enableValidation = true; // Enables validation checks
1335
+ helperText; // Optional helper text
1336
+ selectIconName = null; // Icon name/class for clear button
1337
+ errorText = 'This field is required'; // Validation error text
1338
+ // Output event emitter to notify selection changes
1339
+ selectionChange = new EventEmitter();
1340
+ // Internal state properties
1341
+ selectedOption = null; // Currently selected option object
1342
+ searchQuery = ''; // Search input value
1343
+ filteredOptions = []; // Options filtered based on search
1344
+ isOpen = false; // Dropdown open/close state
1345
+ isInvalid = false; // Validation invalid flag
1346
+ isExceeded = false; // (Optional) exceeded limit flag, currently unused
1347
+ hasOpened = false;
1348
+ hasInteracted = false; // (Optional) touched flag, currently unused
1349
+ lastScrollTop = 0; // Stores last scroll position of dropdown list
1350
+ constructor(elementRef) {
1351
+ this.elementRef = elementRef;
1352
+ }
1353
+ /**
1354
+ * //Handles dropdown scroll events to persist scroll position
1355
+ */
1356
+ onDropdownScroll() {
1357
+ if (this.dropdownListRef) {
1358
+ this.lastScrollTop = this.dropdownListRef.nativeElement.scrollTop;
1359
+ console.log(`Dropdown scrollTop updated to: ${this.lastScrollTop}`);
1360
+ }
1361
+ else {
1362
+ console.warn('dropdownListRef is undefined on scroll');
1363
+ }
1364
+ }
1365
+ /**
1366
+ * Detects clicks outside of the component to close the dropdown if open
1367
+ */
1368
+ onOutsideClick(event) {
1369
+ const clickedInside = this.elementRef.nativeElement.contains(event.target);
1370
+ if (!clickedInside) {
1371
+ if (this.isOpen) {
1372
+ this.isOpen = false; // Close dropdown if clicked outside
1373
+ console.log('Dropdown closed via outside click');
1374
+ this.hasInteracted = true;
1375
+ // Validate only if nothing is selected
1376
+ if (this.enableValidation && !this.selectedOption) {
1377
+ this.isInvalid = true;
1378
+ console.log('Validation failed: no option selected after outside click');
1379
+ }
1380
+ else {
1381
+ this.isInvalid = false;
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Initialize component data on creation
1388
+ */
1389
+ ngOnInit() {
1390
+ // Sample hardcoded options (can be overridden via @Input)
1391
+ this.options = [
1392
+ { value: '1', label: 'Apple' },
1393
+ { value: '2', label: 'Banana' },
1394
+ { value: '3', label: 'Cherry' },
1395
+ { value: '4', label: 'Date' },
1396
+ { value: '5', label: 'Grapes' },
1397
+ { value: '6', label: 'AAAA' },
1398
+ { value: '7', label: 'BBBB' },
1399
+ { value: '8', label: 'CCCC' },
1400
+ { value: '9', label: 'DDDD' },
1401
+ ];
1402
+ // Show all options initially
1403
+ this.filteredOptions = this.options;
1404
+ }
1405
+ ngOnDestroy() {
1406
+ // Placeholder for any cleanup logic if needed
1407
+ }
1408
+ /**
1409
+ * Handles click on the main select box
1410
+ * Prevents opening if disabled
1411
+ */
1412
+ onSelectClick(event) {
1413
+ event.stopPropagation();
1414
+ if (this.isDisabled) {
1415
+ event.stopPropagation();
1416
+ event.preventDefault();
1417
+ return;
1418
+ }
1419
+ this.toggleDropdown(event);
1420
+ }
1421
+ /**
1422
+ * Toggles dropdown open/close state and restores scroll position if opening
1423
+ */
1424
+ toggleDropdown(event) {
1425
+ if (this.isDisabled) {
1426
+ event.stopPropagation();
1427
+ // console.warn('Dropdown is disabled, toggle prevented.');
1428
+ return;
1429
+ }
1430
+ this.isOpen = !this.isOpen;
1431
+ this.hasInteracted = true;
1432
+ // console.log(`Dropdown toggled. isOpen: ${this.isOpen}`);
1433
+ if (this.isOpen) {
1434
+ this.hasOpened = true;
1435
+ setTimeout(() => {
1436
+ if (this.dropdownListRef) {
1437
+ // console.log(`Restoring scrollTop to: ${this.lastScrollTop}`);
1438
+ this.dropdownListRef.nativeElement.scrollTop = this.lastScrollTop;
1439
+ console.log('Scroll Restored');
1440
+ }
1441
+ });
1442
+ }
1443
+ else {
1444
+ // Validate on close
1445
+ this.hasInteracted = true;
1446
+ console.log('User has interacted and closed the dropdown');
1447
+ if (this.enableValidation && !this.selectedOption) {
1448
+ this.isInvalid = true;
1449
+ console.log('No option selected — validation failed');
1450
+ }
1451
+ else {
1452
+ this.isInvalid = false;
1453
+ }
1454
+ }
1455
+ }
1456
+ /**
1457
+ * Filters dropdown options based on current search query
1458
+ */
1459
+ filterOptions() {
1460
+ const query = this.searchQuery.toLowerCase();
1461
+ this.filteredOptions = this.options.filter((option) => option.label.toLowerCase().includes(query));
1462
+ }
1463
+ /**
1464
+ * Handles option selection:
1465
+ * - Updates selected option
1466
+ * - Validates
1467
+ * - Emits selection change event
1468
+ * - Closes dropdown
1469
+ */
1470
+ selectOption(event, option) {
1471
+ event.stopPropagation();
1472
+ this.selectedOption = option;
1473
+ this.isInvalid = false;
1474
+ this.selectionChange.emit(this.selectedOption);
1475
+ this.isOpen = false;
1476
+ }
1477
+ /**
1478
+ * Clears the selected option and resets validation and scroll
1479
+ */
1480
+ clearSelection(event) {
1481
+ // console.log('Clear selection triggered');
1482
+ // this.hasInteracted = false;
1483
+ this.selectedOption = null;
1484
+ this.searchQuery = ''; // Clear the search input field as well
1485
+ this.filteredOptions = this.options; // Show all options again
1486
+ this.isInvalid = false;
1487
+ this.lastScrollTop = 0;
1488
+ this.selectionChange.emit(this.selectedOption);
1489
+ }
1490
+ /**
1491
+ * Checks validity based on current selection if validation is enabled
1492
+ */
1493
+ checkValidity() {
1494
+ if (this.enableValidation) {
1495
+ this.isInvalid = !this.selectedOption;
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Returns the icon path if icon name is provided
1500
+ */
1501
+ getIconPath() {
1502
+ if (this.selectIconName) {
1503
+ return `./assets/icons/${this.selectIconName}.svg`;
1504
+ }
1505
+ return '';
1506
+ }
1507
+ showError() {
1508
+ return this.enableValidation && this.hasInteracted && !this.selectedOption;
1509
+ }
1510
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SingleSelectComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1511
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SingleSelectComponent, isStandalone: true, selector: "dso-single-select", inputs: { options: "options", dropdownPosition: "dropdownPosition", placeholder: "placeholder", inputTextLabel: "inputTextLabel", isDisabled: "isDisabled", enableValidation: "enableValidation", helperText: "helperText", selectIconName: "selectIconName", errorText: "errorText" }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, viewQueries: [{ propertyName: "dropdownListRef", first: true, predicate: ["dropdownList"], descendants: true }], ngImport: i0, template: "<div class=\"select-wrapper\">\r\n<!-- Label (optional) -->\r\n<div *ngIf=\"inputTextLabel\" class=\"select-label\">{{ inputTextLabel }}</div>\r\n \r\n<!-- <div class=\"select-container\" (click)=\"toggleDropdown($event)\" [class.disabled]=\"isDisabled\"> -->\r\n<div class=\"select-container\" [class.disabled]=\"isDisabled\" [class.error]=\"enableValidation && hasInteracted && isInvalid\" [class.focused]=\"isOpen\"\r\n > \r\n <div class=\"selected-option\"\r\n (click)=\"onSelectClick($event)\" \r\n [ngClass]=\"{'placeholder': !selectedOption, \r\n 'selected': selectedOption }\">\r\n <span>{{ selectedOption?.label || placeholder }}</span>\r\n <div class=\"clearbtn-wrapper\"> \r\n <dso-button \r\n btnLabel=\"Clear\" \r\n btnType=\"ghost\" \r\n btnSize='smBtn'\r\n *ngIf=\"selectedOption && !isDisabled\" \r\n (click)=\"clearSelection($event)\"></dso-button>\r\n <dso-icon *ngIf=\"selectIconName\" [iconName]=\"selectIconName\" [size]=\"'small'\"></dso-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Dropdown -->\r\n <div class=\"dropdown\" *ngIf=\"isOpen\" \r\n (click)=\"$event.stopPropagation()\" \r\n [ngClass]=\"{'dropdown-bottom': dropdownPosition === 'bottom', 'dropdown-top': dropdownPosition === 'top'}\" >\r\n <input type=\"text\" \r\n [(ngModel)]=\"searchQuery\" \r\n (input)=\"filterOptions()\" \r\n placeholder=\"Search...\" />\r\n <ul #dropdownList \r\n (scroll)=\"onDropdownScroll()\" \r\n class=\"dropdown-list\">\r\n <li *ngFor=\"let option of filteredOptions\" (click)=\"selectOption($event, option)\" [ngClass]=\"{'active': option.value === selectedOption?.value}\">\r\n {{ option.label }}\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n\r\n\r\n<!-- Helper Text (when validation is disabled) -->\r\n<div *ngIf=\"enableValidation && helperText && !(hasInteracted && isInvalid)\" class=\"helper-text\">\r\n {{ helperText }}\r\n</div>\r\n\r\n<!-- Error Message (when validation is enabled) -->\r\n<div *ngIf=\"enableValidation && hasInteracted && isInvalid\" class=\"error-message\">\r\n {{ errorText }}\r\n</div>\r\n</div>", styles: [":host{display:inline-block;font-family:inherit}.select-label{font-size:16px;color:#333;display:block}.select-wrapper{display:flex;flex-direction:column;gap:8px}.select-container{position:relative;width:300px;display:flex;flex-direction:column;border:1px solid #ccc;border-radius:4px;background-color:#fff;box-sizing:border-box}.select-container:hover{border:1px solid #071d35}.select-container.disabled{background-color:#f5f5f5;border-color:#ccc;cursor:not-allowed}.select-container.disabled .selected-option{pointer-events:none;color:#aaa}.select-container.error{border-color:red}.select-container.focused{border-color:#007bff}.dropdown-list{max-height:150px;overflow-y:auto}.selected-option{padding:12px 8px;display:flex;justify-content:space-between;align-items:center;cursor:pointer}.selected-option.placeholder{color:#aaa}.selected-option.selected{color:#000}img.button-icon{width:20px;height:20px}.clearbtn-wrapper{display:flex;gap:4px;justify-content:space-between;align-items:center}.dropdown{width:300px;position:absolute;background-color:#fff;border:1px solid #ccc;max-height:200px;overflow-y:auto;box-shadow:0 4px 6px #0000000d}.dropdown input{width:100%;padding:10px;box-sizing:border-box;border:none;border-bottom:1px solid #eee;outline:none}.dropdown ul{list-style:none;padding:0;margin:0}.dropdown ul li{padding:8px 12px;cursor:pointer;transition:background-color .2s}.dropdown ul li:hover{background-color:#f0f0f0}.dropdown ul li.active{background-color:#3784d6;color:#fff;font-weight:700}.dropdown-bottom{top:100%}.dropdown-top{bottom:100%}.helper-text,.error-message{font-size:16px}.helper-text{color:#888}.error-message{color:red}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }] });
1512
+ }
1513
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SingleSelectComponent, decorators: [{
1514
+ type: Component,
1515
+ args: [{ selector: 'dso-single-select', standalone: true, imports: [FormsModule, CommonModule, ButtonComponent, IconComponent], template: "<div class=\"select-wrapper\">\r\n<!-- Label (optional) -->\r\n<div *ngIf=\"inputTextLabel\" class=\"select-label\">{{ inputTextLabel }}</div>\r\n \r\n<!-- <div class=\"select-container\" (click)=\"toggleDropdown($event)\" [class.disabled]=\"isDisabled\"> -->\r\n<div class=\"select-container\" [class.disabled]=\"isDisabled\" [class.error]=\"enableValidation && hasInteracted && isInvalid\" [class.focused]=\"isOpen\"\r\n > \r\n <div class=\"selected-option\"\r\n (click)=\"onSelectClick($event)\" \r\n [ngClass]=\"{'placeholder': !selectedOption, \r\n 'selected': selectedOption }\">\r\n <span>{{ selectedOption?.label || placeholder }}</span>\r\n <div class=\"clearbtn-wrapper\"> \r\n <dso-button \r\n btnLabel=\"Clear\" \r\n btnType=\"ghost\" \r\n btnSize='smBtn'\r\n *ngIf=\"selectedOption && !isDisabled\" \r\n (click)=\"clearSelection($event)\"></dso-button>\r\n <dso-icon *ngIf=\"selectIconName\" [iconName]=\"selectIconName\" [size]=\"'small'\"></dso-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Dropdown -->\r\n <div class=\"dropdown\" *ngIf=\"isOpen\" \r\n (click)=\"$event.stopPropagation()\" \r\n [ngClass]=\"{'dropdown-bottom': dropdownPosition === 'bottom', 'dropdown-top': dropdownPosition === 'top'}\" >\r\n <input type=\"text\" \r\n [(ngModel)]=\"searchQuery\" \r\n (input)=\"filterOptions()\" \r\n placeholder=\"Search...\" />\r\n <ul #dropdownList \r\n (scroll)=\"onDropdownScroll()\" \r\n class=\"dropdown-list\">\r\n <li *ngFor=\"let option of filteredOptions\" (click)=\"selectOption($event, option)\" [ngClass]=\"{'active': option.value === selectedOption?.value}\">\r\n {{ option.label }}\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n\r\n\r\n<!-- Helper Text (when validation is disabled) -->\r\n<div *ngIf=\"enableValidation && helperText && !(hasInteracted && isInvalid)\" class=\"helper-text\">\r\n {{ helperText }}\r\n</div>\r\n\r\n<!-- Error Message (when validation is enabled) -->\r\n<div *ngIf=\"enableValidation && hasInteracted && isInvalid\" class=\"error-message\">\r\n {{ errorText }}\r\n</div>\r\n</div>", styles: [":host{display:inline-block;font-family:inherit}.select-label{font-size:16px;color:#333;display:block}.select-wrapper{display:flex;flex-direction:column;gap:8px}.select-container{position:relative;width:300px;display:flex;flex-direction:column;border:1px solid #ccc;border-radius:4px;background-color:#fff;box-sizing:border-box}.select-container:hover{border:1px solid #071d35}.select-container.disabled{background-color:#f5f5f5;border-color:#ccc;cursor:not-allowed}.select-container.disabled .selected-option{pointer-events:none;color:#aaa}.select-container.error{border-color:red}.select-container.focused{border-color:#007bff}.dropdown-list{max-height:150px;overflow-y:auto}.selected-option{padding:12px 8px;display:flex;justify-content:space-between;align-items:center;cursor:pointer}.selected-option.placeholder{color:#aaa}.selected-option.selected{color:#000}img.button-icon{width:20px;height:20px}.clearbtn-wrapper{display:flex;gap:4px;justify-content:space-between;align-items:center}.dropdown{width:300px;position:absolute;background-color:#fff;border:1px solid #ccc;max-height:200px;overflow-y:auto;box-shadow:0 4px 6px #0000000d}.dropdown input{width:100%;padding:10px;box-sizing:border-box;border:none;border-bottom:1px solid #eee;outline:none}.dropdown ul{list-style:none;padding:0;margin:0}.dropdown ul li{padding:8px 12px;cursor:pointer;transition:background-color .2s}.dropdown ul li:hover{background-color:#f0f0f0}.dropdown ul li.active{background-color:#3784d6;color:#fff;font-weight:700}.dropdown-bottom{top:100%}.dropdown-top{bottom:100%}.helper-text,.error-message{font-size:16px}.helper-text{color:#888}.error-message{color:red}\n"] }]
1516
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { dropdownListRef: [{
1517
+ type: ViewChild,
1518
+ args: ['dropdownList']
1519
+ }], options: [{
1520
+ type: Input
1521
+ }], dropdownPosition: [{
1522
+ type: Input
1523
+ }], placeholder: [{
1524
+ type: Input
1525
+ }], inputTextLabel: [{
1526
+ type: Input
1527
+ }], isDisabled: [{
1528
+ type: Input
1529
+ }], enableValidation: [{
1530
+ type: Input
1531
+ }], helperText: [{
1532
+ type: Input
1533
+ }], selectIconName: [{
1534
+ type: Input
1535
+ }], errorText: [{
1536
+ type: Input
1537
+ }], selectionChange: [{
1538
+ type: Output
1539
+ }], onOutsideClick: [{
1540
+ type: HostListener,
1541
+ args: ['document:click', ['$event']]
1542
+ }] } });
1543
+
1544
+ /**
1545
+ * DSO Side Navigation Component
1546
+ *
1547
+ * Features:
1548
+ * - Configurable title/logo area.
1549
+ * - Dynamic menu items with icons and routing.
1550
+ * - Supports default active item by route or index.
1551
+ * - Click-outside detection to close the sidebar.
1552
+ * - Optional overlay for dimming the background.
1553
+ */
1554
+ class SideNavComponent {
1555
+ host;
1556
+ /** Title displayed next to the logo */
1557
+ title = '';
1558
+ /** List of navigation items */
1559
+ items = [];
1560
+ /** Controls whether the sidebar is visible */
1561
+ open = true;
1562
+ /** Enable/disable background overlay when sidebar is open */
1563
+ overlay = true;
1564
+ /** Optional: default active menu item by route */
1565
+ defaultActiveRoute;
1566
+ /** Optional: default active menu item by index */
1567
+ defaultActiveIndex;
1568
+ /** Event emitted when sidebar is closed */
1569
+ closed = new EventEmitter();
1570
+ /**
1571
+ * ElementRef to the component host element.
1572
+ * Used for detecting click-outside events.
1573
+ */
1574
+ constructor(host) {
1575
+ this.host = host;
1576
+ }
1577
+ /**
1578
+ * Lifecycle hook: initialize default active menu item.
1579
+ */
1580
+ ngOnInit() {
1581
+ // Reset all items to inactive first
1582
+ this.items.forEach(item => item.isActive = false);
1583
+ // Set default active item
1584
+ const defaultItem = this.defaultActiveRoute
1585
+ ? this.items.find(item => item.route === this.defaultActiveRoute)
1586
+ : this.items[this.defaultActiveIndex ?? 0]; // fallback to first item if no index
1587
+ if (defaultItem) {
1588
+ defaultItem.isActive = true;
1589
+ }
1590
+ }
1591
+ /**
1592
+ * Close the sidebar and emit the `closed` event.
1593
+ * Can be called manually or via click-outside detection.
1594
+ */
1595
+ closeSidebar() {
1596
+ this.open = false;
1597
+ this.closed.emit();
1598
+ }
1599
+ /**
1600
+ * HostListener to detect clicks anywhere in the document.
1601
+ * If the click happens outside the sidebar while it's open, it triggers closeSidebar().
1602
+ *
1603
+ * @param event MouseEvent triggered on document click
1604
+ */
1605
+ onDocumentClick(event) {
1606
+ if (!this.open)
1607
+ return;
1608
+ // Check if click was inside the sidebar
1609
+ const clickedInside = this.host.nativeElement.contains(event.target);
1610
+ if (!clickedInside) {
1611
+ this.closeSidebar();
1612
+ }
1613
+ }
1614
+ /**
1615
+ * Called when a sidebar menu item is clicked.
1616
+ * - Marks the clicked item as active.
1617
+ * - Optionally, navigation can be handled via `[routerLink]`.
1618
+ *
1619
+ * @param item The menu item that was clicked
1620
+ */
1621
+ onItemClick(item) {
1622
+ this.items.forEach(i => i.isActive = false);
1623
+ item.isActive = true;
1624
+ }
1625
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SideNavComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1626
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SideNavComponent, isStandalone: true, selector: "dso-side-nav", inputs: { title: "title", items: "items", open: "open", overlay: "overlay", defaultActiveRoute: "defaultActiveRoute", defaultActiveIndex: "defaultActiveIndex" }, outputs: { closed: "closed" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<!-- Optional overlay -->\r\n<div \r\n class=\"sidebar-overlay\" \r\n *ngIf=\"open && overlay\"\r\n (click)=\"closeSidebar()\">\r\n</div>\r\n\r\n<nav class=\"dso-side-nav\" [class.open]=\"open\">\r\n\r\n <!-- Logo and Title -->\r\n <div class=\"side-nav-header\">\r\n <ng-content select=\"[dso-side-logo]\"></ng-content>\r\n <span class=\"side-nav-title\">{{ title }}</span>\r\n </div>\r\n\r\n <!-- Navigation list -->\r\n <ul class=\"side-nav-list\">\r\n <li *ngFor=\"let item of items\" [class.active]=\"item.isActive\">\r\n <a\r\n class=\"nav-item\"\r\n [routerLink]=\"item.route\"\r\n routerLinkActive=\"active\"\r\n (click)=\"onItemClick(item)\"\r\n >\r\n <!-- <i class=\"nav-icon\" [class]=\"item.icon\"></i> -->\r\n <dso-icon *ngIf=\"item.iconName\" [iconName]=\"item.iconName\" size=\"small\"></dso-icon>\r\n\r\n <span class=\"nav-label\">{{ item.label }}</span>\r\n </a>\r\n </li>\r\n </ul>\r\n\r\n <!-- Footer slot (optional) -->\r\n <div class=\"side-nav-footer\">\r\n <ng-content select=\"[dso-side-footer]\"></ng-content>\r\n </div>\r\n\r\n</nav>\r\n", styles: [".dso-side-nav{position:fixed;left:0;top:0;width:240px;height:100vh;background:#fff;border-right:1px solid #e5e5e5;display:flex;flex-direction:column;transform:translate(-100%);transition:transform .25s ease-in-out;z-index:1000}.dso-side-nav.open{transform:translate(0)}.dso-side-nav .side-nav-header{display:flex;align-items:center;gap:.75rem;padding:1rem;white-space:nowrap}.dso-side-nav .side-nav-title{font-size:1rem;font-weight:600}.dso-side-nav .side-nav-list{flex:1;list-style:none;padding:0 4px;margin:.5rem 0}.dso-side-nav .side-nav-list li{margin:.25rem 0}.dso-side-nav .side-nav-list li a.nav-item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-radius:4px;text-decoration:none;color:#333}.dso-side-nav .side-nav-list li a.nav-item:hover{background:#f4f4f4}.dso-side-nav .side-nav-list li.active a.nav-item,.dso-side-nav .side-nav-list li a.nav-item.active{background:#e6f0ff;border-left:3px solid #007bff}.dso-side-nav .side-nav-list li.active a.nav-item .nav-label,.dso-side-nav .side-nav-list li a.nav-item.active .nav-label{font-weight:600}.dso-side-nav .side-nav-footer{padding:1rem;border-top:1px solid #e5e5e5}.sidebar-overlay{position:fixed;inset:0;background:#00000059;z-index:900}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }] });
1627
+ }
1628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SideNavComponent, decorators: [{
1629
+ type: Component,
1630
+ args: [{ selector: 'dso-side-nav', standalone: true, imports: [CommonModule, RouterModule, IconComponent], template: "<!-- Optional overlay -->\r\n<div \r\n class=\"sidebar-overlay\" \r\n *ngIf=\"open && overlay\"\r\n (click)=\"closeSidebar()\">\r\n</div>\r\n\r\n<nav class=\"dso-side-nav\" [class.open]=\"open\">\r\n\r\n <!-- Logo and Title -->\r\n <div class=\"side-nav-header\">\r\n <ng-content select=\"[dso-side-logo]\"></ng-content>\r\n <span class=\"side-nav-title\">{{ title }}</span>\r\n </div>\r\n\r\n <!-- Navigation list -->\r\n <ul class=\"side-nav-list\">\r\n <li *ngFor=\"let item of items\" [class.active]=\"item.isActive\">\r\n <a\r\n class=\"nav-item\"\r\n [routerLink]=\"item.route\"\r\n routerLinkActive=\"active\"\r\n (click)=\"onItemClick(item)\"\r\n >\r\n <!-- <i class=\"nav-icon\" [class]=\"item.icon\"></i> -->\r\n <dso-icon *ngIf=\"item.iconName\" [iconName]=\"item.iconName\" size=\"small\"></dso-icon>\r\n\r\n <span class=\"nav-label\">{{ item.label }}</span>\r\n </a>\r\n </li>\r\n </ul>\r\n\r\n <!-- Footer slot (optional) -->\r\n <div class=\"side-nav-footer\">\r\n <ng-content select=\"[dso-side-footer]\"></ng-content>\r\n </div>\r\n\r\n</nav>\r\n", styles: [".dso-side-nav{position:fixed;left:0;top:0;width:240px;height:100vh;background:#fff;border-right:1px solid #e5e5e5;display:flex;flex-direction:column;transform:translate(-100%);transition:transform .25s ease-in-out;z-index:1000}.dso-side-nav.open{transform:translate(0)}.dso-side-nav .side-nav-header{display:flex;align-items:center;gap:.75rem;padding:1rem;white-space:nowrap}.dso-side-nav .side-nav-title{font-size:1rem;font-weight:600}.dso-side-nav .side-nav-list{flex:1;list-style:none;padding:0 4px;margin:.5rem 0}.dso-side-nav .side-nav-list li{margin:.25rem 0}.dso-side-nav .side-nav-list li a.nav-item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-radius:4px;text-decoration:none;color:#333}.dso-side-nav .side-nav-list li a.nav-item:hover{background:#f4f4f4}.dso-side-nav .side-nav-list li.active a.nav-item,.dso-side-nav .side-nav-list li a.nav-item.active{background:#e6f0ff;border-left:3px solid #007bff}.dso-side-nav .side-nav-list li.active a.nav-item .nav-label,.dso-side-nav .side-nav-list li a.nav-item.active .nav-label{font-weight:600}.dso-side-nav .side-nav-footer{padding:1rem;border-top:1px solid #e5e5e5}.sidebar-overlay{position:fixed;inset:0;background:#00000059;z-index:900}\n"] }]
1631
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { title: [{
1632
+ type: Input
1633
+ }], items: [{
1634
+ type: Input
1635
+ }], open: [{
1636
+ type: Input
1637
+ }], overlay: [{
1638
+ type: Input
1639
+ }], defaultActiveRoute: [{
1640
+ type: Input
1641
+ }], defaultActiveIndex: [{
1642
+ type: Input
1643
+ }], closed: [{
1644
+ type: Output
1645
+ }], onDocumentClick: [{
1646
+ type: HostListener,
1647
+ args: ['document:click', ['$event']]
1648
+ }] } });
1649
+
1650
+ class SpinnerComponent {
1651
+ el;
1652
+ /** Size presets */
1653
+ size = 'medium';
1654
+ /** Color variants */
1655
+ color = 'primary';
1656
+ /** Overlay background toggle */
1657
+ overlay = false;
1658
+ /** Custom overlay background color */
1659
+ overlayColor = 'rgba(0,0,0,0.4)';
1660
+ /** Optional illustration (inside spinner) */
1661
+ illustrationSrc;
1662
+ /** Optional label text (below spinner) */
1663
+ label;
1664
+ // NEW: optional Lottie animation
1665
+ animationSrc;
1666
+ constructor(el) {
1667
+ this.el = el;
1668
+ }
1669
+ ngAfterViewInit() {
1670
+ if (this.animationSrc) {
1671
+ lottie.loadAnimation({
1672
+ container: this.el.nativeElement.querySelector('.spinner-lottie'),
1673
+ renderer: 'svg',
1674
+ loop: true,
1675
+ autoplay: true,
1676
+ path: this.animationSrc, // path to your JSON animation
1677
+ });
1678
+ }
1679
+ }
1680
+ get isIllustrated() {
1681
+ return !!this.illustrationSrc;
1682
+ }
1683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1684
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SpinnerComponent, isStandalone: true, selector: "dso-spinner", inputs: { size: "size", color: "color", overlay: "overlay", overlayColor: "overlayColor", illustrationSrc: "illustrationSrc", label: "label", animationSrc: "animationSrc" }, ngImport: i0, template: "<div *ngIf=\"overlay\" class=\"spinner-overlay\" [ngStyle]=\"{'background-color': overlayColor}\">\r\n <div class=\"spinner-wrapper\">\r\n \r\n <!-- Static spinner / illustration -->\r\n <div \r\n class=\"spinner\" \r\n *ngIf=\"!animationSrc\"\r\n [ngClass]=\"[isIllustrated ? 'illustrated' : size, color]\">\r\n <img *ngIf=\"illustrationSrc\" [src]=\"illustrationSrc\" class=\"spinner-illustration\" alt=\"Loading illustration\" />\r\n </div>\r\n\r\n <!-- Lottie animation -->\r\n <div *ngIf=\"animationSrc\" class=\"spinner-lottie\"></div>\r\n\r\n <!-- Optional label -->\r\n <p *ngIf=\"label\" class=\"spinner-label\">{{ label }}</p>\r\n </div>\r\n</div>\r\n\r\n<div *ngIf=\"!overlay\" class=\"spinner-wrapper\">\r\n \r\n <!-- Static spinner / illustration -->\r\n <div \r\n class=\"spinner\" \r\n *ngIf=\"!animationSrc\"\r\n [ngClass]=\"[isIllustrated ? 'illustrated' : size, color]\">\r\n <img *ngIf=\"illustrationSrc\" [src]=\"illustrationSrc\" class=\"spinner-illustration\" alt=\"Loading illustration\" />\r\n </div>\r\n\r\n <!-- Lottie animation -->\r\n <div *ngIf=\"animationSrc\" class=\"spinner-lottie\"></div>\r\n\r\n <!-- Optional label -->\r\n <p *ngIf=\"label\" class=\"spinner-label\">{{ label }}</p>\r\n</div>\r\n", styles: [".spinner-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;justify-content:center;align-items:center;z-index:9999}.spinner-wrapper{display:flex;flex-direction:column;align-items:center;gap:8px}.spinner{position:relative;display:flex;justify-content:center;align-items:center}.spinner:before{content:\"\";position:absolute;inset:0;border-radius:50%;border:3px solid rgba(0,0,0,.1);border-top-color:#007bff;animation:spin .8s linear infinite}.spinner.small{width:32px;height:32px}.spinner.small:before{border-width:2px}.spinner.medium{width:48px;height:48px}.spinner.medium:before{border-width:3px}.spinner.large{width:72px;height:72px}.spinner.large:before{border-width:4px}.spinner.illustrated{width:240px;height:240px}.spinner.illustrated:before{border-width:6px}.spinner.illustrated .spinner-illustration{width:180px;height:180px}.spinner.primary:before{border-top-color:#007bff}.spinner.success:before{border-top-color:#28a745}.spinner.warning:before{border-top-color:#ffc107}.spinner.danger:before{border-top-color:#dc3545}.spinner-illustration{object-fit:contain;z-index:1}.spinner-label{font-size:14px;color:#fff;text-align:center}@keyframes spin{to{transform:rotate(360deg)}}.spinner-lottie{width:80px;height:80px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
1685
+ }
1686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, decorators: [{
1687
+ type: Component,
1688
+ args: [{ selector: 'dso-spinner', standalone: true, imports: [CommonModule], template: "<div *ngIf=\"overlay\" class=\"spinner-overlay\" [ngStyle]=\"{'background-color': overlayColor}\">\r\n <div class=\"spinner-wrapper\">\r\n \r\n <!-- Static spinner / illustration -->\r\n <div \r\n class=\"spinner\" \r\n *ngIf=\"!animationSrc\"\r\n [ngClass]=\"[isIllustrated ? 'illustrated' : size, color]\">\r\n <img *ngIf=\"illustrationSrc\" [src]=\"illustrationSrc\" class=\"spinner-illustration\" alt=\"Loading illustration\" />\r\n </div>\r\n\r\n <!-- Lottie animation -->\r\n <div *ngIf=\"animationSrc\" class=\"spinner-lottie\"></div>\r\n\r\n <!-- Optional label -->\r\n <p *ngIf=\"label\" class=\"spinner-label\">{{ label }}</p>\r\n </div>\r\n</div>\r\n\r\n<div *ngIf=\"!overlay\" class=\"spinner-wrapper\">\r\n \r\n <!-- Static spinner / illustration -->\r\n <div \r\n class=\"spinner\" \r\n *ngIf=\"!animationSrc\"\r\n [ngClass]=\"[isIllustrated ? 'illustrated' : size, color]\">\r\n <img *ngIf=\"illustrationSrc\" [src]=\"illustrationSrc\" class=\"spinner-illustration\" alt=\"Loading illustration\" />\r\n </div>\r\n\r\n <!-- Lottie animation -->\r\n <div *ngIf=\"animationSrc\" class=\"spinner-lottie\"></div>\r\n\r\n <!-- Optional label -->\r\n <p *ngIf=\"label\" class=\"spinner-label\">{{ label }}</p>\r\n</div>\r\n", styles: [".spinner-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;justify-content:center;align-items:center;z-index:9999}.spinner-wrapper{display:flex;flex-direction:column;align-items:center;gap:8px}.spinner{position:relative;display:flex;justify-content:center;align-items:center}.spinner:before{content:\"\";position:absolute;inset:0;border-radius:50%;border:3px solid rgba(0,0,0,.1);border-top-color:#007bff;animation:spin .8s linear infinite}.spinner.small{width:32px;height:32px}.spinner.small:before{border-width:2px}.spinner.medium{width:48px;height:48px}.spinner.medium:before{border-width:3px}.spinner.large{width:72px;height:72px}.spinner.large:before{border-width:4px}.spinner.illustrated{width:240px;height:240px}.spinner.illustrated:before{border-width:6px}.spinner.illustrated .spinner-illustration{width:180px;height:180px}.spinner.primary:before{border-top-color:#007bff}.spinner.success:before{border-top-color:#28a745}.spinner.warning:before{border-top-color:#ffc107}.spinner.danger:before{border-top-color:#dc3545}.spinner-illustration{object-fit:contain;z-index:1}.spinner-label{font-size:14px;color:#fff;text-align:center}@keyframes spin{to{transform:rotate(360deg)}}.spinner-lottie{width:80px;height:80px}\n"] }]
1689
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { size: [{
1690
+ type: Input
1691
+ }], color: [{
1692
+ type: Input
1693
+ }], overlay: [{
1694
+ type: Input
1695
+ }], overlayColor: [{
1696
+ type: Input
1697
+ }], illustrationSrc: [{
1698
+ type: Input
1699
+ }], label: [{
1700
+ type: Input
1701
+ }], animationSrc: [{
1702
+ type: Input
1703
+ }] } });
1704
+
1705
+ class TooltipDirective {
1706
+ el;
1707
+ renderer;
1708
+ tooltipElement = null;
1709
+ // Make input reactive so it updates dynamically
1710
+ _tooltipText = '';
1711
+ set dsoDirectiveTooltip(value) {
1712
+ this._tooltipText = value;
1713
+ if (this.tooltipElement) {
1714
+ this.tooltipElement.textContent = value;
1715
+ }
1716
+ }
1717
+ get dsoDirectiveTooltip() {
1718
+ return this._tooltipText;
1719
+ }
1720
+ position = 'top';
1721
+ tooltipWidth = 'auto';
1722
+ gap = 4; // distance from the element
1723
+ constructor(el, renderer) {
1724
+ this.el = el;
1725
+ this.renderer = renderer;
1726
+ }
1727
+ onMouseEnter() {
1728
+ if (!this.dsoDirectiveTooltip)
1729
+ return;
1730
+ // Create tooltip element if not exists
1731
+ if (!this.tooltipElement) {
1732
+ this.tooltipElement = this.renderer.createElement('div');
1733
+ this.renderer.addClass(this.tooltipElement, 'tooltip-directive');
1734
+ // Style
1735
+ this.renderer.setStyle(this.tooltipElement, 'position', 'fixed'); // fixed so it won't be clipped
1736
+ this.renderer.setStyle(this.tooltipElement, 'background', '#333');
1737
+ this.renderer.setStyle(this.tooltipElement, 'color', '#fff');
1738
+ this.renderer.setStyle(this.tooltipElement, 'padding', '4px 8px');
1739
+ this.renderer.setStyle(this.tooltipElement, 'border-radius', '4px');
1740
+ this.renderer.setStyle(this.tooltipElement, 'font-size', '12px');
1741
+ this.renderer.setStyle(this.tooltipElement, 'z-index', '1000');
1742
+ this.renderer.setStyle(this.tooltipElement, 'max-width', this.tooltipWidth);
1743
+ this.renderer.setStyle(this.tooltipElement, 'white-space', 'normal'); // allow wrapping
1744
+ this.renderer.setStyle(this.tooltipElement, 'word-break', 'break-word'); // break long words
1745
+ this.renderer.appendChild(document.body, this.tooltipElement);
1746
+ }
1747
+ // Step 3: set tooltip text
1748
+ if (this.tooltipElement) {
1749
+ this.tooltipElement.textContent = this.dsoDirectiveTooltip;
1750
+ const rect = this.el.nativeElement.getBoundingClientRect();
1751
+ const tooltipHeight = this.tooltipElement.offsetHeight;
1752
+ const tooltipWidth = this.tooltipElement.offsetWidth;
1753
+ // Step 4: position tooltip based on input
1754
+ switch (this.position) {
1755
+ case 'top':
1756
+ this.renderer.setStyle(this.tooltipElement, 'top', `${rect.top - tooltipHeight - this.gap}px`);
1757
+ this.renderer.setStyle(this.tooltipElement, 'left', `${rect.left + (rect.width - tooltipWidth) / 2}px`);
1758
+ break;
1759
+ case 'bottom':
1760
+ this.renderer.setStyle(this.tooltipElement, 'top', `${rect.bottom + this.gap}px`);
1761
+ this.renderer.setStyle(this.tooltipElement, 'left', `${rect.left + (rect.width - tooltipWidth) / 2}px`);
1762
+ break;
1763
+ case 'left':
1764
+ this.renderer.setStyle(this.tooltipElement, 'top', `${rect.top + (rect.height - tooltipHeight) / 2}px`);
1765
+ this.renderer.setStyle(this.tooltipElement, 'left', `${rect.left - tooltipWidth - this.gap}px`);
1766
+ break;
1767
+ case 'right':
1768
+ this.renderer.setStyle(this.tooltipElement, 'top', `${rect.top + (rect.height - tooltipHeight) / 2}px`);
1769
+ this.renderer.setStyle(this.tooltipElement, 'left', `${rect.right + this.gap}px`);
1770
+ break;
1771
+ }
1772
+ this.renderer.setStyle(this.tooltipElement, 'visibility', 'visible');
1773
+ }
1774
+ }
1775
+ onMouseLeave() {
1776
+ if (this.tooltipElement) {
1777
+ this.renderer.setStyle(this.tooltipElement, 'visibility', 'hidden');
1778
+ }
1779
+ }
1780
+ ngOnDestroy() {
1781
+ if (this.tooltipElement) {
1782
+ this.renderer.removeChild(document.body, this.tooltipElement);
1783
+ }
1784
+ }
1785
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TooltipDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
1786
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: TooltipDirective, isStandalone: true, selector: "[dsoDirectiveTooltip]", inputs: { dsoDirectiveTooltip: "dsoDirectiveTooltip", position: "position", tooltipWidth: "tooltipWidth" }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 });
1787
+ }
1788
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TooltipDirective, decorators: [{
1789
+ type: Directive,
1790
+ args: [{
1791
+ selector: '[dsoDirectiveTooltip]', // This directive will be used with the attribute dsoTooltip
1792
+ standalone: true
1793
+ }]
1794
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { dsoDirectiveTooltip: [{
1795
+ type: Input
1796
+ }], position: [{
1797
+ type: Input
1798
+ }], tooltipWidth: [{
1799
+ type: Input
1800
+ }], onMouseEnter: [{
1801
+ type: HostListener,
1802
+ args: ['mouseenter']
1803
+ }], onMouseLeave: [{
1804
+ type: HostListener,
1805
+ args: ['mouseleave']
1806
+ }] } });
1807
+
1808
+ class TableComponent {
1809
+ /* =====================
1810
+ Inputs
1811
+ ===================== */
1812
+ columns = [];
1813
+ rows = [];
1814
+ defaultSortColumn;
1815
+ defaultSortDirection = 'asc';
1816
+ stickyHeader = false;
1817
+ maxTableHeight;
1818
+ /* =====================
1819
+ Outputs
1820
+ ===================== */
1821
+ sortChange = new EventEmitter();
1822
+ /* =====================
1823
+ UI state only
1824
+ ===================== */
1825
+ sortColumn = null;
1826
+ sortDirection = 'asc';
1827
+ /* =====================
1828
+ Lifecycle
1829
+ ===================== */
1830
+ ngOnChanges() {
1831
+ // Initialize visual sort state ONLY
1832
+ if (this.defaultSortColumn && !this.sortColumn) {
1833
+ this.sortColumn = this.defaultSortColumn;
1834
+ this.sortDirection = this.defaultSortDirection;
1835
+ // Emit once so parent applies default sort
1836
+ this.sortChange.emit({
1837
+ column: this.sortColumn,
1838
+ direction: this.sortDirection
1839
+ });
1840
+ }
1841
+ }
1842
+ /* =====================
1843
+ Sorting (emit only)
1844
+ ===================== */
1845
+ sort(colKey) {
1846
+ let direction = 'asc';
1847
+ if (this.sortColumn === colKey) {
1848
+ direction = this.sortDirection === 'asc' ? 'desc' : 'asc';
1849
+ }
1850
+ this.sortColumn = colKey;
1851
+ this.sortDirection = direction;
1852
+ this.sortChange.emit({ column: colKey, direction });
1853
+ }
1854
+ /* =====================
1855
+ Column sizing
1856
+ ===================== */
1857
+ getColumnStyle(col) {
1858
+ return {
1859
+ 'min-width': col.minWidth || null,
1860
+ 'max-width': col.maxWidth || null,
1861
+ 'width': !col.minWidth && !col.maxWidth ? 'auto' : null,
1862
+ };
1863
+ }
1864
+ /* =====================
1865
+ Truncation
1866
+ ===================== */
1867
+ shouldTruncateNative(cell) {
1868
+ if (!cell)
1869
+ return false;
1870
+ const content = cell.querySelector('.cell-text');
1871
+ if (!content)
1872
+ return false;
1873
+ return content.scrollWidth > content.clientWidth;
1874
+ }
1875
+ /* =====================
1876
+ Selection (unchanged)
1877
+ ===================== */
1878
+ selectedRows = new Set();
1879
+ toggleRow(row) {
1880
+ if (this.selectedRows.has(row)) {
1881
+ this.selectedRows.delete(row);
1882
+ }
1883
+ else {
1884
+ this.selectedRows.add(row);
1885
+ }
1886
+ }
1887
+ isRowSelected(row) {
1888
+ return this.selectedRows.has(row);
1889
+ }
1890
+ get allSelected() {
1891
+ return this.rows.length > 0 &&
1892
+ this.selectedRows.size === this.rows.length;
1893
+ }
1894
+ toggleSelectAll() {
1895
+ if (this.allSelected) {
1896
+ this.selectedRows.clear();
1897
+ }
1898
+ else {
1899
+ this.rows.forEach(row => this.selectedRows.add(row));
1900
+ }
1901
+ }
1902
+ onEditRow(row) {
1903
+ console.log('Edit clicked for row:', row);
1904
+ }
1905
+ onDeleteRow(row) {
1906
+ console.log('Delete clicked for row:', row);
1907
+ }
1908
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1909
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TableComponent, isStandalone: true, selector: "dso-table", inputs: { columns: "columns", rows: "rows", defaultSortColumn: "defaultSortColumn", defaultSortDirection: "defaultSortDirection", stickyHeader: "stickyHeader", maxTableHeight: "maxTableHeight" }, outputs: { sortChange: "sortChange" }, usesOnChanges: true, ngImport: i0, template: "<div #tableContainer class=\"dso-table-container\" [class.sticky-header]=\"stickyHeader\" [style.max-height]=\"maxTableHeight\">\r\n <table class=\"dso-table\">\r\n<thead>\r\n <tr>\r\n <th *ngFor=\"let col of columns\"\r\n [class.sticky]=\"stickyHeader || col.sticky\"\r\n [class.sticky-left]=\"stickyHeader && col.type === 'checkbox'\"\r\n [class.sticky-right]=\"stickyHeader && col.type === 'action'\"\r\n [ngStyle]=\"getColumnStyle(col)\"\r\n [class.active]=\"sortColumn === col.key\"\r\n class=\"sortable\"\r\n (click)=\"col.sortable !== false && col.type !== 'checkbox' && sort(col.key)\">\r\n\r\n <!-- If this column is a checkbox column -->\r\n <ng-container *ngIf=\"col.type === 'checkbox'; else normalHeader\">\r\n <dso-checkbox\r\n size=\"small\"\r\n [isChecked]=\"allSelected\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"toggleSelectAll()\">\r\n </dso-checkbox>\r\n </ng-container>\r\n\r\n <!-- Your original header stays EXACTLY as-is -->\r\n <ng-template #normalHeader>\r\n <div class=\"header-content\">\r\n {{ col.label }}\r\n\r\n <dso-icon\r\n *ngIf=\"col.sortable !== false && sortColumn === col.key\"\r\n [iconName]=\"sortDirection === 'asc'\r\n ? 'icon-van'\r\n : 'icon-chevron_down'\"\r\n size=\"small\"\r\n class=\"sort-icon\">\r\n </dso-icon>\r\n\r\n <dso-icon\r\n *ngIf=\"col.sortable !== false && sortColumn !== col.key\"\r\n iconName=\"icon-chevron_up\"\r\n size=\"small\"\r\n class=\"hover-indicator\">\r\n </dso-icon>\r\n </div>\r\n </ng-template>\r\n\r\n </th>\r\n </tr>\r\n</thead>\r\n\r\n\r\n<tbody>\r\n <tr *ngFor=\"let row of rows\">\r\n <td\r\n #cell\r\n *ngFor=\"let col of columns\"\r\n [class.sticky-left]=\"stickyHeader && col.type === 'checkbox'\"\r\n [class.sticky-right]=\"stickyHeader && col.type === 'action'\"\r\n [ngStyle]=\"getColumnStyle(col)\"\r\n [dsoDirectiveTooltip]=\"shouldTruncateNative(cell) ? row[col.key] : ''\">\r\n <div class=\"cell-wrapper\" style=\"display:flex; align-items:center;\">\r\n <dso-checkbox\r\n *ngIf=\"col.type==='checkbox'\"\r\n size=\"small\"\r\n [isChecked]=\"isRowSelected(row)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"toggleRow(row)\">\r\n </dso-checkbox>\r\n\r\n <!-- Action button cell -->\r\n <ng-container *ngIf=\"col.type==='action'\">\r\n <dso-button\r\n btnType=\"filled\"\r\n btnSize=\"smBtn\"\r\n btnLabel=\"Edit\"\r\n (click)=\"onEditRow(row)\">\r\n </dso-button>\r\n <dso-button\r\n btnType=\"outlined\"\r\n btnSize=\"smBtn\"\r\n btnLabel=\"Delete\"\r\n (click)=\"onDeleteRow(row)\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n <!-- Normal text cell -->\r\n <span *ngIf=\"!col.type || (col.type !== 'checkbox' && col.type !== 'action')\" class=\"cell-text\">\r\n {{ row[col.key] }}\r\n </span>\r\n </div>\r\n </td>\r\n </tr>\r\n</tbody>\r\n\r\n </table>\r\n</div>\r\n", styles: [".dso-table-container{overflow:auto;border:1px solid #ddd;position:relative}.dso-table{width:100%;border-collapse:collapse}.dso-table th,.dso-table td{padding:8px 12px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dso-table thead th{background:#2c2f36;color:#fff;font-weight:600}.dso-table-container.sticky-header .dso-table th.sticky{position:sticky;top:0}.dso-table th.sticky{top:0;z-index:20;box-shadow:0 2px 2px -1px #00000040}.dso-table thead th.sortable:hover{background:#3a3e46;cursor:pointer}.dso-table thead th.active{background:#464b54!important}.dso-table thead th:last-child{border-right:none}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}td .cell-text{display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cell-wrapper{display:inline-flex;align-items:center;margin:0;padding:0;gap:8px}.sticky-left{position:sticky;left:0;z-index:15;background:#fff}.sticky-right{position:sticky;right:0;z-index:15;background:#fff}.dso-table thead th.sticky-left,.dso-table thead th.sticky-right{z-index:25}.sticky-left.scroll-shadow{box-shadow:4px 0 6px -3px #00000040}.sticky-right.scroll-shadow{box-shadow:-4px 0 6px -3px #00000040}.dso-table tbody tr:hover{background:#f5f7fa}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "dso-button", inputs: ["btnLabel", "btnType", "btnSize", "btnIconName", "isDisabled", "isActive"], outputs: ["onClick"] }, { kind: "component", type: CheckboxComponent, selector: "dso-checkbox", inputs: ["label", "isChecked", "disabled", "size", "error", "required", "errorMessage", "iconName"], outputs: ["change"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }, { kind: "directive", type: TooltipDirective, selector: "[dsoDirectiveTooltip]", inputs: ["dsoDirectiveTooltip", "position", "tooltipWidth"] }] });
1910
+ }
1911
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TableComponent, decorators: [{
1912
+ type: Component,
1913
+ args: [{ selector: 'dso-table', standalone: true, imports: [
1914
+ ButtonComponent,
1915
+ CheckboxComponent,
1916
+ CommonModule,
1917
+ IconComponent,
1918
+ TooltipDirective
1919
+ ], template: "<div #tableContainer class=\"dso-table-container\" [class.sticky-header]=\"stickyHeader\" [style.max-height]=\"maxTableHeight\">\r\n <table class=\"dso-table\">\r\n<thead>\r\n <tr>\r\n <th *ngFor=\"let col of columns\"\r\n [class.sticky]=\"stickyHeader || col.sticky\"\r\n [class.sticky-left]=\"stickyHeader && col.type === 'checkbox'\"\r\n [class.sticky-right]=\"stickyHeader && col.type === 'action'\"\r\n [ngStyle]=\"getColumnStyle(col)\"\r\n [class.active]=\"sortColumn === col.key\"\r\n class=\"sortable\"\r\n (click)=\"col.sortable !== false && col.type !== 'checkbox' && sort(col.key)\">\r\n\r\n <!-- If this column is a checkbox column -->\r\n <ng-container *ngIf=\"col.type === 'checkbox'; else normalHeader\">\r\n <dso-checkbox\r\n size=\"small\"\r\n [isChecked]=\"allSelected\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"toggleSelectAll()\">\r\n </dso-checkbox>\r\n </ng-container>\r\n\r\n <!-- Your original header stays EXACTLY as-is -->\r\n <ng-template #normalHeader>\r\n <div class=\"header-content\">\r\n {{ col.label }}\r\n\r\n <dso-icon\r\n *ngIf=\"col.sortable !== false && sortColumn === col.key\"\r\n [iconName]=\"sortDirection === 'asc'\r\n ? 'icon-van'\r\n : 'icon-chevron_down'\"\r\n size=\"small\"\r\n class=\"sort-icon\">\r\n </dso-icon>\r\n\r\n <dso-icon\r\n *ngIf=\"col.sortable !== false && sortColumn !== col.key\"\r\n iconName=\"icon-chevron_up\"\r\n size=\"small\"\r\n class=\"hover-indicator\">\r\n </dso-icon>\r\n </div>\r\n </ng-template>\r\n\r\n </th>\r\n </tr>\r\n</thead>\r\n\r\n\r\n<tbody>\r\n <tr *ngFor=\"let row of rows\">\r\n <td\r\n #cell\r\n *ngFor=\"let col of columns\"\r\n [class.sticky-left]=\"stickyHeader && col.type === 'checkbox'\"\r\n [class.sticky-right]=\"stickyHeader && col.type === 'action'\"\r\n [ngStyle]=\"getColumnStyle(col)\"\r\n [dsoDirectiveTooltip]=\"shouldTruncateNative(cell) ? row[col.key] : ''\">\r\n <div class=\"cell-wrapper\" style=\"display:flex; align-items:center;\">\r\n <dso-checkbox\r\n *ngIf=\"col.type==='checkbox'\"\r\n size=\"small\"\r\n [isChecked]=\"isRowSelected(row)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"toggleRow(row)\">\r\n </dso-checkbox>\r\n\r\n <!-- Action button cell -->\r\n <ng-container *ngIf=\"col.type==='action'\">\r\n <dso-button\r\n btnType=\"filled\"\r\n btnSize=\"smBtn\"\r\n btnLabel=\"Edit\"\r\n (click)=\"onEditRow(row)\">\r\n </dso-button>\r\n <dso-button\r\n btnType=\"outlined\"\r\n btnSize=\"smBtn\"\r\n btnLabel=\"Delete\"\r\n (click)=\"onDeleteRow(row)\">\r\n </dso-button>\r\n </ng-container>\r\n\r\n <!-- Normal text cell -->\r\n <span *ngIf=\"!col.type || (col.type !== 'checkbox' && col.type !== 'action')\" class=\"cell-text\">\r\n {{ row[col.key] }}\r\n </span>\r\n </div>\r\n </td>\r\n </tr>\r\n</tbody>\r\n\r\n </table>\r\n</div>\r\n", styles: [".dso-table-container{overflow:auto;border:1px solid #ddd;position:relative}.dso-table{width:100%;border-collapse:collapse}.dso-table th,.dso-table td{padding:8px 12px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dso-table thead th{background:#2c2f36;color:#fff;font-weight:600}.dso-table-container.sticky-header .dso-table th.sticky{position:sticky;top:0}.dso-table th.sticky{top:0;z-index:20;box-shadow:0 2px 2px -1px #00000040}.dso-table thead th.sortable:hover{background:#3a3e46;cursor:pointer}.dso-table thead th.active{background:#464b54!important}.dso-table thead th:last-child{border-right:none}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}td .cell-text{display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cell-wrapper{display:inline-flex;align-items:center;margin:0;padding:0;gap:8px}.sticky-left{position:sticky;left:0;z-index:15;background:#fff}.sticky-right{position:sticky;right:0;z-index:15;background:#fff}.dso-table thead th.sticky-left,.dso-table thead th.sticky-right{z-index:25}.sticky-left.scroll-shadow{box-shadow:4px 0 6px -3px #00000040}.sticky-right.scroll-shadow{box-shadow:-4px 0 6px -3px #00000040}.dso-table tbody tr:hover{background:#f5f7fa}\n"] }]
1920
+ }], propDecorators: { columns: [{
1921
+ type: Input
1922
+ }], rows: [{
1923
+ type: Input
1924
+ }], defaultSortColumn: [{
1925
+ type: Input
1926
+ }], defaultSortDirection: [{
1927
+ type: Input
1928
+ }], stickyHeader: [{
1929
+ type: Input
1930
+ }], maxTableHeight: [{
1931
+ type: Input
1932
+ }], sortChange: [{
1933
+ type: Output
1934
+ }] } });
1935
+
1936
+ // tab.component.ts
1937
+ class TabComponent {
1938
+ /** Label for the tab shown in the tabs header */
1939
+ label = '';
1940
+ /** Is this tab currently active? Controlled by TabsComponent */
1941
+ active = false;
1942
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1943
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TabComponent, isStandalone: true, selector: "dso-tab", inputs: { label: "label" }, ngImport: i0, template: "<!-- tab.component.html -->\r\n<!-- Only display the tab content if it is active -->\r\n<div *ngIf=\"active\" class=\"tab-content\">\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1944
+ }
1945
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TabComponent, decorators: [{
1946
+ type: Component,
1947
+ args: [{ selector: 'dso-tab', imports: [CommonModule], standalone: true, template: "<!-- tab.component.html -->\r\n<!-- Only display the tab content if it is active -->\r\n<div *ngIf=\"active\" class=\"tab-content\">\r\n <ng-content></ng-content>\r\n</div>\r\n" }]
1948
+ }], propDecorators: { label: [{
1949
+ type: Input
1950
+ }] } });
1951
+
1952
+ // tabs.component.ts
1953
+ class TabsComponent {
1954
+ /** Query all child tabs inside <dso-tabs> */
1955
+ tabs;
1956
+ /** Optional: two-way binding for active tab index */
1957
+ tabIndex = 0;
1958
+ tabIndexChange = new EventEmitter();
1959
+ ngAfterContentInit() {
1960
+ // Activate the default tab
1961
+ this.selectTab(this.tabIndex);
1962
+ }
1963
+ /** When a tab header is clicked */
1964
+ selectTab(index) {
1965
+ if (!this.tabs)
1966
+ return;
1967
+ this.tabs.forEach((tab, i) => tab.active = i === index);
1968
+ // Update internal tabIndex and emit event for two-way binding
1969
+ this.tabIndex = index;
1970
+ this.tabIndexChange.emit(this.tabIndex);
1971
+ }
1972
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1973
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TabsComponent, isStandalone: true, selector: "dso-tabs", inputs: { tabIndex: "tabIndex" }, outputs: { tabIndexChange: "tabIndexChange" }, queries: [{ propertyName: "tabs", predicate: TabComponent }], ngImport: i0, template: "<!-- tabs.component.html -->\r\n<div class=\"tabs-container\">\r\n <!-- Tab headers -->\r\n <div class=\"tab-headers\">\r\n <button\r\n *ngFor=\"let tab of tabs; let i = index\"\r\n [class.active]=\"tab.active\"\r\n (click)=\"selectTab(i)\">\r\n {{ tab.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- Tab content will render via ng-content inside TabComponent -->\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: [".tabs-container{border:1px solid #ccc;border-radius:4px}.tab-headers{display:flex;border-bottom:1px solid #ccc}.tab-headers button{flex:1;padding:8px 12px;background:none;border:none;cursor:pointer;font-weight:500}.tab-headers button.active{border-bottom:2px solid #007bff;font-weight:700}.tab-headers button:hover{background-color:#f5f5f5}.tab-content{padding:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
1974
+ }
1975
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TabsComponent, decorators: [{
1976
+ type: Component,
1977
+ args: [{ selector: 'dso-tabs', standalone: true, imports: [CommonModule, TabComponent], template: "<!-- tabs.component.html -->\r\n<div class=\"tabs-container\">\r\n <!-- Tab headers -->\r\n <div class=\"tab-headers\">\r\n <button\r\n *ngFor=\"let tab of tabs; let i = index\"\r\n [class.active]=\"tab.active\"\r\n (click)=\"selectTab(i)\">\r\n {{ tab.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- Tab content will render via ng-content inside TabComponent -->\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: [".tabs-container{border:1px solid #ccc;border-radius:4px}.tab-headers{display:flex;border-bottom:1px solid #ccc}.tab-headers button{flex:1;padding:8px 12px;background:none;border:none;cursor:pointer;font-weight:500}.tab-headers button.active{border-bottom:2px solid #007bff;font-weight:700}.tab-headers button:hover{background-color:#f5f5f5}.tab-content{padding:12px}\n"] }]
1978
+ }], propDecorators: { tabs: [{
1979
+ type: ContentChildren,
1980
+ args: [TabComponent]
1981
+ }], tabIndex: [{
1982
+ type: Input
1983
+ }], tabIndexChange: [{
1984
+ type: Output
1985
+ }] } });
1986
+
1987
+ class TagComponent {
1988
+ variant = 'neutral';
1989
+ removable = false;
1990
+ label = '';
1991
+ removed = false;
1992
+ removeTag() {
1993
+ this.removed = true;
1994
+ }
1995
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TagComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1996
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TagComponent, isStandalone: true, selector: "dso-tag", inputs: { variant: "variant", removable: "removable", label: "label" }, ngImport: i0, template: "<div\r\n *ngIf=\"!removed\"\r\n class=\"tag\"\r\n [ngClass]=\"'tag-' + variant\"\r\n>\r\n <span class=\"tag-label\">{{ label }}</span>\r\n <!-- <dso-button></dso-button> -->\r\n <button \r\n *ngIf=\"removable\" \r\n class=\"tag-remove-btn\" \r\n (click)=\"removeTag()\"\r\n aria-label=\"Remove tag\"\r\n >\r\n &times;\r\n </button>\r\n</div>\r\n\r\n", styles: [".tag{display:inline-flex;align-items:center;padding:4px 12px;font-size:.875rem;border-radius:16px;font-weight:500;white-space:nowrap;background-color:#e0e0e0;color:#333;gap:2px}.tag-success{background-color:#d4edda;color:#155724}.tag-info{background-color:#d1ecf1;color:#0c5460}.tag-warning{background-color:#fff3cd;color:#856404}.tag-danger{background-color:#f8d7da;color:#721c24}.tag-neutral{background-color:#e2e3e5;color:#383d41}.tag-remove-btn{background:transparent;border:none;font-size:1rem;line-height:1;cursor:pointer;color:inherit}.tag-remove-btn:hover{opacity:.6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1997
+ }
1998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TagComponent, decorators: [{
1999
+ type: Component,
2000
+ args: [{ selector: 'dso-tag', standalone: true, imports: [CommonModule, ButtonComponent], template: "<div\r\n *ngIf=\"!removed\"\r\n class=\"tag\"\r\n [ngClass]=\"'tag-' + variant\"\r\n>\r\n <span class=\"tag-label\">{{ label }}</span>\r\n <!-- <dso-button></dso-button> -->\r\n <button \r\n *ngIf=\"removable\" \r\n class=\"tag-remove-btn\" \r\n (click)=\"removeTag()\"\r\n aria-label=\"Remove tag\"\r\n >\r\n &times;\r\n </button>\r\n</div>\r\n\r\n", styles: [".tag{display:inline-flex;align-items:center;padding:4px 12px;font-size:.875rem;border-radius:16px;font-weight:500;white-space:nowrap;background-color:#e0e0e0;color:#333;gap:2px}.tag-success{background-color:#d4edda;color:#155724}.tag-info{background-color:#d1ecf1;color:#0c5460}.tag-warning{background-color:#fff3cd;color:#856404}.tag-danger{background-color:#f8d7da;color:#721c24}.tag-neutral{background-color:#e2e3e5;color:#383d41}.tag-remove-btn{background:transparent;border:none;font-size:1rem;line-height:1;cursor:pointer;color:inherit}.tag-remove-btn:hover{opacity:.6}\n"] }]
2001
+ }], propDecorators: { variant: [{
2002
+ type: Input
2003
+ }], removable: [{
2004
+ type: Input
2005
+ }], label: [{
2006
+ type: Input
2007
+ }] } });
2008
+
2009
+ class TextAreaComponent {
2010
+ inputLabel = ''; // Optional label for the textarea
2011
+ placeholder = '';
2012
+ value = ''; // Two-way binding for value
2013
+ helperText = 'helpering '; // Optional helper text
2014
+ errorText = 'tes test'; // Optional error text
2015
+ isDisabled = false; // Optional disabled state
2016
+ enableValidation = true; // Toggle validation
2017
+ maxLength = 300; // Maximum character length (for example)
2018
+ isTouched = false;
2019
+ isInvalid = false; // Error state
2020
+ charCount = 0; // To track character count
2021
+ isExceeded = false; // To track if the limit is exceeded
2022
+ onInputChange(event) {
2023
+ const inputValue = event.target.value;
2024
+ console.log(inputValue);
2025
+ const charCount = inputValue.length;
2026
+ // Check if the limit has been exceeded
2027
+ this.isExceeded = charCount > this.maxLength;
2028
+ this.isTouched = true;
2029
+ this.checkValidity();
2030
+ }
2031
+ // Method to determine the style of the character count
2032
+ getCharCountStyle() {
2033
+ const charCount = this.value.length;
2034
+ if (this.isExceeded) {
2035
+ // this.isExceeded = true;
2036
+ return 'char-count-error';
2037
+ }
2038
+ // If character count exceeds 50% of maxLength, change color to black
2039
+ if (charCount > this.maxLength / 2) {
2040
+ return '';
2041
+ }
2042
+ // Otherwise, keep it grey
2043
+ return '';
2044
+ }
2045
+ onBlur() {
2046
+ this.isTouched = true;
2047
+ this.checkValidity();
2048
+ }
2049
+ checkValidity() {
2050
+ // Set to invalid if it's required and empty
2051
+ this.isInvalid = this.isTouched && !this.value.trim();
2052
+ }
2053
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextAreaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2054
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TextAreaComponent, isStandalone: true, selector: "dso-text-area", inputs: { inputLabel: "inputLabel", placeholder: "placeholder", value: "value", helperText: "helperText", errorText: "errorText", isDisabled: "isDisabled", enableValidation: "enableValidation", maxLength: "maxLength" }, ngImport: i0, template: "<div class=\"text-area-container\">\r\n <div class=\"text-wrapper\">\r\n <!-- Optional label -->\r\n <label *ngIf=\"inputLabel\">\r\n {{ inputLabel }}\r\n </label> \r\n <!-- Character count inside the container -->\r\n <div class=\"char-count\" [ngClass]=\"getCharCountStyle()\" *ngIf=\"maxLength\">\r\n <span>\r\n {{ value.length }}\r\n </span>\r\n / {{ maxLength }}\r\n </div>\r\n </div>\r\n\r\n <textarea\r\n [placeholder]=\"placeholder\"\r\n [(ngModel)]=\"value\"\r\n (input)=\"onInputChange($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"isDisabled\"\r\n [class.invalid]=\"(isInvalid && isTouched && enableValidation) || isExceeded\"\r\n [class.disabled]=\"isDisabled\"\r\n >\r\n </textarea>\r\n\r\n <!-- Helper Text \r\n This condition checks if the helper text should be displayed:\r\n - If the character limit is **not exceeded** and validation is **not enabled**, show the helper text.\r\n - If validation **is enabled**, show the helper text only if the input is **not invalid** and `helperText` is provided.\r\n - If the character limit is exceeded, the helper text will be replaced with an error message. -->\r\n <div *ngIf=\"!isExceeded && !enableValidation || ((!isInvalid && helperText) && !isExceeded)\" class=\"helper-text\">\r\n {{ helperText }}\r\n </div>\r\n\r\n <!-- Specified Error Text -->\r\n <div *ngIf=\"isInvalid && isTouched && enableValidation\" class=\"error-message\">\r\n {{ errorText }}\r\n </div>\r\n\r\n <!-- Error message if character limit exceeded -->\r\n <div *ngIf=\"isExceeded\" class=\"error-message\">\r\n Character limit exceeded.\r\n </div>\r\n</div>\r\n", styles: [".text-area-container{display:flex;flex-direction:column;width:500px;gap:8px}.text-wrapper{display:flex;justify-content:space-between}textarea{width:100%;height:160px;padding:12px 8px;font-size:1rem;resize:vertical;border:1px solid #ccc;border-radius:4px}textarea:hover{border:1px solid #071d35}textarea:focus{border-color:#007bff;outline:none}.char-count{font-size:12px;color:#666;align-self:flex-end}.char-count span,.char-count-error{font-size:12px;color:#666}.char-count-error span{font-size:12px;color:red}.helper-text{font-size:16px;color:#555}.error-message{font-size:16px;color:red}span .error-message{color:red;font-size:12px}textarea.invalid{border-color:red}textarea.disabled{background-color:#f0f0f0;cursor:not-allowed;border-color:#ccc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
2055
+ }
2056
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextAreaComponent, decorators: [{
2057
+ type: Component,
2058
+ args: [{ selector: 'dso-text-area', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"text-area-container\">\r\n <div class=\"text-wrapper\">\r\n <!-- Optional label -->\r\n <label *ngIf=\"inputLabel\">\r\n {{ inputLabel }}\r\n </label> \r\n <!-- Character count inside the container -->\r\n <div class=\"char-count\" [ngClass]=\"getCharCountStyle()\" *ngIf=\"maxLength\">\r\n <span>\r\n {{ value.length }}\r\n </span>\r\n / {{ maxLength }}\r\n </div>\r\n </div>\r\n\r\n <textarea\r\n [placeholder]=\"placeholder\"\r\n [(ngModel)]=\"value\"\r\n (input)=\"onInputChange($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"isDisabled\"\r\n [class.invalid]=\"(isInvalid && isTouched && enableValidation) || isExceeded\"\r\n [class.disabled]=\"isDisabled\"\r\n >\r\n </textarea>\r\n\r\n <!-- Helper Text \r\n This condition checks if the helper text should be displayed:\r\n - If the character limit is **not exceeded** and validation is **not enabled**, show the helper text.\r\n - If validation **is enabled**, show the helper text only if the input is **not invalid** and `helperText` is provided.\r\n - If the character limit is exceeded, the helper text will be replaced with an error message. -->\r\n <div *ngIf=\"!isExceeded && !enableValidation || ((!isInvalid && helperText) && !isExceeded)\" class=\"helper-text\">\r\n {{ helperText }}\r\n </div>\r\n\r\n <!-- Specified Error Text -->\r\n <div *ngIf=\"isInvalid && isTouched && enableValidation\" class=\"error-message\">\r\n {{ errorText }}\r\n </div>\r\n\r\n <!-- Error message if character limit exceeded -->\r\n <div *ngIf=\"isExceeded\" class=\"error-message\">\r\n Character limit exceeded.\r\n </div>\r\n</div>\r\n", styles: [".text-area-container{display:flex;flex-direction:column;width:500px;gap:8px}.text-wrapper{display:flex;justify-content:space-between}textarea{width:100%;height:160px;padding:12px 8px;font-size:1rem;resize:vertical;border:1px solid #ccc;border-radius:4px}textarea:hover{border:1px solid #071d35}textarea:focus{border-color:#007bff;outline:none}.char-count{font-size:12px;color:#666;align-self:flex-end}.char-count span,.char-count-error{font-size:12px;color:#666}.char-count-error span{font-size:12px;color:red}.helper-text{font-size:16px;color:#555}.error-message{font-size:16px;color:red}span .error-message{color:red;font-size:12px}textarea.invalid{border-color:red}textarea.disabled{background-color:#f0f0f0;cursor:not-allowed;border-color:#ccc}\n"] }]
2059
+ }], propDecorators: { inputLabel: [{
2060
+ type: Input
2061
+ }], placeholder: [{
2062
+ type: Input
2063
+ }], value: [{
2064
+ type: Input
2065
+ }], helperText: [{
2066
+ type: Input
2067
+ }], errorText: [{
2068
+ type: Input
2069
+ }], isDisabled: [{
2070
+ type: Input
2071
+ }], enableValidation: [{
2072
+ type: Input
2073
+ }], maxLength: [{
2074
+ type: Input
2075
+ }] } });
2076
+
2077
+ class ToastService {
2078
+ // private toastSubject = new Subject<ToastMessage>();
2079
+ toastSubject = new ReplaySubject; // keep last message
2080
+ toast$ = this.toastSubject.asObservable();
2081
+ show(header, message, variant = 'neutral', duration) {
2082
+ this.toastSubject.next({ header, message, variant, duration });
2083
+ console.log('Toast Service Broadcasting:', { header, message, variant, duration }); // <-- debug
2084
+ }
2085
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2086
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastService, providedIn: 'root' });
2087
+ }
2088
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastService, decorators: [{
2089
+ type: Injectable,
2090
+ args: [{ providedIn: 'root' }]
2091
+ }] });
2092
+
2093
+ class ToastComponent {
2094
+ toastService;
2095
+ toasts = [];
2096
+ position = 'top-right';
2097
+ toastData; // Stores the current toast being displayed
2098
+ constructor(toastService) {
2099
+ this.toastService = toastService;
2100
+ } // Inject toast service
2101
+ ngOnInit() {
2102
+ // Subscribe to toast messages from the service
2103
+ this.toastService.toast$.subscribe((toast) => {
2104
+ console.log('Toast Child Listening:', toast); // Add this debug log!
2105
+ this.toasts.push(toast); // Add the new toast to the array for display
2106
+ // Auto-hide after specified duration (default 3000ms)
2107
+ setTimeout(() => {
2108
+ // this.visible = false; // Hide toast
2109
+ this.toasts.shift(); // Remove first toast from the array
2110
+ }, toast.duration || 3000);
2111
+ });
2112
+ }
2113
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastComponent, deps: [{ token: ToastService }], target: i0.ɵɵFactoryTarget.Component });
2114
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ToastComponent, isStandalone: true, selector: "dso-toast", inputs: { position: "position" }, ngImport: i0, template: "<div class=\"toast-wrapper\" \r\n [ngClass]=\"['toast-' + (position || 'top-right')]\">\r\n <div\r\n *ngFor=\"let toast of toasts; let i = index\"\r\n class=\"toast-container\"\r\n [ngClass]=\"['toast-' + (toast.variant || 'neutral')]\"\r\n >\r\n \r\n <div class=\"toast-header\" *ngIf=\"toast.header\">{{ toast.header }}</div>\r\n <div class=\"toast-body\">\r\n <div class=\"toast-message\">{{ toast.message }}</div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<!-- \r\n[ngStyle]=\"{\r\n animation: 'fadeInOut ' + (toast.duration || 3000) + 'ms ease forwards'}\"> -->", styles: [".toast-wrapper{position:fixed;display:flex;flex-direction:column;z-index:1000;gap:16px}.toast-container{padding:12px 16px;border-radius:8px;box-shadow:0 4px 12px #0003;color:#fff;width:280px;font-family:sans-serif;animation:fadeIn .3s ease forwards}.toast-top-right{top:20px;right:20px;align-items:flex-end}.toast-top-left{top:20px;left:20px;align-items:flex-start}.toast-bottom-right{bottom:20px;right:20px;align-items:flex-end}.toast-bottom-left{bottom:20px;left:20px;align-items:flex-start}.toast-top-center{top:20px;left:50%;transform:translate(-50%)}.toast-bottom-center{bottom:20px;left:50%;transform:translate(-50%)}.toast-success{background-color:#28a745}.toast-info{background-color:#17a2b8}.toast-warning{background-color:#ffc107;color:#333}.toast-danger{background-color:#dc3545}.toast-neutral{background-color:#6c757d}.toast-header{font-weight:600;margin-bottom:4px}.toast-subtext{font-size:.85rem;opacity:.8}@keyframes fadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-10px)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2115
+ }
2116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastComponent, decorators: [{
2117
+ type: Component,
2118
+ args: [{ selector: 'dso-toast', standalone: true, imports: [CommonModule] // Import CommonModule for ngIf, ngFor etc.
2119
+ , template: "<div class=\"toast-wrapper\" \r\n [ngClass]=\"['toast-' + (position || 'top-right')]\">\r\n <div\r\n *ngFor=\"let toast of toasts; let i = index\"\r\n class=\"toast-container\"\r\n [ngClass]=\"['toast-' + (toast.variant || 'neutral')]\"\r\n >\r\n \r\n <div class=\"toast-header\" *ngIf=\"toast.header\">{{ toast.header }}</div>\r\n <div class=\"toast-body\">\r\n <div class=\"toast-message\">{{ toast.message }}</div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<!-- \r\n[ngStyle]=\"{\r\n animation: 'fadeInOut ' + (toast.duration || 3000) + 'ms ease forwards'}\"> -->", styles: [".toast-wrapper{position:fixed;display:flex;flex-direction:column;z-index:1000;gap:16px}.toast-container{padding:12px 16px;border-radius:8px;box-shadow:0 4px 12px #0003;color:#fff;width:280px;font-family:sans-serif;animation:fadeIn .3s ease forwards}.toast-top-right{top:20px;right:20px;align-items:flex-end}.toast-top-left{top:20px;left:20px;align-items:flex-start}.toast-bottom-right{bottom:20px;right:20px;align-items:flex-end}.toast-bottom-left{bottom:20px;left:20px;align-items:flex-start}.toast-top-center{top:20px;left:50%;transform:translate(-50%)}.toast-bottom-center{bottom:20px;left:50%;transform:translate(-50%)}.toast-success{background-color:#28a745}.toast-info{background-color:#17a2b8}.toast-warning{background-color:#ffc107;color:#333}.toast-danger{background-color:#dc3545}.toast-neutral{background-color:#6c757d}.toast-header{font-weight:600;margin-bottom:4px}.toast-subtext{font-size:.85rem;opacity:.8}@keyframes fadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-10px)}}\n"] }]
2120
+ }], ctorParameters: () => [{ type: ToastService }], propDecorators: { position: [{
2121
+ type: Input
2122
+ }] } });
2123
+
2124
+ class TooltipComponent {
2125
+ el;
2126
+ text; // Tooltip text to display
2127
+ position = 'top'; // Position of the tooltip (default is 'top')
2128
+ tooltipWidth = '300px'; // Default width, can be overridden
2129
+ constructor(el) {
2130
+ this.el = el;
2131
+ }
2132
+ showTooltip = false; // This flag controls the visibility of the tooltip
2133
+ isHovered = false; // This flag checks if the mouse is over the tooltip
2134
+ // Triggered when the mouse enters the wrapper area (button + tooltip)
2135
+ onMouseEnter() {
2136
+ // console.log('Mouse entered');
2137
+ this.showTooltip = true; // Show tooltip
2138
+ }
2139
+ // Triggered when the mouse leaves the wrapper area (button + tooltip)
2140
+ onMouseLeave() {
2141
+ // console.log('Mouse left');
2142
+ this.showTooltip = false; // Hide tooltip
2143
+ }
2144
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TooltipComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2145
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TooltipComponent, isStandalone: true, selector: "dso-tooltip", inputs: { text: "text", position: "position", tooltipWidth: "tooltipWidth" }, ngImport: i0, template: "<!-- Tooltip wrapper: listens for mouse enter/leave -->\r\n<div class=\"tooltip-wrapper\" (mouseenter)=\"onMouseEnter()\" (mouseleave)=\"onMouseLeave()\">\r\n <ng-content></ng-content> <!-- This will allow the button or other content to be passed inside -->\r\n\r\n <!-- Tooltip box -->\r\n <div \r\n class=\"tooltip\" \r\n *ngIf=\"showTooltip\" \r\n [ngClass]=\"position\"\r\n [ngStyle]=\"{ 'width': tooltipWidth }\"\r\n >\r\n {{ text }}\r\n </div>\r\n</div>", styles: [".tooltip-wrapper{position:relative}.tooltip{position:absolute;background-color:#000;color:#fff;display:block;white-space:normal;word-break:break-word;border-radius:4px;pointer-events:auto;padding:12px;font-size:12px;z-index:100}.tooltip.top{bottom:calc(100% + 4px);transform:translate(-25%)}.tooltip.bottom{top:calc(100% + 4px);transform:translate(-25%)}.tooltip.left{top:0%;right:calc(100% + 4px);transform:translateY(-25%)}.tooltip.right{bottom:0%;left:calc(100% + 4px);transform:translateY(25%)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
2146
+ }
2147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TooltipComponent, decorators: [{
2148
+ type: Component,
2149
+ args: [{ selector: 'dso-tooltip', standalone: true, imports: [CommonModule], template: "<!-- Tooltip wrapper: listens for mouse enter/leave -->\r\n<div class=\"tooltip-wrapper\" (mouseenter)=\"onMouseEnter()\" (mouseleave)=\"onMouseLeave()\">\r\n <ng-content></ng-content> <!-- This will allow the button or other content to be passed inside -->\r\n\r\n <!-- Tooltip box -->\r\n <div \r\n class=\"tooltip\" \r\n *ngIf=\"showTooltip\" \r\n [ngClass]=\"position\"\r\n [ngStyle]=\"{ 'width': tooltipWidth }\"\r\n >\r\n {{ text }}\r\n </div>\r\n</div>", styles: [".tooltip-wrapper{position:relative}.tooltip{position:absolute;background-color:#000;color:#fff;display:block;white-space:normal;word-break:break-word;border-radius:4px;pointer-events:auto;padding:12px;font-size:12px;z-index:100}.tooltip.top{bottom:calc(100% + 4px);transform:translate(-25%)}.tooltip.bottom{top:calc(100% + 4px);transform:translate(-25%)}.tooltip.left{top:0%;right:calc(100% + 4px);transform:translateY(-25%)}.tooltip.right{bottom:0%;left:calc(100% + 4px);transform:translateY(25%)}\n"] }]
2150
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { text: [{
2151
+ type: Input
2152
+ }], position: [{
2153
+ type: Input
2154
+ }], tooltipWidth: [{
2155
+ type: Input
2156
+ }] } });
2157
+
2158
+ class TopNavComponent {
2159
+ /** App name displayed next to logo */
2160
+ appName = '';
2161
+ /** Dynamic menu items */
2162
+ menuItems = [];
2163
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TopNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2164
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TopNavComponent, isStandalone: true, selector: "dso-top-nav", inputs: { appName: "appName", menuItems: "menuItems" }, ngImport: i0, template: "<nav class=\"dso-top-nav\">\r\n\r\n <!-- Left section: menu+logo -->\r\n <div class=\"nav-left\">\r\n <div class=\"nav-left-group menu-logo\">\r\n <ng-content select=\"[dso-menu-logo]\"></ng-content>\r\n <span class=\"top-nav-app-name\" *ngIf=\"appName\">{{ appName }}</span>\r\n </div>\r\n\r\n <div class=\"nav-left-group menu-buttons\">\r\n <ng-container *ngFor=\"let item of menuItems\">\r\n <!-- Router link item -->\r\n <a \r\n *ngIf=\"item.route; else clickItem\" \r\n [routerLink]=\"item.route\" \r\n class=\"top-nav-item\"\r\n >\r\n <dso-icon *ngIf=\"item.iconName\" [iconName]=\"item.iconName\" size=\"small\"></dso-icon>\r\n <span>{{ item.label }}</span>\r\n </a>\r\n\r\n <!-- Click handler item -->\r\n <ng-template #clickItem>\r\n <button class=\"top-nav-item\" (click)=\"item.click?.()\">\r\n <dso-icon *ngIf=\"item.iconName\" [iconName]=\"item.iconName\" size=\"small\"></dso-icon>\r\n <span>{{ item.label }}</span>\r\n </button>\r\n </ng-template>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Right section -->\r\n <div class=\"nav-right\">\r\n <!-- \u2705 Project each element individually with dso-nav-right -->\r\n <ng-content select=\"[dso-nav-right]\"></ng-content>\r\n </div>\r\n\r\n</nav>\r\n", styles: ["@charset \"UTF-8\";.dso-top-nav{display:flex;align-items:center;justify-content:space-between;height:52px;padding:0 1rem;background-color:#fff;border:1px solid #e5e5e5}.dso-top-nav .nav-left{display:flex;align-items:center;gap:2rem}.dso-top-nav .nav-left .nav-left-group{display:flex;align-items:center}.dso-top-nav .nav-left .menu-logo{gap:.5rem}.dso-top-nav .nav-left .menu-buttons{gap:1rem}.dso-top-nav .nav-right{display:flex;align-items:center;gap:1rem;flex-direction:row;justify-content:flex-end}.dso-top-nav .nav-right dso-button{margin:0}.dso-top-nav .nav-right p{margin:0;font-weight:500}.top-nav-item{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;background:none;border:none;cursor:pointer;font:inherit;color:inherit;text-decoration:none}.top-nav-item:hover{background-color:#f2f2f2;border-radius:4px}.top-nav-app-name{font-weight:700;font-size:1rem;margin-left:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IconComponent, selector: "dso-icon", inputs: ["iconName", "size"] }] });
2165
+ }
2166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TopNavComponent, decorators: [{
2167
+ type: Component,
2168
+ args: [{ selector: 'dso-top-nav', standalone: true, imports: [CommonModule, RouterModule, IconComponent], template: "<nav class=\"dso-top-nav\">\r\n\r\n <!-- Left section: menu+logo -->\r\n <div class=\"nav-left\">\r\n <div class=\"nav-left-group menu-logo\">\r\n <ng-content select=\"[dso-menu-logo]\"></ng-content>\r\n <span class=\"top-nav-app-name\" *ngIf=\"appName\">{{ appName }}</span>\r\n </div>\r\n\r\n <div class=\"nav-left-group menu-buttons\">\r\n <ng-container *ngFor=\"let item of menuItems\">\r\n <!-- Router link item -->\r\n <a \r\n *ngIf=\"item.route; else clickItem\" \r\n [routerLink]=\"item.route\" \r\n class=\"top-nav-item\"\r\n >\r\n <dso-icon *ngIf=\"item.iconName\" [iconName]=\"item.iconName\" size=\"small\"></dso-icon>\r\n <span>{{ item.label }}</span>\r\n </a>\r\n\r\n <!-- Click handler item -->\r\n <ng-template #clickItem>\r\n <button class=\"top-nav-item\" (click)=\"item.click?.()\">\r\n <dso-icon *ngIf=\"item.iconName\" [iconName]=\"item.iconName\" size=\"small\"></dso-icon>\r\n <span>{{ item.label }}</span>\r\n </button>\r\n </ng-template>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Right section -->\r\n <div class=\"nav-right\">\r\n <!-- \u2705 Project each element individually with dso-nav-right -->\r\n <ng-content select=\"[dso-nav-right]\"></ng-content>\r\n </div>\r\n\r\n</nav>\r\n", styles: ["@charset \"UTF-8\";.dso-top-nav{display:flex;align-items:center;justify-content:space-between;height:52px;padding:0 1rem;background-color:#fff;border:1px solid #e5e5e5}.dso-top-nav .nav-left{display:flex;align-items:center;gap:2rem}.dso-top-nav .nav-left .nav-left-group{display:flex;align-items:center}.dso-top-nav .nav-left .menu-logo{gap:.5rem}.dso-top-nav .nav-left .menu-buttons{gap:1rem}.dso-top-nav .nav-right{display:flex;align-items:center;gap:1rem;flex-direction:row;justify-content:flex-end}.dso-top-nav .nav-right dso-button{margin:0}.dso-top-nav .nav-right p{margin:0;font-weight:500}.top-nav-item{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;background:none;border:none;cursor:pointer;font:inherit;color:inherit;text-decoration:none}.top-nav-item:hover{background-color:#f2f2f2;border-radius:4px}.top-nav-app-name{font-weight:700;font-size:1rem;margin-left:.5rem}\n"] }]
2169
+ }], propDecorators: { appName: [{
2170
+ type: Input
2171
+ }], menuItems: [{
2172
+ type: Input
2173
+ }] } });
2174
+
125
2175
  /**
126
2176
  * Generated bundle index. Do not edit.
127
2177
  */
128
2178
 
129
- export { ButtonComponent, IconComponent };
2179
+ export { AlertComponent, BadgeComponent, BadgeDirective, BreadcrumbComponent, ButtonComponent, CheckboxComponent, DatepickerComponent, DialogComponent, DropdownListComponent, FileUploadItemComponent, FileUploadMultiComponent, FileUploadSingleComponent, IconComponent, PaginationComponent, ProgressBarComponent, RadioComponent, SideNavComponent, SingleSelectComponent, SpinnerComponent, TabComponent, TableComponent, TabsComponent, TagComponent, TextAreaComponent, TextInputComponent, ToastComponent, TooltipComponent, TooltipDirective, TopNavComponent };
130
2180
  //# sourceMappingURL=dso-design-system-ui.mjs.map