ng-text-highlight
Version:
A lightweight standalone Angular 19 component/module for highlighting text.
1 lines • 15.6 kB
Source Map (JSON)
{"version":3,"file":"ng-text-highlight.mjs","sources":["../../../projects/ng-text-highlight/src/lib/services/text-highlight.service.ts","../../../projects/ng-text-highlight/src/lib/text-highlight.component.ts","../../../projects/ng-text-highlight/src/lib/text-highlight.component.html","../../../projects/ng-text-highlight/src/lib/text-highlight.module.ts","../../../projects/ng-text-highlight/src/public-api.ts","../../../projects/ng-text-highlight/src/ng-text-highlight.ts"],"sourcesContent":["import { TextChunk } from \"../models/text-chunk\";\n\n/**\n * Finds all matching chunks of text based on the provided search words.\n * It processes the text using the provided options (e.g., case sensitivity, escaping).\n * @param autoEscape - Whether to escape special characters in search words.\n * @param caseSensitive - Whether the search should be case-sensitive.\n * @param findChunks - Custom function to find chunks (default is `defaultChunkFinder`).\n * @param sanitize - Function to sanitize the input text and search words.\n * @param searchWords - Array of keywords to search for in the text.\n * @param textToSearch - The main text where the search is performed.\n * @returns Array of `TextChunk` objects representing the highlighted and non-highlighted segments.\n */\nexport const findAllMatches = ({\n autoEscape,\n caseSensitive = false,\n findChunks = defaultChunkFinder,\n sanitize,\n searchWords,\n textToSearch,\n}: {\n autoEscape?: boolean;\n caseSensitive?: boolean;\n findChunks?: typeof defaultChunkFinder;\n sanitize?: typeof defaultSanitize;\n searchWords: Array<string>;\n textToSearch: string;\n}): Array<TextChunk> =>\n fillChunks({\n chunksToHighlight: mergeChunks({\n chunks: findChunks({\n autoEscape,\n caseSensitive,\n sanitize,\n searchWords,\n textToSearch,\n }),\n }),\n totalLength: textToSearch ? textToSearch.length : 0,\n });\n\n/**\n * Merges overlapping or adjacent chunks into a single chunk.\n * Ensures that the chunks are non-overlapping and sorted.\n * @param chunks - Array of `TextChunk` objects to merge.\n * @returns Array of merged `TextChunk` objects.\n */\nconst mergeChunks = ({\n chunks,\n}: {\n chunks: Array<TextChunk>;\n}): Array<TextChunk> => {\n chunks = chunks\n .sort((first, second) => first.start - second.start)\n .reduce((mergedChunks: Array<TextChunk>, nextChunk) => {\n if (mergedChunks.length === 0) {\n return [nextChunk];\n } else {\n const previousChunk: TextChunk | undefined = mergedChunks.pop();\n if (nextChunk.start <= previousChunk!.end) {\n const endIndex = Math.max(previousChunk!.end, nextChunk.end);\n mergedChunks.push({\n highlight: false,\n start: previousChunk!.start,\n end: endIndex,\n });\n } else {\n mergedChunks.push(previousChunk!, nextChunk);\n }\n return mergedChunks;\n }\n }, []);\n\n return chunks;\n};\n\n/**\n * Finds chunks of text that match the search words.\n * Uses regular expressions to locate the matches in the text.\n * @param autoEscape - Whether to escape special characters in search words.\n * @param caseSensitive - Whether the search should be case-sensitive.\n * @param sanitize - Function to sanitize the input text and search words.\n * @param searchWords - Array of keywords to search for in the text.\n * @param textToSearch - The main text where the search is performed.\n * @returns Array of `TextChunk` objects representing the matching segments.\n */\nconst defaultChunkFinder = ({\n autoEscape,\n caseSensitive,\n sanitize = defaultSanitize,\n searchWords,\n textToSearch,\n}: {\n autoEscape?: boolean;\n caseSensitive?: boolean;\n sanitize?: typeof defaultSanitize;\n searchWords: Array<string>;\n textToSearch: string;\n}): Array<TextChunk> => {\n textToSearch = sanitize(textToSearch);\n return searchWords\n .filter((searchWord) => searchWord)\n .reduce((chunks: Array<TextChunk>, searchWord) => {\n searchWord = sanitize(searchWord);\n\n if (autoEscape) {\n searchWord = escapeRegExp(searchWord);\n }\n\n const regex = new RegExp(searchWord, caseSensitive ? \"g\" : \"gi\");\n\n let match;\n while ((match = regex.exec(textToSearch))) {\n let start = match.index;\n let end = regex.lastIndex;\n if (end > start) {\n chunks.push({ highlight: false, start, end });\n }\n if (match.index === regex.lastIndex) {\n regex.lastIndex++;\n }\n }\n\n return chunks;\n }, []);\n};\n\n/**\n * Fills in the gaps between highlighted chunks to create a complete representation of the text.\n * Ensures that non-highlighted segments are included between highlighted ones.\n * @param chunksToHighlight - Array of `TextChunk` objects representing highlighted segments.\n * @param totalLength - Total length of the text being processed.\n * @returns Array of `TextChunk` objects representing the full text with highlighted and non-highlighted segments.\n */\nconst fillChunks = ({\n chunksToHighlight,\n totalLength,\n}: {\n chunksToHighlight: Array<TextChunk>;\n totalLength: number;\n}): Array<TextChunk> => {\n const allChunks: Array<TextChunk> = [];\n const appendChunk = (start: number, end: number, highlight: boolean) => {\n if (end - start > 0) {\n allChunks.push({\n start,\n end,\n highlight,\n });\n }\n };\n\n if (chunksToHighlight.length === 0) {\n appendChunk(0, totalLength, false);\n } else {\n let lastIndex = 0;\n chunksToHighlight.forEach((chunk) => {\n appendChunk(lastIndex, chunk.start, false);\n appendChunk(chunk.start, chunk.end, true);\n lastIndex = chunk.end;\n });\n appendChunk(lastIndex, totalLength, false);\n }\n return allChunks;\n};\n\n/**\n * Default sanitization function that returns the input string as-is.\n * Can be overridden to implement custom sanitization logic.\n * @param string - The input string to sanitize.\n * @returns The sanitized string.\n */\nfunction defaultSanitize(string: string): string {\n return string;\n}\n\n/**\n * Escapes special characters in a string to make it safe for use in a regular expression.\n * @param string - The input string to escape.\n * @returns The escaped string.\n */\nfunction escapeRegExp(string: string): string {\n return string.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\");\n}\n","import { NgClass, NgFor, NgIf, NgStyle } from \"@angular/common\";\nimport { Component, Input, OnChanges, SimpleChanges } from \"@angular/core\";\nimport { TextSegment } from \"./models/text-segment\";\nimport { findAllMatches } from \"./services/text-highlight.service\";\nimport { TextChunk } from \"./models/text-chunk\";\n\n/**\n * TextHighlightComponent\n * A standalone Angular component that highlights specified keywords within a block of text.\n */\n@Component({\n selector: \"ng-text-highlight\",\n templateUrl: \"./text-highlight.component.html\",\n styleUrls: [\"./text-highlight.component.scss\"],\n imports: [NgFor, NgIf, NgClass, NgStyle],\n standalone: true,\n})\nexport class TextHighlightComponent implements OnChanges {\n /**\n * The main text where keywords will be highlighted.\n */\n @Input() fullText = \"\";\n\n /**\n * List of keywords to highlight in the text.\n */\n @Input() keywords!: Array<string>;\n\n /**\n * Whether the search should be case-sensitive.\n */\n @Input() caseSensitive: boolean = false;\n\n /**\n * Custom CSS class for highlighted text.\n */\n @Input() highlightClass: string = \"highlight\";\n\n /**\n * Inline styles for highlighted text.\n */\n @Input() highlightStyle: { [key: string]: string } = {};\n\n /**\n * Array of text segments, each representing a portion of the text\n * with information about whether it is highlighted.\n */\n highlightedSegments!: Array<TextSegment>;\n\n /**\n * Lifecycle hook that is triggered when any data-bound property changes.\n * Calls the method to generate highlighted segments.\n * @param changes - The changes to the input properties.\n */\n ngOnChanges(changes: SimpleChanges): void {\n this.generateHighlightedSegments();\n }\n\n /**\n * Generates the highlighted segments of the text based on the provided keywords.\n * If no keywords are provided, the entire text is treated as unhighlighted.\n */\n generateHighlightedSegments(): void {\n this.highlightedSegments = [];\n if (!this.keywords || this.keywords.length === 0) {\n this.highlightedSegments = [\n {\n text: this.fullText,\n isHighlighted: false,\n },\n ];\n return;\n }\n\n // Find all matching chunks in the text\n const textChunks = findAllMatches({\n searchWords: this.keywords,\n textToSearch: this.fullText,\n autoEscape: true,\n caseSensitive: this.caseSensitive,\n });\n\n // Map chunks to highlighted segments\n textChunks.forEach((chunk: TextChunk) => {\n const { end, highlight, start } = chunk;\n const segmentText = this.fullText.substring(start, end);\n this.highlightedSegments.push({\n text: segmentText,\n isHighlighted: highlight,\n });\n });\n }\n}\n","<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","import { NgModule } from \"@angular/core\";\nimport { TextHighlightComponent } from \"./text-highlight.component\";\n\n@NgModule({\n declarations: [],\n imports: [TextHighlightComponent],\n exports: [TextHighlightComponent],\n})\nexport class TextHighlightModule {}\n","/*\n * Public API Surface of ng-text-highlight\n */\n\nexport * from './lib/text-highlight.module';\nexport * from './lib/text-highlight.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}