UNPKG

@empathyco/x-components

Version:
124 lines (121 loc) 4.83 kB
import { defineComponent, computed } from 'vue'; import { normalizeString } from '../utils/normalize.js'; import '../utils/storage.js'; /** * Highlights the given part of the text. The component is smart enough to do matches * between special characters like tilde, cedilla, eñe, capital letters... * * @public */ var _sfc_main = defineComponent({ name: 'Highlight', props: { /** The text to highlight some part of it. */ text: { type: String, default: '', }, /** The part of the text to be highlighted. */ highlight: { type: String, default: '', }, /** CSS Class to add when the `text` string contains the `highlight` string. */ matchClass: { type: String, default: '', }, /** CSS Class to add when the given `text` doesn't contain the `highlight` string. */ noMatchClass: { type: String, default: '', }, /** CSS Class to add to the matching text. */ matchingPartClass: { type: String, default: '', }, }, setup(props, { slots }) { /** * Splits the label in three parts based on two indexes. * * @param label - The string that will be divided in three parts. * @param start - The first index that the label will be divided by. * @param end - The second index that the label will be divided by. * * @returns The three parts of the divided label. */ function splitAt(label, start, end) { return { start: label.substring(0, start), match: label.substring(start, end), end: label.substring(end), }; } /** * Splits the text to highlight into 3 parts: a starting part, the matching part * and the ending part. If there is no match between the text and the highlight, the `start` * property will contain the whole text. * * @returns An object containing the different parts of the text. */ const matchParts = computed(() => { const matcherIndex = normalizeString(props.text).indexOf(normalizeString(props.highlight)); return matcherIndex !== -1 && props.highlight ? splitAt(props.text.trim(), matcherIndex, matcherIndex + props.highlight.trim().length) : { start: props.text, match: '', end: '' }; }); /** * Checks if the normalized suggestion query matches with the module's query, so it has a * matching part. * * @returns True if there is a match between the text and the highlight strings. */ const hasMatch = computed(() => !!matchParts.value.match); /** * CSS classes to add depending on the component state. * * @remarks * `x-highlight--has-match`: When there is a match between the text and the part to highlight. * `[matchClass]`: When there is a match between the text and * the part to highlight. * `[noMatchClass]`: when there is no match between the text to highlight. * @returns The CSS classes. */ const dynamicCSSClasses = computed(() => { const classes = { 'x-highlight--has-match': hasMatch.value, 'x-highlight-text': hasMatch.value, [props.matchClass]: hasMatch.value, }; if (props.noMatchClass) { classes[props.noMatchClass] = !hasMatch.value; } return classes; }); /** * Render function to execute the `default` slot, binding `slotsProps` and getting only the * first `vNode` to avoid Fragments and Text root nodes. * * @remarks `slotProps` must be values without Vue reactivity and located inside the * render-function to update the binding data properly. * * @returns The root `vNode` of the `default` slot. */ function renderDefaultSlot() { const slotProps = { text: props.text, hasMatch: hasMatch.value, ...matchParts.value, }; return slots.default?.(slotProps)[0]; } /* Hack to render through a render-function, the `default` slot or, in its absence, the component itself. It is the alternative for the NoElement antipattern. */ const componentProps = { hasMatch, matchParts, dynamicCSSClasses }; return (slots.default ? renderDefaultSlot : componentProps); }, }); export { _sfc_main as default }; //# sourceMappingURL=highlight.vue2.js.map