UNPKG

@ng-prime-extensions/px-searchable-accordion

Version:
230 lines (225 loc) 13.3 kB
import * as i0 from '@angular/core'; import { DOCUMENT, Input, Inject, ViewEncapsulation, Component } from '@angular/core'; import { Accordion, AccordionPanel, AccordionHeader, AccordionContent } from 'primeng/accordion'; import { NgTemplateOutlet, AsyncPipe } from '@angular/common'; import { BehaviorSubject, debounceTime } from 'rxjs'; import * as i1 from '@angular/router'; class PxSearchableAccordionComponent { constructor(router, activatedRoute, changeDetector, document) { this.router = router; this.activatedRoute = activatedRoute; this.changeDetector = changeDetector; this.document = document; /** * Search delay time in milliseconds. */ this.searchDebounceTime = 250; /** * Weather searched words will be highlighted. */ this.highlightWords = true; /** * The css class applied to the highlighted words. */ this.highlightClass = 'px-searchable-accordion-highlight'; /** * If true, url fragments will be generated based on the set entry id. */ this.allowUrlFragments = true; this.filteredEntries = new BehaviorSubject([]); this.activeEntryIndex = 0; this.lastSearchString = ''; this.searchQueryRegex = /[^a-zA-Z0-9\s]/g; } ngOnChanges(changes) { if (changes['searchString']) { this.lastSearchString = ''; this.searchSub?.unsubscribe(); delete this.searchSub; if (typeof this.searchString === "string") { this.search(this.searchString).then(); } else { this.search(this.lastSearchString).then(); this.searchSub = this.searchString .pipe(debounceTime(this.searchDebounceTime)) .subscribe(searchStr => this.search(searchStr).then()); } } else if (changes['entries']) { if (typeof this.searchString === "string") { this.search(this.searchString).then(); } else { this.search(this.lastSearchString).then(); } } } ngAfterViewInit() { if (this.allowUrlFragments) { this.routeFragmentsSub = this.activatedRoute.fragment .subscribe((fragment) => { if (fragment) { const activeEntries = this.filteredEntries.getValue(); const activeEntriesLen = activeEntries.length; for (let i = 0; i < activeEntriesLen; i++) { const entry = activeEntries[i]; if (entry.id === fragment && this.activeEntryIndex !== i) { this.activeEntryIndex = i; this.changeDetector.detectChanges(); this.document.getElementById(entry.id)?.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' }); break; } } } }); } } /** * Searches the available entries for the given search string * @param searchStr */ async search(searchStr) { searchStr = searchStr.replace(this.searchQueryRegex, '').trim().toLowerCase(); if (searchStr === '') { this.filteredEntries.next(this.entries); return; } else if (searchStr === this.lastSearchString) { return; } this.lastSearchString = searchStr; const entries = this.entries; const entriesLen = entries.length; const filteredEntries = []; for (let entryIndex = 0; entryIndex < entriesLen; ++entryIndex) { const entry = entries[entryIndex]; const lowerCaseHeader = entry.header.toLowerCase(); const lowerCaseContent = entry.content.toLowerCase(); const headerIncludesStr = lowerCaseHeader.indexOf(searchStr) !== -1; const contentIncludesStr = lowerCaseContent.indexOf(searchStr) !== -1; if (!(headerIncludesStr || contentIncludesStr)) { continue; } filteredEntries.push(this.highlightWords ? { header: headerIncludesStr ? this.highlightString(entry.header, lowerCaseHeader, searchStr) : entry.header, content: contentIncludesStr ? this.highlightString(entry.content, lowerCaseContent, searchStr) : entry.content, data: entry.data, } : entry); } this.filteredEntries.next(filteredEntries); } activeIndexChanged() { let activeIndex; if (Array.isArray(this.activeEntryIndex)) { activeIndex = this.activeEntryIndex[0]; } else { activeIndex = this.activeEntryIndex; } if (!(activeIndex && this.allowUrlFragments)) { return; } const entries = this.filteredEntries.getValue(); if (!entries.length) { return; } const activeEntry = entries[activeIndex]; if (!activeEntry.id) { return; } this.router.navigate([], { fragment: activeEntry.id, }).then(); } highlightString(sourceString, sourceStringLower, searchStr) { const destString = []; const sourceStringLen = sourceString.length; const highlightClass = this.highlightClass; let highlightedChars = []; let inHtmlTag = false; for (let i = 0; i < sourceStringLen; ++i) { let char = sourceString[i]; if (inHtmlTag) { if (char === '>') { inHtmlTag = false; } } else if (char === '<') { inHtmlTag = true; } else if (searchStr.indexOf(sourceStringLower[i]) !== -1) { highlightedChars.push(char); continue; } else if (highlightedChars.length) { if (highlightedChars.length === searchStr.length) { char = '<span class="' + highlightClass + '">' + highlightedChars.join('') + '</span>' + char; } else { char = highlightedChars.join('') + char; } highlightedChars = []; } destString.push(char); } if (highlightedChars.length) { if (highlightedChars.length === searchStr.length) { destString.push('<span class="' + highlightClass + '">' + highlightedChars.join('') + '</span>'); } else { destString.push(highlightedChars.join('')); } } return destString.join(''); } ngOnDestroy() { this.searchSub?.unsubscribe(); this.filteredEntries.complete(); this.routeFragmentsSub?.unsubscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PxSearchableAccordionComponent, deps: [{ token: i1.Router }, { token: i1.ActivatedRoute }, { token: i0.ChangeDetectorRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PxSearchableAccordionComponent, isStandalone: true, selector: "px-searchable-accordion", inputs: { entries: "entries", searchString: "searchString", headerTemplate: "headerTemplate", contentTemplate: "contentTemplate", noResultsTemplate: "noResultsTemplate", searchDebounceTime: "searchDebounceTime", highlightWords: "highlightWords", highlightClass: "highlightClass", allowUrlFragments: "allowUrlFragments" }, usesOnChanges: true, ngImport: i0, template: "<p-accordion [(value)]=\"activeEntryIndex\" (valueChange)=\"activeIndexChanged()\">\n @if(!(filteredEntries | async)?.length)\n {\n @if(noResultsTemplate)\n {\n <ng-container *ngTemplateOutlet=\"noResultsTemplate ? noResultsTemplate : defaultNoResultsTemplate\"></ng-container>\n }\n }\n\n @for(entry of filteredEntries | async; track entry.id || $index)\n {\n <p-accordion-panel [value]=\"$index\" [attr.id]=\"entry.id\">\n <p-accordion-header>\n <ng-container *ngTemplateOutlet=\"headerTemplate ? headerTemplate : defaultHeader; context: { $implicit: entry }\"></ng-container>\n </p-accordion-header>\n\n <p-accordion-content>\n <ng-container *ngTemplateOutlet=\"contentTemplate ? contentTemplate : defaultContent; context: { $implicit: entry }\"></ng-container>\n </p-accordion-content>\n </p-accordion-panel>\n }\n</p-accordion>\n\n<ng-template #defaultHeader let-entry>\n <span [innerHTML]=\"entry.header\"></span>\n</ng-template>\n<ng-template #defaultContent let-entry>\n <p [innerHTML]=\"entry.content\"></p>\n</ng-template>\n<ng-template #defaultNoResultsTemplate>\n\n</ng-template>\n", styles: [".px-searchable-accordion-highlight{padding:0;margin:0;background:var(--p-highlight-background);color:var(--p-highlight-color);font-weight:700}\n"], dependencies: [{ kind: "component", type: Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "pipe", type: AsyncPipe, name: "async" }], encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PxSearchableAccordionComponent, decorators: [{ type: Component, args: [{ selector: 'px-searchable-accordion', imports: [ Accordion, AsyncPipe, NgTemplateOutlet, AccordionPanel, AccordionHeader, AccordionContent ], encapsulation: ViewEncapsulation.None, template: "<p-accordion [(value)]=\"activeEntryIndex\" (valueChange)=\"activeIndexChanged()\">\n @if(!(filteredEntries | async)?.length)\n {\n @if(noResultsTemplate)\n {\n <ng-container *ngTemplateOutlet=\"noResultsTemplate ? noResultsTemplate : defaultNoResultsTemplate\"></ng-container>\n }\n }\n\n @for(entry of filteredEntries | async; track entry.id || $index)\n {\n <p-accordion-panel [value]=\"$index\" [attr.id]=\"entry.id\">\n <p-accordion-header>\n <ng-container *ngTemplateOutlet=\"headerTemplate ? headerTemplate : defaultHeader; context: { $implicit: entry }\"></ng-container>\n </p-accordion-header>\n\n <p-accordion-content>\n <ng-container *ngTemplateOutlet=\"contentTemplate ? contentTemplate : defaultContent; context: { $implicit: entry }\"></ng-container>\n </p-accordion-content>\n </p-accordion-panel>\n }\n</p-accordion>\n\n<ng-template #defaultHeader let-entry>\n <span [innerHTML]=\"entry.header\"></span>\n</ng-template>\n<ng-template #defaultContent let-entry>\n <p [innerHTML]=\"entry.content\"></p>\n</ng-template>\n<ng-template #defaultNoResultsTemplate>\n\n</ng-template>\n", styles: [".px-searchable-accordion-highlight{padding:0;margin:0;background:var(--p-highlight-background);color:var(--p-highlight-color);font-weight:700}\n"] }] }], ctorParameters: () => [{ type: i1.Router }, { type: i1.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: Document, decorators: [{ type: Inject, args: [DOCUMENT] }] }], propDecorators: { entries: [{ type: Input, args: [{ required: true }] }], searchString: [{ type: Input, args: [{ required: true }] }], headerTemplate: [{ type: Input }], contentTemplate: [{ type: Input }], noResultsTemplate: [{ type: Input }], searchDebounceTime: [{ type: Input }], highlightWords: [{ type: Input }], highlightClass: [{ type: Input }], allowUrlFragments: [{ type: Input }] } }); /* * Public API Surface of px-searchable-accordion */ /** * Generated bundle index. Do not edit. */ export { PxSearchableAccordionComponent }; //# sourceMappingURL=ng-prime-extensions-px-searchable-accordion.mjs.map