Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #15373 - MultiSelect | Add new template options & allow partial… #15375

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/app/components/multiselect/multiselect.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,31 @@ export interface MultiSelectTemplates {
filtericon(): TemplateRef<any>;
/**
* Custom check icon template.
* @deprecated Use headercheckboxicon or itemcheckboxicon instead.
*/
checkicon(): TemplateRef<any>;
/**
* Custom check icon template for the header checkbox.
*/
headercheckboxicon(context: {
/**
* Defines if all items are selected.
*/
$implicit: boolean;
/**
* Defines if items are partially selected.
*/
partialSelected: boolean;
}): TemplateRef<{ $implicit: boolean; partialSelected: boolean }>;
/**
* Custom check icon template for the item checkbox.
*/
itemcheckboxicon(context: {
/**
* Selection status of the item.
*/
$implicit: boolean;
}): TemplateRef<{ $implicit: boolean }>;
/**
* Custom close icon template.
*/
Expand Down
43 changes: 38 additions & 5 deletions src/app/components/multiselect/multiselect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { ChevronDownIcon } from 'primeng/icons/chevrondown';
import { Nullable } from 'primeng/ts-helpers';
import { AutoFocusModule } from 'primeng/autofocus';
import { MultiSelectRemoveEvent, MultiSelectFilterOptions, MultiSelectFilterEvent, MultiSelectBlurEvent, MultiSelectChangeEvent, MultiSelectFocusEvent, MultiSelectLazyLoadEvent, MultiSelectSelectAllChangeEvent } from './multiselect.interface';
import { MinusIcon } from 'primeng/icons/minus';

export const MULTISELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
Expand Down Expand Up @@ -78,9 +79,10 @@ export const MULTISELECT_VALUE_ACCESSOR: any = {
<div class="p-checkbox p-component">
<div class="p-checkbox-box" [ngClass]="{ 'p-highlight': selected }">
<ng-container *ngIf="selected">
<CheckIcon *ngIf="!checkIconTemplate" [styleClass]="'p-checkbox-icon'" [attr.aria-hidden]="true" />
<CheckIcon *ngIf="!checkIconTemplate || !itemCheckboxIconTemplate" [styleClass]="'p-checkbox-icon'" [attr.aria-hidden]="true" />
<span *ngIf="checkIconTemplate" class="p-checkbox-icon" [attr.aria-hidden]="true">
<ng-template *ngTemplateOutlet="checkIconTemplate"></ng-template>
<ng-template *ngTemplateOutlet="checkIconTemplate && !itemCheckboxIconTemplate"></ng-template>
<ng-template *ngTemplateOutlet="itemCheckboxIconTemplate; context: { $implicit: selected }"></ng-template>
</span>
</ng-container>
</div>
Expand Down Expand Up @@ -117,6 +119,8 @@ export class MultiSelectItem {

@Input() checkIconTemplate: TemplateRef<any> | undefined;

@Input() itemCheckboxIconTemplate: TemplateRef<any> | undefined;

@Output() onClick: EventEmitter<any> = new EventEmitter();

@Output() onMouseEnter: EventEmitter<any> = new EventEmitter();
Expand Down Expand Up @@ -263,11 +267,17 @@ export class MultiSelectItem {
[attr.aria-checked]="allSelected()"
[ngClass]="{ 'p-highlight': allSelected(), 'p-focus': headerCheckboxFocus, 'p-disabled': disabled || toggleAllDisabled }"
>
<ng-container *ngIf="allSelected()">
<CheckIcon [styleClass]="'p-checkbox-icon'" *ngIf="!checkIconTemplate" [attr.aria-hidden]="true" />
<ng-container *ngIf="allSelected() || partialSelected()">
<ng-container *ngIf="!checkIconTemplate && !headerCheckboxIconTemplate">
<CheckIcon [styleClass]="'p-checkbox-icon'" *ngIf="allSelected()" [attr.aria-hidden]="true" />
</ng-container>

<span *ngIf="checkIconTemplate" class="p-checkbox-icon" [attr.aria-hidden]="true">
<ng-template *ngTemplateOutlet="checkIconTemplate; context: { $implicit: allSelected() }"></ng-template>
</span>
<span *ngIf="headerCheckboxIconTemplate" class="p-checkbox-icon" [attr.aria-hidden]="true">
<ng-template *ngTemplateOutlet="headerCheckboxIconTemplate; context: { $implicit: allSelected(), partialSelected: partialSelected() }"></ng-template>
</span>
</ng-container>
</div>
</div>
Expand Down Expand Up @@ -349,6 +359,7 @@ export class MultiSelectItem {
[disabled]="isOptionDisabled(option)"
[template]="itemTemplate"
[checkIconTemplate]="checkIconTemplate"
[itemCheckboxIconTemplate]="itemCheckboxIconTemplate"
[itemSize]="scrollerOptions.itemSize"
[focused]="focusedOptionIndex() === getOptionIndex(i, scrollerOptions)"
[ariaPosInset]="getAriaPosInset(getOptionIndex(i, scrollerOptions))"
Expand Down Expand Up @@ -965,6 +976,10 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft

dropdownIconTemplate: TemplateRef<any> | undefined;

itemCheckboxIconTemplate: TemplateRef<any> | undefined;

headerCheckboxIconTemplate: TemplateRef<any> | undefined;

public headerCheckboxFocus: boolean | undefined;

filterOptions: MultiSelectFilterOptions | undefined;
Expand Down Expand Up @@ -1196,6 +1211,11 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft

case 'checkicon':
this.checkIconTemplate = item.template;
console.warn('checkicon is deprecated and will removed in v18. Use itemcheckboxicon or headercheckboxicon templates instead.');
break;

case 'headercheckboxicon':
this.headerCheckboxIconTemplate = item.template;
break;

case 'filtericon':
Expand All @@ -1218,6 +1238,10 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
this.dropdownIconTemplate = item.template;
break;

case 'itemcheckboxicon':
this.itemCheckboxIconTemplate = item.template;
break;

default:
this.itemTemplate = item.template;
break;
Expand Down Expand Up @@ -1872,6 +1896,11 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}
}

if (this.partialSelected()) {
this.selectedOptions = null;
this.cd.markForCheck();
}

this.onChange.emit({ originalEvent: event, value: this.value });
DomHandler.focus(this.headerCheckboxViewChild?.nativeElement);
this.headerCheckboxFocus = true;
Expand Down Expand Up @@ -1932,6 +1961,10 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
return this.selectAll !== null ? this.selectAll : ObjectUtils.isNotEmpty(this.visibleOptions()) && this.visibleOptions().every((option) => this.isOptionGroup(option) || this.isOptionDisabled(option) || this.isSelected(option));
}

partialSelected() {
return this.selectedOptions && this.selectedOptions.length > 0 && this.selectedOptions.length < this.options.length;
}

/**
* Displays the panel.
* @group Method
Expand Down Expand Up @@ -2160,7 +2193,7 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}

@NgModule({
imports: [CommonModule, OverlayModule, SharedModule, TooltipModule, RippleModule, ScrollerModule, AutoFocusModule, CheckIcon, SearchIcon, TimesCircleIcon, TimesIcon, ChevronDownIcon, CheckIcon],
imports: [CommonModule, OverlayModule, SharedModule, TooltipModule, RippleModule, ScrollerModule, AutoFocusModule, CheckIcon, SearchIcon, TimesCircleIcon, TimesIcon, ChevronDownIcon, CheckIcon, MinusIcon],
exports: [MultiSelect, OverlayModule, SharedModule, ScrollerModule],
declarations: [MultiSelect, MultiSelectItem]
})
Expand Down
25 changes: 24 additions & 1 deletion src/app/showcase/doc/apidoc/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -16546,7 +16546,30 @@
"parent": "multiselect",
"name": "checkicon",
"parameters": [],
"description": "Custom check icon template."
"description": "Custom check icon template.",
"deprecated": "Use headercheckboxicon or itemcheckboxicon instead."
},
{
"parent": "multiselect",
"name": "headercheckboxicon",
"parameters": [
{
"name": "context",
"type": "{\n \t $implicit: boolean, // Defines if all items are selected.\n \t partialSelected: boolean, // Defines if items are partially selected.\n }"
}
],
"description": "Custom check icon template for the header checkbox."
},
{
"parent": "multiselect",
"name": "itemcheckboxicon",
"parameters": [
{
"name": "context",
"type": "{\n \t $implicit: boolean, // Selection status of the item.\n }"
}
],
"description": "Custom check icon template for the item checkbox."
},
{
"parent": "multiselect",
Expand Down
53 changes: 32 additions & 21 deletions src/app/showcase/doc/multiselect/virtualscrolldoc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { Component, ViewChild } from '@angular/core';
import { Code } from '../../domain/code';
import { MultiSelect } from 'primeng/multiselect';

@Component({
selector: 'virtual-scroll-doc',
Expand All @@ -23,29 +24,31 @@ import { Code } from '../../domain/code';
class="multiselect-custom-virtual-scroll"
placeholder="Select Cities"
(onSelectAllChange)="onSelectAllChange($event)"
(onChange)="onChange($event)"
></p-multiSelect>
#ms
>
<ng-template pTemplate="headercheckboxicon" let-allSelected let-partialSelected="partialSelected">
<i class="pi pi-check" *ngIf="allSelected"></i>
<i class="pi pi-minus" *ngIf="partialSelected" [ngStyle]="{ color: 'var(--text-color)' }"></i>
</ng-template>
</p-multiSelect>
</div>
<app-code [code]="code" selector="multi-select-virtual-scroll-demo"></app-code>
`
})
export class VirtualScrollDoc {
@ViewChild('ms') ms: MultiSelect;

items = Array.from({ length: 100000 }, (_, i) => ({ label: `Item #${i}`, value: i }));

selectedItems!: any[];

selectAll: boolean = false;

onSelectAllChange(event) {
this.selectedItems = event.checked ? [...this.items] : [];
this.selectedItems = event.checked ? [...this.ms.visibleOptions()] : [];
this.selectAll = event.checked;
}

onChange(event) {
const { value } = event;
if (value) this.selectAll = value.length === this.items.length;
}

code: Code = {
basic: `<p-multiSelect
[options]="items"
Expand All @@ -59,8 +62,13 @@ export class VirtualScrollDoc {
class="multiselect-custom-virtual-scroll"
placeholder="Select Cities"
(onSelectAllChange)="onSelectAllChange($event)"
(onChange)="onChange($event)"
></p-multiSelect>`,
#ms
>
<ng-template pTemplate="headercheckboxicon" let-allSelected let-partialSelected="partialSelected">
<i class="pi pi-check" *ngIf="allSelected"></i>
<i class="pi pi-minus" *ngIf="partialSelected" [ngStyle]="{ color: 'var(--text-color)' }"></i>
</ng-template>
</p-multiSelect>`,

html: `
<div class="card flex justify-content-center">
Expand All @@ -76,34 +84,37 @@ export class VirtualScrollDoc {
class="multiselect-custom-virtual-scroll"
placeholder="Select Cities"
(onSelectAllChange)="onSelectAllChange($event)"
(onChange)="onChange($event)"
></p-multiSelect>
#ms
>
<ng-template pTemplate="headercheckboxicon" let-allSelected let-partialSelected="partialSelected">
<i class="pi pi-check" *ngIf="allSelected"></i>
<i class="pi pi-minus" *ngIf="partialSelected" [ngStyle]="{ color: 'var(--text-color)' }"></i>
</ng-template>
</p-multiSelect>
</div>`,

typescript: `
import { Component } from '@angular/core';
import { Component, ViewChild } from '@angular/core';
import { MultiSelect } from 'primeng/multiselect';

@Component({
selector: 'multi-select-virtual-scroll-demo',
templateUrl: './multi-select-virtual-scroll-demo.html'
})
export class MultiSelectVirtualScrollDemo {
@ViewChild('ms') ms: MultiSelect;

items = Array.from({ length: 100000 }, (_, i) => ({ label: \`Item #\${i}\`, value: i }))

selectedItems!: any[];

selectAll = false;
selectAll: boolean = false;

onSelectAllChange(event) {
this.selectedItems = event.checked ? [...this.items] : [];
this.selectedItems = event.checked ? [...this.ms.visibleOptions()] : [];
this.selectAll = event.checked;
}

onChange(event) {
const { originalEvent, value } = event
if(value) this.selectAll = value.length === this.items.length;
}

}`
};
}
Loading