UNPKG

@zohodesk/components

Version:

Dot UI is a customizable React component library built to deliver a clean, accessible, and developer-friendly UI experience. It offers a growing set of reusable components designed to align with modern design systems and streamline application development

141 lines (126 loc) 5.87 kB
import React from 'react'; import cssJSLogic from "../css/cssJSLogic"; import { renderNode, isRenderable } from '@zohodesk/utils'; import typographyStyle from "./../css/Typography.module.css"; import { DUMMY_ARRAY, DUMMY_OBJECT } from "../../utils/Common"; export function highlightText(config) { const { text: sourceText, data: highlightData, styleConfiguration: highlightStyleConfig = DUMMY_OBJECT, shouldExcludeIndices = false, isCaseSensitive: enableCaseSensitiveMatch = false, isWholeWord: matchWholeWordOnly = false, as: asTag, tagName: tagNameLegacy, renderHighlight: customRenderer } = config; const wrapperTagName = asTag ?? tagNameLegacy ?? 'span'; if (!sourceText || !highlightData?.length) { return sourceText; } const cssClassCache = new Map(); const normalizedHighlightTerms = highlightData.map(highlightItem => { const searchTerm = typeof highlightItem === 'string' ? highlightItem : highlightItem?.text; if (!searchTerm) return null; const itemConfiguration = typeof highlightItem === 'string' ? DUMMY_OBJECT : highlightItem; const hasStyleConfigrationFromItem = itemConfiguration.styleConfiguration !== undefined; const hasRenderHighlightFromItem = itemConfiguration.renderHighlight !== undefined; const finalStyleConfig = itemConfiguration.styleConfiguration !== undefined ? itemConfiguration.styleConfiguration : highlightStyleConfig; const { customStyle, ...restStyleConfig } = finalStyleConfig; const cacheKey = JSON.stringify(restStyleConfig); let typographyClass; if (Object.keys(restStyleConfig).length !== 0) { if (cssClassCache.has(cacheKey)) { typographyClass = cssClassCache.get(cacheKey); } else { const { typographyClass: computedClass } = cssJSLogic({ props: restStyleConfig, style: typographyStyle }); typographyClass = computedClass; cssClassCache.set(cacheKey, typographyClass); } } return { searchTerm, targetIndices: itemConfiguration.index !== undefined ? itemConfiguration.index : DUMMY_ARRAY, shouldExcludeIndices: itemConfiguration.shouldExcludeIndices !== undefined ? itemConfiguration.shouldExcludeIndices : shouldExcludeIndices, isCaseSensitive: itemConfiguration.isCaseSensitive !== undefined ? itemConfiguration.isCaseSensitive : enableCaseSensitiveMatch, matchWholeWordOnly: itemConfiguration.isWholeWord !== undefined ? itemConfiguration.isWholeWord : matchWholeWordOnly, wrapperTagName: itemConfiguration.as !== undefined ? itemConfiguration.as : itemConfiguration.tagName !== undefined ? itemConfiguration.tagName : wrapperTagName, customStyle: customStyle, customRenderer: itemConfiguration.renderHighlight !== undefined ? itemConfiguration.renderHighlight : customRenderer, highlightClasses: typographyClass, hasStyleConfigrationFromItem, hasRenderHighlightFromItem }; }).filter(highlightConfig => highlightConfig && highlightConfig.searchTerm.trim()); if (!normalizedHighlightTerms.length) { return sourceText; } const regexPatterns = normalizedHighlightTerms.map(highlightConfig => { // Escape special regex characters in the searchTerm so it can be safely used in a RegExp const escapedTerm = highlightConfig.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // If matchWholeWordOnly is true, wrap the term with word boundaries (\b) to match whole words only return highlightConfig.matchWholeWordOnly ? `\\b${escapedTerm}\\b` : escapedTerm; }); const combinedRegexPattern = regexPatterns.join('|'); const textSegments = sourceText.split(new RegExp(`(${combinedRegexPattern})`, 'gi')); const occurrenceCountByTerm = {}; return textSegments.map((textSegment, segmentIndex) => { const matchingHighlightConfig = normalizedHighlightTerms.find(highlightConfig => { const segmentToCompare = highlightConfig.isCaseSensitive ? textSegment : textSegment.toLowerCase(); const termToCompare = highlightConfig.isCaseSensitive ? highlightConfig.searchTerm : highlightConfig.searchTerm.toLowerCase(); return segmentToCompare === termToCompare; }); if (!matchingHighlightConfig) { return textSegment; } const termKey = matchingHighlightConfig.searchTerm; occurrenceCountByTerm[termKey] = (occurrenceCountByTerm[termKey] || 0) + 1; const currentOccurrenceIndex = occurrenceCountByTerm[termKey]; const { targetIndices, shouldExcludeIndices } = matchingHighlightConfig; const isIndicesArray = Array.isArray(targetIndices); let shouldApplyHighlight; if (isIndicesArray) { shouldApplyHighlight = (targetIndices.length === 0 || targetIndices.includes(currentOccurrenceIndex)) !== shouldExcludeIndices; } else { shouldApplyHighlight = (targetIndices === 0 || targetIndices === currentOccurrenceIndex) !== shouldExcludeIndices; } if (!shouldApplyHighlight) { return textSegment; } const { customStyle, highlightClasses, customRenderer, hasStyleConfigrationFromItem, hasRenderHighlightFromItem, wrapperTagName: WrapperTag } = matchingHighlightConfig; let shouldUseCustomRenderer = false; if (customRenderer) { if (hasRenderHighlightFromItem) { shouldUseCustomRenderer = true; } else if (!hasStyleConfigrationFromItem && customRenderer) { shouldUseCustomRenderer = true; } } if (shouldUseCustomRenderer && isRenderable(customRenderer)) { return renderNode(customRenderer, textSegment); } return /*#__PURE__*/React.createElement(WrapperTag, { key: `${textSegment}_${currentOccurrenceIndex}`, style: customStyle, className: highlightClasses }, textSegment); }); }