From 710d014455779412dc28b1b5bc34d96b3f0b27b3 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 16 Nov 2021 18:58:10 +0200 Subject: [PATCH] don't use mouseenter if not needed --- .../listing/listing-component.directive.ts | 10 -- src/lib/listing/listing.module.ts | 3 +- .../table-content.component.html | 43 +++++ .../table-content.component.scss | 149 ++++++++++++++++++ .../table-content/table-content.component.ts | 68 ++++++++ src/lib/listing/table/table.component.html | 35 ++-- src/lib/listing/table/table.component.scss | 149 ------------------ src/lib/listing/table/table.component.ts | 45 +----- 8 files changed, 276 insertions(+), 226 deletions(-) create mode 100644 src/lib/listing/table-content/table-content.component.html create mode 100644 src/lib/listing/table-content/table-content.component.scss create mode 100644 src/lib/listing/table-content/table-content.component.ts diff --git a/src/lib/listing/listing-component.directive.ts b/src/lib/listing/listing-component.directive.ts index c90035e..e238f8d 100644 --- a/src/lib/listing/listing-component.directive.ts +++ b/src/lib/listing/listing-component.directive.ts @@ -22,7 +22,6 @@ export abstract class ListingComponent extends AutoUnsubscr readonly noMatch$ = this._noMatch$; readonly noContent$ = this._noContent$; readonly sortedDisplayedEntities$ = this._sortedDisplayedEntities$; - readonly listingMode$: Observable; abstract readonly tableColumnConfigs: readonly TableColumnConfig[]; abstract readonly tableHeaderLabel: string; @@ -34,21 +33,12 @@ export abstract class ListingComponent extends AutoUnsubscr protected constructor(protected readonly _injector: Injector) { super(); - this.listingMode$ = this._listingMode$.asObservable(); } get allEntities(): T[] { return this.entitiesService.all; } - get listingMode(): ListingMode { - return this._listingMode$.value; - } - - set listingMode(listingMode: ListingMode) { - this._listingMode$.next(listingMode); - } - private get _sortedDisplayedEntities$(): Observable { const sort = (entities: T[]) => this.sortingService.defaultSort(entities); const sortedEntities$ = this.listingService.displayed$.pipe(map(sort)); diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index 4e22d91..0fa3b7e 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -19,6 +19,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; import { PageHeaderComponent } from './page-header/page-header.component'; import { IqserButtonsModule } from '../buttons'; import { IqserHelpModeModule } from '../help-mode'; +import { TableContentComponent } from './table-content/table-content.component'; const matModules = [MatTooltipModule]; const components = [ @@ -45,7 +46,7 @@ const modules = [ const utils = [SyncWidthDirective]; @NgModule({ - declarations: [...components, ...utils], + declarations: [...components, ...utils, TableContentComponent], exports: [...components, ...utils], imports: [CommonModule, ...modules, ...matModules], }) diff --git a/src/lib/listing/table-content/table-content.component.html b/src/lib/listing/table-content/table-content.component.html new file mode 100644 index 0000000..3911d36 --- /dev/null +++ b/src/lib/listing/table-content/table-content.component.html @@ -0,0 +1,43 @@ + + + + + +
+ +
+
+ + +
+ +
+
+
+
+ + +
+ +
+ + + +
+
diff --git a/src/lib/listing/table-content/table-content.component.scss b/src/lib/listing/table-content/table-content.component.scss new file mode 100644 index 0000000..7015a81 --- /dev/null +++ b/src/lib/listing/table-content/table-content.component.scss @@ -0,0 +1,149 @@ +@use '../../../assets/styles/common-mixins' as mixins; + +:host cdk-virtual-scroll-viewport { + height: calc(100vh - 50px - 31px - 111px); + overflow-y: hidden !important; + @include mixins.scroll-bar; + + &.no-data { + display: none; + } + + &.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: var(--gridTemplateColumnsHover); + } + + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: var(--gridTemplateColumns); + display: grid; + + .table-item { + display: contents; + + > *:not(.selection-column):not(.scrollbar-placeholder) { + display: contents; + } + + > div, + .cell { + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + box-sizing: border-box; + border-bottom: 1px solid var(--iqser-separator); + height: var(--itemSize); + padding: 0 10px; + + &.center { + align-items: center; + justify-content: center; + } + } + + .cell { + min-width: 110px; + + &:first-of-type { + padding: 0 24px; + } + } + + .selection-column { + padding-right: 0 !important; + + iqser-round-checkbox .wrapper { + opacity: 0; + transition: opacity 0.2s; + + &.active { + opacity: 1; + } + } + + & + * > .cell:first-of-type { + padding: 0 10px; + } + } + + &.disabled > div, + &.disabled .cell { + background-color: var(--iqser-grey-2); + color: var(--iqser-disabled); + + .action-buttons { + color: initial; + } + } + + .table-item-title { + font-weight: 600; + @include mixins.line-clamp(1); + width: fit-content; + max-width: 100%; + } + + .action-buttons { + position: absolute; + display: none; + right: -11px; + top: 0; + height: 100%; + width: fit-content; + flex-direction: row; + align-items: center; + padding-left: 100px; + padding-right: 21px; + z-index: 1; + background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, var(--iqser-grey-2) 35%); + + mat-icon { + width: 14px; + } + + iqser-circle-button:not(:last-child) { + margin-right: 2px; + } + } + + input, + mat-select { + margin-top: 0; + } + + &:hover { + .selection-column iqser-round-checkbox .wrapper { + opacity: 1; + } + + .action-buttons { + display: flex; + } + } + + &:hover:not(.disabled) { + > div, + > * > div { + background-color: var(--iqser-not-disabled-table-item); + } + } + } + } + + &:hover { + overflow-y: auto !important; + + &.has-scrollbar { + .table-item { + .action-buttons { + right: 0; + padding-right: 13px; + } + + .scrollbar-placeholder { + display: none; + } + } + } + } +} diff --git a/src/lib/listing/table-content/table-content.component.ts b/src/lib/listing/table-content/table-content.component.ts new file mode 100644 index 0000000..fb7cf14 --- /dev/null +++ b/src/lib/listing/table-content/table-content.component.ts @@ -0,0 +1,68 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Inject, Input, OnDestroy, ViewChild } from '@angular/core'; +import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { tap } from 'rxjs/operators'; +import { AutoUnsubscribe } from '../../utils'; +import { IListable } from '../models'; +import { ListingComponent, ListingService } from '../index'; +import { HasScrollbarDirective } from '../../scrollbar'; + +@Component({ + selector: 'iqser-table-content', + templateUrl: './table-content.component.html', + styleUrls: ['./table-content.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TableContentComponent extends AutoUnsubscribe implements OnDestroy, AfterViewInit { + @Input() itemSize!: number; + @Input() itemMouseEnterFn?: (entity: T) => void; + @Input() itemMouseLeaveFn?: (entity: T) => void; + @Input() tableItemClasses?: Record boolean>; + @Input() selectionEnabled!: boolean; + + @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; + @ViewChild(HasScrollbarDirective, { static: true }) readonly hasScrollbarDirective!: HasScrollbarDirective; + + private _lastScrolledIndex = 0; + + constructor( + @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, + readonly listingService: ListingService, + ) { + super(); + this.addSubscription = this.listingComponent.noContent$.subscribe(() => { + setTimeout(() => { + this.scrollViewport?.checkViewportSize(); + }, 0); + }); + } + + ngAfterViewInit(): void { + this.addSubscription = this.scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))).subscribe(); + this.addSubscription = this.listingService.displayedLength$.subscribe(() => { + setTimeout(() => { + this.hasScrollbarDirective.process(); + }, 0); + }); + } + + scrollToLastIndex(): void { + this.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth'); + } + + trackById(index: number, entity: T): string { + return entity.id; + } + + getTableItemClasses(entity: T): Record { + const classes: Record = { + 'table-item': true, + pointer: !!entity.routerLink && entity.routerLink.length > 0, + }; + for (const key in this.tableItemClasses) { + if (Object.prototype.hasOwnProperty.call(this.tableItemClasses, key)) { + classes[key] = this.tableItemClasses[key](entity); + } + } + return classes; + } +} diff --git a/src/lib/listing/table/table.component.html b/src/lib/listing/table/table.component.html index 32f27c6..cb6baed 100644 --- a/src/lib/listing/table/table.component.html +++ b/src/lib/listing/table/table.component.html @@ -22,34 +22,17 @@ - - -
-
- -
- - - -
-
-
-
+ [selectionEnabled]="selectionEnabled" + [tableItemClasses]="tableItemClasses" +> diff --git a/src/lib/listing/table/table.component.scss b/src/lib/listing/table/table.component.scss index 7015a81..e69de29 100644 --- a/src/lib/listing/table/table.component.scss +++ b/src/lib/listing/table/table.component.scss @@ -1,149 +0,0 @@ -@use '../../../assets/styles/common-mixins' as mixins; - -:host cdk-virtual-scroll-viewport { - height: calc(100vh - 50px - 31px - 111px); - overflow-y: hidden !important; - @include mixins.scroll-bar; - - &.no-data { - display: none; - } - - &.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: var(--gridTemplateColumnsHover); - } - - ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: var(--gridTemplateColumns); - display: grid; - - .table-item { - display: contents; - - > *:not(.selection-column):not(.scrollbar-placeholder) { - display: contents; - } - - > div, - .cell { - display: flex; - flex-direction: column; - justify-content: center; - position: relative; - box-sizing: border-box; - border-bottom: 1px solid var(--iqser-separator); - height: var(--itemSize); - padding: 0 10px; - - &.center { - align-items: center; - justify-content: center; - } - } - - .cell { - min-width: 110px; - - &:first-of-type { - padding: 0 24px; - } - } - - .selection-column { - padding-right: 0 !important; - - iqser-round-checkbox .wrapper { - opacity: 0; - transition: opacity 0.2s; - - &.active { - opacity: 1; - } - } - - & + * > .cell:first-of-type { - padding: 0 10px; - } - } - - &.disabled > div, - &.disabled .cell { - background-color: var(--iqser-grey-2); - color: var(--iqser-disabled); - - .action-buttons { - color: initial; - } - } - - .table-item-title { - font-weight: 600; - @include mixins.line-clamp(1); - width: fit-content; - max-width: 100%; - } - - .action-buttons { - position: absolute; - display: none; - right: -11px; - top: 0; - height: 100%; - width: fit-content; - flex-direction: row; - align-items: center; - padding-left: 100px; - padding-right: 21px; - z-index: 1; - background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, var(--iqser-grey-2) 35%); - - mat-icon { - width: 14px; - } - - iqser-circle-button:not(:last-child) { - margin-right: 2px; - } - } - - input, - mat-select { - margin-top: 0; - } - - &:hover { - .selection-column iqser-round-checkbox .wrapper { - opacity: 1; - } - - .action-buttons { - display: flex; - } - } - - &:hover:not(.disabled) { - > div, - > * > div { - background-color: var(--iqser-not-disabled-table-item); - } - } - } - } - - &:hover { - overflow-y: auto !important; - - &.has-scrollbar { - .table-item { - .action-buttons { - right: 0; - padding-right: 13px; - } - - .scrollbar-placeholder { - display: none; - } - } - } - } -} diff --git a/src/lib/listing/table/table.component.ts b/src/lib/listing/table/table.component.ts index d5e669e..7e6e3f2 100644 --- a/src/lib/listing/table/table.component.ts +++ b/src/lib/listing/table/table.component.ts @@ -11,12 +11,11 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { AutoUnsubscribe, Required } from '../../utils'; import { IListable, ListingModes, TableColumnConfig } from '../models'; import { ListingComponent } from '../listing-component.directive'; -import { EntitiesService, ListingService } from '../services'; -import { HasScrollbarDirective } from '../../scrollbar'; +import { EntitiesService } from '../services'; +import { TableContentComponent } from '../table-content/table-content.component'; const SCROLLBAR_WIDTH = 11; @@ -43,19 +42,17 @@ export class TableComponent extends AutoUnsubscribe impleme @Input() noDataButtonIcon?: string; @Input() noDataButtonLabel?: string; @Input() showNoDataButton = false; - @Output() readonly noDataAction = new EventEmitter(); @Input() noMatchText?: string; - @Input() tableItemClasses?: { [key: string]: (e: T) => boolean }; + @Input() tableItemClasses?: Record boolean>; @Input() itemMouseEnterFn?: (entity: T) => void; @Input() itemMouseLeaveFn?: (entity: T) => void; - @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; - @ViewChild(HasScrollbarDirective, { static: true }) hasScrollbarDirective!: HasScrollbarDirective; + @Output() readonly noDataAction = new EventEmitter(); + @ViewChild(TableContentComponent, { static: true }) readonly tableContent!: TableContentComponent; constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, private readonly _hostRef: ViewContainerRef, readonly entitiesService: EntitiesService, - readonly listingService: ListingService, ) { super(); } @@ -68,42 +65,10 @@ export class TableComponent extends AutoUnsubscribe impleme return this.listingComponent.tableHeaderLabel; } - trackById(index: number, entity: T): string { - return entity.id; - } - ngOnInit(): void { - this.addSubscription = this.listingService.displayedLength$.subscribe(() => { - setTimeout(() => { - this.hasScrollbarDirective.process(); - }, 0); - }); - this.addSubscription = this.listingComponent.noContent$.subscribe(() => { - setTimeout(() => { - this.scrollViewport?.checkViewportSize(); - }, 0); - }); - this._patchConfig(); this._setStyles(); } - getTableItemClasses(entity: T): { [key: string]: boolean } { - const classes: { [key: string]: boolean } = { - 'table-item': true, - pointer: !!entity.routerLink && entity.routerLink.length > 0, - }; - for (const key in this.tableItemClasses) { - if (Object.prototype.hasOwnProperty.call(this.tableItemClasses, key)) { - classes[key] = this.tableItemClasses[key](entity); - } - } - return classes; - } - - private _patchConfig() { - this.tableColumnConfigs[this.tableColumnConfigs.length - 1].last = true; - } - private _setStyles(): void { const element = this._hostRef.element.nativeElement as HTMLElement; this._setColumnsWidth(element);