diff --git a/src/app/components/button/button.interface.ts b/src/app/components/button/button.interface.ts index 3d2d5dcda70..6002d5a0b4f 100644 --- a/src/app/components/button/button.interface.ts +++ b/src/app/components/button/button.interface.ts @@ -1,3 +1,4 @@ +import { NgClass } from '@angular/common'; import { TemplateRef } from '@angular/core'; /** @@ -12,9 +13,19 @@ export interface ButtonTemplates { /** * Custom template of icon. */ - icon(): TemplateRef; + icon(context: { + /** + * Icon class. + */ + class: NgClass; + }): TemplateRef; /** * Custom template of loadingicon. */ - loadingicon(): TemplateRef; + loadingicon(context: { + /** + * Icon class. + */ + class: NgClass; + }): TemplateRef; } diff --git a/src/app/components/button/button.ts b/src/app/components/button/button.ts index 5d094a1f64d..7b073d12eb0 100755 --- a/src/app/components/button/button.ts +++ b/src/app/components/button/button.ts @@ -354,18 +354,14 @@ export class ButtonDirective implements AfterViewInit, OnDestroy { - + - - - + - - - + {{ label }} {{ badge }} @@ -529,6 +525,7 @@ export class Button implements AfterContentInit { iconClass() { return { + [`p-button-loading-icon pi-spin ${this.loadingIcon ?? ''}`]: this.loading, 'p-button-icon': true, 'p-button-icon-left': this.iconPos === 'left' && this.label, 'p-button-icon-right': this.iconPos === 'right' && this.label, diff --git a/src/app/components/inputmask/inputmask.ts b/src/app/components/inputmask/inputmask.ts index 404e5520f11..a6e8b27adc7 100755 --- a/src/app/components/inputmask/inputmask.ts +++ b/src/app/components/inputmask/inputmask.ts @@ -94,7 +94,7 @@ export const INPUTMASK_VALUE_ACCESSOR: any = { (keydown)="onInputKeydown($event)" (keypress)="onKeyPress($event)" pAutoFocus - [variant]="variant" + [attr.variant]="variant" [autofocus]="autofocus" (input)="onInputChange($event)" (paste)="handleInputChange($event)" diff --git a/src/app/components/inputnumber/inputnumber.ts b/src/app/components/inputnumber/inputnumber.ts index 6fe94ec2102..c5e68828dac 100644 --- a/src/app/components/inputnumber/inputnumber.ts +++ b/src/app/components/inputnumber/inputnumber.ts @@ -68,7 +68,7 @@ export const INPUTNUMBER_VALUE_ACCESSOR: any = { [ngStyle]="inputStyle" [class]="inputStyleClass" [value]="formattedValue()" - [variant]="variant" + [attr.variant]="variant" [attr.aria-valuemin]="min" [attr.aria-valuemax]="max" [attr.aria-valuenow]="value" diff --git a/src/app/components/inputotp/inputotp.ts b/src/app/components/inputotp/inputotp.ts index b2061e3f174..8b52ecf6c72 100644 --- a/src/app/components/inputotp/inputotp.ts +++ b/src/app/components/inputotp/inputotp.ts @@ -321,18 +321,20 @@ export class InputOtp implements AfterContentInit { } onPaste(event) { - let paste = event.clipboardData.getData('text'); + if (!this.disabled && !this.readonly) { + let paste = event.clipboardData.getData('text'); - if (paste.length) { - let pastedCode = paste.substring(0, this.length + 1); + if (paste.length) { + let pastedCode = paste.substring(0, this.length + 1); - if (!this.integerOnly || !isNaN(pastedCode)) { - this.tokens = pastedCode.split(''); - this.updateModel(event); + if (!this.integerOnly || !isNaN(pastedCode)) { + this.tokens = pastedCode.split(''); + this.updateModel(event); + } } - } - event.preventDefault(); + event.preventDefault(); + } } getRange(n: number): number[] { diff --git a/src/app/components/listbox/listbox.spec.ts b/src/app/components/listbox/listbox.spec.ts index adace760cd4..730a626bd8b 100755 --- a/src/app/components/listbox/listbox.spec.ts +++ b/src/app/components/listbox/listbox.spec.ts @@ -43,7 +43,7 @@ describe('Listbox', () => { { label: 'VW', value: 'VW' }, { label: 'Volvo', value: 'Volvo' } ]; - const onOptionSelectSpy = spyOn(listbox, 'onOptionSelect').and.callThrough(); + const onClickSpy = spyOn(listbox.onClick, 'emit').and.callThrough(); fixture.detectChanges(); const bmwEl = fixture.debugElement.query(By.css('ul')).children[1].nativeElement; @@ -52,7 +52,7 @@ describe('Listbox', () => { const filterInputEl = fixture.debugElement.query(By.css('.p-listbox-filter-container')).query(By.css('input')).nativeElement; expect(filterInputEl.disabled).toEqual(true); - expect(onOptionSelectSpy).not.toHaveBeenCalled(); + expect(onClickSpy).not.toHaveBeenCalled(); }); it('should call onOptionTouchEnd', () => { @@ -101,7 +101,7 @@ describe('Listbox', () => { fixture.detectChanges(); expect(onOptionTouchEndSpy).toHaveBeenCalled(); - expect(listbox.optionTouched).toEqual(undefined); + expect(listbox.optionTouched).toBeTrue(); }); it('should change style and styleClass', () => { @@ -259,7 +259,6 @@ describe('Listbox', () => { fixture.detectChanges(); expect(listbox.value.length).toEqual(10); - expect(listbox.selectAll).toEqual(true); expect(selectAllEl.className).toContain('p-highlight'); expect(onToggleAllSpy).toHaveBeenCalled(); }); @@ -278,6 +277,7 @@ describe('Listbox', () => { { label: 'Volvo', value: 'Volvo' } ]; listbox.filter = true; + listbox.optionLabel = 'label'; fixture.detectChanges(); const filterInputEl = fixture.debugElement.query(By.css('.p-listbox-filter-container')).children[0].nativeElement; @@ -503,7 +503,6 @@ describe('Listbox', () => { fixture.detectChanges(); expect(listbox.value.length).toEqual(0); - expect(listbox.selectAll).toEqual(false); expect(selectAllEl.className).not.toContain('p-highlight'); expect(toggleAllSpy).toHaveBeenCalledTimes(2); }); @@ -577,6 +576,7 @@ describe('Listbox', () => { listbox.checkbox = true; listbox.filter = true; listbox.filterValue = 'o'; + listbox.optionLabel = 'label'; fixture.detectChanges(); const headerCheckBoxReadonlyEl = fixture.debugElement.query(By.css('.p-checkbox.p-component')).query(By.css('input')); @@ -609,27 +609,22 @@ describe('Listbox', () => { { label: 'VW', value: 'VW' }, { label: 'Volvo', value: 'Volvo' } ]; - const findNextOptionIndexSpy = spyOn(listbox, 'findNextOptionIndex').and.callThrough(); - const findPrevOptionIndexSpy = spyOn(listbox, 'findPrevOptionIndex').and.callThrough(); + const onArrowDownKeySpy = spyOn(listbox, 'onArrowDownKey').and.callThrough(); + const onArrowUpKeySpy = spyOn(listbox, 'onArrowUpKey').and.callThrough(); fixture.detectChanges(); - const bmwEl = fixture.debugElement.query(By.css('ul')).children[1].nativeElement; - const event: any = document.createEvent('CustomEvent'); - event.which = 40; - event.initEvent('keydown'); - bmwEl.dispatchEvent(event); + const bmwEl = fixture.debugElement.query(By.css('.p-listbox-list')).nativeElement; + bmwEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowDown' })); fixture.detectChanges(); - event.which = 38; - bmwEl.dispatchEvent(event); + bmwEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowUp' })); fixture.detectChanges(); - event.which = 13; - bmwEl.dispatchEvent(event); + bmwEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); fixture.detectChanges(); - expect(findNextOptionIndexSpy).toHaveBeenCalled(); - expect(findPrevOptionIndexSpy).toHaveBeenCalled(); - expect(listbox.value).toEqual('BMW'); + expect(onArrowDownKeySpy).toHaveBeenCalled(); + expect(onArrowUpKeySpy).toHaveBeenCalled(); + expect(listbox.value).toEqual('Audi'); }); }); diff --git a/src/app/components/listbox/listbox.ts b/src/app/components/listbox/listbox.ts index c7679b2cb2b..8873b611fbc 100755 --- a/src/app/components/listbox/listbox.ts +++ b/src/app/components/listbox/listbox.ts @@ -179,7 +179,7 @@ export const LISTBOX_VALUE_ACCESSOR: any = { [attr.aria-selected]="isSelected(option)" [attr.aria-disabled]="isOptionDisabled(option)" [attr.aria-setsize]="ariaSetSize" - [ariaPosInset]="getAriaPosInset(getOptionIndex(i, scrollerOptions))" + [attr.ariaPosInset]="getAriaPosInset(getOptionIndex(i, scrollerOptions))" (click)="onOptionSelect($event, option, getOptionIndex(i, scrollerOptions))" (dblclick)="onOptionDoubleClick($event, option)" (mousedown)="onOptionMouseDown($event, getOptionIndex(i, scrollerOptions))" diff --git a/src/app/components/treeselect/treeselect.ts b/src/app/components/treeselect/treeselect.ts index 282fb372005..fa0a2ac8283 100755 --- a/src/app/components/treeselect/treeselect.ts +++ b/src/app/components/treeselect/treeselect.ts @@ -170,6 +170,7 @@ export const TREESELECT_VALUE_ACCESSOR: any = { [virtualScrollItemSize]="virtualScrollItemSize" [virtualScrollOptions]="virtualScrollOptions" [_templateMap]="templateMap" + [loading]="loading" > @@ -425,6 +426,11 @@ export class TreeSelect implements AfterContentInit { this._hideTransitionOptions = val; console.warn('The hideTransitionOptions property is deprecated since v14.2.0, use overlayOptions property instead.'); } + /** + * Displays a loader to indicate data load is in progress. + * @group Props + */ + @Input({ transform: booleanAttribute }) loading: boolean | undefined; /** * Callback to invoke when a node is expanded. * @param {TreeSelectNodeExpandEvent} event - Custom node expand event. diff --git a/src/app/showcase/doc/terminal/basicdoc.ts b/src/app/showcase/doc/terminal/basicdoc.ts index f6d60118e0a..d8201e1865e 100644 --- a/src/app/showcase/doc/terminal/basicdoc.ts +++ b/src/app/showcase/doc/terminal/basicdoc.ts @@ -34,10 +34,14 @@ export class BasicDoc implements OnDestroy { } code: Code = { - basic: `

Enter "date" to display the current date, "greet {0}" for a message and "random" to get a random number.

+ basic: `

Enter "date" to display the current date, +"greet {0}" for a message and "random" +to get a random number.

`, html: `
-

Enter "date" to display the current date, "greet {0}" for a message and "random" to get a random number.

+

Enter "date" to display the current date, + "greet {0}" for a message and "random" + to get a random number.

`, typescript: `import { Component, OnDestroy } from '@angular/core'; diff --git a/src/app/showcase/doc/treeselect/lazydoc.ts b/src/app/showcase/doc/treeselect/lazydoc.ts new file mode 100644 index 00000000000..75d4d68a3cd --- /dev/null +++ b/src/app/showcase/doc/treeselect/lazydoc.ts @@ -0,0 +1,228 @@ +import { ChangeDetectorRef, Component } from '@angular/core'; +import { Code } from '@domain/code'; +import { NodeService } from '@service/nodeservice'; +import { TreeNode } from 'primeng/api'; + +@Component({ + selector: 'lazy-doc', + template: ` + +

Lazy loading is useful when dealing with huge datasets, in this example nodes are dynamically loaded on demand using loading property and onNodeExpand method.

+
+
+ +
+ + ` +}) +export class LazyDoc { + selectedNodes: TreeNode[] = []; + + nodes!: TreeNode[]; + + loading: boolean = false; + + constructor(private cd: ChangeDetectorRef) {} + + ngOnInit() { + this.loading = true; + + setTimeout(() => { + this.nodes = this.initiateNodes(); + this.loading = false; + this.cd.markForCheck(); + }, 2000); + } + + initiateNodes(): TreeNode[] { + return [ + { + key: '0', + label: 'Node 0', + leaf: false + }, + { + key: '1', + label: 'Node 1', + leaf: false + }, + { + key: '2', + label: 'Node 2', + leaf: false + } + ]; + } + + onNodeExpand(event: any) { + if (!event.node.children) { + this.loading = true; + + setTimeout(() => { + let _node = { ...event.node }; + _node.children = []; + + for (let i = 0; i < 1500; i++) { + _node.children.push({ + key: event.node.key + '-' + i, + label: 'Lazy ' + event.node.label + '-' + i + }); + } + + this.nodes[parseInt(event.node.key, 10)] = _node; + + this.loading = false; + this.cd.markForCheck(); + }, 500); + } + } + + code: Code = { + basic: ``, + + html: `
+ +
`, + + typescript: `import { Component } from '@angular/core'; +import { NodeService } from '@service/nodeservice'; +import { FormsModule } from '@angular/forms'; +import { TreeSelectModule } from 'primeng/treeselect'; + +@Component({ + selector: 'tree-select-lazy-demo', + templateUrl: './tree-select-lazy-demo.html', + standalone: true, + imports: [FormsModule, TreeSelectModule] + }) +export class TreeSelectLazyDemo { + selectedNodes: TreeNode[] = []; + + nodes!: TreeNode[]; + + loading: boolean = false; + + constructor(private cd: ChangeDetectorRef) {} + + ngOnInit() { + this.loading = true; + + setTimeout(() => { + this.nodes = this.initiateNodes(); + this.loading = false; + this.cd.markForCheck(); + }, 2000); + } + + initiateNodes(): TreeNode[] { + return [ + { + key: '0', + label: 'Node 0', + leaf: false + }, + { + key: '1', + label: 'Node 1', + leaf: false + }, + { + key: '2', + label: 'Node 2', + leaf: false + } + ]; + } + + onNodeExpand(event: any) { + if (!event.node.children) { + this.loading = true; + + setTimeout(() => { + let _node = { ...event.node }; + _node.children = []; + + for (let i = 0; i < 150; i++) { + _node.children.push({ + key: event.node.key + '-' + i, + label: 'Lazy ' + event.node.label + '-' + i + }); + } + + this.nodes[parseInt(event.node.key, 10)] = _node; + + this.loading = false; + this.cd.markForCheck(); + }, 500); + } + } +}`, + + service: ['NodeService'], + + data: ` + /* NodeService */ +{ + key: '0', + label: 'Documents', + data: 'Documents Folder', + icon: 'pi pi-fw pi-inbox', + children: [ + { + key: '0-0', + label: 'Work', + data: 'Work Folder', + icon: 'pi pi-fw pi-cog', + children: [ + { key: '0-0-0', label: 'Expenses.doc', icon: 'pi pi-fw pi-file', data: 'Expenses Document' }, + { key: '0-0-1', label: 'Resume.doc', icon: 'pi pi-fw pi-file', data: 'Resume Document' } + ] + }, + { + key: '0-1', + label: 'Home', + data: 'Home Folder', + icon: 'pi pi-fw pi-home', + children: [{ key: '0-1-0', label: 'Invoices.txt', icon: 'pi pi-fw pi-file', data: 'Invoices for this month' }] + } + ] +}, +...` + }; +} diff --git a/src/app/showcase/doc/treeselect/treeselectdoc.module.ts b/src/app/showcase/doc/treeselect/treeselectdoc.module.ts index 02c68e9035e..e0cf8e3e397 100644 --- a/src/app/showcase/doc/treeselect/treeselectdoc.module.ts +++ b/src/app/showcase/doc/treeselect/treeselectdoc.module.ts @@ -19,10 +19,11 @@ import { StyleDoc } from './styledoc'; import { VirtualScrollDoc } from './virtualscrolldoc'; import { FloatLabelModule } from 'primeng/floatlabel'; import { FilledDoc } from './filleddoc'; +import { LazyDoc } from './lazydoc'; @NgModule({ imports: [CommonModule, AppCodeModule, AppDocModule, TreeSelectModule, FormsModule, ReactiveFormsModule, RouterModule, FloatLabelModule], exports: [AppDocModule], - declarations: [ImportDoc, BasicDoc, MultipleDoc, CheckboxDoc, VirtualScrollDoc, FilterDoc, FloatLabelDoc, InvalidDoc, DisabledDoc, StyleDoc, AccessibilityDoc, ReactiveFormsDoc, FilledDoc] + declarations: [ImportDoc, BasicDoc, MultipleDoc, CheckboxDoc, LazyDoc, VirtualScrollDoc, FilterDoc, FloatLabelDoc, InvalidDoc, DisabledDoc, StyleDoc, AccessibilityDoc, ReactiveFormsDoc, FilledDoc] }) export class TreeSelectDocModule {} diff --git a/src/app/showcase/pages/treeselect/treeselectdemo.ts b/src/app/showcase/pages/treeselect/treeselectdemo.ts index 3264c971760..a060b433a42 100644 --- a/src/app/showcase/pages/treeselect/treeselectdemo.ts +++ b/src/app/showcase/pages/treeselect/treeselectdemo.ts @@ -12,6 +12,7 @@ import { MultipleDoc } from '@doc/treeselect/multipledoc'; import { StyleDoc } from '@doc/treeselect/styledoc'; import { VirtualScrollDoc } from '@doc/treeselect/virtualscrolldoc'; import { FilledDoc } from '@doc/treeselect/filleddoc'; +import { LazyDoc } from '@doc/treeselect/lazydoc'; @Component({ templateUrl: './treeselectdemo.html' @@ -48,6 +49,11 @@ export class TreeSelectDemo { label: 'Virtual Scroll', component: VirtualScrollDoc }, + { + id: 'lazy', + label: 'Lazy', + component: LazyDoc + }, { id: 'filter', label: 'Filter',