Skip to content
Merged
3 changes: 3 additions & 0 deletions npm/ng-packs/packages/feature-management/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"@abp/ng.theme.shared": "~10.1.0-rc.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/aria": "~21.0.0"
},
"publishConfig": {
"access": "public"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ <h3>
<ng-template #abpBody>
<div class="row">
@if (groups.length) {
<div class="col-md-4">
<ul
ngbNav
#nav="ngbNav"
[(activeId)]="selectedGroupDisplayName"
class="nav-pills"
orientation="vertical"
>
<div ngTabs orientation="vertical" class="row">
<div class="col-md-4">
<div ngTabList orientation="vertical" selectionMode="follow" [(selectedTab)]="selectedGroupDisplayName" class="nav nav-pills flex-column">
@for (group of groups; track group.name) {
<button ngTab #tab="ngTab" type="button" [value]="group.displayName" class="nav-link text-start" [class.active]="tab.selected()">
{{ group.displayName }}
</button>
}
</div>
</div>
<ng-template #descTmp let-description>
@if (description) {
<small class="d-block form-text text-muted">{{ description }}</small>
}
</ng-template>

<div class="col-md-8">
@for (group of groups; track group.name) {
<li [ngbNavItem]="group.displayName">
<a ngbNavLink>{{ group.displayName }}</a>
<ng-template ngbNavContent>
<div ngTabPanel [value]="group.displayName">
<ng-template ngTabContent>
<h4>{{ selectedGroupDisplayName }}</h4>
<hr class="mt-2 mb-3" />

Expand Down Expand Up @@ -128,18 +136,10 @@ <h4>{{ selectedGroupDisplayName }}</h4>
</div>
}
</ng-template>
</li>
</div>
}
</ul>
</div>
</div>

<ng-template #descTmp let-description>
@if (description) {
<small class="d-block form-text text-muted">{{ description }}</small>
}
</ng-template>

<div class="col-md-8"><div class="py-0" [ngbNavOutlet]="nav"></div></div>
}

@if (!groups.length) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, Output, inject, DOCUMENT } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import { NgTemplateOutlet, NgStyle } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ConfigStateService, LocalizationPipe, TrackByService } from '@abp/ng.core';
import {
Expand All @@ -17,7 +17,7 @@ import {
ModalComponent,
ToasterService,
} from '@abp/ng.theme.shared';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { Tabs, TabList, Tab, TabPanel, TabContent } from '@angular/aria/tabs';
import { finalize } from 'rxjs/operators';
import { FreeTextInputDirective } from '../../directives';
import { FeatureManagement } from '../../models';
Expand All @@ -36,11 +36,16 @@ const DEFAULT_PROVIDER_NAME = 'D';
exportAs: 'abpFeatureManagement',
imports: [
NgTemplateOutlet,
NgStyle,
ButtonComponent,
ModalComponent,
LocalizationPipe,
FormsModule,
NgbNavModule,
Tabs,
TabList,
Tab,
TabPanel,
TabContent,
FreeTextInputDirective,
ModalCloseDirective,
],
Expand Down
3 changes: 3 additions & 0 deletions npm/ng-packs/packages/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"@abp/ng.theme.shared": "~10.1.0-rc.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/aria": "~21.0.0"
},
"publishConfig": {
"access": "public"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,47 @@ <h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser') | abpLocali
<ng-template #abpBody>
@if (form) {
<form [formGroup]="form" (ngSubmit)="save()">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>
<ng-template ngbNavContent>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</ng-template>
</li>
<div ngTabs selectionMode="follow">
<div ngTabList [(selectedTab)]="selectedTab" class="nav nav-tabs">
<button ngTab #tabInfo="ngTab" [value]="'user-info'" class="nav-link" [class.active]="tabInfo.selected()" type="button">
{{ 'AbpIdentity::UserInformations' | abpLocalization }}
</button>
<button ngTab #tabRoles="ngTab" [value]="'roles'" class="nav-link" [class.active]="tabRoles.selected()" type="button">
{{ 'AbpIdentity::Roles' | abpLocalization }}
</button>
</div>

<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::Roles' | abpLocalization }}</a>
<ng-template ngbNavContent>
@for (roleGroup of roleGroups; track $index; let i = $index) {
<div class="form-check mb-2">
<abp-checkbox
*abpReplaceableTemplate="{
inputs: {
checkboxId: 'roles-' + i,
label: roles[i].name,
formControl: roleGroup.controls[roles[i].name]
},
componentKey: inputKey
}"
[checkboxId]="'roles-' + i"
[formControl]="roleGroup.controls[roles[i].name]"
[label]="roles[i].name"
>
</abp-checkbox>
</div>
}
</ng-template>
</li>
</ul>
<div class="mt-2 fade-in-top" [ngbNavOutlet]="nav"></div>
<div class="mt-2 fade-in-top">
<div ngTabPanel [value]="'user-info'">
<ng-template ngTabContent>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</ng-template>
</div>

<div ngTabPanel [value]="'roles'">
<ng-template ngTabContent>
@for (roleGroup of roleGroups; track $index; let i = $index) {
<div class="form-check mb-2">
<abp-checkbox
*abpReplaceableTemplate="{
inputs: {
checkboxId: 'roles-' + i,
label: roles[i].name,
formControl: roleGroup.controls[roles[i].name]
},
componentKey: inputKey
}"
[checkboxId]="'roles-' + i"
[formControl]="roleGroup.controls[roles[i].name]"
[label]="roles[i].name"
>
</abp-checkbox>
</div>
}
</ng-template>
</div>
</div>
</div>
</form>
} @else {
<div class="text-center"><i class="fa fa-pulse fa-spinner" aria-hidden="true"></i></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import {
import { finalize, switchMap, tap } from 'rxjs/operators';
import { eIdentityComponents } from '../../enums/components';
import { PageComponent } from '@abp/ng.components/page';
import { NgbDropdownModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { Tabs, TabList, Tab, TabPanel, TabContent } from '@angular/aria/tabs';
import { NgxValidateCoreModule } from '@ngx-validate/core';

@Component({
Expand All @@ -70,7 +71,11 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
FormsModule,
PermissionManagementComponent,
PageComponent,
NgbNavModule,
Tabs,
TabList,
Tab,
TabPanel,
TabContent,
NgbDropdownModule,
NgxValidateCoreModule,
LocalizationPipe,
Expand Down Expand Up @@ -101,6 +106,8 @@ export class UsersComponent implements OnInit {

selected?: IdentityUserDto;

selectedTab = 'user-info';

selectedUserRoles?: IdentityRoleDto[];

roles?: IdentityRoleDto[];
Expand Down Expand Up @@ -159,6 +166,7 @@ export class UsersComponent implements OnInit {
}

openModal() {
this.selectedTab = 'user-info';
this.buildForm();
this.isModalVisible = true;
}
Expand Down
3 changes: 3 additions & 0 deletions npm/ng-packs/packages/permission-management/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"@abp/ng.theme.shared": "~10.1.0-rc.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/aria": "~21.0.0"
},
"publishConfig": {
"access": "public"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,78 +43,93 @@ <h4>
<legend class="px-1 h5 mb-0">
{{ 'AbpPermissionManagement::PermissionGroup' | abpLocalization }}
</legend>
<div class="row">
<div class="row" ngTabs orientation="vertical">
<div class="col-md-4">
<div class="overflow-auto lpx-scroll-pills-container scroll-in-modal">
<ul class="nav nav-pills flex-column">
<div
class="overflow-auto lpx-scroll-pills-container scroll-in-modal"
ngTabList
orientation="vertical"
selectionMode="follow"
[selectedTab]="selectedGroup?.name"
(selectedTabChange)="onTabChange($event)"
>
<div class="nav nav-pills flex-column">
@for (group of permissionGroups(); track $index) {
<li class="border nav-item">
<div class="border nav-item">
@if ({ assignedCount: getAssignedCount(group.name) }; as count) {
<a
class="nav-link pointer"
[class.active]="selectedGroup?.name === group?.name"
(click)="onChangeGroup(group)"
(select)="setDisabled(group.permissions)"
<button
ngTab
[value]="group.name"
class="nav-link pointer text-start w-100"
#tab="ngTab"
[class.active]="tab.selected()"
type="button"
>
<div [class.font-weight-bold]="count.assignedCount">
{{ group?.displayName }}
@if (count.assignedCount > 0) {
<span>({{ count.assignedCount }})</span>
}
</div>
</a>
</button>
}
</li>
</div>
}
</ul>
</div>
</div>
</div>

<div class="col-md-8 scroll-in-modal">
<div class="ps-1">
@if (selectedGroupPermissions.length) {
<div class="form-check mb-2">
<input
#selectAllInThisTabsRef
type="checkbox"
id="select-all-in-this-tabs"
name="select-all-in-this-tabs"
class="form-check-input"
[(ngModel)]="selectThisTab"
[disabled]="disableSelectAllTab"
(click)="onClickSelectThisTab()"
/>
<label class="form-check-label" for="select-all-in-this-tabs">{{
'AbpPermissionManagement::SelectAllInThisTab' | abpLocalization
}}</label>
</div>
<hr class="my-2" />
@for (permission of selectedGroupPermissions; track $index; let i = $index) {
<div [ngStyle]="permission.style" class="form-check mb-2">
<input
#permissionCheckbox
type="checkbox"
[checked]="getChecked(permission.name)"
[value]="getChecked(permission.name)"
[attr.id]="permission.name"
class="form-check-input"
[disabled]="isGrantedByOtherProviderName(permission.grantedProviders)"
(click)="onClickCheckbox(permission, permissionCheckbox.value)"
/>
<label class="form-check-label" [attr.for]="permission.name"
>{{ permission.displayName }}
@if (!hideBadges) {
@for (provider of permission.grantedProviders; track $index) {
<span class="badge bg-primary text-dark"
>{{ provider.providerName }}: {{ provider.providerKey }}</span
>
}
@for (group of permissionGroups(); track $index) {
<div ngTabPanel [value]="group.name">
<ng-template ngTabContent>
<div class="ps-1">
@if (selectedGroupPermissions.length) {
<div class="form-check mb-2">
<input
#selectAllInThisTabsRef
type="checkbox"
id="select-all-in-this-tabs"
name="select-all-in-this-tabs"
class="form-check-input"
[(ngModel)]="selectThisTab"
[disabled]="disableSelectAllTab"
(click)="onClickSelectThisTab()"
/>
<label class="form-check-label" for="select-all-in-this-tabs">{{
'AbpPermissionManagement::SelectAllInThisTab' | abpLocalization
}}</label>
Comment on lines +92 to +101
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The id attribute "select-all-in-this-tabs" is now used multiple times within the @for loop (once per tab panel). HTML id attributes must be unique within the document. Consider using a dynamic id like "select-all-in-tab-{{group.name}}" and update the corresponding label's for attribute to match.

Suggested change
id="select-all-in-this-tabs"
name="select-all-in-this-tabs"
class="form-check-input"
[(ngModel)]="selectThisTab"
[disabled]="disableSelectAllTab"
(click)="onClickSelectThisTab()"
/>
<label class="form-check-label" for="select-all-in-this-tabs">{{
'AbpPermissionManagement::SelectAllInThisTab' | abpLocalization
}}</label>
[attr.id]="'select-all-in-tab-' + group.name"
name="select-all-in-this-tabs"
class="form-check-input"
[(ngModel)]="selectThisTab"
[disabled]="disableSelectAllTab"
(click)="onClickSelectThisTab()"
/>
<label
class="form-check-label"
[attr.for]="'select-all-in-tab-' + group.name"
>{{
'AbpPermissionManagement::SelectAllInThisTab' | abpLocalization
}}</label
>

Copilot uses AI. Check for mistakes.
</div>
<hr class="my-2" />
@for (permission of selectedGroupPermissions; track $index; let i = $index) {
<div [ngStyle]="permission.style" class="form-check mb-2">
<input
#permissionCheckbox
type="checkbox"
[checked]="getChecked(permission.name)"
[value]="getChecked(permission.name)"
[attr.id]="permission.name"
class="form-check-input"
[disabled]="isGrantedByOtherProviderName(permission.grantedProviders)"
(click)="onClickCheckbox(permission, permissionCheckbox.value)"
/>
<label class="form-check-label" [attr.for]="permission.name"
>{{ permission.displayName }}
@if (!hideBadges) {
@for (provider of permission.grantedProviders; track $index) {
<span class="badge bg-primary text-dark"
>{{ provider.providerName }}: {{ provider.providerKey }}</span
>
}
}
</label>
</div>
}
</label>
}
</div>
}
}
</div>
</ng-template>
</div>
}
</div>
</div>
</fieldset>
Expand Down
Loading
Loading