-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Michele Nuzzi <m.nuzzi@cittametropolitana.ba.it> Co-authored-by: Antonino Bonanno <nino.bonanno96@gmail.com> Co-authored-by: Andrea Stagi <stagi.andrea@gmail.com>
- Loading branch information
1 parent
a7ed045
commit 0f039f4
Showing
12 changed files
with
576 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
...ign-angular-kit/src/lib/components/core/table/sort/sort-header/sort-header.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<!-- | ||
We set the `tabindex` on an element inside the table header, rather than the header itself, | ||
because of a bug in NVDA where having a `tabindex` on a `th` breaks keyboard navigation in the | ||
table (see https://github.com/nvaccess/nvda/issues/7718). This allows for the header to both | ||
be focusable, and have screen readers read out its `aria-sort` state. We prefer this approach | ||
over having a button with an `aria-label` inside the header, because the button's `aria-label` | ||
will be read out as the user is navigating the table's cell (see #13012). | ||
The approach is based off of: https://dequeuniversity.com/library/aria/tables/sf-sortable-grid | ||
--> | ||
<div class="it-sort-header-container it-focus-indicator" | ||
[class.it-sort-header-sorted]="isSorted" | ||
[class.it-sort-header-position-before]="arrowPosition === 'before'" | ||
[attr.tabindex]="isDisabled ? null : 0" | ||
[attr.role]="isDisabled ? null : 'button'"> | ||
|
||
<!-- | ||
We have to keep it due to a large number of screenshot diff failures. It should be removed eventually. | ||
Note that the difference isn't visible with a shorter header, but once it breaks up into multiple lines, this element | ||
causes it to be center-aligned, whereas removing it will keep the text to the left. | ||
--> | ||
<div class="it-sort-header-content"> | ||
<ng-content></ng-content> | ||
</div> | ||
|
||
<it-icon class="it-sort-arrow" size="sm" [name]="arrowIconClass" /> | ||
</div> |
55 changes: 55 additions & 0 deletions
55
...ign-angular-kit/src/lib/components/core/table/sort/sort-header/sort-header.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
.it-sort-header-container { | ||
display: flex; | ||
cursor: pointer; | ||
align-items: center; | ||
justify-content: space-between; | ||
letter-spacing: normal; | ||
|
||
// Needs to be reset since we don't want an outline around the inner | ||
// div which is focusable. We have our own alternate focus styling. | ||
outline: 0; | ||
|
||
.it-sort-header-disabled & { | ||
cursor: default; | ||
|
||
.it-sort-arrow { | ||
opacity: 0 !important; | ||
} | ||
} | ||
|
||
// For the sort-header element, default inset/offset values are necessary to ensure that | ||
// the focus indicator is sufficiently contrastive and renders appropriately. | ||
&::before { | ||
$border-width: 3px; | ||
$offset: calc(#{$border-width} + 2px); | ||
margin: calc(#{$offset} * -1); | ||
} | ||
|
||
&.it-sort-header-position-before { | ||
flex-direction: row-reverse; | ||
justify-content: left; | ||
gap: 0.5rem; | ||
} | ||
|
||
.it-sort-arrow { | ||
opacity: 0; | ||
transition: opacity .3s ease-out; | ||
-moz-transition: opacity .3s ease-out; | ||
-webkit-transition: opacity .3s ease-out; | ||
-o-transition: opacity .3s ease-out; | ||
} | ||
|
||
&:hover { | ||
.it-sort-arrow { | ||
opacity: 0.5; | ||
} | ||
} | ||
|
||
&.it-sort-header-sorted { | ||
.it-sort-arrow { | ||
opacity: 1 !important; | ||
} | ||
} | ||
} | ||
|
||
|
30 changes: 30 additions & 0 deletions
30
...-angular-kit/src/lib/components/core/table/sort/sort-header/sort-header.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { ItSortHeaderComponent } from './sort-header.component'; | ||
import { ItSortDirective } from '../sort.directive'; | ||
import {tb_base} from "../../../../../../test"; | ||
|
||
describe('ItSortHeaderComponent', () => { | ||
let component: ItSortHeaderComponent; | ||
let fixture: ComponentFixture<ItSortHeaderComponent>; | ||
let sortDirective: ItSortDirective = new ItSortDirective(); | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [ | ||
ItSortHeaderComponent, | ||
], | ||
providers: [ | ||
{ provide: ItSortDirective, useValue: sortDirective }, | ||
...tb_base.providers | ||
], | ||
}); | ||
fixture = TestBed.createComponent(ItSortHeaderComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
174 changes: 174 additions & 0 deletions
174
...esign-angular-kit/src/lib/components/core/table/sort/sort-header/sort-header.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import { | ||
booleanAttribute, | ||
ChangeDetectionStrategy, | ||
ChangeDetectorRef, | ||
Component, | ||
HostBinding, | ||
HostListener, | ||
Inject, | ||
Input, | ||
OnDestroy, | ||
OnInit, | ||
Optional, | ||
ViewEncapsulation | ||
} from '@angular/core'; | ||
import {CommonModule} from '@angular/common'; | ||
import {ItSortDirective,} from '../sort.directive'; | ||
import {merge, Subscription} from 'rxjs'; | ||
import {ItIconComponent} from '../../../../utils/icon/icon.component'; | ||
import {IconName} from "../../../../../interfaces/icon"; | ||
import { | ||
IT_SORT_DEFAULT_OPTIONS, | ||
ItSortable, | ||
ItSortDefaultOptions, | ||
SortDirection, | ||
SortHeaderArrowPosition | ||
} from "../../../../../interfaces/sortable-table"; | ||
|
||
|
||
/** | ||
* Applies sorting behavior (click to change sort) and styles to an element, including an | ||
* arrow to display the current sort direction. | ||
* | ||
* Must be provided with an id and contained within a parent ItSort directive. | ||
* | ||
* If used on header cells in a CdkTable, it will automatically default its id from its containing | ||
* column definition. | ||
*/ | ||
@Component({ | ||
// eslint-disable-next-line @angular-eslint/component-selector | ||
selector: '[it-sort-header]', | ||
exportAs: 'itSortHeader', | ||
standalone: true, | ||
imports: [CommonModule, ItIconComponent], | ||
templateUrl: './sort-header.component.html', | ||
styleUrls: ['./sort-header.component.scss'], | ||
encapsulation: ViewEncapsulation.None, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class ItSortHeaderComponent implements ItSortable, OnDestroy, OnInit { | ||
/** | ||
* ID of this sort header. If used within the context of a CdkColumnDef, this will default to | ||
* the column's name. | ||
*/ | ||
@Input('it-sort-header') id!: string; | ||
|
||
/** Sets the position of the arrow that displays when sorted. */ | ||
@Input() arrowPosition: SortHeaderArrowPosition = 'after'; | ||
|
||
/** Overrides the sort start value of the containing MatSort for this SortHeaderComponent. */ | ||
@Input() start?: SortDirection; | ||
|
||
/** whether the sort header is disabled. */ | ||
@Input({transform: booleanAttribute}) | ||
sortDisabled: boolean = false; | ||
|
||
/** Overrides the disable clear value of the containing SortDirective for this MatSortable. */ | ||
@Input({transform: booleanAttribute}) | ||
disableSortClear?: boolean; | ||
|
||
@HostBinding('class') | ||
public readonly sortHeaderClass = 'it-sort-header'; | ||
|
||
private _rerenderSubscription?: Subscription; | ||
|
||
/** The direction the arrow should be facing according to the current state. */ | ||
private _arrowDirection?: SortDirection; | ||
|
||
constructor( | ||
private readonly _changeDetectorRef: ChangeDetectorRef, | ||
// `SortDirective` is not optionally injected, but just asserted manually w/ better error. | ||
@Optional() public readonly _sort: ItSortDirective, | ||
@Optional() @Inject(IT_SORT_DEFAULT_OPTIONS) defaultOptions?: ItSortDefaultOptions, | ||
) { | ||
if (defaultOptions?.arrowPosition) { | ||
this.arrowPosition = defaultOptions?.arrowPosition; | ||
} | ||
|
||
this._handleStateChanges(); | ||
} | ||
|
||
ngOnInit() { | ||
// Initialize the direction of the arrow and set the view state to be immediately that state. | ||
this.updateArrowDirection(); | ||
this._sort.register(this); | ||
} | ||
|
||
ngOnDestroy() { | ||
this._sort.deregister(this); | ||
this._rerenderSubscription?.unsubscribe(); | ||
} | ||
|
||
@HostListener('click') | ||
_handleClick() { | ||
if (!this.isDisabled) { | ||
this._sort.sort(this); | ||
} | ||
} | ||
|
||
/** | ||
* Whether this MatSortHeader is currently sorted in either ascending or descending order. | ||
*/ | ||
protected get isSorted() { | ||
return ( | ||
this._sort.active == this.id && | ||
(this._sort.direction === 'asc' || this._sort.direction === 'desc') | ||
); | ||
} | ||
|
||
/** | ||
* Returns the icon class by the arrow direction | ||
*/ | ||
protected get arrowIconClass(): IconName { | ||
return `${this._arrowDirection == 'asc' ? 'arrow-up' : 'arrow-down'}`; | ||
} | ||
|
||
/** | ||
* Updates the direction the arrow should be pointing. If it is not sorted, the arrow should be | ||
* facing the start direction. Otherwise if it is sorted, the arrow should point in the currently | ||
* active sorted direction. The reason this is updated through a function is because the direction | ||
* should only be changed at specific times - when deactivated but the hint is displayed and when | ||
* the sort is active and the direction changes. Otherwise the arrow's direction should linger | ||
* in cases such as the sort becoming deactivated but we want to animate the arrow away while | ||
* preserving its direction, even though the next sort direction is actually different and should | ||
* only be changed once the arrow displays again (hint or activation). | ||
*/ | ||
private updateArrowDirection() { | ||
this._arrowDirection = this.isSorted ? this._sort.direction : this.start || this._sort.start; | ||
} | ||
|
||
@HostBinding('class.it-sort-header-disabled') | ||
public get isDisabled() { | ||
return this._sort.sortDisabled || this.sortDisabled; | ||
} | ||
|
||
/** | ||
* Gets the aria-sort attribute that should be applied to this sort header. If this header | ||
* is not sorted, returns null so that the attribute is removed from the host element. Aria spec | ||
* says that the aria-sort property should only be present on one header at a time, so removing | ||
* ensures this is true. | ||
*/ | ||
@HostBinding('attr.aria-sort') | ||
public get ariaSortAttribute() { | ||
if (!this.isSorted) { | ||
return 'none'; | ||
} | ||
|
||
return this._sort.direction == 'asc' ? 'ascending' : 'descending'; | ||
} | ||
|
||
|
||
/** Handles changes in the sorting state. */ | ||
private _handleStateChanges() { | ||
this._rerenderSubscription = merge( | ||
this._sort.sortChange, | ||
this._sort._stateChanges, | ||
).subscribe(() => { | ||
if (this.isSorted) { | ||
this.updateArrowDirection(); | ||
} | ||
this._changeDetectorRef.markForCheck(); | ||
}); | ||
} | ||
|
||
} |
75 changes: 75 additions & 0 deletions
75
projects/design-angular-kit/src/lib/components/core/table/sort/sort.directive.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||
import {ItSortDirective} from './sort.directive'; | ||
import {tb_base} from 'projects/design-angular-kit/src/test'; | ||
import {TranslateModule} from '@ngx-translate/core'; | ||
import {ChangeDetectionStrategy, Component, DebugElement} from '@angular/core'; | ||
import {By} from '@angular/platform-browser'; | ||
import {ItSortHeaderComponent} from './sort-header/sort-header.component'; | ||
import {ItSortEvent} from "../../../../interfaces/sortable-table"; | ||
|
||
@Component({ | ||
standalone: true, | ||
template: ` | ||
<it-table itSort (sortChange)="sortData($event)"> | ||
<ng-container thead> | ||
<tr> | ||
<th it-sort-header="name" scope="col">Nome</th> | ||
</tr> | ||
</ng-container> | ||
<ng-container tbody> | ||
<tr> | ||
<td>Mario</td> | ||
</tr> | ||
<tr> | ||
<td>Alessandro</td> | ||
</tr> | ||
<tr> | ||
<td>Francesco</td> | ||
</tr> | ||
</ng-container> | ||
</it-table>`, | ||
imports: [ItSortDirective, ItSortHeaderComponent], | ||
}) | ||
class TestComponent { | ||
sortData(event: ItSortEvent) { | ||
} | ||
} | ||
|
||
describe('ItSortDirective', () => { | ||
let component: TestComponent; | ||
let fixture: ComponentFixture<TestComponent>; | ||
let des: DebugElement[]; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [ItSortDirective, ItSortHeaderComponent, TestComponent, TranslateModule.forRoot()], | ||
providers: tb_base.providers | ||
}) | ||
.overrideComponent(TestComponent, { | ||
set: {changeDetection: ChangeDetectionStrategy.Default} | ||
}) | ||
.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(TestComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
|
||
// all elements with an attached SortDirective | ||
des = fixture.debugElement.queryAll(By.directive(ItSortHeaderComponent)); | ||
|
||
}); | ||
|
||
it('should create an instance', () => { | ||
const directive = new ItSortDirective(); | ||
expect(directive).toBeTruthy(); | ||
}); | ||
|
||
it('should emit sort event', () => { | ||
const th = des[0].nativeElement as HTMLTableColElement; | ||
spyOn(component, "sortData"); | ||
th.dispatchEvent(new Event('click')); | ||
fixture.detectChanges(); | ||
expect(component.sortData).toHaveBeenCalledWith({active: "name", direction: "asc"}); | ||
}); | ||
}); |
Oops, something went wrong.
0f039f4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
design-angular-kit – ./
design-angular-kit-git-main-dip-trasformazione-digitale.vercel.app
design-angular-kit.vercel.app
design-angular-kit-dip-trasformazione-digitale.vercel.app