UNPKG

primeng

Version:

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![npm version](https://badge.fury.io/js/primeng.svg)](https://badge.fury.io/js/primeng) [![Discord](https://img.shields.io/discord/557940238991753

589 lines (585 loc) 23.7 kB
import { forwardRef, EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, Input, Output, ChangeDetectorRef, ViewChild, ContentChildren, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PrimeTemplate, SharedModule } from 'primeng/api'; import { ObjectUtils } from 'primeng/utils'; import { DomHandler, ConnectedOverlayScrollHandler } from 'primeng/dom'; import { trigger, transition, style, animate } from '@angular/animations'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { RippleModule } from 'primeng/ripple'; const CASCADESELECT_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CascadeSelect), multi: true }; class CascadeSelectSub { constructor(el) { this.el = el; this.level = 0; this.onSelect = new EventEmitter(); this.onGroupSelect = new EventEmitter(); this.activeOption = null; } get parentActive() { return this._parentActive; } ; set parentActive(val) { if (!val) { this.activeOption = null; } this._parentActive = val; } ngOnInit() { if (this.selectionPath && this.options && !this.dirty) { for (let option of this.options) { if (this.selectionPath.includes(option)) { this.activeOption = option; break; } } } if (!this.root) { this.position(); } } onOptionClick(event, option) { if (this.isOptionGroup(option)) { this.activeOption = (this.activeOption === option) ? null : option; this.onGroupSelect.emit({ originalEvent: event, value: option }); } else { this.onSelect.emit({ originalEvent: event, value: this.getOptionValue(option) }); } } onOptionSelect(event) { this.onSelect.emit(event); } onOptionGroupSelect(event) { this.onGroupSelect.emit(event); } getOptionLabel(option) { return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option; } getOptionValue(option) { return this.optionValue ? ObjectUtils.resolveFieldData(option, this.optionValue) : option; } getOptionGroupLabel(optionGroup) { return this.optionGroupLabel ? ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel) : null; } getOptionGroupChildren(optionGroup) { return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren[this.level]); } isOptionGroup(option) { return Object.prototype.hasOwnProperty.call(option, this.optionGroupChildren[this.level]); } getOptionLabelToRender(option) { return this.isOptionGroup(option) ? this.getOptionGroupLabel(option) : this.getOptionLabel(option); } getItemClass(option) { return { 'p-cascadeselect-item': true, 'p-cascadeselect-item-group': this.isOptionGroup(option), 'p-cascadeselect-item-active p-highlight': this.isOptionActive(option) }; } isOptionActive(option) { return this.activeOption === option; } onKeyDown(event, option, index) { let listItem = event.currentTarget.parentElement; switch (event.key) { case 'Down': case 'ArrowDown': var nextItem = this.el.nativeElement.children[0].children[index + 1]; if (nextItem) { nextItem.children[0].focus(); } break; case 'Up': case 'ArrowUp': var prevItem = this.el.nativeElement.children[0].children[index - 1]; if (prevItem) { prevItem.children[0].focus(); } break; case 'Right': case 'ArrowRight': if (this.isOptionGroup(option)) { if (this.isOptionActive(option)) { listItem.children[1].children[0].children[0].children[0].focus(); } else { this.activeOption = option; } } break; case 'Left': case 'ArrowLeft': this.activeOption = null; var parentList = listItem.parentElement.parentElement.parentElement; if (parentList) { parentList.children[0].focus(); } break; case 'Enter': this.onOptionClick(event, option); break; } event.preventDefault(); } position() { const parentItem = this.el.nativeElement.parentElement; const containerOffset = DomHandler.getOffset(parentItem); const viewport = DomHandler.getViewport(); const sublistWidth = this.el.nativeElement.children[0].offsetParent ? this.el.nativeElement.children[0].offsetWidth : DomHandler.getHiddenElementOuterWidth(this.el.nativeElement.children[0]); const itemOuterWidth = DomHandler.getOuterWidth(parentItem.children[0]); if ((parseInt(containerOffset.left, 10) + itemOuterWidth + sublistWidth) > (viewport.width - DomHandler.calculateScrollbarWidth())) { this.el.nativeElement.children[0].style.left = '-200%'; } } } CascadeSelectSub.decorators = [ { type: Component, args: [{ selector: 'p-cascadeSelectSub', template: ` <ul class="p-cascadeselect-panel p-cascadeselect-items" [ngClass]="{'p-cascadeselect-panel-root': root}" role="listbox" aria-orientation="horizontal"> <ng-template ngFor let-option [ngForOf]="options" let-i="index"> <li [ngClass]="getItemClass(option)" role="none"> <div class="p-cascadeselect-item-content" (click)="onOptionClick($event, option)" tabindex="0" (keydown)="onKeyDown($event, option, i)" pRipple> <ng-container *ngIf="optionTemplate;else defaultOptionTemplate"> <ng-container *ngTemplateOutlet="optionTemplate; context: {$implicit: option}"></ng-container> </ng-container> <ng-template #defaultOptionTemplate> <span class="p-cascadeselect-item-text">{{getOptionLabelToRender(option)}}</span> </ng-template> <span class="p-cascadeselect-group-icon pi pi-angle-right" *ngIf="isOptionGroup(option)"></span> </div> <p-cascadeSelectSub *ngIf="isOptionGroup(option) && isOptionActive(option)" class="p-cascadeselect-sublist" [selectionPath]="selectionPath" [options]="getOptionGroupChildren(option)" [optionLabel]="optionLabel" [optionValue]="optionValue" [level]="level + 1" (onSelect)="onOptionSelect($event)" (onOptionGroupSelect)="onOptionGroupSelect()" [optionGroupLabel]="optionGroupLabel" [optionGroupChildren]="optionGroupChildren" [parentActive]="isOptionActive(option)" [dirty]="dirty" [optionTemplate]="optionTemplate"> </p-cascadeSelectSub> </li> </ng-template> </ul> `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush },] } ]; CascadeSelectSub.ctorParameters = () => [ { type: ElementRef } ]; CascadeSelectSub.propDecorators = { selectionPath: [{ type: Input }], options: [{ type: Input }], optionGroupChildren: [{ type: Input }], optionTemplate: [{ type: Input }], level: [{ type: Input }], optionLabel: [{ type: Input }], optionValue: [{ type: Input }], optionGroupLabel: [{ type: Input }], dirty: [{ type: Input }], root: [{ type: Input }], onSelect: [{ type: Output }], onGroupSelect: [{ type: Output }], parentActive: [{ type: Input }] }; class CascadeSelect { constructor(el, cd) { this.el = el; this.cd = cd; this.showTransitionOptions = '.12s cubic-bezier(0, 0, 0.2, 1)'; this.hideTransitionOptions = '.1s linear'; this.onChange = new EventEmitter(); this.onGroupChange = new EventEmitter(); this.onShow = new EventEmitter(); this.onHide = new EventEmitter(); this.onBeforeShow = new EventEmitter(); this.onBeforeHide = new EventEmitter(); this.selectionPath = null; this.focused = false; this.filled = false; this.overlayVisible = false; this.dirty = false; this.onModelChange = () => { }; this.onModelTouched = () => { }; } ngOnInit() { this.updateSelectionPath(); } ngAfterContentInit() { this.templates.forEach((item) => { switch (item.getType()) { case 'value': this.valueTemplate = item.template; break; case 'option': this.optionTemplate = item.template; break; } }); } onOptionSelect(event) { this.value = event.value; this.updateSelectionPath(); this.onModelChange(this.value); this.onChange.emit(event); this.hide(); this.focusInputEl.nativeElement.focus(); } onOptionGroupSelect(event) { this.dirty = true; this.onGroupChange.emit(event); } getOptionLabel(option) { return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option; } getOptionValue(option) { return this.optionValue ? ObjectUtils.resolveFieldData(option, this.optionValue) : option; } getOptionGroupChildren(optionGroup, level) { return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren[level]); } isOptionGroup(option, level) { return Object.prototype.hasOwnProperty.call(option, this.optionGroupChildren[level]); } updateSelectionPath() { let path; if (this.value != null && this.options) { for (let option of this.options) { path = this.findModelOptionInGroup(option, 0); if (path) { break; } } } this.selectionPath = path; this.updateFilledState(); } updateFilledState() { this.filled = !(this.selectionPath == null || this.selectionPath.length == 0); } findModelOptionInGroup(option, level) { if (this.isOptionGroup(option, level)) { let selectedOption; for (let childOption of this.getOptionGroupChildren(option, level)) { selectedOption = this.findModelOptionInGroup(childOption, level + 1); if (selectedOption) { selectedOption.unshift(option); return selectedOption; } } } else if ((ObjectUtils.equals(this.value, this.getOptionValue(option), this.dataKey))) { return [option]; } return null; } show() { this.onBeforeShow.emit(); this.overlayVisible = true; } hide() { this.onBeforeHide.emit(); this.overlayVisible = false; this.cd.markForCheck(); } onClick(event) { if (this.disabled) { return; } if (!this.overlayEl || !this.overlayEl || !this.overlayEl.contains(event.target)) { if (this.overlayVisible) { this.hide(); } else { this.show(); } this.focusInputEl.nativeElement.focus(); } } onFocus() { this.focused = true; } onBlur() { this.focused = false; } onOverlayAnimationStart(event) { switch (event.toState) { case 'visible': this.overlayEl = event.element; this.onOverlayEnter(); break; } } onOverlayAnimationDone(event) { switch (event.toState) { case 'void': this.onOverlayLeave(); break; } } onOverlayEnter() { this.overlayEl.style.zIndex = String(DomHandler.generateZIndex()); this.appendContainer(); this.alignOverlay(); this.bindOutsideClickListener(); this.bindScrollListener(); this.bindResizeListener(); this.onShow.emit(); } onOverlayLeave() { this.unbindOutsideClickListener(); this.unbindScrollListener(); this.unbindResizeListener(); this.onHide.emit(); this.overlayEl = null; this.dirty = false; } writeValue(value) { this.value = value; this.updateSelectionPath(); this.cd.markForCheck(); } registerOnChange(fn) { this.onModelChange = fn; } registerOnTouched(fn) { this.onModelTouched = fn; } setDisabledState(val) { this.disabled = val; this.cd.markForCheck(); } alignOverlay() { if (this.appendTo) { DomHandler.absolutePosition(this.overlayEl, this.containerEl.nativeElement); this.overlayEl.style.minWidth = DomHandler.getOuterWidth(this.containerEl.nativeElement) + 'px'; } else { DomHandler.relativePosition(this.overlayEl, this.containerEl.nativeElement); } } bindOutsideClickListener() { if (!this.outsideClickListener) { this.outsideClickListener = (event) => { if (this.overlayVisible && this.overlayEl && !this.containerEl.nativeElement.contains(event.target) && !this.overlayEl.contains(event.target)) { this.hide(); } }; document.addEventListener('click', this.outsideClickListener); } } unbindOutsideClickListener() { if (this.outsideClickListener) { document.removeEventListener('click', this.outsideClickListener); this.outsideClickListener = null; } } bindScrollListener() { if (!this.scrollHandler) { this.scrollHandler = new ConnectedOverlayScrollHandler(this.containerEl.nativeElement, () => { if (this.overlayVisible) { this.hide(); } }); } this.scrollHandler.bindScrollListener(); } unbindScrollListener() { if (this.scrollHandler) { this.scrollHandler.unbindScrollListener(); } } bindResizeListener() { if (!this.resizeListener) { this.resizeListener = () => { if (this.overlayVisible) { this.hide(); } }; window.addEventListener('resize', this.resizeListener); } } unbindResizeListener() { if (this.resizeListener) { window.removeEventListener('resize', this.resizeListener); this.resizeListener = null; } } appendContainer() { if (this.appendTo) { if (this.appendTo === 'body') document.body.appendChild(this.overlayEl); else document.getElementById(this.appendTo).appendChild(this.overlayEl); } } restoreAppend() { if (this.overlayEl && this.appendTo) { if (this.appendTo === 'body') document.body.removeChild(this.overlayEl); else document.getElementById(this.appendTo).removeChild(this.overlayEl); } } label() { if (this.selectionPath) return this.getOptionLabel(this.selectionPath[this.selectionPath.length - 1]); else return this.placeholder || 'p-emptylabel'; } onKeyDown(event) { switch (event.key) { case 'Down': case 'ArrowDown': if (this.overlayVisible) { DomHandler.findSingle(this.overlayEl, '.p-cascadeselect-item').children[0].focus(); } else if (event.altKey && this.options && this.options.length) { this.show(); } event.preventDefault(); break; case 'Escape': if (this.overlayVisible) { this.hide(); event.preventDefault(); } break; case 'Tab': this.hide(); break; } } containerClass() { return { 'p-cascadeselect p-component p-inputwrapper': true, 'p-disabled': this.disabled, 'p-focus': this.focused }; } labelClass() { return { 'p-cascadeselect-label': true, 'p-placeholder': this.label() === this.placeholder, 'p-cascadeselect-label-empty': !this.value && (this.label() === 'p-emptylabel' || this.label().length === 0) }; } ngOnDestroy() { this.restoreAppend(); this.unbindOutsideClickListener(); this.unbindResizeListener(); if (this.scrollHandler) { this.scrollHandler.destroy(); this.scrollHandler = null; } this.overlayEl = null; } } CascadeSelect.decorators = [ { type: Component, args: [{ selector: 'p-cascadeSelect', template: ` <div #container [ngClass]="containerClass()" [class]="styleClass" [ngStyle]="style" (click)="onClick($event)"> <div class="p-hidden-accessible"> <input #focusInput type="text" [attr.id]="inputId" readonly [disabled]="disabled" (focus)="onFocus()" (blur)="onBlur()" (keydown)="onKeyDown($event)" [attr.tabindex]="tabindex" aria-haspopup="listbox" [attr.aria-expanded]="overlayVisible" [attr.aria-labelledby]="ariaLabelledBy"> </div> <span [ngClass]="labelClass()"> <ng-container *ngIf="valueTemplate;else defaultValueTemplate"> <ng-container *ngTemplateOutlet="valueTemplate; context: {$implicit: value, placeholder: placeholder}"></ng-container> </ng-container> <ng-template #defaultValueTemplate> {{label()}} </ng-template> </span> <div class="p-cascadeselect-trigger" role="button" aria-haspopup="listbox" [attr.aria-expanded]="overlayVisible"> <span class="p-cascadeselect-trigger-icon pi pi-chevron-down"></span> </div> <div class="p-cascadeselect-panel p-component" *ngIf="overlayVisible" [@overlayAnimation]="{value: 'visible', params: {showTransitionParams: showTransitionOptions, hideTransitionParams: hideTransitionOptions}}" (@overlayAnimation.start)="onOverlayAnimationStart($event)" (@overlayAnimation.done)="onOverlayAnimationDone($event)"> <div class="p-cascadeselect-items-wrapper"> <p-cascadeSelectSub [options]="options" [selectionPath]="selectionPath" class="p-cascadeselect-items" [optionLabel]="optionLabel" [optionValue]="optionValue" [level]="0" [optionTemplate]="optionTemplate" [optionGroupLabel]="optionGroupLabel" [optionGroupChildren]="optionGroupChildren" (onSelect)="onOptionSelect($event)" (onGroupSelect)="onOptionGroupSelect($event)" [dirty]="dirty" [root]="true"> </p-cascadeSelectSub> </div> </div> </div> `, animations: [ trigger('overlayAnimation', [ transition(':enter', [ style({ opacity: 0, transform: 'scaleY(0.8)' }), animate('{{showTransitionParams}}') ]), transition(':leave', [ animate('{{hideTransitionParams}}', style({ opacity: 0 })) ]) ]) ], host: { '[class.p-inputwrapper-filled]': 'filled', '[class.p-inputwrapper-focus]': 'focused || overlayVisible' }, providers: [CASCADESELECT_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: [".p-cascadeselect{-ms-user-select:none;-webkit-user-select:none;cursor:pointer;display:inline-flex;position:relative;user-select:none}.p-cascadeselect-trigger{align-items:center;display:flex;flex-shrink:0;justify-content:center}.p-cascadeselect-label{cursor:pointer;display:block;flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:1%}.p-cascadeselect-label-empty{overflow:hidden;visibility:hidden}.p-cascadeselect .p-cascadeselect-panel{min-width:100%}.p-cascadeselect-panel{position:absolute}.p-cascadeselect-item{cursor:pointer;font-weight:400;white-space:nowrap}.p-cascadeselect-item-content{align-items:center;display:flex;overflow:hidden;position:relative}.p-cascadeselect-group-icon{margin-left:auto}.p-cascadeselect-items{list-style-type:none;margin:0;padding:0}.p-fluid .p-cascadeselect{display:flex}.p-fluid .p-cascadeselect .p-cascadeselect-label{width:1%}.p-cascadeselect-sublist{display:none;min-width:100%;position:absolute;z-index:1}.p-cascadeselect-item-active{overflow:visible!important}.p-cascadeselect-item-active>.p-cascadeselect-sublist{display:block;left:100%;top:0}"] },] } ]; CascadeSelect.ctorParameters = () => [ { type: ElementRef }, { type: ChangeDetectorRef } ]; CascadeSelect.propDecorators = { styleClass: [{ type: Input }], style: [{ type: Input }], options: [{ type: Input }], optionLabel: [{ type: Input }], optionValue: [{ type: Input }], optionGroupLabel: [{ type: Input }], optionGroupChildren: [{ type: Input }], placeholder: [{ type: Input }], value: [{ type: Input }], dataKey: [{ type: Input }], inputId: [{ type: Input }], tabindex: [{ type: Input }], ariaLabelledBy: [{ type: Input }], appendTo: [{ type: Input }], disabled: [{ type: Input }], rounded: [{ type: Input }], showTransitionOptions: [{ type: Input }], hideTransitionOptions: [{ type: Input }], focusInputEl: [{ type: ViewChild, args: ['focusInput',] }], containerEl: [{ type: ViewChild, args: ['container',] }], onChange: [{ type: Output }], onGroupChange: [{ type: Output }], onShow: [{ type: Output }], onHide: [{ type: Output }], onBeforeShow: [{ type: Output }], onBeforeHide: [{ type: Output }], templates: [{ type: ContentChildren, args: [PrimeTemplate,] }] }; class CascadeSelectModule { } CascadeSelectModule.decorators = [ { type: NgModule, args: [{ imports: [CommonModule, SharedModule, RippleModule], exports: [CascadeSelect, CascadeSelectSub, SharedModule], declarations: [CascadeSelect, CascadeSelectSub] },] } ]; /** * Generated bundle index. Do not edit. */ export { CASCADESELECT_VALUE_ACCESSOR, CascadeSelect, CascadeSelectModule, CascadeSelectSub }; //# sourceMappingURL=primeng-cascadeselect.js.map