Skip to content

Commit

Permalink
Feature/change infinity scroll to cdk virtual scroll on select search…
Browse files Browse the repository at this point in the history
… and select multi tags (#86)

* feature: change infinity scroll to cdk-virtual-scroll on sq-select-search

* feature: change sq-select-multi-tag from infinity-scroll to cdk-virtual-scroll

* remove searchFromAnotherArray

* add dynamic height

* handle multi-tags with collapses

* bump

* bump
  • Loading branch information
JoaoBianco authored Sep 26, 2024
1 parent 8d8add9 commit 7b4b6cd
Show file tree
Hide file tree
Showing 11 changed files with 3,199 additions and 2,737 deletions.
5,661 changes: 3,123 additions & 2,538 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"dependencies": {
"@angular/animations": "^17.3.5",
"@angular/common": "^17.3.5",
"@angular/cdk": "^17.3.5",
"@angular/compiler": "^17.3.5",
"@angular/core": "^17.3.5",
"@angular/forms": "^17.3.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,11 @@
<span class="icon icon-external textarea-icon"><i class='fas fa-search'></i></span>
</div>
</div>
<sq-infinity-scroll
class="list"
elementToScrollId="sq-select-multi-tags-scroll"
loaderColor="var(--primary_color)"
[length]="_options.length || 0"
[hasMore]="hasMoreOptions"
[loading]="loadingScroll"
(scrolledEmitter)="addMoreOptions()"
>
<ng-container *ngFor="let opt of _options | searchFromAlternativeArray:searchText:options; let i = index; trackBy: trackByOptValue">
<ng-template *ngTemplateOutlet="option; context: { opt: opt, i: i }"></ng-template>
<cdk-virtual-scroll-viewport [itemSize]="cdkItemSize" [ngStyle]="{ 'height': cdkVirtualScrollViewportHeight }" class="list scrollbar">
<ng-container *cdkVirtualFor="let opt of _options | search:searchText; i as index; trackBy: trackByOptValue">
<ng-template *ngTemplateOutlet="option; context: { opt: opt, i: index }"></ng-template>
</ng-container>
</sq-infinity-scroll>
</cdk-virtual-scroll-viewport>
<p *ngIf="!_options?.length">
{{ 'forms.searchSelectEmpty' | translateInternal | async }}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,39 +240,29 @@ export class SqSelectMultiTagsComponent implements OnChanges {
nativeElement: ElementRef

/**
* Control pagination for options
* Control options to render
*/
_options: Array<OptionMulti> = []

/**
* Indicate if has more options to add on _options
*/
hasMoreOptions = true

/**
* Loading for sq-infinity-scroll
* Control the readonly on reach the maxTags
*/
loadingScroll = false
isMaxTags = false

/**
* Control quantity for limit and to addMore on _options
* Timeout for input changes.
*/
quantity = 15
timeoutInput!: ReturnType<typeof setTimeout>

/**
* Control the _options limit
* The height for the cdk-virtual-scroll-viewport (default 305px).
*/
limit = this.quantity
cdkVirtualScrollViewportHeight = '305px'

/**
* Control the readonly on reach the maxTags
* The size for the cdk-virtual-scroll-viewport (default 32px).
*/
isMaxTags = false

/**
* Timeout for input changes.
*/
timeoutInput!: ReturnType<typeof setTimeout>
cdkItemSize: string | null = '32'

/**
* Constructs a new SqSelectMultiTagsComponent.
Expand All @@ -291,9 +281,6 @@ export class SqSelectMultiTagsComponent implements OnChanges {
* @param changes - The changes detected in the component's input properties.
*/
async ngOnChanges(changes: SimpleChanges) {
if (this.open && changes.hasOwnProperty('options')) {
this.addMoreOptions(true)
}
if (changes.hasOwnProperty('value') || changes.hasOwnProperty('minTags') || changes.hasOwnProperty('maxTags')) {
this.validate()
}
Expand Down Expand Up @@ -402,7 +389,10 @@ export class SqSelectMultiTagsComponent implements OnChanges {
}, 300))
this.changeDetector.detectChanges()
} else {
this.addMoreOptions()
if (this.options.length < 15) {
this.cdkVirtualScrollViewportHeight = this.options.length * 32 + 'px'
}
this._options = this.options
this.renderOptionsList = true
this.open = await new Promise<boolean>(resolve => setTimeout(() => {
resolve(true)
Expand All @@ -417,20 +407,25 @@ export class SqSelectMultiTagsComponent implements OnChanges {
closeDropdown() {
this.open = false
this._options = []
this.limit = this.quantity
this.hasMoreOptions = true
this.searchText = ''
this.closeChange.emit(this.valueChanged)
this.valueChanged = false
}

/**
* Handles the collapse of an item.
* Handles the collapse of an item and set the cdkItemSize if the item is open.
*
* @param {OptionMulti} item - The item to collapse.
*/
handleCollapse(item: OptionMulti) {
item.open = !item.open
if (item.children) {
if (this.options.find((option) => option.open) || item.open) {
this.cdkItemSize = null
} else {
this.cdkItemSize = '32'
}
}
}

/**
Expand Down Expand Up @@ -487,18 +482,4 @@ export class SqSelectMultiTagsComponent implements OnChanges {
}
}

/**
* Function to add more values on _options
*/
addMoreOptions(isOnChange = false) {
if (this.hasMoreOptions || isOnChange) {
this.loadingScroll = true
const limitState = this.limit > this.options?.length ? this.options.length : this.limit
this._options = this.options.slice(0, limitState)
this.limit = this.limit + this.quantity
this.hasMoreOptions = limitState !== this.options.length
this.loadingScroll = false
this.changeDetector.detectChanges()
}
}
}
16 changes: 4 additions & 12 deletions src/components/sq-select-search/sq-select-search.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,11 @@
<span class="icon icon-external textarea-icon"><i class='fas fa-search'></i></span>
</div>
</div>
<sq-infinity-scroll
class="list"
elementToScrollId="sq-select-search-scroll"
loaderColor="var(--primary_color)"
[length]="_options.length || 0"
[hasMore]="hasMoreOptions"
[loading]="loadingScroll"
(scrolledEmitter)="addMoreOptions()"
>
<ng-container *ngFor="let opt of _options | searchFromAlternativeArray:searchText:options; let i = index; trackBy: trackByOptValue">
<ng-template *ngTemplateOutlet="option; context: { opt: opt, i: i }"></ng-template>
<cdk-virtual-scroll-viewport itemSize="22" [ngStyle]="{ 'height': cdkVirtualScrollViewportHeight }" class="list scrollbar">
<ng-container *cdkVirtualFor="let opt of _options | search:searchText; i as index; trackBy: trackByOptValue">
<ng-template *ngTemplateOutlet="option; context: { opt: opt, i: index }"></ng-template>
</ng-container>
</sq-infinity-scroll>
</cdk-virtual-scroll-viewport>
<p class="mb-0 mt-3" *ngIf="!_options?.length">
{{ 'forms.searchSelectEmpty' | translateInternal | async }}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
max-height: 400px;
background: var(--background_secondary);
padding: 0 0.7rem 1.1rem;
overflow-y: auto;
overflow-y: hidden;
box-shadow: var(--box_shadow);
}
}
Expand Down
63 changes: 11 additions & 52 deletions src/components/sq-select-search/sq-select-search.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, Input, OnChanges, Optional, Output, SimpleChanges, TemplateRef, TrackByFunction } from '@angular/core'
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, Input, Optional, Output, TemplateRef, TrackByFunction } from '@angular/core'
import { TranslateService } from '@ngx-translate/core'
import { useMemo } from '../../helpers/memo.helper'
import { Option } from '../../interfaces/option.interface'
Expand All @@ -15,8 +15,6 @@ import { Option } from '../../interfaces/option.interface'
* (searchChange)="handleSearch($event)"
* >
* </sq-select-search>
*
* @implements {OnChanges}
*/
@Component({
selector: 'sq-select-search',
Expand All @@ -25,7 +23,7 @@ import { Option } from '../../interfaces/option.interface'
styleUrls: ['./sq-select-search.component.scss'],
providers: [],
})
export class SqSelectSearchComponent implements OnChanges {
export class SqSelectSearchComponent {
/**
* The name attribute for the search-based select input.
*/
Expand Down Expand Up @@ -197,34 +195,19 @@ export class SqSelectSearchComponent implements OnChanges {
open = false

/**
* Control pagination for options
* Control options to render
*/
_options: Array<Option> = []

/**
* Indicate if has more options to add on _options
*/
hasMoreOptions = true

/**
* Loading for sq-infinity-scroll
*/
loadingScroll = false

/**
* Control quantity for limit and to addMore on _options
* Timeout for input changes.
*/
quantity = 15
timeoutInput!: ReturnType<typeof setTimeout>

/**
* Control the _options limit
* The height for the cdk-virtual-scroll-viewport (default 305px).
*/
limit = this.quantity

/**
* Timeout for input changes.
*/
timeoutInput!: ReturnType<typeof setTimeout>
cdkVirtualScrollViewportHeight = '305px'

/**
* Constructs a new SqSelectSearchComponent.
Expand All @@ -237,17 +220,6 @@ export class SqSelectSearchComponent implements OnChanges {
this.nativeElement = element.nativeElement
}

/**
* Lifecycle hook called when any input properties change.
*
* @param changes - The changes detected in the component's input properties.
*/
async ngOnChanges(changes: SimpleChanges) {
if (this.open && changes.hasOwnProperty('options')) {
this.addMoreOptions(true)
}
}

/**
* Emits the selected value and closes the dropdown.
*
Expand Down Expand Up @@ -286,7 +258,10 @@ export class SqSelectSearchComponent implements OnChanges {
}, 300))
this.changeDetector.detectChanges()
} else {
this.addMoreOptions()
if (this.options.length < 15) {
this.cdkVirtualScrollViewportHeight = this.options.length * 22 + 'px'
}
this._options = this.options
this.renderOptionsList = true
this.open = await new Promise<boolean>(resolve => setTimeout(() => {
resolve(true)
Expand All @@ -301,8 +276,6 @@ export class SqSelectSearchComponent implements OnChanges {
closeDropdown() {
this.open = false
this._options = []
this.limit = this.quantity
this.hasMoreOptions = true
this.searchText = ''
}

Expand Down Expand Up @@ -338,18 +311,4 @@ export class SqSelectSearchComponent implements OnChanges {
}
}

/**
* Function to add more values on _options
*/
addMoreOptions(isOnChange = false) {
if (this.hasMoreOptions || isOnChange) {
this.loadingScroll = true
const limitState = this.limit > this.options.length ? this.options.length : this.limit
this._options = this.options.slice(0, limitState)
this.limit = this.limit + this.quantity
this.hasMoreOptions = limitState !== this.options.length
this.loadingScroll = false
this.changeDetector.detectChanges()
}
}
}
61 changes: 30 additions & 31 deletions src/main.module.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
import { ScrollingModule } from '@angular/cdk/scrolling'
import { CommonModule } from '@angular/common'
import { NgModule, Type } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'
import { SqAccordionComponent } from './components/sq-accordion/sq-accordion.component'
import { SqCollapseComponent } from './components/sq-accordion/sq-collapse/sq-collapse.component'
import { SqButtonComponent } from './components/sq-button/sq-button.component'
import { CommonModule } from '@angular/common'
import { SqCountdownComponent } from './components/sq-countdown/sq-countdown.component'
import { SqInfinityComponent } from './components/sq-infinity-scroll/sq-infinity-scroll.component'
import { SqInputDateComponent } from './components/sq-input-date/sq-input-date.component'
import { SqInputFileComponent } from './components/sq-input-file/sq-input-file.component'
import { SqInputMaskComponent } from './components/sq-input-mask/sq-input-mask.component'
import { SqInputMoneyComponent } from './components/sq-input-money/sq-input-money.component'
import { SqInputNumberComponent } from './components/sq-input-number/sq-input-number.component'
import { SqInputRangeComponent } from './components/sq-input-range/sq-input-range.component'
import { SqInputComponent } from './components/sq-input/sq-input.component'
import { SqLoaderComponent } from './components/sq-loader/sq-loader.component'
import { SqModalComponent } from './components/sq-modal/sq-modal.component'
import { SqOverlayComponent } from './components/sq-overlay/sq-overlay.component'
import { SqPaginationComponent } from './components/sq-pagination/sq-pagination.component'
import { SqProgressBarComponent } from './components/sq-progress-bar/sq-progress-bar.component'
import { SqSelectMultiTagsComponent } from './components/sq-select-multi-tags/sq-select-multi-tags.component'
import { SqSelectSearchComponent } from './components/sq-select-search/sq-select-search.component'
import { SqSelectComponent } from './components/sq-select/sq-select.component'
import { SqSelectorComponent } from './components/sq-selector/sq-selector.component'
import { SqStepsComponent } from './components/sq-steps/sq-steps.component'
import { SqTooltipDirective } from './directives/sq-tooltip/sq-tooltip.directive'
import { SqTooltipComponent } from './components/sq-tooltip/sq-tooltip.component'
import { UniversalSafePipe } from './pipes/universal-safe/universal-safe.pipe'
import { ThousandSuffixesPipe } from './pipes/thousands/thousands.pipe'
import { SqTabComponent } from './components/sq-tabs/sq-tab/sq-tab.component'
import { SqTabsComponent } from './components/sq-tabs/sq-tabs.component'
import { SqInfinityComponent } from './components/sq-infinity-scroll/sq-infinity-scroll.component'
import { SqOverlayComponent } from './components/sq-overlay/sq-overlay.component'
import { SqModalComponent } from './components/sq-modal/sq-modal.component'
import { SqCollapseComponent } from './components/sq-accordion/sq-collapse/sq-collapse.component'
import { SqPaginationComponent } from './components/sq-pagination/sq-pagination.component'
import { SqTagComponent } from './components/sq-tag/sq-tag.component'
import { SqAccordionComponent } from './components/sq-accordion/sq-accordion.component'
import { SqTextAreaComponent } from './components/sq-textarea/sq-textarea.component'
import { FormsModule } from '@angular/forms'
import { SqSelectComponent } from './components/sq-select/sq-select.component'
import { SqSelectorComponent } from './components/sq-selector/sq-selector.component'
import { SqInputComponent } from './components/sq-input/sq-input.component'
import { SqInputDateComponent } from './components/sq-input-date/sq-input-date.component'
import { SqInputRangeComponent } from './components/sq-input-range/sq-input-range.component'
import { SqInputFileComponent } from './components/sq-input-file/sq-input-file.component'
import { SqDropdownDirective } from './directives/sq-dropdown/sq-dropdown.directive'
import { SqSelectSearchComponent } from './components/sq-select-search/sq-select-search.component'
import { SqTooltipComponent } from './components/sq-tooltip/sq-tooltip.component'
import { SqClickOutsideDirective } from './directives/sq-click-outside/sq-click-outside.directive'
import { SearchPipe } from './pipes/search/search.pipe'
import { SqDropdownDirective } from './directives/sq-dropdown/sq-dropdown.directive'
import { SqTooltipDirective } from './directives/sq-tooltip/sq-tooltip.directive'
import { BirthdatePipe } from './pipes/birthdate/birthdate.pipe'
import { RemoveHtmlTagsPipe } from './pipes/remove-html-tags/remove-html-tags.pipe'
import { SearchFromAlternativeArrayPipe } from './pipes/search-from-alternative-array/search-from-alternative-array.pipe'
import { SearchValidValuesPipe } from './pipes/search-valid-values/search-valid-values.pipe'
import { SqSelectMultiTagsComponent } from './components/sq-select-multi-tags/sq-select-multi-tags.component'
import { SqInputMaskComponent } from './components/sq-input-mask/sq-input-mask.component'
import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'
import { SqInputMoneyComponent } from './components/sq-input-money/sq-input-money.component'
import { SqInputNumberComponent } from './components/sq-input-number/sq-input-number.component'
import { SearchPipe } from './pipes/search/search.pipe'
import { ThousandSuffixesPipe } from './pipes/thousands/thousands.pipe'
import { TranslateInternalPipe } from './pipes/translate-internal/translate-internal.pipe'
import { SqCountdownComponent } from './components/sq-countdown/sq-countdown.component'

import { UniversalSafePipe } from './pipes/universal-safe/universal-safe.pipe'
/**
* Array containing a collection of Angular components, directives, and pipes.
* These elements can be used within the SquidCSSModule for building UI features.
Expand Down Expand Up @@ -77,7 +76,6 @@ const components: (Type<any> | any)[] = [
SqClickOutsideDirective,
SearchPipe,
BirthdatePipe,
SearchFromAlternativeArrayPipe,
SearchValidValuesPipe,
SqSelectMultiTagsComponent,
SqInputMaskComponent,
Expand All @@ -98,7 +96,8 @@ const components: (Type<any> | any)[] = [
FormsModule,
SqCountdownComponent,
NgxMaskDirective,
NgxMaskPipe
NgxMaskPipe,
ScrollingModule
],
providers: [
provideNgxMask()
Expand Down
Loading

0 comments on commit 7b4b6cd

Please sign in to comment.