UNPKG

ng-text-highlight

Version:

A lightweight standalone Angular 19 component/module for highlighting text.

248 lines (241 loc) 11.9 kB
import * as i0 from '@angular/core'; import { Input, Component, NgModule } from '@angular/core'; import { NgFor, NgIf, NgClass, NgStyle } from '@angular/common'; /** * Finds all matching chunks of text based on the provided search words. * It processes the text using the provided options (e.g., case sensitivity, escaping). * @param autoEscape - Whether to escape special characters in search words. * @param caseSensitive - Whether the search should be case-sensitive. * @param findChunks - Custom function to find chunks (default is `defaultChunkFinder`). * @param sanitize - Function to sanitize the input text and search words. * @param searchWords - Array of keywords to search for in the text. * @param textToSearch - The main text where the search is performed. * @returns Array of `TextChunk` objects representing the highlighted and non-highlighted segments. */ const findAllMatches = ({ autoEscape, caseSensitive = false, findChunks = defaultChunkFinder, sanitize, searchWords, textToSearch, }) => fillChunks({ chunksToHighlight: mergeChunks({ chunks: findChunks({ autoEscape, caseSensitive, sanitize, searchWords, textToSearch, }), }), totalLength: textToSearch ? textToSearch.length : 0, }); /** * Merges overlapping or adjacent chunks into a single chunk. * Ensures that the chunks are non-overlapping and sorted. * @param chunks - Array of `TextChunk` objects to merge. * @returns Array of merged `TextChunk` objects. */ const mergeChunks = ({ chunks, }) => { chunks = chunks .sort((first, second) => first.start - second.start) .reduce((mergedChunks, nextChunk) => { if (mergedChunks.length === 0) { return [nextChunk]; } else { const previousChunk = mergedChunks.pop(); if (nextChunk.start <= previousChunk.end) { const endIndex = Math.max(previousChunk.end, nextChunk.end); mergedChunks.push({ highlight: false, start: previousChunk.start, end: endIndex, }); } else { mergedChunks.push(previousChunk, nextChunk); } return mergedChunks; } }, []); return chunks; }; /** * Finds chunks of text that match the search words. * Uses regular expressions to locate the matches in the text. * @param autoEscape - Whether to escape special characters in search words. * @param caseSensitive - Whether the search should be case-sensitive. * @param sanitize - Function to sanitize the input text and search words. * @param searchWords - Array of keywords to search for in the text. * @param textToSearch - The main text where the search is performed. * @returns Array of `TextChunk` objects representing the matching segments. */ const defaultChunkFinder = ({ autoEscape, caseSensitive, sanitize = defaultSanitize, searchWords, textToSearch, }) => { textToSearch = sanitize(textToSearch); return searchWords .filter((searchWord) => searchWord) .reduce((chunks, searchWord) => { searchWord = sanitize(searchWord); if (autoEscape) { searchWord = escapeRegExp(searchWord); } const regex = new RegExp(searchWord, caseSensitive ? "g" : "gi"); let match; while ((match = regex.exec(textToSearch))) { let start = match.index; let end = regex.lastIndex; if (end > start) { chunks.push({ highlight: false, start, end }); } if (match.index === regex.lastIndex) { regex.lastIndex++; } } return chunks; }, []); }; /** * Fills in the gaps between highlighted chunks to create a complete representation of the text. * Ensures that non-highlighted segments are included between highlighted ones. * @param chunksToHighlight - Array of `TextChunk` objects representing highlighted segments. * @param totalLength - Total length of the text being processed. * @returns Array of `TextChunk` objects representing the full text with highlighted and non-highlighted segments. */ const fillChunks = ({ chunksToHighlight, totalLength, }) => { const allChunks = []; const appendChunk = (start, end, highlight) => { if (end - start > 0) { allChunks.push({ start, end, highlight, }); } }; if (chunksToHighlight.length === 0) { appendChunk(0, totalLength, false); } else { let lastIndex = 0; chunksToHighlight.forEach((chunk) => { appendChunk(lastIndex, chunk.start, false); appendChunk(chunk.start, chunk.end, true); lastIndex = chunk.end; }); appendChunk(lastIndex, totalLength, false); } return allChunks; }; /** * Default sanitization function that returns the input string as-is. * Can be overridden to implement custom sanitization logic. * @param string - The input string to sanitize. * @returns The sanitized string. */ function defaultSanitize(string) { return string; } /** * Escapes special characters in a string to make it safe for use in a regular expression. * @param string - The input string to escape. * @returns The escaped string. */ function escapeRegExp(string) { return string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } /** * TextHighlightComponent * A standalone Angular component that highlights specified keywords within a block of text. */ class TextHighlightComponent { constructor() { /** * The main text where keywords will be highlighted. */ this.fullText = ""; /** * Whether the search should be case-sensitive. */ this.caseSensitive = false; /** * Custom CSS class for highlighted text. */ this.highlightClass = "highlight"; /** * Inline styles for highlighted text. */ this.highlightStyle = {}; } /** * Lifecycle hook that is triggered when any data-bound property changes. * Calls the method to generate highlighted segments. * @param changes - The changes to the input properties. */ ngOnChanges(changes) { this.generateHighlightedSegments(); } /** * Generates the highlighted segments of the text based on the provided keywords. * If no keywords are provided, the entire text is treated as unhighlighted. */ generateHighlightedSegments() { this.highlightedSegments = []; if (!this.keywords || this.keywords.length === 0) { this.highlightedSegments = [ { text: this.fullText, isHighlighted: false, }, ]; return; } // Find all matching chunks in the text const textChunks = findAllMatches({ searchWords: this.keywords, textToSearch: this.fullText, autoEscape: true, caseSensitive: this.caseSensitive, }); // Map chunks to highlighted segments textChunks.forEach((chunk) => { const { end, highlight, start } = chunk; const segmentText = this.fullText.substring(start, end); this.highlightedSegments.push({ text: segmentText, isHighlighted: highlight, }); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextHighlightComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TextHighlightComponent, isStandalone: true, selector: "ng-text-highlight", inputs: { fullText: "fullText", keywords: "keywords", caseSensitive: "caseSensitive", highlightClass: "highlightClass", highlightStyle: "highlightStyle" }, usesOnChanges: true, ngImport: i0, template: "<span *ngFor=\"let segment of highlightedSegments\">\n <span\n *ngIf=\"segment.isHighlighted; else normalText\"\n [ngClass]=\"highlightClass\"\n [ngStyle]=\"highlightStyle\"\n >{{ segment.text }}</span>\n <ng-template #normalText>{{ segment.text }}</ng-template>\n</span>\n", styles: [".highlight{background-color:#ff0;color:#000;font-weight:700}.highlight-green{background-color:#90ee90;color:#000;font-weight:700}.highlight-blue{background-color:#add8e6;color:#000;font-style:italic}.highlight-red{background-color:#f08080;color:#fff;font-weight:700;text-decoration:underline}.highlight-purple{background-color:#e6e6fa;color:#9400d3;font-weight:700;font-style:italic}.highlight-orange{background-color:orange;color:#000;font-weight:700}\n"], dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextHighlightComponent, decorators: [{ type: Component, args: [{ selector: "ng-text-highlight", imports: [NgFor, NgIf, NgClass, NgStyle], standalone: true, template: "<span *ngFor=\"let segment of highlightedSegments\">\n <span\n *ngIf=\"segment.isHighlighted; else normalText\"\n [ngClass]=\"highlightClass\"\n [ngStyle]=\"highlightStyle\"\n >{{ segment.text }}</span>\n <ng-template #normalText>{{ segment.text }}</ng-template>\n</span>\n", styles: [".highlight{background-color:#ff0;color:#000;font-weight:700}.highlight-green{background-color:#90ee90;color:#000;font-weight:700}.highlight-blue{background-color:#add8e6;color:#000;font-style:italic}.highlight-red{background-color:#f08080;color:#fff;font-weight:700;text-decoration:underline}.highlight-purple{background-color:#e6e6fa;color:#9400d3;font-weight:700;font-style:italic}.highlight-orange{background-color:orange;color:#000;font-weight:700}\n"] }] }], propDecorators: { fullText: [{ type: Input }], keywords: [{ type: Input }], caseSensitive: [{ type: Input }], highlightClass: [{ type: Input }], highlightStyle: [{ type: Input }] } }); class TextHighlightModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextHighlightModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: TextHighlightModule, imports: [TextHighlightComponent], exports: [TextHighlightComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextHighlightModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextHighlightModule, decorators: [{ type: NgModule, args: [{ declarations: [], imports: [TextHighlightComponent], exports: [TextHighlightComponent], }] }] }); /* * Public API Surface of ng-text-highlight */ /** * Generated bundle index. Do not edit. */ export { TextHighlightComponent, TextHighlightModule }; //# sourceMappingURL=ng-text-highlight.mjs.map