integra-ng 21.0.17 → 21.0.19

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.
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { EventEmitter, Output, Input, Component, ContentChildren, ElementRef, ViewChild, HostBinding, createComponent, Directive, Optional, Self, forwardRef, HostListener, ChangeDetectionStrategy, signal, inject, EnvironmentInjector, ApplicationRef, Injectable, effect, computed, input, ViewEncapsulation, ChangeDetectorRef, ViewChildren, Inject } from '@angular/core';
2
+ import { EventEmitter, Output, Input, Component, ContentChildren, ElementRef, ViewChild, HostBinding, createComponent, Directive, Optional, Self, forwardRef, HostListener, ChangeDetectionStrategy, signal, inject, EnvironmentInjector, ApplicationRef, Injectable, effect, InjectionToken, computed, input, ViewEncapsulation, ChangeDetectorRef, ViewChildren, Inject } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { NgClass, CommonModule, NgTemplateOutlet, NgStyle, DOCUMENT } from '@angular/common';
5
5
  import * as i1 from '@angular/forms';
6
6
  import { FormsModule, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
7
- import { take } from 'rxjs/operators';
8
- import { Subject, filter, BehaviorSubject } from 'rxjs';
7
+ import { take, takeUntil, switchMap, map } from 'rxjs/operators';
8
+ import { Subject, BehaviorSubject, of, forkJoin, filter } from 'rxjs';
9
9
  import * as i2 from '@angular/router';
10
10
  import { RouterLink, RouterLinkActive, NavigationEnd, RouterOutlet } from '@angular/router';
11
11
  import { trigger, state, transition, style, animate } from '@angular/animations';
@@ -3159,14 +3159,163 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
3159
3159
  type: Input
3160
3160
  }] } });
3161
3161
 
3162
+ /**
3163
+ * Injection token for providing a claims checking service.
3164
+ * Applications using claims-based menu filtering should provide
3165
+ * an implementation of ClaimsChecker using this token.
3166
+ *
3167
+ * @example
3168
+ * // In your app.config.ts or module:
3169
+ * providers: [
3170
+ * {
3171
+ * provide: CLAIMS_CHECKER,
3172
+ * useExisting: ClaimsClient // Your claims service
3173
+ * }
3174
+ * ]
3175
+ */
3176
+ const CLAIMS_CHECKER = new InjectionToken('ClaimsChecker');
3177
+ /**
3178
+ * Provides a claims checker service for menu item filtering.
3179
+ * This is the recommended way to configure claims-based access control in your application.
3180
+ *
3181
+ * @param claimsService The claims service instance that implements the ClaimsChecker interface
3182
+ * @returns Provider configuration for dependency injection
3183
+ *
3184
+ * @example
3185
+ * // In your app.config.ts:
3186
+ * import { provideMenuClaimsChecker } from 'integra-ng';
3187
+ * import { ClaimsService } from './services/claims.service';
3188
+ *
3189
+ * export const appConfig: ApplicationConfig = {
3190
+ * providers: [
3191
+ * provideMenuClaimsChecker(ClaimsService),
3192
+ * // ... other providers
3193
+ * ]
3194
+ * };
3195
+ *
3196
+ * @example
3197
+ * // Or provide an existing service instance:
3198
+ * providers: [
3199
+ * ClaimsService,
3200
+ * provideMenuClaimsChecker(ClaimsService)
3201
+ * ]
3202
+ */
3203
+ function provideMenuClaimsChecker(claimsService) {
3204
+ return {
3205
+ provide: CLAIMS_CHECKER,
3206
+ useExisting: claimsService,
3207
+ };
3208
+ }
3209
+
3162
3210
  class MenuComponent {
3163
- model = [];
3211
+ claimsChecker = inject(CLAIMS_CHECKER, { optional: true });
3212
+ destroy$ = new Subject();
3213
+ modelSubject$ = new BehaviorSubject([]);
3214
+ filteredModel = signal([], ...(ngDevMode ? [{ debugName: "filteredModel" }] : []));
3215
+ set model(value) {
3216
+ this.modelSubject$.next(value);
3217
+ }
3218
+ ngOnInit() {
3219
+ this.modelSubject$
3220
+ .pipe(takeUntil(this.destroy$), switchMap((model) => this.filterModelByClaims(model)))
3221
+ .subscribe((filteredModel) => {
3222
+ this.filteredModel.set(filteredModel);
3223
+ });
3224
+ }
3225
+ ngOnDestroy() {
3226
+ this.destroy$.next();
3227
+ this.destroy$.complete();
3228
+ }
3229
+ filterModelByClaims(model) {
3230
+ // If no claims checker is provided, return all menu items (backward compatible)
3231
+ if (!this.claimsChecker) {
3232
+ return of(model);
3233
+ }
3234
+ // Collect all unique claims from the model
3235
+ const claims = this.collectClaims(model);
3236
+ if (claims.size === 0) {
3237
+ return of(model);
3238
+ }
3239
+ // Check all claims in parallel using forkJoin
3240
+ const claimChecks = {};
3241
+ claims.forEach((claim) => {
3242
+ claimChecks[claim] = this.claimsChecker.hasClaim(claim);
3243
+ });
3244
+ if (Object.keys(claimChecks).length === 0) {
3245
+ return of(model);
3246
+ }
3247
+ return forkJoin(claimChecks).pipe(map((claimsMap) => this.filterModel(model, claimsMap)));
3248
+ }
3249
+ collectClaims(model) {
3250
+ const claims = new Set();
3251
+ model.forEach((group) => {
3252
+ if (group.claim) {
3253
+ claims.add(group.claim);
3254
+ }
3255
+ group.items.forEach((item) => {
3256
+ this.collectItemClaims(item, claims);
3257
+ });
3258
+ });
3259
+ return claims;
3260
+ }
3261
+ collectItemClaims(item, claims) {
3262
+ if (item.claim) {
3263
+ claims.add(item.claim);
3264
+ }
3265
+ if (item.items) {
3266
+ item.items.forEach((subItem) => {
3267
+ this.collectItemClaims(subItem, claims);
3268
+ });
3269
+ }
3270
+ }
3271
+ filterModel(model, claimsMap) {
3272
+ return model
3273
+ .filter((group) => {
3274
+ // If group has a claim, check if user has access
3275
+ if (group.claim && !claimsMap[group.claim]) {
3276
+ return false;
3277
+ }
3278
+ return true;
3279
+ })
3280
+ .map((group) => ({
3281
+ ...group,
3282
+ items: this.filterItems(group.items, claimsMap),
3283
+ }))
3284
+ .filter((group) => group.items.length > 0); // Remove groups with no visible items
3285
+ }
3286
+ filterItems(items, claimsMap) {
3287
+ return items
3288
+ .filter((item) => {
3289
+ // If item has a claim, check if user has access
3290
+ if (item.claim && !claimsMap[item.claim]) {
3291
+ return false;
3292
+ }
3293
+ return true;
3294
+ })
3295
+ .map((item) => {
3296
+ // If item has nested items, filter them recursively
3297
+ if (item.items) {
3298
+ return {
3299
+ ...item,
3300
+ items: this.filterItems(item.items, claimsMap),
3301
+ };
3302
+ }
3303
+ return item;
3304
+ })
3305
+ .filter((item) => {
3306
+ // If item has nested items, keep it only if it has visible nested items
3307
+ if (item.items) {
3308
+ return item.items.length > 0;
3309
+ }
3310
+ return true;
3311
+ });
3312
+ }
3164
3313
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: MenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3165
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: MenuComponent, isStandalone: true, selector: "i-menu", inputs: { model: "model" }, ngImport: i0, template: "<ul class=\"menu\">\n @for(group of model; track $index) {\n <li class=\"menu-group\">\n @if (group.label) {\n <div class=\"menu-group-label\">\n {{ group.label }}\n </div>\n }\n\n <ul class=\"menu-items\">\n @for(item of group.items; track $index) {\n <li class=\"menu-item\">\n <a\n [routerLink]=\"item.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (item.icon) {\n <i class=\"{{ item.icon }}\"></i>\n }\n <span>{{ item.label }}</span>\n </a>\n\n @if (item.items) {\n <ul class=\"submenu\">\n @for(subItem of item.items; track $index) {\n <li class=\"submenu-item\">\n <a\n [routerLink]=\"subItem.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (subItem.icon) {\n <i class=\"{{ subItem.icon }}\"></i>\n }\n <span>{{ subItem.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n\n @if (group.separator) {\n <hr class=\"separator\" />\n }\n </li>\n }\n</ul>\n", styles: [".menu{list-style:none;margin:0;padding:0}.menu-group{margin-bottom:1rem}.menu-group .menu-group-label{font-size:1em;font-weight:600;text-transform:uppercase;padding:.5rem 1rem;color:var(--color-contrast)}.menu-group .menu-items{list-style:none;margin:0;padding:0}.menu-group .menu-items .menu-item a{display:flex;align-items:center;padding:.75rem 1rem;color:var(--color-text-secondary);text-decoration:none;font-size:1em;border-left:3px solid transparent;transition:all .2s ease}.menu-group .menu-items .menu-item a i{margin-right:.5rem;font-size:1em}.menu-group .menu-items .menu-item a:hover{background-color:var(--surface-hover);border-radius:6px}.menu-group .menu-items .menu-item a.active{background-color:var(--surface-hover);border-radius:6px;border-left:3px solid var(--color-primary);font-weight:600}.menu-group .menu-items .menu-item .submenu{list-style:none;padding-left:1.5rem}.menu-group .menu-items .menu-item .submenu .submenu-item a{padding:.5rem 1rem;font-size:.95em;color:var(--color-text-tertiary)}.menu-group .menu-items .menu-item .submenu .submenu-item a:hover{background-color:var(--surface-hover);color:var(--color-text-primary)}.menu-group .menu-items .menu-item .submenu .submenu-item a.active{color:var(--color-primary);font-weight:600}.separator{border:none;border-top:1px solid #334155;margin:.5rem 0}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
3314
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: MenuComponent, isStandalone: true, selector: "i-menu", inputs: { model: "model" }, ngImport: i0, template: "<ul class=\"menu\">\n @for(group of filteredModel(); track $index) {\n <li class=\"menu-group\">\n @if (group.label) {\n <div class=\"menu-group-label\">\n {{ group.label }}\n </div>\n }\n\n <ul class=\"menu-items\">\n @for(item of group.items; track $index) {\n <li class=\"menu-item\">\n <a\n [routerLink]=\"item.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (item.icon) {\n <i class=\"{{ item.icon }}\"></i>\n }\n <span>{{ item.label }}</span>\n </a>\n\n @if (item.items) {\n <ul class=\"submenu\">\n @for(subItem of item.items; track $index) {\n <li class=\"submenu-item\">\n <a\n [routerLink]=\"subItem.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (subItem.icon) {\n <i class=\"{{ subItem.icon }}\"></i>\n }\n <span>{{ subItem.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n\n @if (group.separator) {\n <hr class=\"separator\" />\n }\n </li>\n }\n</ul>\n", styles: [".menu{list-style:none;margin:0;padding:0}.menu-group{margin-bottom:1rem}.menu-group .menu-group-label{font-size:1em;font-weight:600;text-transform:uppercase;padding:.5rem 1rem;color:var(--color-contrast)}.menu-group .menu-items{list-style:none;margin:0;padding:0}.menu-group .menu-items .menu-item a{display:flex;align-items:center;padding:.75rem 1rem;color:var(--color-text-secondary);text-decoration:none;font-size:1em;border-left:3px solid transparent;transition:all .2s ease}.menu-group .menu-items .menu-item a i{margin-right:.5rem;font-size:1em}.menu-group .menu-items .menu-item a:hover{background-color:var(--surface-hover);border-radius:6px}.menu-group .menu-items .menu-item a.active{background-color:var(--surface-hover);border-radius:6px;border-left:3px solid var(--color-primary);font-weight:600}.menu-group .menu-items .menu-item .submenu{list-style:none;padding-left:1.5rem}.menu-group .menu-items .menu-item .submenu .submenu-item a{padding:.5rem 1rem;font-size:.95em;color:var(--color-text-tertiary)}.menu-group .menu-items .menu-item .submenu .submenu-item a:hover{background-color:var(--surface-hover);color:var(--color-text-primary)}.menu-group .menu-items .menu-item .submenu .submenu-item a.active{color:var(--color-primary);font-weight:600}.separator{border:none;border-top:1px solid #334155;margin:.5rem 0}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
3166
3315
  }
3167
3316
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: MenuComponent, decorators: [{
3168
3317
  type: Component,
3169
- args: [{ selector: 'i-menu', imports: [RouterLink, RouterLinkActive], template: "<ul class=\"menu\">\n @for(group of model; track $index) {\n <li class=\"menu-group\">\n @if (group.label) {\n <div class=\"menu-group-label\">\n {{ group.label }}\n </div>\n }\n\n <ul class=\"menu-items\">\n @for(item of group.items; track $index) {\n <li class=\"menu-item\">\n <a\n [routerLink]=\"item.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (item.icon) {\n <i class=\"{{ item.icon }}\"></i>\n }\n <span>{{ item.label }}</span>\n </a>\n\n @if (item.items) {\n <ul class=\"submenu\">\n @for(subItem of item.items; track $index) {\n <li class=\"submenu-item\">\n <a\n [routerLink]=\"subItem.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (subItem.icon) {\n <i class=\"{{ subItem.icon }}\"></i>\n }\n <span>{{ subItem.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n\n @if (group.separator) {\n <hr class=\"separator\" />\n }\n </li>\n }\n</ul>\n", styles: [".menu{list-style:none;margin:0;padding:0}.menu-group{margin-bottom:1rem}.menu-group .menu-group-label{font-size:1em;font-weight:600;text-transform:uppercase;padding:.5rem 1rem;color:var(--color-contrast)}.menu-group .menu-items{list-style:none;margin:0;padding:0}.menu-group .menu-items .menu-item a{display:flex;align-items:center;padding:.75rem 1rem;color:var(--color-text-secondary);text-decoration:none;font-size:1em;border-left:3px solid transparent;transition:all .2s ease}.menu-group .menu-items .menu-item a i{margin-right:.5rem;font-size:1em}.menu-group .menu-items .menu-item a:hover{background-color:var(--surface-hover);border-radius:6px}.menu-group .menu-items .menu-item a.active{background-color:var(--surface-hover);border-radius:6px;border-left:3px solid var(--color-primary);font-weight:600}.menu-group .menu-items .menu-item .submenu{list-style:none;padding-left:1.5rem}.menu-group .menu-items .menu-item .submenu .submenu-item a{padding:.5rem 1rem;font-size:.95em;color:var(--color-text-tertiary)}.menu-group .menu-items .menu-item .submenu .submenu-item a:hover{background-color:var(--surface-hover);color:var(--color-text-primary)}.menu-group .menu-items .menu-item .submenu .submenu-item a.active{color:var(--color-primary);font-weight:600}.separator{border:none;border-top:1px solid #334155;margin:.5rem 0}\n"] }]
3318
+ args: [{ selector: 'i-menu', imports: [RouterLink, RouterLinkActive], template: "<ul class=\"menu\">\n @for(group of filteredModel(); track $index) {\n <li class=\"menu-group\">\n @if (group.label) {\n <div class=\"menu-group-label\">\n {{ group.label }}\n </div>\n }\n\n <ul class=\"menu-items\">\n @for(item of group.items; track $index) {\n <li class=\"menu-item\">\n <a\n [routerLink]=\"item.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (item.icon) {\n <i class=\"{{ item.icon }}\"></i>\n }\n <span>{{ item.label }}</span>\n </a>\n\n @if (item.items) {\n <ul class=\"submenu\">\n @for(subItem of item.items; track $index) {\n <li class=\"submenu-item\">\n <a\n [routerLink]=\"subItem.routerLink\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n >\n @if (subItem.icon) {\n <i class=\"{{ subItem.icon }}\"></i>\n }\n <span>{{ subItem.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n\n @if (group.separator) {\n <hr class=\"separator\" />\n }\n </li>\n }\n</ul>\n", styles: [".menu{list-style:none;margin:0;padding:0}.menu-group{margin-bottom:1rem}.menu-group .menu-group-label{font-size:1em;font-weight:600;text-transform:uppercase;padding:.5rem 1rem;color:var(--color-contrast)}.menu-group .menu-items{list-style:none;margin:0;padding:0}.menu-group .menu-items .menu-item a{display:flex;align-items:center;padding:.75rem 1rem;color:var(--color-text-secondary);text-decoration:none;font-size:1em;border-left:3px solid transparent;transition:all .2s ease}.menu-group .menu-items .menu-item a i{margin-right:.5rem;font-size:1em}.menu-group .menu-items .menu-item a:hover{background-color:var(--surface-hover);border-radius:6px}.menu-group .menu-items .menu-item a.active{background-color:var(--surface-hover);border-radius:6px;border-left:3px solid var(--color-primary);font-weight:600}.menu-group .menu-items .menu-item .submenu{list-style:none;padding-left:1.5rem}.menu-group .menu-items .menu-item .submenu .submenu-item a{padding:.5rem 1rem;font-size:.95em;color:var(--color-text-tertiary)}.menu-group .menu-items .menu-item .submenu .submenu-item a:hover{background-color:var(--surface-hover);color:var(--color-text-primary)}.menu-group .menu-items .menu-item .submenu .submenu-item a.active{color:var(--color-primary);font-weight:600}.separator{border:none;border-top:1px solid #334155;margin:.5rem 0}\n"] }]
3170
3319
  }], propDecorators: { model: [{
3171
3320
  type: Input
3172
3321
  }] } });
@@ -9137,5 +9286,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
9137
9286
  * Generated bundle index. Do not edit.
9138
9287
  */
9139
9288
 
9140
- export { AbstractDialog, ConfirmationDialogComponent, ConfirmationDialogService, DataUpdateEventService, DialogService, EmptyStateComponent, IAccordion, IAccordionList, IButton, ICalendar, ICard, IChart, ICheckbox, IChip, IChipsComponent, IDialog, IDialogActions, IDialogBase, IInputText, IListbox, IMessage, IMultiSelect, IOverlayPanel, IPanel, IPlaceholder, IProgressSpinner, IRadioButton, ISelect, ITabPanel, ITable, ITabs, ITreeView, IWhisper, LayoutComponent, LayoutService, LocalStorageColorSchemeKey, MenuComponent, NoContentComponent, SeoService, SidebarComponent, StructuredDataService, TooltipComponent, TooltipDirective, TopbarComponent, UniqueComponentId, WhisperService, ZIndexUtils, lastId };
9289
+ export { AbstractDialog, CLAIMS_CHECKER, ConfirmationDialogComponent, ConfirmationDialogService, DataUpdateEventService, DialogService, EmptyStateComponent, IAccordion, IAccordionList, IButton, ICalendar, ICard, IChart, ICheckbox, IChip, IChipsComponent, IDialog, IDialogActions, IDialogBase, IInputText, IListbox, IMessage, IMultiSelect, IOverlayPanel, IPanel, IPlaceholder, IProgressSpinner, IRadioButton, ISelect, ITabPanel, ITable, ITabs, ITreeView, IWhisper, LayoutComponent, LayoutService, LocalStorageColorSchemeKey, MenuComponent, NoContentComponent, SeoService, SidebarComponent, StructuredDataService, TooltipComponent, TooltipDirective, TopbarComponent, UniqueComponentId, WhisperService, ZIndexUtils, lastId, provideMenuClaimsChecker };
9141
9290
  //# sourceMappingURL=integra-ng.mjs.map