@flusys/ng-shared 0.1.0-alpha.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.
@@ -0,0 +1,1543 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output, HostListener, NgModule, resource, Component, Injector, model, runInInjectionContext, untracked, forwardRef, viewChild, ChangeDetectionStrategy, InjectionToken } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { isPlatformServer, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe } from '@angular/common';
5
+ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
6
+ import { APP_CONFIG, getServiceUrl, ApiLoaderService } from '@flusys/ng-core';
7
+ import { of, firstValueFrom, skip, debounceTime, distinctUntilChanged, tap as tap$1 } from 'rxjs';
8
+ import { tap, catchError, map } from 'rxjs/operators';
9
+ import * as i4 from '@angular/forms';
10
+ import { NgControl, ReactiveFormsModule, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
11
+ import { RouterOutlet, RouterLink, Router } from '@angular/router';
12
+ import { AutoCompleteModule } from 'primeng/autocomplete';
13
+ import { ButtonModule } from 'primeng/button';
14
+ import { CardModule } from 'primeng/card';
15
+ import * as i2 from 'primeng/checkbox';
16
+ import { CheckboxModule } from 'primeng/checkbox';
17
+ import { DatePickerModule } from 'primeng/datepicker';
18
+ import { DialogModule } from 'primeng/dialog';
19
+ import { DividerModule } from 'primeng/divider';
20
+ import { FileUploadModule } from 'primeng/fileupload';
21
+ import { IconFieldModule } from 'primeng/iconfield';
22
+ import { ImageModule } from 'primeng/image';
23
+ import { InputIconModule } from 'primeng/inputicon';
24
+ import { InputNumberModule } from 'primeng/inputnumber';
25
+ import * as i1$1 from 'primeng/inputtext';
26
+ import { InputTextModule } from 'primeng/inputtext';
27
+ import { ListboxModule } from 'primeng/listbox';
28
+ import { MultiSelectModule } from 'primeng/multiselect';
29
+ import { PaginatorModule } from 'primeng/paginator';
30
+ import { PanelModule } from 'primeng/panel';
31
+ import { PasswordModule } from 'primeng/password';
32
+ import { PopoverModule } from 'primeng/popover';
33
+ import { ProgressBarModule } from 'primeng/progressbar';
34
+ import { RadioButtonModule } from 'primeng/radiobutton';
35
+ import { RippleModule } from 'primeng/ripple';
36
+ import * as i3 from 'primeng/select';
37
+ import { SelectModule } from 'primeng/select';
38
+ import { SelectButtonModule } from 'primeng/selectbutton';
39
+ import { SplitButtonModule } from 'primeng/splitbutton';
40
+ import { StepsModule } from 'primeng/steps';
41
+ import { TableModule } from 'primeng/table';
42
+ import { TabsModule } from 'primeng/tabs';
43
+ import { TagModule } from 'primeng/tag';
44
+ import { TextareaModule } from 'primeng/textarea';
45
+ import { ToggleSwitchModule } from 'primeng/toggleswitch';
46
+ import { TooltipModule } from 'primeng/tooltip';
47
+ import { TreeTableModule } from 'primeng/treetable';
48
+ import { toSignal, toObservable } from '@angular/core/rxjs-interop';
49
+
50
+ ;
51
+ ;
52
+
53
+ var ContactTypeEnum;
54
+ (function (ContactTypeEnum) {
55
+ ContactTypeEnum[ContactTypeEnum["PHONE"] = 1] = "PHONE";
56
+ ContactTypeEnum[ContactTypeEnum["EMAIL"] = 2] = "EMAIL";
57
+ })(ContactTypeEnum || (ContactTypeEnum = {}));
58
+
59
+ var IconTypeEnum;
60
+ (function (IconTypeEnum) {
61
+ IconTypeEnum[IconTypeEnum["PRIMENG_ICON"] = 1] = "PRIMENG_ICON";
62
+ IconTypeEnum[IconTypeEnum["IMAGE_FILE_LINK"] = 2] = "IMAGE_FILE_LINK";
63
+ IconTypeEnum[IconTypeEnum["DIRECT_TAG_SVG"] = 3] = "DIRECT_TAG_SVG";
64
+ })(IconTypeEnum || (IconTypeEnum = {}));
65
+
66
+ class PlatformService {
67
+ platformId = inject(PLATFORM_ID);
68
+ get isServer() {
69
+ return isPlatformServer(this.platformId);
70
+ }
71
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PlatformService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
72
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PlatformService, providedIn: 'root' });
73
+ }
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PlatformService, decorators: [{
75
+ type: Injectable,
76
+ args: [{
77
+ providedIn: 'root'
78
+ }]
79
+ }] });
80
+
81
+ class CookieService {
82
+ doc = inject(DOCUMENT, { optional: true });
83
+ platformService = inject(PlatformService);
84
+ request = inject(REQUEST, { optional: true });
85
+ get() {
86
+ return !this.platformService.isServer ? this.doc.cookie : this.request?.headers.get('cookie') ?? "";
87
+ }
88
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: CookieService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
89
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: CookieService, providedIn: 'root' });
90
+ }
91
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: CookieService, decorators: [{
92
+ type: Injectable,
93
+ args: [{
94
+ providedIn: 'root',
95
+ }]
96
+ }] });
97
+
98
+ /**
99
+ * Service to fetch file URLs from the backend.
100
+ * Uses POST /file-manager/get-files endpoint which:
101
+ * - Handles presigned URLs for cloud storage (AWS S3, Azure)
102
+ * - Auto-refreshes expired URLs
103
+ * - Validates file access permissions
104
+ * - Works with all storage providers (Local, S3, Azure, SFTP)
105
+ */
106
+ class FileUrlService {
107
+ http = inject(HttpClient);
108
+ appConfig = inject(APP_CONFIG);
109
+ /** Cache of file URLs by file ID */
110
+ urlCache = signal(new Map(), ...(ngDevMode ? [{ debugName: "urlCache" }] : []));
111
+ /**
112
+ * Get file URL by ID from cache (reactive signal)
113
+ */
114
+ getFileUrl(fileId) {
115
+ if (!fileId)
116
+ return null;
117
+ return this.urlCache().get(fileId)?.url ?? null;
118
+ }
119
+ /**
120
+ * Get file URL signal (computed from cache)
121
+ */
122
+ fileUrlSignal(fileId) {
123
+ return computed(() => this.getFileUrl(fileId));
124
+ }
125
+ /**
126
+ * Fetch file URLs from backend and update cache.
127
+ * Returns Observable of fetched files.
128
+ */
129
+ fetchFileUrls(fileIds) {
130
+ if (!fileIds.length)
131
+ return of([]);
132
+ const requestDto = fileIds.map((id) => ({ id }));
133
+ return this.http.post(`${getServiceUrl(this.appConfig, 'storage')}/file-manager/get-files`, requestDto).pipe(tap((files) => {
134
+ // Update cache
135
+ const cache = new Map(this.urlCache());
136
+ files.forEach((file) => cache.set(file.id, file));
137
+ this.urlCache.set(cache);
138
+ }), catchError((error) => {
139
+ console.error('Failed to fetch file URLs:', error);
140
+ return of([]);
141
+ }));
142
+ }
143
+ /**
144
+ * Fetch a single file URL.
145
+ * Returns Observable of file info or null if not found.
146
+ */
147
+ fetchSingleFileUrl(fileId) {
148
+ return this.fetchFileUrls([fileId]).pipe(map((files) => files[0] ?? null));
149
+ }
150
+ /**
151
+ * Clear the URL cache.
152
+ * Useful on logout or when switching contexts.
153
+ */
154
+ clearCache() {
155
+ this.urlCache.set(new Map());
156
+ }
157
+ /**
158
+ * Remove specific file from cache.
159
+ */
160
+ removeFromCache(fileId) {
161
+ const cache = new Map(this.urlCache());
162
+ cache.delete(fileId);
163
+ this.urlCache.set(cache);
164
+ }
165
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: FileUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
166
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: FileUrlService, providedIn: 'root' });
167
+ }
168
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: FileUrlService, decorators: [{
169
+ type: Injectable,
170
+ args: [{ providedIn: 'root' }]
171
+ }] });
172
+
173
+ /**
174
+ * Permission Validator Service
175
+ *
176
+ * Centralized service for permission validation across all packages.
177
+ * Provides signal-based state management and reactive permission checking.
178
+ *
179
+ * Features:
180
+ * - Signal-based permission storage
181
+ * - Individual permission checks
182
+ * - Permission change detection
183
+ * - Reactive permission state updates
184
+ *
185
+ * Usage:
186
+ * ```typescript
187
+ * // In component
188
+ * readonly permissionValidator = inject(PermissionValidatorService);
189
+ *
190
+ * // Set permissions (typically from auth/IAM)
191
+ * this.permissionValidator.setPermissions(['user.view', 'user.create']);
192
+ *
193
+ * // Check individual permission
194
+ * if (this.permissionValidator.hasPermission('user.view')) {
195
+ * // Show UI element
196
+ * }
197
+ *
198
+ * // Access current permissions reactively
199
+ * const permissions = this.permissionValidator.permissions();
200
+ * ```
201
+ *
202
+ * @packageDocumentation
203
+ */
204
+ class PermissionValidatorService {
205
+ // ==================== SIGNALS ====================
206
+ /**
207
+ * Private writable signal for permissions
208
+ */
209
+ _permissions = signal([], ...(ngDevMode ? [{ debugName: "_permissions" }] : []));
210
+ /**
211
+ * Readonly permission signal for external consumers
212
+ */
213
+ permissions = this._permissions.asReadonly();
214
+ /**
215
+ * Private writable signal for loaded state
216
+ */
217
+ _isLoaded = signal(false, ...(ngDevMode ? [{ debugName: "_isLoaded" }] : []));
218
+ /**
219
+ * Set permissions (replaces existing permissions)
220
+ * @param permissions - Array of permission codes
221
+ */
222
+ setPermissions(permissions) {
223
+ this._permissions.set(permissions);
224
+ this._isLoaded.set(true);
225
+ }
226
+ /**
227
+ * Clear all permissions
228
+ */
229
+ clearPermissions() {
230
+ this._permissions.set([]);
231
+ this._isLoaded.set(false);
232
+ }
233
+ // ==================== PERMISSION CHECKING ====================
234
+ /**
235
+ * Check if user has a specific permission
236
+ * @param permissionCode - Required permission code
237
+ * @returns True if user has permission
238
+ */
239
+ hasPermission(permissionCode) {
240
+ return this.permissions().includes(permissionCode);
241
+ }
242
+ // ==================== UTILITY METHODS ====================
243
+ /**
244
+ * Check if permissions are loaded
245
+ * @returns True if permissions have been loaded
246
+ */
247
+ isPermissionsLoaded() {
248
+ return this._isLoaded();
249
+ }
250
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PermissionValidatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
251
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PermissionValidatorService, providedIn: 'root' });
252
+ }
253
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PermissionValidatorService, decorators: [{
254
+ type: Injectable,
255
+ args: [{
256
+ providedIn: 'root',
257
+ }]
258
+ }] });
259
+
260
+ class EditModeElementChangerDirective {
261
+ el = inject(ElementRef);
262
+ ngControl = inject(NgControl, { optional: true });
263
+ isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
264
+ constructor() {
265
+ effect(() => {
266
+ const editMode = this.isEditMode();
267
+ this.updateElement(editMode);
268
+ this.updateControl(editMode);
269
+ });
270
+ }
271
+ ngAfterViewInit() {
272
+ const editMode = this.isEditMode();
273
+ this.updateElement(editMode);
274
+ this.updateControl(editMode);
275
+ }
276
+ updateControl(editMode) {
277
+ if (!editMode)
278
+ this.ngControl?.control?.disable();
279
+ else
280
+ this.ngControl?.control?.enable();
281
+ }
282
+ updateElement(editMode) {
283
+ const inputElement = this.el.nativeElement;
284
+ if (inputElement instanceof HTMLInputElement) {
285
+ if (!editMode) {
286
+ inputElement.setAttribute('readonly', 'true');
287
+ inputElement?.classList.add('edit-mode-element-css');
288
+ }
289
+ else {
290
+ inputElement.removeAttribute('readonly');
291
+ inputElement?.classList.remove('edit-mode-element-css');
292
+ }
293
+ }
294
+ if (inputElement.tagName == 'P-SELECT') {
295
+ if (!editMode) {
296
+ inputElement.classList.add('edit-mode-element-css');
297
+ inputElement.classList.add('overflow-hidden');
298
+ inputElement.querySelector('.p-select-dropdown').style.display = 'none';
299
+ inputElement.querySelector('.p-select-label').classList.add('edit-mode-element-css');
300
+ }
301
+ else {
302
+ inputElement.classList.remove("edit-mode-element-css");
303
+ inputElement.querySelector('.p-select-dropdown').style.display = 'flex';
304
+ inputElement.querySelector('.p-select-label').classList.remove("edit-mode-element-css");
305
+ inputElement.classList.remove('overflow-hidden');
306
+ }
307
+ }
308
+ if (inputElement.tagName == 'P-CALENDAR') {
309
+ if (!editMode) {
310
+ inputElement.firstElementChild.children[0]?.classList.add('edit-mode-element-css');
311
+ inputElement.firstElementChild.children[0]?.classList.add('cursor-auto');
312
+ inputElement.firstElementChild.children[1]?.classList.add('hidden');
313
+ }
314
+ else {
315
+ inputElement.firstElementChild.children[0]?.classList.remove('edit-mode-element-css');
316
+ inputElement.firstElementChild.children[0]?.classList.remove('cursor-auto');
317
+ inputElement.firstElementChild.children[1]?.classList.remove('hidden');
318
+ }
319
+ }
320
+ }
321
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: EditModeElementChangerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
322
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: EditModeElementChangerDirective, isStandalone: true, selector: "[appEditModeElementChanger]", inputs: { isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
323
+ }
324
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: EditModeElementChangerDirective, decorators: [{
325
+ type: Directive,
326
+ args: [{
327
+ selector: '[appEditModeElementChanger]',
328
+ standalone: true,
329
+ }]
330
+ }], ctorParameters: () => [], propDecorators: { isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }] } });
331
+
332
+ /** Evaluate permission logic (string or ILogicNode) against user permissions */
333
+ function evaluatePermission(logic, permissions) {
334
+ if (!logic)
335
+ return false;
336
+ if (typeof logic === 'string')
337
+ return permissions.includes(logic);
338
+ return evaluateLogicNode(logic, permissions);
339
+ }
340
+ /** Recursively evaluate an ILogicNode tree */
341
+ function evaluateLogicNode(node, permissions) {
342
+ switch (node.type) {
343
+ case 'action':
344
+ return node.actionId ? permissions.includes(node.actionId) : false;
345
+ case 'group':
346
+ if (!node.children || node.children.length === 0)
347
+ return false;
348
+ return node.operator === 'AND'
349
+ ? node.children.every((child) => evaluateLogicNode(child, permissions))
350
+ : node.children.some((child) => evaluateLogicNode(child, permissions));
351
+ default:
352
+ return false;
353
+ }
354
+ }
355
+ /** Check if user has ANY of the specified permissions (OR logic) */
356
+ function hasAnyPermission(permissionCodes, permissions) {
357
+ if (!permissionCodes?.length)
358
+ return false;
359
+ return permissionCodes.some((code) => permissions.includes(code));
360
+ }
361
+ /** Check if user has ALL of the specified permissions (AND logic) */
362
+ function hasAllPermissions(permissionCodes, permissions) {
363
+ if (!permissionCodes?.length)
364
+ return false;
365
+ return permissionCodes.every((code) => permissions.includes(code));
366
+ }
367
+
368
+ /**
369
+ * HasPermission Directive
370
+ *
371
+ * Structural directive for permission-based rendering.
372
+ * Shows/hides elements based on permission logic evaluation.
373
+ *
374
+ * Supports:
375
+ * - Simple permission check (string)
376
+ * - Complex permission logic (ILogicNode with AND/OR operators)
377
+ *
378
+ * Usage:
379
+ * ```html
380
+ * <!-- Simple permission check -->
381
+ * <button *hasPermission="'user.create'">Create User</button>
382
+ *
383
+ * <!-- Complex permission logic -->
384
+ * <button *hasPermission="userManageLogic">Manage Users</button>
385
+ *
386
+ * <!-- Where userManageLogic is: -->
387
+ * <!-- (user.view OR user.create) AND department.manage -->
388
+ * ```
389
+ *
390
+ * The directive automatically reacts to permission changes through signals.
391
+ *
392
+ * @example
393
+ * // In component
394
+ * readonly userManageLogic: ILogicNode = {
395
+ * id: 'root',
396
+ * type: 'group',
397
+ * operator: 'AND',
398
+ * children: [
399
+ * {
400
+ * id: '1',
401
+ * type: 'group',
402
+ * operator: 'OR',
403
+ * children: [
404
+ * { id: '2', type: 'action', actionId: 'user.view' },
405
+ * { id: '3', type: 'action', actionId: 'user.create' }
406
+ * ]
407
+ * },
408
+ * { id: '4', type: 'action', actionId: 'department.manage' }
409
+ * ]
410
+ * };
411
+ *
412
+ * // In template
413
+ * <div *hasPermission="userManageLogic">
414
+ * User management content
415
+ * </div>
416
+ */
417
+ class HasPermissionDirective {
418
+ // ==================== DEPENDENCIES ====================
419
+ templateRef = inject((TemplateRef));
420
+ viewContainer = inject(ViewContainerRef);
421
+ permissionValidator = inject(PermissionValidatorService);
422
+ // ==================== SIGNALS ====================
423
+ /**
424
+ * Permission logic input signal
425
+ * Accepts either:
426
+ * - string: Single permission code to check
427
+ * - ILogicNode: Complex permission logic tree
428
+ */
429
+ hasPermission = input(null, ...(ngDevMode ? [{ debugName: "hasPermission" }] : []));
430
+ /**
431
+ * View created state
432
+ */
433
+ viewCreated = false;
434
+ // ==================== CONSTRUCTOR ====================
435
+ constructor() {
436
+ // Effect to reactively update view when permissions or logic changes
437
+ effect(() => {
438
+ // Track permission changes
439
+ this.permissionValidator.permissions();
440
+ // Track logic changes
441
+ const logic = this.hasPermission();
442
+ // Evaluate and update view
443
+ this.updateView(logic);
444
+ });
445
+ }
446
+ // ==================== VIEW MANAGEMENT ====================
447
+ /**
448
+ * Update view based on permission evaluation
449
+ */
450
+ updateView(logic) {
451
+ // Fail closed: No logic = no access
452
+ if (!logic) {
453
+ this.clearView();
454
+ return;
455
+ }
456
+ // Fail closed: No permissions loaded = no access (unauthenticated state)
457
+ if (!this.permissionValidator.isPermissionsLoaded()) {
458
+ this.clearView();
459
+ return;
460
+ }
461
+ // Use shared evaluatePermission utility
462
+ const hasPermission = evaluatePermission(logic, this.permissionValidator.permissions());
463
+ if (hasPermission && !this.viewCreated) {
464
+ // Create view if not exists
465
+ this.viewContainer.createEmbeddedView(this.templateRef);
466
+ this.viewCreated = true;
467
+ }
468
+ else if (!hasPermission && this.viewCreated) {
469
+ this.clearView();
470
+ }
471
+ }
472
+ /**
473
+ * Clear the view container
474
+ */
475
+ clearView() {
476
+ if (this.viewCreated) {
477
+ this.viewContainer.clear();
478
+ this.viewCreated = false;
479
+ }
480
+ }
481
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: HasPermissionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
482
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: HasPermissionDirective, isStandalone: true, selector: "[hasPermission]", inputs: { hasPermission: { classPropertyName: "hasPermission", publicName: "hasPermission", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
483
+ }
484
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: HasPermissionDirective, decorators: [{
485
+ type: Directive,
486
+ args: [{
487
+ selector: '[hasPermission]',
488
+ standalone: true,
489
+ }]
490
+ }], ctorParameters: () => [], propDecorators: { hasPermission: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasPermission", required: false }] }] } });
491
+
492
+ class IsEmptyImageDirective {
493
+ src = input('', ...(ngDevMode ? [{ debugName: "src" }] : []));
494
+ hasError = signal(false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
495
+ defaultImg = 'lib/assets/images/default/default-image.jpg';
496
+ imageSrc = computed(() => {
497
+ if (this.hasError()) {
498
+ return this.defaultImg;
499
+ }
500
+ const srcValue = this.src();
501
+ return srcValue ? srcValue : this.defaultImg;
502
+ }, ...(ngDevMode ? [{ debugName: "imageSrc" }] : []));
503
+ onError() {
504
+ this.hasError.set(true);
505
+ }
506
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: IsEmptyImageDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
507
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: IsEmptyImageDirective, isStandalone: true, selector: "img", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "error": "onError()" }, properties: { "src": "imageSrc()" } }, ngImport: i0 });
508
+ }
509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: IsEmptyImageDirective, decorators: [{
510
+ type: Directive,
511
+ args: [{
512
+ selector: 'img',
513
+ host: {
514
+ '[src]': 'imageSrc()',
515
+ '(error)': 'onError()',
516
+ },
517
+ standalone: true,
518
+ }]
519
+ }], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }] } });
520
+
521
+ class PreventDefaultDirective {
522
+ eventType = input('click', ...(ngDevMode ? [{ debugName: "eventType" }] : []));
523
+ preventKey = input(undefined, ...(ngDevMode ? [{ debugName: "preventKey" }] : []));
524
+ action = output();
525
+ onClick(event) {
526
+ if (this.eventType() === 'click') {
527
+ this.processEvent(event);
528
+ }
529
+ }
530
+ onKeydown(event) {
531
+ if (this.eventType() === 'keydown') {
532
+ if (!this.preventKey() || event.key === this.preventKey()) {
533
+ this.processEvent(event);
534
+ }
535
+ }
536
+ }
537
+ onKeyup(event) {
538
+ if (this.eventType() === 'keyup') {
539
+ if (!this.preventKey() || event.key === this.preventKey()) {
540
+ this.processEvent(event);
541
+ }
542
+ }
543
+ }
544
+ processEvent(event) {
545
+ event.preventDefault();
546
+ if (this.action)
547
+ this.action.emit(event);
548
+ }
549
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PreventDefaultDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
550
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: PreventDefaultDirective, isStandalone: true, selector: "[appPreventDefault]", inputs: { eventType: { classPropertyName: "eventType", publicName: "eventType", isSignal: true, isRequired: false, transformFunction: null }, preventKey: { classPropertyName: "preventKey", publicName: "preventKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)", "keyup": "onKeyup($event)" } }, ngImport: i0 });
551
+ }
552
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PreventDefaultDirective, decorators: [{
553
+ type: Directive,
554
+ args: [{
555
+ selector: '[appPreventDefault]',
556
+ standalone: true,
557
+ }]
558
+ }], propDecorators: { eventType: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventType", required: false }] }], preventKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventKey", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], onClick: [{
559
+ type: HostListener,
560
+ args: ['click', ['$event']]
561
+ }], onKeydown: [{
562
+ type: HostListener,
563
+ args: ['keydown', ['$event']]
564
+ }], onKeyup: [{
565
+ type: HostListener,
566
+ args: ['keyup', ['$event']]
567
+ }] } });
568
+
569
+ class AngularModule {
570
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: AngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
571
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.0", ngImport: i0, type: AngularModule, imports: [CommonModule,
572
+ RouterOutlet,
573
+ RouterLink,
574
+ IsEmptyImageDirective,
575
+ NgOptimizedImage,
576
+ NgComponentOutlet,
577
+ PreventDefaultDirective], exports: [CommonModule,
578
+ ReactiveFormsModule,
579
+ FormsModule,
580
+ RouterOutlet,
581
+ RouterLink,
582
+ IsEmptyImageDirective,
583
+ NgOptimizedImage,
584
+ NgComponentOutlet,
585
+ PreventDefaultDirective] });
586
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: AngularModule, providers: [
587
+ DatePipe,
588
+ ], imports: [CommonModule, CommonModule,
589
+ ReactiveFormsModule,
590
+ FormsModule] });
591
+ }
592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: AngularModule, decorators: [{
593
+ type: NgModule,
594
+ args: [{
595
+ imports: [
596
+ CommonModule,
597
+ RouterOutlet,
598
+ RouterLink,
599
+ IsEmptyImageDirective,
600
+ NgOptimizedImage,
601
+ NgComponentOutlet,
602
+ PreventDefaultDirective
603
+ ],
604
+ providers: [
605
+ DatePipe,
606
+ ],
607
+ exports: [
608
+ CommonModule,
609
+ ReactiveFormsModule,
610
+ FormsModule,
611
+ RouterOutlet,
612
+ RouterLink,
613
+ IsEmptyImageDirective,
614
+ NgOptimizedImage,
615
+ NgComponentOutlet,
616
+ PreventDefaultDirective
617
+ ],
618
+ }]
619
+ }] });
620
+
621
+ class PrimeModule {
622
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PrimeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
623
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.0", ngImport: i0, type: PrimeModule, exports: [InputTextModule,
624
+ TagModule,
625
+ SelectButtonModule,
626
+ PasswordModule,
627
+ ButtonModule,
628
+ TooltipModule,
629
+ CheckboxModule,
630
+ StepsModule,
631
+ RippleModule,
632
+ PanelModule,
633
+ PaginatorModule,
634
+ TableModule,
635
+ InputNumberModule,
636
+ TextareaModule,
637
+ ProgressBarModule,
638
+ FileUploadModule,
639
+ CardModule,
640
+ SelectModule,
641
+ InputIconModule,
642
+ IconFieldModule,
643
+ PopoverModule,
644
+ ListboxModule,
645
+ RadioButtonModule,
646
+ ToggleSwitchModule,
647
+ ImageModule,
648
+ DatePickerModule,
649
+ SplitButtonModule,
650
+ DividerModule,
651
+ MultiSelectModule,
652
+ AutoCompleteModule,
653
+ TabsModule,
654
+ DialogModule,
655
+ TreeTableModule] });
656
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PrimeModule, imports: [InputTextModule,
657
+ TagModule,
658
+ SelectButtonModule,
659
+ PasswordModule,
660
+ ButtonModule,
661
+ TooltipModule,
662
+ CheckboxModule,
663
+ StepsModule,
664
+ RippleModule,
665
+ PanelModule,
666
+ PaginatorModule,
667
+ TableModule,
668
+ InputNumberModule,
669
+ TextareaModule,
670
+ ProgressBarModule,
671
+ FileUploadModule,
672
+ CardModule,
673
+ SelectModule,
674
+ InputIconModule,
675
+ IconFieldModule,
676
+ PopoverModule,
677
+ ListboxModule,
678
+ RadioButtonModule,
679
+ ToggleSwitchModule,
680
+ ImageModule,
681
+ DatePickerModule,
682
+ SplitButtonModule,
683
+ DividerModule,
684
+ MultiSelectModule,
685
+ AutoCompleteModule,
686
+ TabsModule,
687
+ DialogModule,
688
+ TreeTableModule] });
689
+ }
690
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PrimeModule, decorators: [{
691
+ type: NgModule,
692
+ args: [{
693
+ exports: [
694
+ InputTextModule,
695
+ TagModule,
696
+ SelectButtonModule,
697
+ PasswordModule,
698
+ ButtonModule,
699
+ TooltipModule,
700
+ CheckboxModule,
701
+ StepsModule,
702
+ RippleModule,
703
+ PanelModule,
704
+ PaginatorModule,
705
+ TableModule,
706
+ InputNumberModule,
707
+ TextareaModule,
708
+ ProgressBarModule,
709
+ FileUploadModule,
710
+ CardModule,
711
+ SelectModule,
712
+ InputIconModule,
713
+ IconFieldModule,
714
+ PopoverModule,
715
+ ListboxModule,
716
+ RadioButtonModule,
717
+ ToggleSwitchModule,
718
+ ImageModule,
719
+ DatePickerModule,
720
+ SplitButtonModule,
721
+ DividerModule,
722
+ MultiSelectModule,
723
+ AutoCompleteModule,
724
+ TabsModule,
725
+ DialogModule,
726
+ TreeTableModule,
727
+ ],
728
+ }]
729
+ }] });
730
+
731
+ // =============================================================================
732
+ // API Resource Service - Signal-based CRUD with Angular Resource API
733
+ // =============================================================================
734
+ /**
735
+ * Abstract base class for API services using Angular 21 resource() API.
736
+ * Provides signal-based reactive data fetching with automatic loading states.
737
+ * Response types match FLUSYS_NEST backend DTOs.
738
+ *
739
+ * ## Endpoint Mapping
740
+ *
741
+ * All endpoints use POST method (RPC-style API):
742
+ * - `POST /{resource}/insert` - Create single item
743
+ * - `POST /{resource}/insert-many` - Create multiple items
744
+ * - `POST /{resource}/get/:id` - Get single item by ID
745
+ * - `POST /{resource}/get-all?q=` - List with pagination/filter
746
+ * - `POST /{resource}/update` - Update single item
747
+ * - `POST /{resource}/update-many` - Update multiple items
748
+ * - `POST /{resource}/delete` - Delete/restore/permanent delete
749
+ *
750
+ * @example
751
+ * ```typescript
752
+ * // Define service
753
+ * @Injectable({ providedIn: 'root' })
754
+ * export class UserService extends ApiResourceService<UserDto, User> {
755
+ * constructor(http: HttpClient) {
756
+ * super('users', http);
757
+ * }
758
+ * }
759
+ *
760
+ * // In component - use signals
761
+ * userService = inject(UserService);
762
+ * users = this.userService.data; // Signal<User[]>
763
+ * isLoading = this.userService.isLoading; // Signal<boolean>
764
+ * total = this.userService.total; // Signal<number>
765
+ *
766
+ * // Trigger fetch
767
+ * this.userService.fetchList('search', { pagination: { currentPage: 0, pageSize: 10 } });
768
+ *
769
+ * // CRUD operations
770
+ * await this.userService.insertAsync({ name: 'John', email: 'john@example.com' });
771
+ * await this.userService.updateAsync({ id: '123', name: 'John Updated' });
772
+ * await this.userService.deleteAsync({ id: '123', type: 'delete' });
773
+ * ```
774
+ */
775
+ class ApiResourceService {
776
+ baseUrl;
777
+ loaderService = inject(ApiLoaderService);
778
+ http;
779
+ moduleApiName;
780
+ // ==========================================================================
781
+ // State Signals for List Queries
782
+ // ==========================================================================
783
+ /** Current search term */
784
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
785
+ /** Current filter and pagination state */
786
+ filterData = signal({
787
+ pagination: { currentPage: 0, pageSize: 10 },
788
+ filter: {},
789
+ select: [],
790
+ sort: {},
791
+ }, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
792
+ /** Resource for list data - uses IListResponse matching backend */
793
+ listResource;
794
+ // ==========================================================================
795
+ // Computed State Accessors
796
+ // ==========================================================================
797
+ /** Whether data is currently loading */
798
+ isLoading = computed(() => this.listResource.isLoading(), ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
799
+ /** List data array */
800
+ data = computed(() => this.listResource.value()?.data ?? [], ...(ngDevMode ? [{ debugName: "data" }] : []));
801
+ /** Total count of items */
802
+ total = computed(() => this.listResource.value()?.meta?.total ?? 0, ...(ngDevMode ? [{ debugName: "total" }] : []));
803
+ /** Pagination metadata */
804
+ pageInfo = computed(() => this.listResource.value()?.meta, ...(ngDevMode ? [{ debugName: "pageInfo" }] : []));
805
+ /** Whether there are more pages */
806
+ hasMore = computed(() => {
807
+ const meta = this.listResource.value()?.meta;
808
+ if (!meta)
809
+ return false;
810
+ return meta.hasMore ?? (meta.page + 1) * meta.pageSize < meta.total;
811
+ }, ...(ngDevMode ? [{ debugName: "hasMore" }] : []));
812
+ constructor(moduleApiName, http) {
813
+ this.moduleApiName = moduleApiName;
814
+ this.baseUrl = inject(APP_CONFIG).apiBaseUrl + '/' + moduleApiName;
815
+ this.http = http;
816
+ // Create resource that reacts to search and filter changes
817
+ this.listResource = resource({ ...(ngDevMode ? { debugName: "listResource" } : {}), params: () => ({
818
+ search: this.searchTerm(),
819
+ filter: this.filterData(),
820
+ }),
821
+ loader: async ({ params }) => {
822
+ const { search, filter } = params;
823
+ return this.fetchAllAsync(search, filter);
824
+ } });
825
+ }
826
+ getHttpOptions(endpoint, params) {
827
+ return {
828
+ headers: new HttpHeaders({
829
+ 'x-loader-tag': `${this.moduleApiName}/${endpoint}`,
830
+ }),
831
+ ...(params ? { params } : {}),
832
+ };
833
+ }
834
+ // ==========================================================================
835
+ // List Management Methods
836
+ // ==========================================================================
837
+ /**
838
+ * Fetch list data (triggers resource reload)
839
+ */
840
+ fetchList(search = '', filter) {
841
+ this.searchTerm.set(search);
842
+ if (filter) {
843
+ this.filterData.update((prev) => ({ ...prev, ...filter }));
844
+ }
845
+ }
846
+ /**
847
+ * Update pagination
848
+ */
849
+ setPagination(pagination) {
850
+ this.filterData.update((prev) => ({ ...prev, pagination }));
851
+ }
852
+ /**
853
+ * Go to next page
854
+ */
855
+ nextPage() {
856
+ this.filterData.update((prev) => ({
857
+ ...prev,
858
+ pagination: {
859
+ ...prev.pagination,
860
+ currentPage: (prev.pagination?.currentPage ?? 0) + 1,
861
+ },
862
+ }));
863
+ }
864
+ /**
865
+ * Reset to first page
866
+ */
867
+ resetPagination() {
868
+ this.filterData.update((prev) => ({
869
+ ...prev,
870
+ pagination: { currentPage: 0, pageSize: prev.pagination?.pageSize ?? 10 },
871
+ }));
872
+ }
873
+ /**
874
+ * Reload current data
875
+ */
876
+ reload() {
877
+ this.listResource.reload();
878
+ }
879
+ // ==========================================================================
880
+ // Observable-based API Methods (IApiService interface)
881
+ // ==========================================================================
882
+ /**
883
+ * Insert single item (Observable)
884
+ * POST /{resource}/insert
885
+ */
886
+ insert(dto) {
887
+ return this.http.post(`${this.baseUrl}/insert`, dto, this.getHttpOptions('insert'));
888
+ }
889
+ /**
890
+ * Insert multiple items (Observable)
891
+ * POST /{resource}/insert-many
892
+ */
893
+ insertMany(dtos) {
894
+ return this.http.post(`${this.baseUrl}/insert-many`, dtos, this.getHttpOptions('insert-many'));
895
+ }
896
+ /**
897
+ * Find single item by ID (Observable)
898
+ * POST /{resource}/get/:id
899
+ */
900
+ findById(id, select) {
901
+ return this.http.post(`${this.baseUrl}/get/${id}`, { select }, this.getHttpOptions(`get/${id}`));
902
+ }
903
+ /**
904
+ * Get all items with pagination (Observable)
905
+ * POST /{resource}/get-all?q=search
906
+ */
907
+ getAll(search, filter) {
908
+ let params = new HttpParams();
909
+ if (search) {
910
+ params = params.append('q', search);
911
+ }
912
+ return this.http.post(`${this.baseUrl}/get-all`, filter, this.getHttpOptions('get-all', params));
913
+ }
914
+ /**
915
+ * Update single item (Observable)
916
+ * POST /{resource}/update
917
+ */
918
+ update(dto) {
919
+ return this.http.post(`${this.baseUrl}/update`, dto, this.getHttpOptions('update'));
920
+ }
921
+ /**
922
+ * Update multiple items (Observable)
923
+ * POST /{resource}/update-many
924
+ */
925
+ updateMany(dtos) {
926
+ return this.http.post(`${this.baseUrl}/update-many`, dtos, this.getHttpOptions('update-many'));
927
+ }
928
+ /**
929
+ * Delete items (Observable)
930
+ * POST /{resource}/delete
931
+ * @param deleteDto - { id: string | string[], type: 'delete' | 'restore' | 'permanent' }
932
+ */
933
+ delete(deleteDto) {
934
+ return this.http.post(`${this.baseUrl}/delete`, deleteDto, this.getHttpOptions('delete'));
935
+ }
936
+ // ==========================================================================
937
+ // Promise-based API Methods (Async)
938
+ // ==========================================================================
939
+ /**
940
+ * Fetch paginated list (async)
941
+ */
942
+ async fetchAllAsync(search, filter) {
943
+ return firstValueFrom(this.getAll(search, filter));
944
+ }
945
+ /**
946
+ * Find single item by ID (async)
947
+ */
948
+ async findByIdAsync(id, select) {
949
+ return firstValueFrom(this.findById(id, select));
950
+ }
951
+ /**
952
+ * Insert single item (async)
953
+ */
954
+ async insertAsync(dto) {
955
+ return firstValueFrom(this.insert(dto));
956
+ }
957
+ /**
958
+ * Insert multiple items (async)
959
+ */
960
+ async insertManyAsync(dtos) {
961
+ return firstValueFrom(this.insertMany(dtos));
962
+ }
963
+ /**
964
+ * Update single item (async)
965
+ */
966
+ async updateAsync(dto) {
967
+ return firstValueFrom(this.update(dto));
968
+ }
969
+ /**
970
+ * Update multiple items (async)
971
+ */
972
+ async updateManyAsync(dtos) {
973
+ return firstValueFrom(this.updateMany(dtos));
974
+ }
975
+ /**
976
+ * Delete items (async)
977
+ */
978
+ async deleteAsync(deleteDto) {
979
+ return firstValueFrom(this.delete(deleteDto));
980
+ }
981
+ }
982
+
983
+ class IconComponent {
984
+ icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : []));
985
+ iconType = input(IconTypeEnum.PRIMENG_ICON, ...(ngDevMode ? [{ debugName: "iconType" }] : []));
986
+ IconTypeEnum = IconTypeEnum; // Needed for template reference
987
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
988
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: IconComponent, isStandalone: true, selector: "lib-icon", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
989
+ @if(icon()){ @if(iconType()==IconTypeEnum.PRIMENG_ICON){
990
+ <i [ngClass]="icon()"></i>
991
+ }@else if(iconType()==IconTypeEnum.IMAGE_FILE_LINK){
992
+ <img [alt]="icon()" [src]="icon()" />
993
+ }@else if(iconType()==IconTypeEnum.DIRECT_TAG_SVG){
994
+ {{ icon() }}
995
+ }@else{ I } }
996
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }] });
997
+ }
998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: IconComponent, decorators: [{
999
+ type: Component,
1000
+ args: [{
1001
+ selector: 'lib-icon',
1002
+ imports: [AngularModule],
1003
+ template: `
1004
+ @if(icon()){ @if(iconType()==IconTypeEnum.PRIMENG_ICON){
1005
+ <i [ngClass]="icon()"></i>
1006
+ }@else if(iconType()==IconTypeEnum.IMAGE_FILE_LINK){
1007
+ <img [alt]="icon()" [src]="icon()" />
1008
+ }@else if(iconType()==IconTypeEnum.DIRECT_TAG_SVG){
1009
+ {{ icon() }}
1010
+ }@else{ I } }
1011
+ `,
1012
+ }]
1013
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }] } });
1014
+
1015
+ /**
1016
+ * Base class for form controls that support ALL Angular form patterns:
1017
+ *
1018
+ * 1. **Template-driven forms:** `[(value)]="mySignal"` or `[(ngModel)]="myValue"`
1019
+ * 2. **Reactive forms:** `[formControl]="ctrl"` or `formControlName="field"`
1020
+ * 3. **Signal forms (Angular 21+):** `[formField]="formTree.field"`
1021
+ *
1022
+ * Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
1023
+ *
1024
+ * @example
1025
+ * ```typescript
1026
+ * @Component({
1027
+ * providers: [provideValueAccessor(MySelectComponent)]
1028
+ * })
1029
+ * export class MySelectComponent extends BaseFormControl<string | null> {
1030
+ * override readonly value = model<string | null>(null);
1031
+ *
1032
+ * constructor() {
1033
+ * super();
1034
+ * this.initializeFormControl();
1035
+ * }
1036
+ * }
1037
+ *
1038
+ * // Usage:
1039
+ * // Template-driven: <my-select [(value)]="selectedId" />
1040
+ * // Reactive forms: <my-select [formControl]="myControl" />
1041
+ * // Signal forms: <my-select [formField]="formTree.myField" />
1042
+ * ```
1043
+ */
1044
+ class BaseFormControl {
1045
+ injector = inject(Injector);
1046
+ /**
1047
+ * Disabled state model - bound to form control disabled state.
1048
+ * Using model() to satisfy FormValueControl interface requirements.
1049
+ */
1050
+ disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1051
+ /**
1052
+ * Touched state model - for validation styling.
1053
+ * Using model() to satisfy FormValueControl interface requirements.
1054
+ */
1055
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : []));
1056
+ onChange = () => { };
1057
+ onTouched = () => { };
1058
+ isWritingValue = false;
1059
+ /**
1060
+ * Initialize the form control synchronization for ControlValueAccessor.
1061
+ * Must be called in the subclass constructor after super().
1062
+ *
1063
+ * Note: For signal forms ([formField]), this is not needed as
1064
+ * the FormField directive binds directly to the value model.
1065
+ */
1066
+ initializeFormControl() {
1067
+ runInInjectionContext(this.injector, () => {
1068
+ effect(() => {
1069
+ const currentValue = this.value();
1070
+ if (!this.isWritingValue) {
1071
+ untracked(() => this.onChange(currentValue));
1072
+ }
1073
+ });
1074
+ });
1075
+ }
1076
+ // ============================================
1077
+ // ControlValueAccessor Implementation
1078
+ // (For reactive forms: formControl, formControlName)
1079
+ // ============================================
1080
+ /** ControlValueAccessor: Write value from form control to signal */
1081
+ writeValue(value) {
1082
+ this.isWritingValue = true;
1083
+ this.value.set(value);
1084
+ this.isWritingValue = false;
1085
+ }
1086
+ /** ControlValueAccessor: Register change callback */
1087
+ registerOnChange(fn) {
1088
+ this.onChange = fn;
1089
+ }
1090
+ /** ControlValueAccessor: Register touched callback */
1091
+ registerOnTouched(fn) {
1092
+ this.onTouched = fn;
1093
+ }
1094
+ /** ControlValueAccessor: Set disabled state from form control */
1095
+ setDisabledState(isDisabled) {
1096
+ this.disabled.set(isDisabled);
1097
+ }
1098
+ // ============================================
1099
+ // FormValueControl Implementation
1100
+ // (For signal forms: [formField])
1101
+ // ============================================
1102
+ // The FormValueControl interface only requires:
1103
+ // - value: ModelSignal<T> (already declared as abstract above)
1104
+ // The FormField directive automatically binds to the value model.
1105
+ /** Mark the control as touched (call on blur or panel close) */
1106
+ markAsTouched() {
1107
+ if (!this.touched()) {
1108
+ this.touched.set(true);
1109
+ this.onTouched();
1110
+ }
1111
+ }
1112
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BaseFormControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1113
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: BaseFormControl, isStandalone: true, inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", touched: "touchedChange" }, ngImport: i0 });
1114
+ }
1115
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BaseFormControl, decorators: [{
1116
+ type: Directive
1117
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }] } });
1118
+ /**
1119
+ * Factory function to provide NG_VALUE_ACCESSOR for a component.
1120
+ * Use in component providers array.
1121
+ *
1122
+ * @example
1123
+ * ```typescript
1124
+ * @Component({
1125
+ * providers: [provideValueAccessor(LazySelectComponent)]
1126
+ * })
1127
+ * export class LazySelectComponent extends BaseFormControl<string | null> {}
1128
+ * ```
1129
+ */
1130
+ function provideValueAccessor(component) {
1131
+ return {
1132
+ provide: NG_VALUE_ACCESSOR,
1133
+ useExisting: forwardRef(() => component),
1134
+ multi: true
1135
+ };
1136
+ }
1137
+
1138
+ /**
1139
+ * Lazy-loading multi-select component with search, pagination, and select-all.
1140
+ *
1141
+ * Supports ALL Angular form patterns:
1142
+ * - Template-driven: `[(value)]="selectedIds"`
1143
+ * - Reactive forms: `[formControl]="ctrl"` or `formControlName="field"`
1144
+ * - Signal forms: `[formField]="formTree.field"`
1145
+ */
1146
+ class LazyMultiSelectComponent extends BaseFormControl {
1147
+ // Inputs
1148
+ placeHolder = input('Select Options', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
1149
+ isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
1150
+ isLoading = input.required(...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1151
+ total = input.required(...(ngDevMode ? [{ debugName: "total" }] : []));
1152
+ pagination = input.required(...(ngDevMode ? [{ debugName: "pagination" }] : []));
1153
+ selectDataList = input.required(...(ngDevMode ? [{ debugName: "selectDataList" }] : []));
1154
+ /** Two-way bound value using model() for signal forms compatibility */
1155
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
1156
+ // Outputs
1157
+ onSearch = output();
1158
+ onPagination = output();
1159
+ // UI signals
1160
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
1161
+ /** Computed: Display text for selected values (replaces getSelectedValue method) */
1162
+ selectedValueDisplay = computed(() => {
1163
+ const selectedValues = this.value() ?? [];
1164
+ if (selectedValues.length === 0)
1165
+ return '';
1166
+ if (selectedValues.length > 3) {
1167
+ return `${selectedValues.length} Items Selected`;
1168
+ }
1169
+ return this.selectDataList()
1170
+ .filter(item => selectedValues.includes(item.value))
1171
+ .map(item => item.label)
1172
+ .join(', ');
1173
+ }, ...(ngDevMode ? [{ debugName: "selectedValueDisplay" }] : []));
1174
+ /** Computed: Whether all items are selected (replaces isSelectAll signal) */
1175
+ isSelectAll = computed(() => {
1176
+ const selectedValues = this.value() ?? [];
1177
+ const allValues = this.selectDataList().map(item => item.value);
1178
+ if (selectedValues.length === 0 || allValues.length === 0)
1179
+ return false;
1180
+ return allValues.every(val => selectedValues.includes(val));
1181
+ }, ...(ngDevMode ? [{ debugName: "isSelectAll" }] : []));
1182
+ constructor() {
1183
+ super();
1184
+ this.initializeFormControl();
1185
+ // Search debounce effect
1186
+ runInInjectionContext(this.injector, () => {
1187
+ toSignal(toObservable(this.searchTerm).pipe(skip(1), debounceTime(500), distinctUntilChanged(), tap$1((value) => {
1188
+ this.onSearch.emit(value);
1189
+ })), { initialValue: this.searchTerm() });
1190
+ });
1191
+ }
1192
+ // Signal to toggle panel
1193
+ scrollTargetEl = null;
1194
+ onScrollBound = this.onScroll.bind(this);
1195
+ multiScrollContainer = viewChild.required('multiScrollContainer');
1196
+ onScroll(event) {
1197
+ const el = event.target;
1198
+ const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
1199
+ if (nearBottom && !this.isLoading()) {
1200
+ const pagination = this.pagination();
1201
+ const nextPage = pagination.currentPage + 1;
1202
+ const hasMore = nextPage * pagination.pageSize < (this.total() ?? 0);
1203
+ if (hasMore) {
1204
+ this.onPagination.emit({ ...pagination, currentPage: nextPage });
1205
+ }
1206
+ }
1207
+ }
1208
+ pSelectRef = viewChild.required('pSelect');
1209
+ openOptions = signal(false, ...(ngDevMode ? [{ debugName: "openOptions" }] : []));
1210
+ onSelectClick(event) {
1211
+ if (this.disabled())
1212
+ return;
1213
+ this.pSelectRef()?.nativeElement.classList.add('p-focus');
1214
+ this.openOptions.update((isOpen) => !isOpen);
1215
+ }
1216
+ onOverlayClick(event) {
1217
+ event.stopPropagation();
1218
+ }
1219
+ handleDocumentClick(event) {
1220
+ const clickedInside = this.pSelectRef()?.nativeElement.contains(event.target);
1221
+ if (!clickedInside) {
1222
+ this.openOptions.set(false);
1223
+ this.pSelectRef()?.nativeElement.classList.remove('p-focus');
1224
+ this.markAsTouched();
1225
+ }
1226
+ }
1227
+ isSelected(data) {
1228
+ return this.value()?.includes(data.value);
1229
+ }
1230
+ key(option) {
1231
+ return option.value;
1232
+ }
1233
+ selectValue(event, option) {
1234
+ const previousValue = this.value() ?? [];
1235
+ if (event.checked) {
1236
+ if (!previousValue.includes(option.value)) {
1237
+ this.value.set([...previousValue, option.value]);
1238
+ }
1239
+ }
1240
+ else {
1241
+ const updated = previousValue.filter((v) => v !== option.value);
1242
+ this.value.set(updated);
1243
+ }
1244
+ }
1245
+ changeSelectAll(event) {
1246
+ if (event.checked) {
1247
+ this.value.set(this.selectDataList().map((item) => item.value));
1248
+ }
1249
+ else {
1250
+ this.value.set([]);
1251
+ }
1252
+ }
1253
+ clear(event) {
1254
+ event.stopPropagation();
1255
+ this.value.set([]);
1256
+ }
1257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: LazyMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: LazyMultiSelectComponent, isStandalone: true, selector: "lib-lazy-multi-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, host: { listeners: { "document:click": "handleDocumentClick($event)" } }, providers: [provideValueAccessor(LazyMultiSelectComponent)], viewQueries: [{ propertyName: "multiScrollContainer", first: true, predicate: ["multiScrollContainer"], descendants: true, isSignal: true }, { propertyName: "pSelectRef", first: true, predicate: ["pSelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\"\n [class.p-disabled]=\"disabled()\">\n @if(selectedValueDisplay()){\n <span class=\"p-select-label\">{{selectedValueDisplay()}}</span>\n }@else {\n <span class=\"p-select-label p-placeholder\">{{placeHolder()}}</span>\n }\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\"><i class=\"pi pi-times\"></i></span>\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\n data-p-icon=\"chevron-down\" class=\"p-multiselect-dropdown-icon p-icon ng-star-inserted\"\n data-pc-section=\"triggericon\" aria-hidden=\"true\" pc75=\"\">\n <path\n d=\"M7.01744 10.398C6.91269 10.3985 6.8089 10.378 6.71215 10.3379C6.61541 10.2977 6.52766 10.2386 6.45405 10.1641L1.13907 4.84913C1.03306 4.69404 0.985221 4.5065 1.00399 4.31958C1.02276 4.13266 1.10693 3.95838 1.24166 3.82747C1.37639 3.69655 1.55301 3.61742 1.74039 3.60402C1.92777 3.59062 2.11386 3.64382 2.26584 3.75424L7.01744 8.47394L11.769 3.75424C11.9189 3.65709 12.097 3.61306 12.2748 3.62921C12.4527 3.64535 12.6199 3.72073 12.7498 3.84328C12.8797 3.96582 12.9647 4.12842 12.9912 4.30502C13.0177 4.48162 12.9841 4.662 12.8958 4.81724L7.58083 10.1322C7.50996 10.2125 7.42344 10.2775 7.32656 10.3232C7.22968 10.3689 7.12449 10.3944 7.01744 10.398Z\"\n fill=\"currentColor\"></path>\n </svg>\n </span>\n </div>\n @if(openOptions()){\n <div class=\"p-select-overlay\" (click)=\"onOverlayClick($event)\">\n <div class=\"p-select-header flex flex-row gap-2 items-center\">\n <p-checkbox binary=\"true\" (onChange)=\"changeSelectAll($event)\" [ngModel]=\"isSelectAll()\" [disabled]=\"disabled()\"/>\n <input type=\"text\" pInputText class=\"w-full\" [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\" [ngModelOptions]=\"{ standalone: true }\"\n placeholder=\"Search...\" />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track key(data); let i = $index) {\n <li class=\"p-select-option flex flex-row gap-2 items-center\"\n [ngClass]=\"{ 'p-select-option-selected': isSelected(data) }\">\n <p-checkbox binary=\"true\" (onChange)=\"selectValue($event,data)\" [ngModel]=\"isSelected(data)\" [disabled]=\"disabled()\" />\n <span>{{data.label}}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>", styles: [".p-select-overlay{top:33px;z-index:1004;transform-origin:center top;margin-top:2px}.p-select-option:hover{background:var(--p-select-option-focus-background);color:var(--p-select-option-focus-color)}.p-select-list-container{max-height:200px}.p-select-option.p-select-option-selected{background:var(--p-select-option-selected-background);color:var(--p-select-option-selected-color)}.p-select-option.p-select-option-selected:hover{background:var(--p-select-option-selected-focus-background);color:var(--p-select-option-selected-focus-color)}\n"], dependencies: [{ kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i1$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i2.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1259
+ }
1260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: LazyMultiSelectComponent, decorators: [{
1261
+ type: Component,
1262
+ args: [{ selector: 'lib-lazy-multi-select', imports: [PrimeModule, AngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazyMultiSelectComponent)], template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\"\n [class.p-disabled]=\"disabled()\">\n @if(selectedValueDisplay()){\n <span class=\"p-select-label\">{{selectedValueDisplay()}}</span>\n }@else {\n <span class=\"p-select-label p-placeholder\">{{placeHolder()}}</span>\n }\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\"><i class=\"pi pi-times\"></i></span>\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\n data-p-icon=\"chevron-down\" class=\"p-multiselect-dropdown-icon p-icon ng-star-inserted\"\n data-pc-section=\"triggericon\" aria-hidden=\"true\" pc75=\"\">\n <path\n d=\"M7.01744 10.398C6.91269 10.3985 6.8089 10.378 6.71215 10.3379C6.61541 10.2977 6.52766 10.2386 6.45405 10.1641L1.13907 4.84913C1.03306 4.69404 0.985221 4.5065 1.00399 4.31958C1.02276 4.13266 1.10693 3.95838 1.24166 3.82747C1.37639 3.69655 1.55301 3.61742 1.74039 3.60402C1.92777 3.59062 2.11386 3.64382 2.26584 3.75424L7.01744 8.47394L11.769 3.75424C11.9189 3.65709 12.097 3.61306 12.2748 3.62921C12.4527 3.64535 12.6199 3.72073 12.7498 3.84328C12.8797 3.96582 12.9647 4.12842 12.9912 4.30502C13.0177 4.48162 12.9841 4.662 12.8958 4.81724L7.58083 10.1322C7.50996 10.2125 7.42344 10.2775 7.32656 10.3232C7.22968 10.3689 7.12449 10.3944 7.01744 10.398Z\"\n fill=\"currentColor\"></path>\n </svg>\n </span>\n </div>\n @if(openOptions()){\n <div class=\"p-select-overlay\" (click)=\"onOverlayClick($event)\">\n <div class=\"p-select-header flex flex-row gap-2 items-center\">\n <p-checkbox binary=\"true\" (onChange)=\"changeSelectAll($event)\" [ngModel]=\"isSelectAll()\" [disabled]=\"disabled()\"/>\n <input type=\"text\" pInputText class=\"w-full\" [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\" [ngModelOptions]=\"{ standalone: true }\"\n placeholder=\"Search...\" />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track key(data); let i = $index) {\n <li class=\"p-select-option flex flex-row gap-2 items-center\"\n [ngClass]=\"{ 'p-select-option-selected': isSelected(data) }\">\n <p-checkbox binary=\"true\" (onChange)=\"selectValue($event,data)\" [ngModel]=\"isSelected(data)\" [disabled]=\"disabled()\" />\n <span>{{data.label}}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>", styles: [".p-select-overlay{top:33px;z-index:1004;transform-origin:center top;margin-top:2px}.p-select-option:hover{background:var(--p-select-option-focus-background);color:var(--p-select-option-focus-color)}.p-select-list-container{max-height:200px}.p-select-option.p-select-option-selected{background:var(--p-select-option-selected-background);color:var(--p-select-option-selected-color)}.p-select-option.p-select-option-selected:hover{background:var(--p-select-option-selected-focus-background);color:var(--p-select-option-selected-focus-color)}\n"] }]
1263
+ }], ctorParameters: () => [], propDecorators: { placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }], multiScrollContainer: [{ type: i0.ViewChild, args: ['multiScrollContainer', { isSignal: true }] }], pSelectRef: [{ type: i0.ViewChild, args: ['pSelect', { isSignal: true }] }], handleDocumentClick: [{
1264
+ type: HostListener,
1265
+ args: ['document:click', ['$event']]
1266
+ }] } });
1267
+
1268
+ /**
1269
+ * Lazy-loading single select component with search and pagination.
1270
+ *
1271
+ * Supports ALL Angular form patterns:
1272
+ * - Template-driven: `[(value)]="selectedId"`
1273
+ * - Reactive forms: `[formControl]="ctrl"` or `formControlName="field"`
1274
+ * - Signal forms: `[formField]="formTree.field"`
1275
+ */
1276
+ class LazySelectComponent extends BaseFormControl {
1277
+ // Inputs
1278
+ placeHolder = input('Select Option', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
1279
+ optionLabel = input.required(...(ngDevMode ? [{ debugName: "optionLabel" }] : []));
1280
+ optionValue = input.required(...(ngDevMode ? [{ debugName: "optionValue" }] : []));
1281
+ isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
1282
+ isLoading = input.required(...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1283
+ total = input.required(...(ngDevMode ? [{ debugName: "total" }] : []));
1284
+ pagination = input.required(...(ngDevMode ? [{ debugName: "pagination" }] : []));
1285
+ selectDataList = input.required(...(ngDevMode ? [{ debugName: "selectDataList" }] : []));
1286
+ /** Two-way bound value using model() for signal forms compatibility */
1287
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
1288
+ // Outputs
1289
+ onSearch = output();
1290
+ onPagination = output();
1291
+ // UI signals
1292
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
1293
+ // Effect hooks
1294
+ constructor() {
1295
+ super();
1296
+ this.initializeFormControl();
1297
+ runInInjectionContext(this.injector, () => {
1298
+ toSignal(toObservable(this.searchTerm).pipe(skip(1), debounceTime(500), distinctUntilChanged(), tap$1(value => {
1299
+ this.onSearch.emit(value);
1300
+ })), { initialValue: this.searchTerm() });
1301
+ });
1302
+ }
1303
+ // Signal to toggle panel
1304
+ isPanelShow = signal(false, ...(ngDevMode ? [{ debugName: "isPanelShow" }] : []));
1305
+ scrollTargetEl = null;
1306
+ onScrollBound = this.onScroll.bind(this);
1307
+ scrollContainer = viewChild.required('scrollContainer');
1308
+ onScroll(event) {
1309
+ const el = event.target;
1310
+ const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
1311
+ if (nearBottom && !this.isLoading()) {
1312
+ const pagination = this.pagination();
1313
+ const nextPage = pagination.currentPage + 1;
1314
+ const hasMore = nextPage * pagination.pageSize < (this.total() ?? 0);
1315
+ if (hasMore) {
1316
+ this.onPagination.emit({ ...pagination, currentPage: nextPage });
1317
+ }
1318
+ }
1319
+ }
1320
+ // Toggle panel and manage scroll event
1321
+ showPanel() {
1322
+ if (this.disabled())
1323
+ return;
1324
+ this.isPanelShow.update(prev => !prev);
1325
+ const isNowVisible = this.isPanelShow();
1326
+ if (isNowVisible) {
1327
+ setTimeout(() => {
1328
+ const containerEl = this.scrollContainer().nativeElement;
1329
+ const target = containerEl.querySelector('.p-select-list-container');
1330
+ if (target) {
1331
+ target.addEventListener('scroll', this.onScrollBound);
1332
+ this.scrollTargetEl = target;
1333
+ }
1334
+ else {
1335
+ console.warn('.p-select-list-container not found after panel show');
1336
+ }
1337
+ }, 0);
1338
+ }
1339
+ else {
1340
+ if (this.scrollTargetEl) {
1341
+ this.scrollTargetEl.removeEventListener('scroll', this.onScrollBound);
1342
+ this.scrollTargetEl = null;
1343
+ }
1344
+ }
1345
+ }
1346
+ onBlur() {
1347
+ this.markAsTouched();
1348
+ }
1349
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: LazySelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1350
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: LazySelectComponent, isStandalone: true, selector: "lib-lazy-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: true, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: true, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazySelectComponent)], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n [options]=\"selectDataList()\"\n [(ngModel)]=\"value\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"placeHolder()\"\n [disabled]=\"disabled()\"\n class=\"w-full\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\">\n <ng-template let-filter #filter>\n <input\n pInputText\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n [ngModelOptions]=\"{standalone:true}\"\n class=\"w-full\" />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template let-item #item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i1$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: EditModeElementChangerDirective, selector: "[appEditModeElementChanger]", inputs: ["isEditMode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1351
+ }
1352
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: LazySelectComponent, decorators: [{
1353
+ type: Component,
1354
+ args: [{ selector: 'lib-lazy-select', imports: [
1355
+ AngularModule,
1356
+ PrimeModule,
1357
+ EditModeElementChangerDirective
1358
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazySelectComponent)], template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n [options]=\"selectDataList()\"\n [(ngModel)]=\"value\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"placeHolder()\"\n [disabled]=\"disabled()\"\n class=\"w-full\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\">\n <ng-template let-filter #filter>\n <input\n pInputText\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n [ngModelOptions]=\"{standalone:true}\"\n class=\"w-full\" />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template let-item #item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n" }]
1359
+ }], ctorParameters: () => [], propDecorators: { placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: true }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: true }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
1360
+
1361
+ /**
1362
+ * Injection Tokens for Provider Interfaces
1363
+ *
1364
+ * These tokens enable dependency injection of provider implementations
1365
+ * without creating direct package dependencies.
1366
+ *
1367
+ * Pattern:
1368
+ * 1. ng-shared defines interfaces and tokens (no implementation)
1369
+ * 2. ng-auth provides implementations
1370
+ * 3. ng-iam and ng-storage inject via tokens
1371
+ * 4. app.config.ts wires everything together
1372
+ *
1373
+ * @example
1374
+ * // In IAM component
1375
+ * private readonly userProvider = inject(USER_PROVIDER);
1376
+ *
1377
+ * // In app.config.ts
1378
+ * providers: [
1379
+ * { provide: USER_PROVIDER, useClass: AuthUserProvider },
1380
+ * ]
1381
+ */
1382
+ /**
1383
+ * User Provider Token
1384
+ *
1385
+ * Provides user data access for IAM user selection.
1386
+ */
1387
+ const USER_PROVIDER = new InjectionToken('USER_PROVIDER', {
1388
+ providedIn: 'root',
1389
+ factory: () => {
1390
+ throw new Error('USER_PROVIDER not configured. Please provide an implementation in app.config.ts');
1391
+ },
1392
+ });
1393
+ /**
1394
+ * Company API Provider Token
1395
+ *
1396
+ * Provides company data access for IAM company selection.
1397
+ */
1398
+ const COMPANY_API_PROVIDER = new InjectionToken('COMPANY_API_PROVIDER', {
1399
+ providedIn: 'root',
1400
+ factory: () => {
1401
+ throw new Error('COMPANY_API_PROVIDER not configured. Please provide an implementation in app.config.ts');
1402
+ },
1403
+ });
1404
+ /**
1405
+ * User Permission Provider Token
1406
+ *
1407
+ * Provides user permission assignment operations for IAM.
1408
+ */
1409
+ const USER_PERMISSION_PROVIDER = new InjectionToken('USER_PERMISSION_PROVIDER', {
1410
+ providedIn: 'root',
1411
+ factory: () => {
1412
+ throw new Error('USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts');
1413
+ },
1414
+ });
1415
+
1416
+ /**
1417
+ * Permission Guard
1418
+ *
1419
+ * Route-level guard for permission-based access control.
1420
+ * Validates permissions before allowing navigation.
1421
+ *
1422
+ * Features:
1423
+ * - Single permission check
1424
+ * - Complex ILogicNode logic trees
1425
+ * - Configurable redirect URL
1426
+ * - Debug logging for denied access
1427
+ *
1428
+ * @example
1429
+ * ```typescript
1430
+ * // Simple permission check
1431
+ * { path: 'users', canActivate: [permissionGuard('user.view')] }
1432
+ *
1433
+ * // Complex logic
1434
+ * { path: 'admin', canActivate: [permissionGuard({
1435
+ * id: 'root',
1436
+ * type: 'group',
1437
+ * operator: 'AND',
1438
+ * children: [
1439
+ * { id: '1', type: 'action', actionId: 'admin.view' },
1440
+ * { id: '2', type: 'action', actionId: 'admin.manage' }
1441
+ * ]
1442
+ * })] }
1443
+ *
1444
+ * // With custom redirect
1445
+ * { path: 'users', canActivate: [permissionGuard('user.view', '/access-denied')] }
1446
+ * ```
1447
+ */
1448
+ function permissionGuard(permission, redirectTo = '/') {
1449
+ return () => {
1450
+ const permissionValidator = inject(PermissionValidatorService);
1451
+ const router = inject(Router);
1452
+ // Check if permissions are loaded
1453
+ if (!permissionValidator.isPermissionsLoaded()) {
1454
+ console.warn('[permissionGuard] Permissions not loaded, denying access to route');
1455
+ return router.createUrlTree([redirectTo]);
1456
+ }
1457
+ const userPermissions = permissionValidator.permissions();
1458
+ const hasPermission = evaluatePermission(permission, userPermissions);
1459
+ if (!hasPermission) {
1460
+ const permissionCode = typeof permission === 'string' ? permission : 'complex-logic';
1461
+ console.warn(`[permissionGuard] Access denied - missing permission: ${permissionCode}`);
1462
+ return router.createUrlTree([redirectTo]);
1463
+ }
1464
+ return true;
1465
+ };
1466
+ }
1467
+ /**
1468
+ * Any Permission Guard (OR logic)
1469
+ *
1470
+ * Allows access if user has ANY of the specified permissions.
1471
+ *
1472
+ * @example
1473
+ * ```typescript
1474
+ * // Allow if user has view OR create permission
1475
+ * { path: 'users', canActivate: [anyPermissionGuard(['user.view', 'user.create'])] }
1476
+ * ```
1477
+ */
1478
+ function anyPermissionGuard(permissions, redirectTo = '/') {
1479
+ return () => {
1480
+ const permissionValidator = inject(PermissionValidatorService);
1481
+ const router = inject(Router);
1482
+ // Validate permissions array
1483
+ if (!permissions || permissions.length === 0) {
1484
+ console.warn('[anyPermissionGuard] Empty permissions array provided, denying access');
1485
+ return router.createUrlTree([redirectTo]);
1486
+ }
1487
+ // Check if permissions are loaded
1488
+ if (!permissionValidator.isPermissionsLoaded()) {
1489
+ console.warn('[anyPermissionGuard] Permissions not loaded, denying access to route');
1490
+ return router.createUrlTree([redirectTo]);
1491
+ }
1492
+ const userPermissions = permissionValidator.permissions();
1493
+ const hasPermission = hasAnyPermission(permissions, userPermissions);
1494
+ if (!hasPermission) {
1495
+ console.warn(`[anyPermissionGuard] Access denied - missing any of: ${permissions.join(', ')}`);
1496
+ return router.createUrlTree([redirectTo]);
1497
+ }
1498
+ return true;
1499
+ };
1500
+ }
1501
+ /**
1502
+ * All Permissions Guard (AND logic)
1503
+ *
1504
+ * Allows access only if user has ALL of the specified permissions.
1505
+ *
1506
+ * @example
1507
+ * ```typescript
1508
+ * // Allow only if user has BOTH view AND create permissions
1509
+ * { path: 'admin', canActivate: [allPermissionsGuard(['admin.view', 'admin.manage'])] }
1510
+ * ```
1511
+ */
1512
+ function allPermissionsGuard(permissions, redirectTo = '/') {
1513
+ return () => {
1514
+ const permissionValidator = inject(PermissionValidatorService);
1515
+ const router = inject(Router);
1516
+ // Validate permissions array
1517
+ if (!permissions || permissions.length === 0) {
1518
+ console.warn('[allPermissionsGuard] Empty permissions array provided, denying access');
1519
+ return router.createUrlTree([redirectTo]);
1520
+ }
1521
+ // Check if permissions are loaded
1522
+ if (!permissionValidator.isPermissionsLoaded()) {
1523
+ console.warn('[allPermissionsGuard] Permissions not loaded, denying access to route');
1524
+ return router.createUrlTree([redirectTo]);
1525
+ }
1526
+ const userPermissions = permissionValidator.permissions();
1527
+ const hasPermission = hasAllPermissions(permissions, userPermissions);
1528
+ if (!hasPermission) {
1529
+ console.warn(`[allPermissionsGuard] Access denied - missing all of: ${permissions.join(', ')}`);
1530
+ return router.createUrlTree([redirectTo]);
1531
+ }
1532
+ return true;
1533
+ };
1534
+ }
1535
+
1536
+ // Interfaces
1537
+
1538
+ /**
1539
+ * Generated bundle index. Do not edit.
1540
+ */
1541
+
1542
+ export { AngularModule, ApiResourceService, ApiResourceService as ApiService, COMPANY_API_PROVIDER, ContactTypeEnum, CookieService, EditModeElementChangerDirective, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LazyMultiSelectComponent, LazySelectComponent, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, USER_PERMISSION_PROVIDER, USER_PROVIDER, allPermissionsGuard, anyPermissionGuard, evaluateLogicNode, evaluatePermission, hasAllPermissions, hasAnyPermission, permissionGuard };
1543
+ //# sourceMappingURL=flusys-ng-shared.mjs.map