@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
JavaScript
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);
});
}