@flusys/ng-shared 3.0.0 → 4.0.0-lts

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ## Package Information
10
10
 
11
11
  - **Package:** `@flusys/ng-shared`
12
- - **Version:** 3.0.0
12
+ - **Version:** 4.0.0-rc
13
13
  - **Dependencies:** ng-core
14
14
  - **Dependents:** ng-layout, ng-auth, ng-iam, ng-storage, flusysng
15
15
  - **Build Command:** `npm run build:ng-shared`
@@ -38,13 +38,13 @@ interface IBaseEntity {
38
38
 
39
39
  **Entity Mixins:**
40
40
 
41
- | Interface | Fields | Purpose |
42
- | --------------- | ---------------------------------- | ---------------------- |
43
- | `ISoftDeletable`| `deletedAt?: Date \| null` | Soft delete support |
44
- | `ITimestampable`| `createdAt, updatedAt` | Timestamp tracking |
45
- | `IActivatable` | `isActive: boolean` | Active status toggle |
46
- | `IOrderable` | `serial?: number \| null` | Ordering/sorting |
47
- | `IMetadata` | `metadata?: Record<string, unknown>` | JSON metadata field |
41
+ | Interface | Fields | Purpose |
42
+ | ---------------- | ------------------------------------ | -------------------- |
43
+ | `ISoftDeletable` | `deletedAt?: Date \| null` | Soft delete support |
44
+ | `ITimestampable` | `createdAt, updatedAt` | Timestamp tracking |
45
+ | `IActivatable` | `isActive: boolean` | Active status toggle |
46
+ | `IOrderable` | `serial?: number \| null` | Ordering/sorting |
47
+ | `IMetadata` | `metadata?: Record<string, unknown>` | JSON metadata field |
48
48
 
49
49
  #### ILoggedUserInfo
50
50
 
@@ -74,7 +74,9 @@ interface IPagination {
74
74
  currentPage: number;
75
75
  }
76
76
 
77
- interface ISort { [key: string]: 'ASC' | 'DESC' }
77
+ interface ISort {
78
+ [key: string]: "ASC" | "DESC";
79
+ }
78
80
 
79
81
  // Filter supports primitives and arrays for multi-value filtering
80
82
  interface IFilter {
@@ -98,10 +100,10 @@ interface IFilterData {
98
100
  Delete request payload matching backend DeleteDto.
99
101
 
100
102
  ```typescript
101
- type DeleteType = 'delete' | 'restore' | 'permanent';
103
+ type DeleteType = "delete" | "restore" | "permanent";
102
104
 
103
105
  interface IDeleteData {
104
- id: string | string[]; // Single or batch delete
106
+ id: string | string[]; // Single or batch delete
105
107
  type: DeleteType;
106
108
  }
107
109
  ```
@@ -124,7 +126,6 @@ interface IUserSelectFilter {
124
126
  page: number;
125
127
  pageSize: number;
126
128
  search: string;
127
- [key: string]: unknown;
128
129
  }
129
130
 
130
131
  type LoadUsersFn = (filter: IUserSelectFilter) => Observable<IListResponse<IUserBasicInfo>>;
@@ -151,7 +152,7 @@ interface IFileUploadOptions {
151
152
  }
152
153
 
153
154
  interface IUploadedFile {
154
- id?: string; // File manager ID (UUID) - available when registered
155
+ id?: string; // File manager ID (UUID) - available when registered
155
156
  name: string;
156
157
  key: string;
157
158
  size: number;
@@ -164,7 +165,6 @@ interface IFileSelectFilter {
164
165
  search: string;
165
166
  contentTypes?: string[];
166
167
  folderId?: string;
167
- [key: string]: unknown;
168
168
  }
169
169
 
170
170
  type LoadFilesFn = (filter: IFileSelectFilter) => Observable<IListResponse<IFileBasicInfo>>;
@@ -175,18 +175,18 @@ type GetFileUrlsFn = (fileIds: string[]) => Observable<ISingleResponse<IFileBasi
175
175
  **File Type Filters (Constants):**
176
176
 
177
177
  ```typescript
178
- import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from '@flusys/ng-shared';
179
-
180
- FILE_TYPE_FILTERS.IMAGES // ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
181
- FILE_TYPE_FILTERS.DOCUMENTS // ['application/pdf', 'application/msword', ...]
182
- FILE_TYPE_FILTERS.VIDEOS // ['video/mp4', 'video/webm', ...]
183
- FILE_TYPE_FILTERS.AUDIO // ['audio/mpeg', 'audio/wav', ...]
184
- FILE_TYPE_FILTERS.ALL // [] (allows all)
185
-
186
- getAcceptString(['image/*']) // 'image/*'
187
- isFileTypeAllowed(file, ['image/*']) // true/false
188
- getFileIconClass('image/png') // 'pi pi-image'
189
- formatFileSize(1024) // '1 KB'
178
+ import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from "@flusys/ng-shared";
179
+
180
+ FILE_TYPE_FILTERS.IMAGES; // ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
181
+ FILE_TYPE_FILTERS.DOCUMENTS; // ['application/pdf', 'application/msword', ...]
182
+ FILE_TYPE_FILTERS.VIDEOS; // ['video/mp4', 'video/webm', ...]
183
+ FILE_TYPE_FILTERS.AUDIO; // ['audio/mpeg', 'audio/wav', ...]
184
+ FILE_TYPE_FILTERS.ALL; // [] (allows all)
185
+
186
+ getAcceptString(["image/*"]); // 'image/*'
187
+ isFileTypeAllowed(file, ["image/*"]); // true/false
188
+ getFileIconClass("image/png"); // 'pi pi-image'
189
+ formatFileSize(1024); // '1 KB'
190
190
  ```
191
191
 
192
192
  ### Response Interfaces
@@ -242,12 +242,12 @@ type ApiResponse<T> = ISingleResponse<T> | IListResponse<T> | IBulkResponse<T> |
242
242
 
243
243
  **Metadata types:**
244
244
 
245
- | Interface | Fields |
246
- | ------------------ | -------------------------------------------- |
247
- | `IRequestMeta` | `requestId?, timestamp?, responseTime?` |
245
+ | Interface | Fields |
246
+ | ------------------ | ----------------------------------------------------- |
247
+ | `IRequestMeta` | `requestId?, timestamp?, responseTime?` |
248
248
  | `IPaginationMeta` | `total, page, pageSize, count, hasMore?, totalPages?` |
249
- | `IBulkMeta` | `count, failed?, total?` |
250
- | `IValidationError` | `field, message, constraint?` |
249
+ | `IBulkMeta` | `count, failed?, total?` |
250
+ | `IValidationError` | `field, message, constraint?` |
251
251
 
252
252
  ### Auth & Storage Response Types
253
253
 
@@ -292,14 +292,14 @@ Discriminated union for building complex permission logic trees.
292
292
  ```typescript
293
293
  // Single permission check
294
294
  interface IActionNode {
295
- type: 'action';
295
+ type: "action";
296
296
  actionId: string;
297
297
  }
298
298
 
299
299
  // Group with AND/OR logic
300
300
  interface IGroupNode {
301
- type: 'group';
302
- operator: 'AND' | 'OR';
301
+ type: "group";
302
+ operator: "AND" | "OR";
303
303
  children: ILogicNode[];
304
304
  }
305
305
 
@@ -374,25 +374,25 @@ Signal-based CRUD service using Angular 21 `resource()` API with **lazy initiali
374
374
  **ServiceName Type:**
375
375
 
376
376
  ```typescript
377
- type ServiceName = 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email';
377
+ type ServiceName = "auth" | "administration" | "iam" | "storage" | "formBuilder" | "email";
378
378
  ```
379
379
 
380
380
  **Define a service:**
381
381
 
382
382
  ```typescript
383
- import { Injectable } from '@angular/core';
384
- import { HttpClient } from '@angular/common/http';
385
- import { ApiResourceService } from '@flusys/ng-shared';
383
+ import { Injectable } from "@angular/core";
384
+ import { HttpClient } from "@angular/common/http";
385
+ import { ApiResourceService } from "@flusys/ng-shared";
386
386
 
387
- @Injectable({ providedIn: 'root' })
387
+ @Injectable({ providedIn: "root" })
388
388
  export class UserService extends ApiResourceService<UserDto, IUser> {
389
389
  constructor(http: HttpClient) {
390
390
  // Option 1: Use global apiBaseUrl (default)
391
- super('users', http);
391
+ super("users", http);
392
392
  // Base URL: APP_CONFIG.apiBaseUrl + '/users'
393
393
 
394
394
  // Option 2: Use feature-specific service URL (recommended)
395
- super('users', http, 'administration');
395
+ super("users", http, "administration");
396
396
  // Base URL: APP_CONFIG.services.administration.baseUrl + '/users'
397
397
  }
398
398
  }
@@ -400,13 +400,14 @@ export class UserService extends ApiResourceService<UserDto, IUser> {
400
400
 
401
401
  **Constructor Parameters:**
402
402
 
403
- | Parameter | Type | Required | Description |
404
- |-----------|------|----------|-------------|
405
- | `moduleApiName` | `string` | Yes | API path segment (e.g., 'users', 'file-manager') |
406
- | `http` | `HttpClient` | Yes | Angular HttpClient instance |
407
- | `serviceName` | `ServiceName` | No | Feature service name for URL resolution |
403
+ | Parameter | Type | Required | Description |
404
+ | --------------- | ------------- | -------- | ------------------------------------------------ |
405
+ | `moduleApiName` | `string` | Yes | API path segment (e.g., 'users', 'file-manager') |
406
+ | `http` | `HttpClient` | Yes | Angular HttpClient instance |
407
+ | `serviceName` | `ServiceName` | No | Feature service name for URL resolution |
408
408
 
409
409
  **Service URL Resolution:**
410
+
410
411
  - If `serviceName` is provided, uses `getServiceUrl(config, serviceName)` to resolve the base URL
411
412
  - Falls back to `APP_CONFIG.apiBaseUrl` if service not found or `serviceName` not provided
412
413
 
@@ -416,7 +417,7 @@ The list resource is **lazy-initialized** to avoid unnecessary HTTP requests on
416
417
 
417
418
  ```typescript
418
419
  // Resource is NOT created until one of these is called:
419
- userService.fetchList('', { pagination: { currentPage: 0, pageSize: 10 } });
420
+ userService.fetchList("", { pagination: { currentPage: 0, pageSize: 10 } });
420
421
  // OR
421
422
  userService.initListResource(); // Explicit initialization
422
423
  ```
@@ -439,35 +440,35 @@ initListResource(): void {
439
440
 
440
441
  **Endpoint mapping:**
441
442
 
442
- | Method | HTTP | Endpoint | Response |
443
- | -------------- | ---- | ------------------------- | ------------------------ |
444
- | `insert(dto)` | POST | `/{resource}/insert` | `ISingleResponse<T>` |
445
- | `insertMany(dtos)` | POST | `/{resource}/insert-many` | `IBulkResponse<T>` |
446
- | `findById(id, select?)` | POST | `/{resource}/get/:id` | `ISingleResponse<T>` |
447
- | `getAll(search, filter)` | POST | `/{resource}/get-all?q=` | `IListResponse<T>` |
448
- | `update(dto)` | POST | `/{resource}/update` | `ISingleResponse<T>` |
449
- | `updateMany(dtos)` | POST | `/{resource}/update-many` | `IBulkResponse<T>` |
450
- | `delete(deleteDto)` | POST | `/{resource}/delete` | `IMessageResponse` |
443
+ | Method | HTTP | Endpoint | Response |
444
+ | ------------------------ | ---- | ------------------------- | -------------------- |
445
+ | `insert(dto)` | POST | `/{resource}/insert` | `ISingleResponse<T>` |
446
+ | `insertMany(dtos)` | POST | `/{resource}/insert-many` | `IBulkResponse<T>` |
447
+ | `findById(id, select?)` | POST | `/{resource}/get/:id` | `ISingleResponse<T>` |
448
+ | `getAll(search, filter)` | POST | `/{resource}/get-all?q=` | `IListResponse<T>` |
449
+ | `update(dto)` | POST | `/{resource}/update` | `ISingleResponse<T>` |
450
+ | `updateMany(dtos)` | POST | `/{resource}/update-many` | `IBulkResponse<T>` |
451
+ | `delete(deleteDto)` | POST | `/{resource}/delete` | `IMessageResponse` |
451
452
 
452
453
  All methods above return `Observable`. Async (Promise) variants are also available: `insertAsync`, `insertManyAsync`, `findByIdAsync`, `updateAsync`, `updateManyAsync`, `deleteAsync`.
453
454
 
454
455
  **Reactive signals:**
455
456
 
456
- | Signal | Type | Description |
457
- | ------------ | ------------------------- | ------------------------ |
458
- | `isLoading` | `Signal<boolean>` | Whether data is loading |
459
- | `data` | `Signal<T[]>` | Current list data |
460
- | `total` | `Signal<number>` | Total item count |
461
- | `pageInfo` | `Signal<IPaginationMeta>` | Pagination metadata |
462
- | `hasMore` | `Signal<boolean>` | More pages available |
463
- | `searchTerm` | `WritableSignal<string>` | Current search term |
457
+ | Signal | Type | Description |
458
+ | ------------ | ----------------------------- | ----------------------- |
459
+ | `isLoading` | `Signal<boolean>` | Whether data is loading |
460
+ | `data` | `Signal<T[]>` | Current list data |
461
+ | `total` | `Signal<number>` | Total item count |
462
+ | `pageInfo` | `Signal<IPaginationMeta>` | Pagination metadata |
463
+ | `hasMore` | `Signal<boolean>` | More pages available |
464
+ | `searchTerm` | `WritableSignal<string>` | Current search term |
464
465
  | `filterData` | `WritableSignal<IFilterData>` | Filter/pagination state |
465
466
 
466
467
  **List management:**
467
468
 
468
469
  ```typescript
469
470
  // Trigger data fetch (updates searchTerm and filterData signals, resource auto-reloads)
470
- userService.fetchList('search term', { pagination: { currentPage: 0, pageSize: 10 } });
471
+ userService.fetchList("search term", { pagination: { currentPage: 0, pageSize: 10 } });
471
472
 
472
473
  // Pagination helpers
473
474
  userService.setPagination({ currentPage: 1, pageSize: 20 });
@@ -530,14 +531,14 @@ export class ProductComponent {
530
531
 
531
532
  **Methods:**
532
533
 
533
- | Method | Returns | Description |
534
- | ----------------------------- | ----------------------------------- | ---------------------------------- |
535
- | `getFileUrl(fileId)` | `string \| null` | Get cached URL (synchronous) |
536
- | `fileUrlSignal(fileId)` | `Signal<string \| null>` | Computed signal from cache |
537
- | `fetchFileUrls(fileIds[])` | `Observable<FilesResponseDto[]>` | Fetch from backend, updates cache |
538
- | `fetchSingleFileUrl(fileId)` | `Observable<FilesResponseDto \| null>` | Fetch single file |
539
- | `clearCache()` | `void` | Clear all cached URLs |
540
- | `removeFromCache(fileId)` | `void` | Remove specific entry from cache |
534
+ | Method | Returns | Description |
535
+ | ---------------------------- | -------------------------------------- | --------------------------------- |
536
+ | `getFileUrl(fileId)` | `string \| null` | Get cached URL (synchronous) |
537
+ | `fileUrlSignal(fileId)` | `Signal<string \| null>` | Computed signal from cache |
538
+ | `fetchFileUrls(fileIds[])` | `Observable<FilesResponseDto[]>` | Fetch from backend, updates cache |
539
+ | `fetchSingleFileUrl(fileId)` | `Observable<FilesResponseDto \| null>` | Fetch single file |
540
+ | `clearCache()` | `void` | Clear all cached URLs |
541
+ | `removeFromCache(fileId)` | `void` | Remove specific entry from cache |
541
542
 
542
543
  ### PermissionValidatorService
543
544
 
@@ -569,18 +570,18 @@ export class MyComponent {
569
570
 
570
571
  **Methods:**
571
572
 
572
- | Method | Returns | Description |
573
- | ---------------------------- | --------- | ---------------------------------- |
574
- | `setPermissions(codes[])` | `void` | Replace all permissions |
575
- | `clearPermissions()` | `void` | Clear all permissions |
576
- | `hasPermission(code)` | `boolean` | Check single permission (supports wildcards) |
577
- | `isPermissionsLoaded()` | `boolean` | **Deprecated** - Use `isLoaded()` signal |
573
+ | Method | Returns | Description |
574
+ | ------------------------- | --------- | -------------------------------------------- |
575
+ | `setPermissions(codes[])` | `void` | Replace all permissions |
576
+ | `clearPermissions()` | `void` | Clear all permissions |
577
+ | `hasPermission(code)` | `boolean` | Check single permission (supports wildcards) |
578
+ | `isPermissionsLoaded()` | `boolean` | **Deprecated** - Use `isLoaded()` signal |
578
579
 
579
580
  **Signals:**
580
581
 
581
- | Signal | Type | Description |
582
- | ------------- | ------------------ | ------------------------------ |
583
- | `permissions` | `Signal<string[]>` | Readonly current permissions |
582
+ | Signal | Type | Description |
583
+ | ------------- | ------------------ | --------------------------------- |
584
+ | `permissions` | `Signal<string[]>` | Readonly current permissions |
584
585
  | `isLoaded` | `Signal<boolean>` | Whether permissions have been set |
585
586
 
586
587
  **Wildcard Support:**
@@ -589,13 +590,13 @@ The `hasPermission()` method uses the permission evaluator utility which support
589
590
 
590
591
  ```typescript
591
592
  // Exact match
592
- hasPermission('user.read') // true if permissions include 'user.read'
593
+ hasPermission("user.read"); // true if permissions include 'user.read'
593
594
 
594
595
  // Global wildcard
595
- hasPermission('user.read') // true if permissions include '*'
596
+ hasPermission("user.read"); // true if permissions include '*'
596
597
 
597
598
  // Module wildcard
598
- hasPermission('user.read') // true if permissions include 'user.*'
599
+ hasPermission("user.read"); // true if permissions include 'user.*'
599
600
  ```
600
601
 
601
602
  ### CookieService
@@ -603,7 +604,7 @@ hasPermission('user.read') // true if permissions include 'user.*'
603
604
  SSR-aware cookie reading service.
604
605
 
605
606
  ```typescript
606
- import { CookieService } from '@flusys/ng-shared';
607
+ import { CookieService } from "@flusys/ng-shared";
607
608
 
608
609
  const cookies = inject(CookieService).get(); // Returns document.cookie (browser) or request header cookie (server)
609
610
  ```
@@ -613,7 +614,7 @@ const cookies = inject(CookieService).get(); // Returns document.cookie (browser
613
614
  SSR environment detection service.
614
615
 
615
616
  ```typescript
616
- import { PlatformService } from '@flusys/ng-shared';
617
+ import { PlatformService } from "@flusys/ng-shared";
617
618
 
618
619
  const platform = inject(PlatformService);
619
620
  if (!platform.isServer) {
@@ -654,21 +655,7 @@ Single-select dropdown with lazy loading, search, and scroll pagination.
654
655
  ```typescript
655
656
  @Component({
656
657
  imports: [LazySelectComponent],
657
- template: `
658
- <lib-lazy-select
659
- [(value)]="selectedId"
660
- [selectDataList]="items()"
661
- [optionLabel]="'label'"
662
- [optionValue]="'value'"
663
- [isEditMode]="true"
664
- [isLoading]="loading()"
665
- [total]="total()"
666
- [pagination]="pagination()"
667
- [placeHolder]="'Select item'"
668
- (onSearch)="handleSearch($event)"
669
- (onPagination)="handlePagination($event)"
670
- />
671
- `,
658
+ template: ` <lib-lazy-select [(value)]="selectedId" [selectDataList]="items()" [optionLabel]="'label'" [optionValue]="'value'" [isEditMode]="true" [isLoading]="loading()" [total]="total()" [pagination]="pagination()" [placeHolder]="'Select item'" (onSearch)="handleSearch($event)" (onPagination)="handlePagination($event)" /> `,
672
659
  })
673
660
  export class MyComponent {
674
661
  readonly selectedId = signal<string | null>(null);
@@ -681,16 +668,16 @@ export class MyComponent {
681
668
 
682
669
  **Inputs:**
683
670
 
684
- | Input | Type | Description |
685
- | ---------------- | ----------------- | -------------------------------- |
686
- | `selectDataList` | `Array<IDropDown>` | Dropdown options (required) |
687
- | `optionLabel` | `string` | Label field name (required) |
688
- | `optionValue` | `string` | Value field name (required) |
689
- | `isEditMode` | `boolean` | Enable/disable editing (required)|
690
- | `isLoading` | `boolean` | Loading state (required) |
691
- | `total` | `number \| undefined` | Total items for pagination |
692
- | `pagination` | `IPagination` | Current pagination state |
693
- | `placeHolder` | `string` | Placeholder text (default: `'Select Option'`) |
671
+ | Input | Type | Description |
672
+ | ---------------- | --------------------- | --------------------------------------------- |
673
+ | `selectDataList` | `Array<IDropDown>` | Dropdown options (required) |
674
+ | `optionLabel` | `string` | Label field name (required) |
675
+ | `optionValue` | `string` | Value field name (required) |
676
+ | `isEditMode` | `boolean` | Enable/disable editing (required) |
677
+ | `isLoading` | `boolean` | Loading state (required) |
678
+ | `total` | `number \| undefined` | Total items for pagination |
679
+ | `pagination` | `IPagination` | Current pagination state |
680
+ | `placeHolder` | `string` | Placeholder text (default: `'Select Option'`) |
694
681
 
695
682
  **Model:** `value` - Two-way bound selected value (`string | null`)
696
683
 
@@ -707,19 +694,7 @@ Multi-select dropdown with lazy loading, search, select-all, and scroll paginati
707
694
  ```typescript
708
695
  @Component({
709
696
  imports: [LazyMultiSelectComponent],
710
- template: `
711
- <lib-lazy-multi-select
712
- [(value)]="selectedIds"
713
- [selectDataList]="items()"
714
- [isEditMode]="true"
715
- [isLoading]="loading()"
716
- [total]="total()"
717
- [pagination]="pagination()"
718
- [placeHolder]="'Select items'"
719
- (onSearch)="handleSearch($event)"
720
- (onPagination)="handlePagination($event)"
721
- />
722
- `,
697
+ template: ` <lib-lazy-multi-select [(value)]="selectedIds" [selectDataList]="items()" [isEditMode]="true" [isLoading]="loading()" [total]="total()" [pagination]="pagination()" [placeHolder]="'Select items'" (onSearch)="handleSearch($event)" (onPagination)="handlePagination($event)" /> `,
723
698
  })
724
699
  export class MyComponent {
725
700
  readonly selectedIds = signal<string[] | null>(null);
@@ -744,17 +719,10 @@ Single user selection with lazy loading. Uses `USER_PROVIDER` internally or acce
744
719
  imports: [UserSelectComponent],
745
720
  template: `
746
721
  <!-- Simple usage - uses USER_PROVIDER internally -->
747
- <lib-user-select
748
- [(value)]="selectedUserId"
749
- [isEditMode]="true"
750
- />
722
+ <lib-user-select [(value)]="selectedUserId" [isEditMode]="true" />
751
723
 
752
724
  <!-- With custom loadUsers function -->
753
- <lib-user-select
754
- [(value)]="selectedUserId"
755
- [isEditMode]="true"
756
- [loadUsers]="customLoadUsers"
757
- />
725
+ <lib-user-select [(value)]="selectedUserId" [isEditMode]="true" [loadUsers]="customLoadUsers" />
758
726
  `,
759
727
  })
760
728
  export class MyComponent {
@@ -764,14 +732,14 @@ export class MyComponent {
764
732
 
765
733
  **Inputs:**
766
734
 
767
- | Input | Type | Default | Description |
768
- | ----- | ---- | ------- | ----------- |
769
- | `isEditMode` | `boolean` | required | Enable/disable editing |
770
- | `placeHolder` | `string` | `'Select User'` | Placeholder text |
771
- | `filterActive` | `boolean` | `true` | Filter active users only |
772
- | `additionalFilters` | `Record<string, unknown>` | `{}` | Extra filter params |
773
- | `pageSize` | `number` | `20` | Page size for pagination |
774
- | `loadUsers` | `LoadUsersFn` | - | Custom user loading function |
735
+ | Input | Type | Default | Description |
736
+ | ------------------- | ------------------------- | --------------- | ---------------------------- |
737
+ | `isEditMode` | `boolean` | required | Enable/disable editing |
738
+ | `placeHolder` | `string` | `'Select User'` | Placeholder text |
739
+ | `filterActive` | `boolean` | `true` | Filter active users only |
740
+ | `additionalFilters` | `Record<string, unknown>` | `{}` | Extra filter params |
741
+ | `pageSize` | `number` | `20` | Page size for pagination |
742
+ | `loadUsers` | `LoadUsersFn` | - | Custom user loading function |
775
743
 
776
744
  **Model:** `value` - Two-way bound selected user ID (`string | null`)
777
745
 
@@ -787,12 +755,7 @@ Multiple user selection with lazy loading. Uses `USER_PROVIDER` internally or ac
787
755
  ```typescript
788
756
  @Component({
789
757
  imports: [UserMultiSelectComponent],
790
- template: `
791
- <lib-user-multi-select
792
- [(value)]="selectedUserIds"
793
- [isEditMode]="true"
794
- />
795
- `,
758
+ template: ` <lib-user-multi-select [(value)]="selectedUserIds" [isEditMode]="true" /> `,
796
759
  })
797
760
  export class MyComponent {
798
761
  readonly selectedUserIds = signal<string[] | null>(null);
@@ -816,44 +779,32 @@ Drag & drop file upload with type filtering. Pass your own `uploadFile` function
816
779
  imports: [FileUploaderComponent],
817
780
  template: `
818
781
  <!-- Single image upload -->
819
- <lib-file-uploader
820
- [uploadFile]="uploadFile"
821
- [acceptTypes]="['image/*']"
822
- [multiple]="false"
823
- (fileUploaded)="onFileUploaded($event)"
824
- />
782
+ <lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="['image/*']" [multiple]="false" (fileUploaded)="onFileUploaded($event)" />
825
783
 
826
784
  <!-- Multiple document upload -->
827
- <lib-file-uploader
828
- [uploadFile]="uploadFile"
829
- [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
830
- [multiple]="true"
831
- [maxFiles]="5"
832
- (filesUploaded)="onFilesUploaded($event)"
833
- />
785
+ <lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS" [multiple]="true" [maxFiles]="5" (filesUploaded)="onFilesUploaded($event)" />
834
786
  `,
835
787
  })
836
788
  export class MyComponent {
837
789
  readonly uploadService = inject(UploadService);
838
790
 
839
- readonly uploadFile: UploadFileFn = (file, options) =>
840
- this.uploadService.uploadSingleFile(file, options);
791
+ readonly uploadFile: UploadFileFn = (file, options) => this.uploadService.uploadSingleFile(file, options);
841
792
  }
842
793
  ```
843
794
 
844
795
  **Inputs:**
845
796
 
846
- | Input | Type | Default | Description |
847
- | ----- | ---- | ------- | ----------- |
848
- | `uploadFile` | `UploadFileFn` | required | Upload function |
849
- | `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
850
- | `multiple` | `boolean` | `false` | Allow multiple files |
851
- | `maxFiles` | `number` | `10` | Max files for multiple |
852
- | `maxSizeMb` | `number` | `10` | Max file size in MB |
853
- | `uploadOptions` | `IFileUploadOptions` | `{}` | Upload options |
854
- | `disabled` | `boolean` | `false` | Disable uploader |
855
- | `showPreview` | `boolean` | `true` | Show selected files preview |
856
- | `autoUpload` | `boolean` | `true` | Upload immediately on selection |
797
+ | Input | Type | Default | Description |
798
+ | --------------- | -------------------- | -------- | ------------------------------- |
799
+ | `uploadFile` | `UploadFileFn` | required | Upload function |
800
+ | `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
801
+ | `multiple` | `boolean` | `false` | Allow multiple files |
802
+ | `maxFiles` | `number` | `10` | Max files for multiple |
803
+ | `maxSizeMb` | `number` | `10` | Max file size in MB |
804
+ | `uploadOptions` | `IFileUploadOptions` | `{}` | Upload options |
805
+ | `disabled` | `boolean` | `false` | Disable uploader |
806
+ | `showPreview` | `boolean` | `true` | Show selected files preview |
807
+ | `autoUpload` | `boolean` | `true` | Upload immediately on selection |
857
808
 
858
809
  **Outputs:** `fileUploaded` (IUploadedFile), `filesUploaded` (IUploadedFile[]), `onError` (Error), `fileSelected` (File[])
859
810
 
@@ -866,15 +817,7 @@ Dialog to browse and select existing files with filtering.
866
817
  ```typescript
867
818
  @Component({
868
819
  imports: [FileSelectorDialogComponent],
869
- template: `
870
- <lib-file-selector-dialog
871
- [(visible)]="showFileSelector"
872
- [loadFiles]="loadFiles"
873
- [acceptTypes]="['image/*']"
874
- [multiple]="false"
875
- (fileSelected)="onFileSelected($event)"
876
- />
877
- `,
820
+ template: ` <lib-file-selector-dialog [(visible)]="showFileSelector" [loadFiles]="loadFiles" [acceptTypes]="['image/*']" [multiple]="false" (fileSelected)="onFileSelected($event)" /> `,
878
821
  })
879
822
  export class MyComponent {
880
823
  readonly showFileSelector = signal(false);
@@ -889,14 +832,14 @@ export class MyComponent {
889
832
 
890
833
  **Inputs:**
891
834
 
892
- | Input | Type | Default | Description |
893
- | ----- | ---- | ------- | ----------- |
894
- | `loadFiles` | `LoadFilesFn` | required | File loading function |
895
- | `header` | `string` | `'Select File'` | Dialog header |
896
- | `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
897
- | `multiple` | `boolean` | `false` | Allow multiple selection |
898
- | `maxSelection` | `number` | `10` | Max files for multiple |
899
- | `pageSize` | `number` | `20` | Page size for pagination |
835
+ | Input | Type | Default | Description |
836
+ | -------------- | ------------- | --------------- | ------------------------ |
837
+ | `loadFiles` | `LoadFilesFn` | required | File loading function |
838
+ | `header` | `string` | `'Select File'` | Dialog header |
839
+ | `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
840
+ | `multiple` | `boolean` | `false` | Allow multiple selection |
841
+ | `maxSelection` | `number` | `10` | Max files for multiple |
842
+ | `pageSize` | `number` | `20` | Page size for pagination |
900
843
 
901
844
  **Model:** `visible` - Two-way bound dialog visibility (`boolean`)
902
845
 
@@ -914,7 +857,7 @@ Structural directive for permission-based rendering. Fail-closed: hides content
914
857
  - **Input:** `hasPermission` - `string | ILogicNode | null`
915
858
 
916
859
  ```typescript
917
- import { HasPermissionDirective, ILogicNode } from '@flusys/ng-shared';
860
+ import { HasPermissionDirective, ILogicNode } from "@flusys/ng-shared";
918
861
 
919
862
  @Component({
920
863
  imports: [HasPermissionDirective],
@@ -928,11 +871,11 @@ import { HasPermissionDirective, ILogicNode } from '@flusys/ng-shared';
928
871
  })
929
872
  export class MyComponent {
930
873
  readonly editLogic: ILogicNode = {
931
- type: 'group',
932
- operator: 'AND',
874
+ type: "group",
875
+ operator: "AND",
933
876
  children: [
934
- { type: 'action', actionId: 'user.view' },
935
- { type: 'action', actionId: 'user.update' },
877
+ { type: "action", actionId: "user.view" },
878
+ { type: "action", actionId: "user.update" },
936
879
  ],
937
880
  };
938
881
  }
@@ -946,8 +889,7 @@ Toggles readonly/disabled state for form controls based on edit mode. Supports `
946
889
  - **Input:** `isEditMode` (required boolean)
947
890
 
948
891
  ```html
949
- <input [appEditModeElementChanger] [isEditMode]="isEditing()" />
950
- <p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
892
+ <input [appEditModeElementChanger] [isEditMode]="isEditing()" /> <p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
951
893
  ```
952
894
 
953
895
  ### IsEmptyImageDirective
@@ -972,8 +914,7 @@ Prevents default browser behavior on specified events and emits the event.
972
914
  - **Output:** `action` - Emits the prevented event
973
915
 
974
916
  ```html
975
- <a href="#" appPreventDefault (action)="handleClick($event)">Click me</a>
976
- <input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />
917
+ <a href="#" appPreventDefault (action)="handleClick($event)">Click me</a> <input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />
977
918
  ```
978
919
 
979
920
  ---
@@ -987,24 +928,29 @@ Route-level guards for permission-based access control. All guards deny access w
987
928
  Single permission or complex logic check.
988
929
 
989
930
  ```typescript
990
- import { permissionGuard } from '@flusys/ng-shared';
931
+ import { permissionGuard } from "@flusys/ng-shared";
991
932
 
992
933
  const routes: Routes = [
993
934
  // Simple permission
994
- { path: 'users', canActivate: [permissionGuard('user.view')] },
935
+ { path: "users", canActivate: [permissionGuard("user.view")] },
995
936
 
996
937
  // Complex logic (ILogicNode)
997
- { path: 'admin', canActivate: [permissionGuard({
998
- type: 'group',
999
- operator: 'AND',
1000
- children: [
1001
- { type: 'action', actionId: 'admin.view' },
1002
- { type: 'action', actionId: 'admin.manage' },
938
+ {
939
+ path: "admin",
940
+ canActivate: [
941
+ permissionGuard({
942
+ type: "group",
943
+ operator: "AND",
944
+ children: [
945
+ { type: "action", actionId: "admin.view" },
946
+ { type: "action", actionId: "admin.manage" },
947
+ ],
948
+ }),
1003
949
  ],
1004
- })] },
950
+ },
1005
951
 
1006
952
  // Custom redirect on deny
1007
- { path: 'settings', canActivate: [permissionGuard('settings.view', '/access-denied')] },
953
+ { path: "settings", canActivate: [permissionGuard("settings.view", "/access-denied")] },
1008
954
  ];
1009
955
  ```
1010
956
 
@@ -1033,34 +979,34 @@ AND logic - allows access only if user has ALL specified permissions.
1033
979
  Pure functions for permission logic evaluation. Used internally by `HasPermissionDirective` and guards. **Supports wildcard permissions.**
1034
980
 
1035
981
  ```typescript
1036
- import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions, hasPermission } from '@flusys/ng-shared';
982
+ import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions, hasPermission } from "@flusys/ng-shared";
1037
983
 
1038
- const userPermissions = ['user.view', 'user.create', 'admin.*'];
984
+ const userPermissions = ["user.view", "user.create", "admin.*"];
1039
985
 
1040
986
  // Low-level hasPermission check with wildcard support
1041
- hasPermission('user.view', userPermissions); // true (exact match)
1042
- hasPermission('admin.manage', userPermissions); // true (matches 'admin.*')
1043
- hasPermission('settings.view', ['*']); // true (global wildcard)
987
+ hasPermission("user.view", userPermissions); // true (exact match)
988
+ hasPermission("admin.manage", userPermissions); // true (matches 'admin.*')
989
+ hasPermission("settings.view", ["*"]); // true (global wildcard)
1044
990
 
1045
991
  // Evaluate string or ILogicNode
1046
- evaluatePermission('user.view', userPermissions); // true
1047
- evaluatePermission(null, userPermissions); // false
992
+ evaluatePermission("user.view", userPermissions); // true
993
+ evaluatePermission(null, userPermissions); // false
1048
994
 
1049
995
  // Evaluate ILogicNode tree recursively
1050
996
  evaluateLogicNode(logicNode, userPermissions);
1051
997
 
1052
998
  // Simple OR/AND checks (also support wildcards)
1053
- hasAnyPermission(['user.view', 'user.delete'], userPermissions); // true (has user.view)
1054
- hasAllPermissions(['user.view', 'user.delete'], userPermissions); // false (missing user.delete)
999
+ hasAnyPermission(["user.view", "user.delete"], userPermissions); // true (has user.view)
1000
+ hasAllPermissions(["user.view", "user.delete"], userPermissions); // false (missing user.delete)
1055
1001
  ```
1056
1002
 
1057
1003
  **Wildcard Rules:**
1058
1004
 
1059
- | Pattern | Matches |
1060
- |---------|---------|
1061
- | `*` | All permissions (global wildcard) |
1062
- | `module.*` | All permissions starting with `module.` |
1063
- | `user.read` | Exact match only |
1005
+ | Pattern | Matches |
1006
+ | ----------- | --------------------------------------- |
1007
+ | `*` | All permissions (global wildcard) |
1008
+ | `module.*` | All permissions starting with `module.` |
1009
+ | `user.read` | Exact match only |
1064
1010
 
1065
1011
  **Implementation Details:**
1066
1012
 
@@ -1072,10 +1018,10 @@ export function hasPermission(requiredPermission: string, userPermissions: strin
1072
1018
  // Wildcard matching
1073
1019
  for (const permission of userPermissions) {
1074
1020
  // Global wildcard
1075
- if (permission === '*') return true;
1021
+ if (permission === "*") return true;
1076
1022
 
1077
1023
  // Module wildcard (e.g., 'user.*' matches 'user.read')
1078
- if (permission.endsWith('.*')) {
1024
+ if (permission.endsWith(".*")) {
1079
1025
  const prefix = permission.slice(0, -1); // 'user.'
1080
1026
  if (requiredPermission.startsWith(prefix)) return true;
1081
1027
  }
@@ -1119,10 +1065,10 @@ onScroll(event: Event): void {
1119
1065
  Abstract base class for custom form controls. Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
1120
1066
 
1121
1067
  ```typescript
1122
- import { BaseFormControl, provideValueAccessor } from '@flusys/ng-shared';
1068
+ import { BaseFormControl, provideValueAccessor } from "@flusys/ng-shared";
1123
1069
 
1124
1070
  @Component({
1125
- selector: 'my-select',
1071
+ selector: "my-select",
1126
1072
  providers: [provideValueAccessor(MySelectComponent)],
1127
1073
  })
1128
1074
  export class MySelectComponent extends BaseFormControl<string | null> {
@@ -1226,7 +1172,7 @@ export class UserListComponent extends BaseListPage<IUser> {
1226
1172
  Re-exports common Angular modules for convenience.
1227
1173
 
1228
1174
  ```typescript
1229
- import { AngularModule } from '@flusys/ng-shared';
1175
+ import { AngularModule } from "@flusys/ng-shared";
1230
1176
  // Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet,
1231
1177
  // RouterLinkActive, NgOptimizedImage, NgComponentOutlet, + directives (IsEmptyImageDirective, PreventDefaultDirective)
1232
1178
  // Providers: DatePipe
@@ -1237,7 +1183,7 @@ import { AngularModule } from '@flusys/ng-shared';
1237
1183
  Re-exports PrimeNG component modules for convenience.
1238
1184
 
1239
1185
  ```typescript
1240
- import { PrimeModule } from '@flusys/ng-shared';
1186
+ import { PrimeModule } from "@flusys/ng-shared";
1241
1187
  // Includes 30 modules:
1242
1188
  // - Layout: AccordionModule, CardModule, DividerModule, PanelModule, SplitterModule, TabsModule, ToolbarModule
1243
1189
  // - Form: AutoCompleteModule, CheckboxModule, DatePickerModule, InputTextModule, InputTextareaModule,
@@ -1273,13 +1219,14 @@ ng-iam/ng-storage (consume interfaces via DI)
1273
1219
  User list access for IAM user selection.
1274
1220
 
1275
1221
  ```typescript
1276
- interface IUserBasicInfo { id: string; name: string; email: string }
1222
+ interface IUserBasicInfo {
1223
+ id: string;
1224
+ name: string;
1225
+ email: string;
1226
+ }
1277
1227
 
1278
1228
  interface IUserProvider {
1279
- getUsers(filter?: {
1280
- page?: number; pageSize?: number; search?: string;
1281
- companyId?: string; branchId?: string;
1282
- }): Observable<IListResponse<IUserBasicInfo>>;
1229
+ getUsers(filter?: { page?: number; pageSize?: number; search?: string; companyId?: string; branchId?: string }): Observable<IListResponse<IUserBasicInfo>>;
1283
1230
  }
1284
1231
  ```
1285
1232
 
@@ -1290,12 +1237,14 @@ interface IUserProvider {
1290
1237
  Company list access for IAM company selection.
1291
1238
 
1292
1239
  ```typescript
1293
- interface ICompanyBasicInfo { id: string; name: string; slug?: string }
1240
+ interface ICompanyBasicInfo {
1241
+ id: string;
1242
+ name: string;
1243
+ slug?: string;
1244
+ }
1294
1245
 
1295
1246
  interface ICompanyApiProvider {
1296
- getCompanies(filter?: {
1297
- page?: number; pageSize?: number; search?: string;
1298
- }): Observable<IListResponse<ICompanyBasicInfo>>;
1247
+ getCompanies(filter?: { page?: number; pageSize?: number; search?: string }): Observable<IListResponse<ICompanyBasicInfo>>;
1299
1248
  }
1300
1249
  ```
1301
1250
 
@@ -1319,31 +1268,18 @@ interface IUserPermissionProvider {
1319
1268
 
1320
1269
  **Token Error Message:** `'USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts'`
1321
1270
 
1322
- #### IProfileUploadProvider / `PROFILE_UPLOAD_PROVIDER`
1271
+ #### IFileProvider / `FILE_PROVIDER`
1323
1272
 
1324
- Profile picture upload for ng-auth profile page. Implemented by ng-storage. **Optional token** - use with `inject(..., { optional: true })`.
1273
+ File operations provider for file selection and upload. Implemented by ng-storage. **Optional token** - use with `inject(..., { optional: true })`.
1325
1274
 
1326
1275
  ```typescript
1327
- interface IProfileUploadResult {
1328
- id: string; // File manager ID (UUID)
1329
- name: string; // Original file name
1330
- key: string; // Storage key/path
1331
- size: number; // File size in bytes
1332
- contentType: string; // MIME type
1333
- }
1334
-
1335
- interface IProfileUploadOptions {
1336
- folderPath?: string;
1337
- compress?: boolean;
1338
- maxWidth?: number;
1339
- maxHeight?: number;
1340
- }
1341
-
1342
- interface IProfileUploadProvider {
1343
- uploadProfilePicture(
1344
- file: File,
1345
- options?: IProfileUploadOptions
1346
- ): Observable<ISingleResponse<IProfileUploadResult>>;
1276
+ interface IFileProvider {
1277
+ loadFiles(filter: IFileSelectFilter): Observable<IListResponse<IFileBasicInfo>>;
1278
+ uploadFile(file: File, options?: IFileUploadOptions): Observable<ISingleResponse<IUploadedFile>>;
1279
+ uploadMultipleFiles?(files: File[], options?: IFileUploadOptions): Observable<ISingleResponse<IUploadedFile[]>>;
1280
+ getFileUrls?(fileIds: string[]): Observable<ISingleResponse<IFileBasicInfo[]>>;
1281
+ loadFolders?(filter: ISelectFilter): Observable<IListResponse<IFolderBasicInfo>>;
1282
+ loadStorageConfigs?(filter: ISelectFilter): Observable<IListResponse<IStorageConfigBasicInfo>>;
1347
1283
  }
1348
1284
  ```
1349
1285
 
@@ -1417,14 +1353,13 @@ interface IUserListItem {
1417
1353
  email: string;
1418
1354
  phone?: string;
1419
1355
  isActive?: boolean;
1420
- [key: string]: unknown;
1421
1356
  }
1422
1357
 
1423
1358
  interface IUserListAction<T = IUserListItem> {
1424
1359
  id: string;
1425
1360
  label: string;
1426
1361
  icon?: string;
1427
- severity?: 'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger';
1362
+ severity?: "primary" | "secondary" | "success" | "info" | "warn" | "danger";
1428
1363
  permission?: string;
1429
1364
  tooltip?: string;
1430
1365
  disabled?: boolean | ((user: T) => boolean);
@@ -1436,7 +1371,7 @@ interface IUserListColumn {
1436
1371
  header: string;
1437
1372
  width?: string;
1438
1373
  sortable?: boolean;
1439
- templateType?: 'text' | 'badge' | 'date' | 'boolean' | 'custom';
1374
+ templateType?: "text" | "badge" | "date" | "boolean" | "custom";
1440
1375
  }
1441
1376
 
1442
1377
  interface IUserListFilter {
@@ -1446,7 +1381,6 @@ interface IUserListFilter {
1446
1381
  isActive?: boolean;
1447
1382
  companyId?: string;
1448
1383
  branchId?: string;
1449
- [key: string]: unknown;
1450
1384
  }
1451
1385
 
1452
1386
  interface IUserListProvider<T extends IUserListItem = IUserListItem> {
@@ -1463,17 +1397,13 @@ interface IUserListProvider<T extends IUserListItem = IUserListItem> {
1463
1397
 
1464
1398
  ```typescript
1465
1399
  // In app.config.ts
1466
- providers: [
1467
- { provide: USER_LIST_PROVIDER, useClass: MyUserListProvider },
1468
- ]
1400
+ providers: [{ provide: USER_LIST_PROVIDER, useClass: MyUserListProvider }];
1469
1401
 
1470
1402
  // Implementation
1471
- @Injectable({ providedIn: 'root' })
1403
+ @Injectable({ providedIn: "root" })
1472
1404
  export class MyUserListProvider implements IUserListProvider {
1473
1405
  getExtraRowActions() {
1474
- return [
1475
- { id: 'assign-role', label: 'Assign Role', icon: 'pi pi-users' },
1476
- ];
1406
+ return [{ id: "assign-role", label: "Assign Role", icon: "pi pi-users" }];
1477
1407
  }
1478
1408
  }
1479
1409
  ```
@@ -1481,13 +1411,13 @@ export class MyUserListProvider implements IUserListProvider {
1481
1411
  ### Usage in Consuming Packages
1482
1412
 
1483
1413
  ```typescript
1484
- import { USER_PROVIDER } from '@flusys/ng-shared';
1414
+ import { USER_PROVIDER } from "@flusys/ng-shared";
1485
1415
 
1486
1416
  export class UserSelectorComponent {
1487
1417
  private readonly userProvider = inject(USER_PROVIDER);
1488
1418
 
1489
1419
  loadUsers() {
1490
- this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe(response => {
1420
+ this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe((response) => {
1491
1421
  this.users.set(response.data ?? []);
1492
1422
  });
1493
1423
  }
@@ -1498,7 +1428,7 @@ export class UserSelectorComponent {
1498
1428
 
1499
1429
  ```typescript
1500
1430
  // app.config.ts
1501
- import { provideAuthProviders } from '@flusys/ng-auth';
1431
+ import { provideAuthProviders } from "@flusys/ng-auth";
1502
1432
 
1503
1433
  export const appConfig: ApplicationConfig = {
1504
1434
  providers: [
@@ -1598,12 +1528,10 @@ providers: [
1598
1528
  { provide: COMPANY_API_PROVIDER, useClass: AuthCompanyApiProvider },
1599
1529
  { provide: USER_PERMISSION_PROVIDER, useClass: AuthUserPermissionProvider },
1600
1530
  { provide: AUTH_STATE_PROVIDER, useClass: AuthStateProviderAdapter },
1601
- ]
1531
+ ];
1602
1532
 
1603
1533
  // OR use the convenience function
1604
- providers: [
1605
- ...provideAuthProviders(),
1606
- ]
1534
+ providers: [...provideAuthProviders()];
1607
1535
  ```
1608
1536
 
1609
1537
  ### Permissions Not Working
@@ -1622,107 +1550,255 @@ providers: [
1622
1550
 
1623
1551
  ### Services
1624
1552
 
1625
- | Service | Description |
1626
- | ------------------------------ | ------------------------------------- |
1627
- | `ApiResourceService<DTO, T>` | Signal-based CRUD with resource() API (lazy-initialized, accepts optional `serviceName`) |
1628
- | `FileUrlService` | Cloud storage URL fetching |
1629
- | `PermissionValidatorService` | Permission state management with wildcards |
1630
- | `CookieService` | SSR-aware cookie reading |
1631
- | `PlatformService` | SSR environment detection |
1553
+ | Service | Description |
1554
+ | ---------------------------- | ---------------------------------------------------------------------------------------- |
1555
+ | `ApiResourceService<DTO, T>` | Signal-based CRUD with resource() API (lazy-initialized, accepts optional `serviceName`) |
1556
+ | `FileUrlService` | Cloud storage URL fetching |
1557
+ | `PermissionValidatorService` | Permission state management with wildcards |
1558
+ | `CookieService` | SSR-aware cookie reading |
1559
+ | `PlatformService` | SSR environment detection |
1632
1560
 
1633
1561
  ### Classes
1634
1562
 
1635
- | Class | Description |
1636
- | ------------------------ | -------------------------------------------- |
1637
- | `ApiResourceService` | Signal-based CRUD base class (alias: `ApiService`) |
1638
- | `BaseFormControl` | Abstract base for custom form controls |
1639
- | `BaseFormPage` | Abstract directive for create/edit pages |
1640
- | `BaseListPage` | Abstract directive for list pages |
1563
+ | Class | Description |
1564
+ | -------------------- | -------------------------------------------------- |
1565
+ | `ApiResourceService` | Signal-based CRUD base class (alias: `ApiService`) |
1566
+ | `BaseFormControl` | Abstract base for custom form controls |
1567
+ | `BaseFormPage` | Abstract directive for create/edit pages |
1568
+ | `BaseListPage` | Abstract directive for list pages |
1641
1569
 
1642
1570
  ### Constants
1643
1571
 
1644
- | Constant | Description |
1645
- | -------------- | --------------------------------------------- |
1646
- | `PERMISSIONS` | Aggregated permission codes by module |
1647
- | `USER_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for users |
1648
- | `ROLE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for roles |
1649
- | `FILE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for files |
1572
+ | Constant | Description |
1573
+ | ------------------- | ----------------------------------------------------- |
1574
+ | `PERMISSIONS` | Aggregated permission codes by module |
1575
+ | `USER_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for users |
1576
+ | `ROLE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for roles |
1577
+ | `FILE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for files |
1650
1578
  | `FILE_TYPE_FILTERS` | Predefined MIME type arrays (IMAGES, DOCUMENTS, etc.) |
1651
1579
 
1652
1580
  ### Components
1653
1581
 
1654
- | Component | Selector | Description |
1655
- | -------------------------- | ---------------------- | ------------------------- |
1656
- | `IconComponent` | `lib-icon` | Flexible icon renderer |
1657
- | `LazySelectComponent` | `lib-lazy-select` | Lazy-loading dropdown |
1658
- | `LazyMultiSelectComponent` | `lib-lazy-multi-select`| Lazy-loading multi-select |
1659
- | `UserSelectComponent` | `lib-user-select` | Single user selector |
1660
- | `UserMultiSelectComponent` | `lib-user-multi-select`| Multiple user selector |
1661
- | `FileUploaderComponent` | `lib-file-uploader` | Drag & drop file upload |
1662
- | `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog |
1582
+ | Component | Selector | Description |
1583
+ | ----------------------------- | -------------------------- | ------------------------- |
1584
+ | `IconComponent` | `lib-icon` | Flexible icon renderer |
1585
+ | `LazySelectComponent` | `lib-lazy-select` | Lazy-loading dropdown |
1586
+ | `LazyMultiSelectComponent` | `lib-lazy-multi-select` | Lazy-loading multi-select |
1587
+ | `UserSelectComponent` | `lib-user-select` | Single user selector |
1588
+ | `UserMultiSelectComponent` | `lib-user-multi-select` | Multiple user selector |
1589
+ | `FileUploaderComponent` | `lib-file-uploader` | Drag & drop file upload |
1590
+ | `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog |
1663
1591
 
1664
1592
  ### Directives
1665
1593
 
1666
- | Directive | Selector | Description |
1667
- | --------------------------------- | --------------------------- | ----------------------------------- |
1668
- | `HasPermissionDirective` | `[hasPermission]` | Permission-based rendering |
1594
+ | Directive | Selector | Description |
1595
+ | --------------------------------- | ----------------------------- | --------------------------------- |
1596
+ | `HasPermissionDirective` | `[hasPermission]` | Permission-based rendering |
1669
1597
  | `EditModeElementChangerDirective` | `[appEditModeElementChanger]` | Toggle edit mode on form controls |
1670
- | `IsEmptyImageDirective` | `img` | Image fallback on error/empty |
1671
- | `PreventDefaultDirective` | `[appPreventDefault]` | Prevent default event behavior |
1598
+ | `IsEmptyImageDirective` | `img` | Image fallback on error/empty |
1599
+ | `PreventDefaultDirective` | `[appPreventDefault]` | Prevent default event behavior |
1672
1600
 
1673
1601
  ### Guards
1674
1602
 
1675
- | Guard | Description |
1676
- | ---------------------- | -------------------------------------- |
1677
- | `permissionGuard` | Single permission or ILogicNode check |
1678
- | `anyPermissionGuard` | OR logic (any of listed permissions) |
1679
- | `allPermissionsGuard` | AND logic (all of listed permissions) |
1603
+ | Guard | Description |
1604
+ | --------------------- | ------------------------------------- |
1605
+ | `permissionGuard` | Single permission or ILogicNode check |
1606
+ | `anyPermissionGuard` | OR logic (any of listed permissions) |
1607
+ | `allPermissionsGuard` | AND logic (all of listed permissions) |
1680
1608
 
1681
1609
  ### Interfaces
1682
1610
 
1683
- | Interface | Description |
1684
- | -------------------- | ------------------------------------ |
1685
- | `IBaseEntity` | Base entity with ID and timestamps |
1686
- | `ILoggedUserInfo` | Current user info with company ctx |
1687
- | `IFilterData` | Filter, pagination, sort payload |
1688
- | `IFilter` | Filter object (supports arrays) |
1689
- | `IDeleteData` | Delete request payload |
1690
- | `IDropDown` | Simple label/value pair |
1691
- | `ISingleResponse<T>` | Single item response |
1692
- | `IListResponse<T>` | List with pagination |
1693
- | `IBulkResponse<T>` | Bulk operation response |
1694
- | `IMessageResponse` | Message-only response |
1695
- | `IErrorResponse` | Error with validation details |
1696
- | `ILogicNode` | Permission logic tree (AND/OR nodes) |
1697
- | `IUserSelectFilter` | User select filter params |
1698
- | `LoadUsersFn` | User loading function type |
1699
- | `IFileBasicInfo` | Basic file info for selectors |
1700
- | `IFileUploadOptions`| Upload options (compression, etc.) |
1701
- | `IUploadedFile` | Uploaded file response |
1702
- | `IFileSelectFilter` | File select filter params |
1703
- | `LoadFilesFn` | File loading function type |
1704
- | `UploadFileFn` | File upload function type |
1705
- | `FilesResponseDto` | File URL service response |
1706
- | `IAuthStateProvider`| Auth state provider interface |
1707
- | `IUserListProvider` | User list extensions provider |
1708
- | `IUserListItem` | Base user for list operations |
1709
- | `IUserListAction` | User list action definition |
1710
- | `IUserListColumn` | Extra column for user list |
1711
- | `IUserListFilter` | User list filter parameters |
1712
- | `IUserBranchPermission` | User permissions per branch |
1713
- | `ServiceName` | `'auth' \| 'administration' \| 'iam' \| 'storage' \| 'formBuilder' \| 'email'` |
1611
+ | Interface | Description |
1612
+ | ----------------------- | ------------------------------------------------------------------------------ |
1613
+ | `IBaseEntity` | Base entity with ID and timestamps |
1614
+ | `ILoggedUserInfo` | Current user info with company ctx |
1615
+ | `IFilterData` | Filter, pagination, sort payload |
1616
+ | `IFilter` | Filter object (supports arrays) |
1617
+ | `IDeleteData` | Delete request payload |
1618
+ | `IDropDown` | Simple label/value pair |
1619
+ | `ISingleResponse<T>` | Single item response |
1620
+ | `IListResponse<T>` | List with pagination |
1621
+ | `IBulkResponse<T>` | Bulk operation response |
1622
+ | `IMessageResponse` | Message-only response |
1623
+ | `IErrorResponse` | Error with validation details |
1624
+ | `ILogicNode` | Permission logic tree (AND/OR nodes) |
1625
+ | `IUserSelectFilter` | User select filter params |
1626
+ | `LoadUsersFn` | User loading function type |
1627
+ | `IFileBasicInfo` | Basic file info for selectors |
1628
+ | `IFolderBasicInfo` | Folder info for selectors |
1629
+ | `IStorageConfigBasicInfo` | Storage config info for selectors |
1630
+ | `IFileUploadOptions` | Upload options (compression, etc.) |
1631
+ | `IUploadedFile` | Uploaded file response |
1632
+ | `IFileSelectFilter` | File select filter params |
1633
+ | `ISelectFilter` | Filter params for folder/config selectors |
1634
+ | `LoadFilesFn` | File loading function type |
1635
+ | `UploadFileFn` | Single file upload function type |
1636
+ | `UploadMultipleFilesFn` | Multiple files upload function type |
1637
+ | `IFileProvider` | File operations provider interface |
1638
+ | `FilesResponseDto` | File URL service response |
1639
+ | `IAuthStateProvider` | Auth state provider interface |
1640
+ | `IUserListProvider` | User list extensions provider |
1641
+ | `IUserListItem` | Base user for list operations |
1642
+ | `IUserListAction` | User list action definition |
1643
+ | `IUserListColumn` | Extra column for user list |
1644
+ | `IUserListFilter` | User list filter parameters |
1645
+ | `IUserBranchPermission` | User permissions per branch |
1646
+ | `ServiceName` | `'auth' \| 'administration' \| 'iam' \| 'storage' \| 'formBuilder' \| 'email'` |
1714
1647
 
1715
1648
  ### Injection Tokens
1716
1649
 
1717
- | Token | Interface | Optional | Description |
1718
- | -------------------------- | ------------------------- | -------- | ---------------------------- |
1719
- | `USER_PROVIDER` | `IUserProvider` | No | User list for IAM |
1720
- | `COMPANY_API_PROVIDER` | `ICompanyApiProvider` | No | Company list for IAM |
1721
- | `USER_PERMISSION_PROVIDER` | `IUserPermissionProvider` | No | User permission queries |
1722
- | `AUTH_STATE_PROVIDER` | `IAuthStateProvider` | No | Auth state for feature packages |
1723
- | `PROFILE_UPLOAD_PROVIDER` | `IProfileUploadProvider` | Yes | Profile picture upload (ng-storage) |
1724
- | `PROFILE_PERMISSION_PROVIDER` | `IProfilePermissionProvider` | Yes | User permissions for profile (ng-iam) |
1725
- | `USER_LIST_PROVIDER` | `IUserListProvider` | Yes | User list extensions |
1650
+ | Token | Interface | Optional | Description |
1651
+ | ----------------------------- | ---------------------------- | -------- | ------------------------------------- |
1652
+ | `USER_PROVIDER` | `IUserProvider` | No | User list for IAM |
1653
+ | `COMPANY_API_PROVIDER` | `ICompanyApiProvider` | No | Company list for IAM |
1654
+ | `USER_PERMISSION_PROVIDER` | `IUserPermissionProvider` | No | User permission queries |
1655
+ | `AUTH_STATE_PROVIDER` | `IAuthStateProvider` | No | Auth state for feature packages |
1656
+ | `FILE_PROVIDER` | `IFileProvider` | Yes | File operations (ng-storage) |
1657
+ | `PROFILE_PERMISSION_PROVIDER` | `IProfilePermissionProvider` | Yes | User permissions for profile (ng-iam) |
1658
+ | `USER_LIST_PROVIDER` | `IUserListProvider` | Yes | User list extensions |
1659
+
1660
+ ---
1661
+
1662
+ ## 10. Translation & Localization
1663
+
1664
+ ### Overview
1665
+
1666
+ The ng-shared package provides optional localization support via two modes:
1667
+
1668
+ 1. **Fallback-Only Mode** - Hardcoded fallback messages (no API)
1669
+ 2. **Full API Mode** - Dynamic translations from API with fallback safety net
1670
+
1671
+ ### Fallback-Only Mode
1672
+
1673
+ Use when you want simple hardcoded translations without a localization system:
1674
+
1675
+ ```typescript
1676
+ // app.config.ts
1677
+ import { provideFallbackLocalization } from '@flusys/ng-shared';
1678
+
1679
+ export const appConfig: ApplicationConfig = {
1680
+ providers: [
1681
+ // ... other providers
1682
+ ...provideFallbackLocalization(), // ← One-liner setup
1683
+ ],
1684
+ };
1685
+ ```
1686
+
1687
+ **What it provides:**
1688
+ - `FALLBACK_MESSAGES_REGISTRY` - Token for storing hardcoded messages
1689
+ - `TRANSLATE_ADAPTER` - Fallback implementation that reads from registry
1690
+ - Works with route resolvers that register fallback messages
1691
+
1692
+ **Message flow:**
1693
+ ```
1694
+ Route with resolveTranslationModule({
1695
+ modules: ['email'],
1696
+ fallbackMessages: EMAIL_MESSAGES
1697
+ })
1698
+
1699
+ Resolver registers in FALLBACK_MESSAGES_REGISTRY
1700
+
1701
+ TRANSLATE_ADAPTER reads from registry
1702
+
1703
+ Components/TranslatePipe render values
1704
+ ```
1705
+
1706
+ ### Full API Mode
1707
+
1708
+ Use when you need dynamic translations, language switching, and management UI:
1709
+
1710
+ ```typescript
1711
+ // app.config.ts
1712
+ import { provideLocalization, getLocalizationConfig } from '@flusys/ng-localization';
1713
+
1714
+ export const appConfig: ApplicationConfig = {
1715
+ providers: [
1716
+ // ... other providers
1717
+ ...provideLocalization(
1718
+ getLocalizationConfig({
1719
+ defaultLanguageCode: 'en',
1720
+ enableLayoutSelector: true, // Show language switcher
1721
+ })
1722
+ ),
1723
+ ],
1724
+ };
1725
+ ```
1726
+
1727
+ **What it provides:**
1728
+ - `LocalizationStateService` - Manages current language and translations
1729
+ - `LocalizationApiService` - Fetches translations from backend
1730
+ - `TranslateService` - Registered as `TRANSLATE_ADAPTER`
1731
+ - Language selector component (if `enableLayoutSelector: true`)
1732
+ - Management pages for languages and translations
1733
+
1734
+ ### Route Resolver Setup
1735
+
1736
+ All routes should use `resolveTranslationModule` with fallback messages:
1737
+
1738
+ ```typescript
1739
+ // email.routes.ts
1740
+ import { resolveTranslationModule, EMAIL_MESSAGES, SHARED_MESSAGES } from '@flusys/ng-shared';
1741
+
1742
+ export const EMAIL_ROUTES: Routes = [
1743
+ {
1744
+ path: '',
1745
+ resolve: {
1746
+ translations: resolveTranslationModule({
1747
+ modules: ['email'],
1748
+ fallbackMessages: { ...EMAIL_MESSAGES, ...SHARED_MESSAGES }
1749
+ })
1750
+ },
1751
+ // ... route definition
1752
+ }
1753
+ ];
1754
+ ```
1755
+
1756
+ ### TranslatePipe Usage
1757
+
1758
+ Works identically in both modes:
1759
+
1760
+ ```typescript
1761
+ // Template
1762
+ <h1>{{ 'email.title' | translate }}</h1>
1763
+ <p>{{ 'email.message' | translate: { count: itemCount } }}</p>
1764
+
1765
+ // Component
1766
+ const title = this.translateAdapter.translate('email.title');
1767
+ ```
1768
+
1769
+ ### Configuration (Full API Mode Only)
1770
+
1771
+ ```typescript
1772
+ interface ILocalizationConfig {
1773
+ defaultLanguageCode: string; // Required: 'en', 'ar', etc.
1774
+ loadStrategy?: 'all' | 'modules'; // Default: 'modules'
1775
+ initialModules?: string[]; // Default: []
1776
+ enableLayoutSelector?: boolean; // Default: false
1777
+ }
1778
+ ```
1779
+
1780
+ ### Key Files
1781
+
1782
+ | File | Purpose |
1783
+ |------|---------|
1784
+ | `providers/fallback-localization.providers.ts` | Fallback mode setup |
1785
+ | `resolvers/translation-module.resolver.ts` | Route resolver for both modes |
1786
+ | `pipes/translate.pipe.ts` | Translation pipe |
1787
+
1788
+ ### When to Use Each Mode
1789
+
1790
+ **Use Fallback-Only:**
1791
+ - Simple applications with static content
1792
+ - No language switching needed
1793
+ - No translation management UI
1794
+ - Lightweight setup
1795
+
1796
+ **Use Full API:**
1797
+ - Multi-language support required
1798
+ - User can switch languages
1799
+ - Admin manages translations
1800
+ - Real-time translation updates
1801
+ - Translation management UI needed
1726
1802
 
1727
1803
  ## See Also
1728
1804
 
@@ -1733,6 +1809,6 @@ providers: [
1733
1809
 
1734
1810
  ---
1735
1811
 
1736
- **Last Updated:** 2026-02-25
1737
- **Version:** 3.0.0
1812
+ **Last Updated:** 2026-03-01
1813
+ **Version:** 3.0.1
1738
1814
  **Angular Version:** 21