react-highlight-me 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/cjs/index.d.ts +7 -1
- package/dist/cjs/index.js +21 -19
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +7 -1
- package/dist/esm/index.js +21 -19
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +30 -19
package/README.md
CHANGED
|
@@ -124,6 +124,33 @@ function App() {
|
|
|
124
124
|
</TextHighlighter>
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### Gettting highlighted words
|
|
128
|
+
|
|
129
|
+
```jsx
|
|
130
|
+
import TextHighlighter from 'react-highlight-me';
|
|
131
|
+
function App() {
|
|
132
|
+
const highlightedWords = ['React', 'JavaScript'];
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
<TextHighlighter words={highlightedWords}>
|
|
137
|
+
<div>
|
|
138
|
+
React is a JavaScript library for building user interfaces.
|
|
139
|
+
</div>
|
|
140
|
+
</TextHighlighter>
|
|
141
|
+
<div>
|
|
142
|
+
<h2>Highlighted Words:</h2>
|
|
143
|
+
<ul>
|
|
144
|
+
{Array.from(document.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR)).map((mark, index) => (
|
|
145
|
+
<li key={index}>{mark.textContent}</li>
|
|
146
|
+
))}
|
|
147
|
+
</ul>
|
|
148
|
+
</div>
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
127
154
|
## Props
|
|
128
155
|
|
|
129
156
|
| Prop | Type | Default | Description |
|
|
@@ -133,6 +160,7 @@ function App() {
|
|
|
133
160
|
| `highlightStyle` | `React.CSSProperties` | `{ backgroundColor: 'yellow', fontWeight: 'bold' }` | CSS styles to apply to highlighted text |
|
|
134
161
|
| `caseSensitive` | `boolean` | `false` | Whether to perform case-sensitive matching |
|
|
135
162
|
| `isWordBoundary` | `boolean` | `false` | Match words only at word boundaries |
|
|
163
|
+
| `escapeRegex` | `RegExp` | `/[.*+?^${}()\|[\]\\]/g` | Escape special regex characters |
|
|
136
164
|
|
|
137
165
|
## Examples
|
|
138
166
|
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -6,8 +6,14 @@ type Props = {
|
|
|
6
6
|
caseSensitive?: boolean;
|
|
7
7
|
isWordBoundary?: boolean;
|
|
8
8
|
isDebug?: boolean;
|
|
9
|
+
escapeRegex?: RegExp;
|
|
9
10
|
};
|
|
10
11
|
export declare const MARK_SELECTOR = "mark[data-highlighter=\"true\"]";
|
|
11
12
|
export declare const MARKS_IN_SCOPE_SELECTOR = ":scope mark[data-highlighter=\"true\"]:not(:scope [data-id=\"react-highlight-me\"] mark[data-highlighter=\"true\"])";
|
|
12
|
-
|
|
13
|
+
interface TextHighlighterComponent extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>> {
|
|
14
|
+
MARKS_IN_SCOPE_SELECTOR: string;
|
|
15
|
+
MARK_SELECTOR: string;
|
|
16
|
+
ROOT_ELEMENT_SELECTOR: string;
|
|
17
|
+
}
|
|
18
|
+
declare const TextHighlighter: TextHighlighterComponent;
|
|
13
19
|
export default TextHighlighter;
|
package/dist/cjs/index.js
CHANGED
|
@@ -41,23 +41,21 @@ const ROOT_ELEMENT_SELECTOR = `[${ROOT_ELEMENT_ATTR}="${ROOT_ELEMENT_ID}"]`;
|
|
|
41
41
|
const MARK_ATTRIBUTE = 'data-highlighter';
|
|
42
42
|
exports.MARK_SELECTOR = `mark[${MARK_ATTRIBUTE}="true"]`;
|
|
43
43
|
exports.MARKS_IN_SCOPE_SELECTOR = `:scope ${exports.MARK_SELECTOR}:not(:scope ${ROOT_ELEMENT_SELECTOR} ${exports.MARK_SELECTOR})`;
|
|
44
|
-
|
|
45
|
-
const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, }, ref) => {
|
|
44
|
+
const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, escapeRegex = /[.*+?^${}()|[\]\\]/g, }, ref) => {
|
|
46
45
|
const containerRef = (0, react_1.useRef)(null);
|
|
47
46
|
const observerRef = (0, react_1.useRef)(null);
|
|
48
47
|
const lastHighlightSignature = (0, react_1.useRef)('');
|
|
49
48
|
const [isInitiallyReady, setIsInitiallyReady] = (0, react_1.useState)(false);
|
|
50
|
-
const
|
|
51
|
-
propsRef
|
|
49
|
+
const currentProps = { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex };
|
|
50
|
+
const propsRef = (0, react_1.useRef)(currentProps);
|
|
51
|
+
propsRef.current = currentProps;
|
|
52
52
|
const nodeFilter = {
|
|
53
|
-
// Custom filter to ignore text nodes inside nested data-id elements
|
|
54
|
-
// This prevents highlighting text inside nested highlighter elements
|
|
55
53
|
acceptNode: (node) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
if (!containerRef.current)
|
|
55
|
+
return NodeFilter.FILTER_SKIP;
|
|
56
|
+
return isInMyScope(containerRef.current, node, ROOT_ELEMENT_SELECTOR)
|
|
57
|
+
? NodeFilter.FILTER_ACCEPT
|
|
58
|
+
: NodeFilter.FILTER_SKIP;
|
|
61
59
|
}
|
|
62
60
|
};
|
|
63
61
|
const getTextSignature = (0, react_1.useCallback)((element) => {
|
|
@@ -74,11 +72,11 @@ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlig
|
|
|
74
72
|
return textParts.join('|');
|
|
75
73
|
}, []);
|
|
76
74
|
const highlightTextInElement = (0, react_1.useCallback)((element) => {
|
|
77
|
-
const { words, highlightStyle, caseSensitive, isWordBoundary } = propsRef.current;
|
|
75
|
+
const { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex } = propsRef.current;
|
|
78
76
|
const wordsArray = Array.isArray(words) ? words : [words];
|
|
79
77
|
isDebug && console.log('Highlighting with words:', wordsArray);
|
|
80
78
|
// Remove existing highlights
|
|
81
|
-
const existingMarks = element.querySelectorAll(
|
|
79
|
+
const existingMarks = element.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR);
|
|
82
80
|
existingMarks.forEach(mark => {
|
|
83
81
|
var _a;
|
|
84
82
|
const textContent = mark.textContent || '';
|
|
@@ -101,7 +99,7 @@ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlig
|
|
|
101
99
|
textNodes.forEach(textNode => {
|
|
102
100
|
var _a;
|
|
103
101
|
const text = textNode.textContent || '';
|
|
104
|
-
if (!text
|
|
102
|
+
if (!text)
|
|
105
103
|
return;
|
|
106
104
|
const pattern = wordsArray
|
|
107
105
|
.filter(word => word)
|
|
@@ -109,7 +107,7 @@ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlig
|
|
|
109
107
|
if (word instanceof RegExp) {
|
|
110
108
|
return word.source;
|
|
111
109
|
}
|
|
112
|
-
let term = word.replace(
|
|
110
|
+
let term = escapeRegex ? word.replace(escapeRegex, '\\$&') : word;
|
|
113
111
|
if (isWordBoundary) {
|
|
114
112
|
term = `\\b${term}\\b`;
|
|
115
113
|
}
|
|
@@ -132,8 +130,8 @@ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlig
|
|
|
132
130
|
return testRegex.test(part);
|
|
133
131
|
}
|
|
134
132
|
return caseSensitive
|
|
135
|
-
? part === word
|
|
136
|
-
: part.toLowerCase() === word.
|
|
133
|
+
? part === word
|
|
134
|
+
: part.toLowerCase() === word.toLowerCase();
|
|
137
135
|
});
|
|
138
136
|
if (shouldHighlight) {
|
|
139
137
|
const mark = document.createElement('mark');
|
|
@@ -197,7 +195,7 @@ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlig
|
|
|
197
195
|
observerRef.current.disconnect();
|
|
198
196
|
}
|
|
199
197
|
};
|
|
200
|
-
}, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref]);
|
|
198
|
+
}, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref, escapeRegex]);
|
|
201
199
|
return (react_1.default.createElement("div", { [ROOT_ELEMENT_ATTR]: ROOT_ELEMENT_ID, ref: (node) => {
|
|
202
200
|
containerRef.current = node;
|
|
203
201
|
if (typeof ref === 'function') {
|
|
@@ -208,9 +206,13 @@ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlig
|
|
|
208
206
|
}
|
|
209
207
|
}, style: {
|
|
210
208
|
visibility: isInitiallyReady ? 'visible' : 'hidden',
|
|
211
|
-
minHeight: isInitiallyReady ? 'auto' : '1em'
|
|
209
|
+
minHeight: isInitiallyReady ? 'auto' : '1em',
|
|
210
|
+
display: 'contents',
|
|
212
211
|
} }, children));
|
|
213
212
|
});
|
|
213
|
+
TextHighlighter.MARK_SELECTOR = exports.MARK_SELECTOR;
|
|
214
|
+
TextHighlighter.ROOT_ELEMENT_SELECTOR = ROOT_ELEMENT_SELECTOR;
|
|
215
|
+
TextHighlighter.MARKS_IN_SCOPE_SELECTOR = exports.MARKS_IN_SCOPE_SELECTOR;
|
|
214
216
|
exports.default = TextHighlighter;
|
|
215
217
|
function isInMyScope(rootElem, target, rootSelector) {
|
|
216
218
|
// Type guard: handle non-Element nodes
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAA0F;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -6,8 +6,14 @@ type Props = {
|
|
|
6
6
|
caseSensitive?: boolean;
|
|
7
7
|
isWordBoundary?: boolean;
|
|
8
8
|
isDebug?: boolean;
|
|
9
|
+
escapeRegex?: RegExp;
|
|
9
10
|
};
|
|
10
11
|
export declare const MARK_SELECTOR = "mark[data-highlighter=\"true\"]";
|
|
11
12
|
export declare const MARKS_IN_SCOPE_SELECTOR = ":scope mark[data-highlighter=\"true\"]:not(:scope [data-id=\"react-highlight-me\"] mark[data-highlighter=\"true\"])";
|
|
12
|
-
|
|
13
|
+
interface TextHighlighterComponent extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>> {
|
|
14
|
+
MARKS_IN_SCOPE_SELECTOR: string;
|
|
15
|
+
MARK_SELECTOR: string;
|
|
16
|
+
ROOT_ELEMENT_SELECTOR: string;
|
|
17
|
+
}
|
|
18
|
+
declare const TextHighlighter: TextHighlighterComponent;
|
|
13
19
|
export default TextHighlighter;
|
package/dist/esm/index.js
CHANGED
|
@@ -5,23 +5,21 @@ const ROOT_ELEMENT_SELECTOR = `[${ROOT_ELEMENT_ATTR}="${ROOT_ELEMENT_ID}"]`;
|
|
|
5
5
|
const MARK_ATTRIBUTE = 'data-highlighter';
|
|
6
6
|
export const MARK_SELECTOR = `mark[${MARK_ATTRIBUTE}="true"]`;
|
|
7
7
|
export const MARKS_IN_SCOPE_SELECTOR = `:scope ${MARK_SELECTOR}:not(:scope ${ROOT_ELEMENT_SELECTOR} ${MARK_SELECTOR})`;
|
|
8
|
-
|
|
9
|
-
const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, }, ref) => {
|
|
8
|
+
const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, escapeRegex = /[.*+?^${}()|[\]\\]/g, }, ref) => {
|
|
10
9
|
const containerRef = useRef(null);
|
|
11
10
|
const observerRef = useRef(null);
|
|
12
11
|
const lastHighlightSignature = useRef('');
|
|
13
12
|
const [isInitiallyReady, setIsInitiallyReady] = useState(false);
|
|
14
|
-
const
|
|
15
|
-
propsRef
|
|
13
|
+
const currentProps = { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex };
|
|
14
|
+
const propsRef = useRef(currentProps);
|
|
15
|
+
propsRef.current = currentProps;
|
|
16
16
|
const nodeFilter = {
|
|
17
|
-
// Custom filter to ignore text nodes inside nested data-id elements
|
|
18
|
-
// This prevents highlighting text inside nested highlighter elements
|
|
19
17
|
acceptNode: (node) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
if (!containerRef.current)
|
|
19
|
+
return NodeFilter.FILTER_SKIP;
|
|
20
|
+
return isInMyScope(containerRef.current, node, ROOT_ELEMENT_SELECTOR)
|
|
21
|
+
? NodeFilter.FILTER_ACCEPT
|
|
22
|
+
: NodeFilter.FILTER_SKIP;
|
|
25
23
|
}
|
|
26
24
|
};
|
|
27
25
|
const getTextSignature = useCallback((element) => {
|
|
@@ -38,11 +36,11 @@ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { b
|
|
|
38
36
|
return textParts.join('|');
|
|
39
37
|
}, []);
|
|
40
38
|
const highlightTextInElement = useCallback((element) => {
|
|
41
|
-
const { words, highlightStyle, caseSensitive, isWordBoundary } = propsRef.current;
|
|
39
|
+
const { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex } = propsRef.current;
|
|
42
40
|
const wordsArray = Array.isArray(words) ? words : [words];
|
|
43
41
|
isDebug && console.log('Highlighting with words:', wordsArray);
|
|
44
42
|
// Remove existing highlights
|
|
45
|
-
const existingMarks = element.querySelectorAll(MARKS_IN_SCOPE_SELECTOR);
|
|
43
|
+
const existingMarks = element.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR);
|
|
46
44
|
existingMarks.forEach(mark => {
|
|
47
45
|
var _a;
|
|
48
46
|
const textContent = mark.textContent || '';
|
|
@@ -65,7 +63,7 @@ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { b
|
|
|
65
63
|
textNodes.forEach(textNode => {
|
|
66
64
|
var _a;
|
|
67
65
|
const text = textNode.textContent || '';
|
|
68
|
-
if (!text
|
|
66
|
+
if (!text)
|
|
69
67
|
return;
|
|
70
68
|
const pattern = wordsArray
|
|
71
69
|
.filter(word => word)
|
|
@@ -73,7 +71,7 @@ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { b
|
|
|
73
71
|
if (word instanceof RegExp) {
|
|
74
72
|
return word.source;
|
|
75
73
|
}
|
|
76
|
-
let term = word.replace(
|
|
74
|
+
let term = escapeRegex ? word.replace(escapeRegex, '\\$&') : word;
|
|
77
75
|
if (isWordBoundary) {
|
|
78
76
|
term = `\\b${term}\\b`;
|
|
79
77
|
}
|
|
@@ -96,8 +94,8 @@ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { b
|
|
|
96
94
|
return testRegex.test(part);
|
|
97
95
|
}
|
|
98
96
|
return caseSensitive
|
|
99
|
-
? part === word
|
|
100
|
-
: part.toLowerCase() === word.
|
|
97
|
+
? part === word
|
|
98
|
+
: part.toLowerCase() === word.toLowerCase();
|
|
101
99
|
});
|
|
102
100
|
if (shouldHighlight) {
|
|
103
101
|
const mark = document.createElement('mark');
|
|
@@ -161,7 +159,7 @@ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { b
|
|
|
161
159
|
observerRef.current.disconnect();
|
|
162
160
|
}
|
|
163
161
|
};
|
|
164
|
-
}, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref]);
|
|
162
|
+
}, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref, escapeRegex]);
|
|
165
163
|
return (React.createElement("div", { [ROOT_ELEMENT_ATTR]: ROOT_ELEMENT_ID, ref: (node) => {
|
|
166
164
|
containerRef.current = node;
|
|
167
165
|
if (typeof ref === 'function') {
|
|
@@ -172,9 +170,13 @@ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { b
|
|
|
172
170
|
}
|
|
173
171
|
}, style: {
|
|
174
172
|
visibility: isInitiallyReady ? 'visible' : 'hidden',
|
|
175
|
-
minHeight: isInitiallyReady ? 'auto' : '1em'
|
|
173
|
+
minHeight: isInitiallyReady ? 'auto' : '1em',
|
|
174
|
+
display: 'contents',
|
|
176
175
|
} }, children));
|
|
177
176
|
});
|
|
177
|
+
TextHighlighter.MARK_SELECTOR = MARK_SELECTOR;
|
|
178
|
+
TextHighlighter.ROOT_ELEMENT_SELECTOR = ROOT_ELEMENT_SELECTOR;
|
|
179
|
+
TextHighlighter.MARKS_IN_SCOPE_SELECTOR = MARKS_IN_SCOPE_SELECTOR;
|
|
178
180
|
export default TextHighlighter;
|
|
179
181
|
function isInMyScope(rootElem, target, rootSelector) {
|
|
180
182
|
// Type guard: handle non-Element nodes
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAY1F,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAC7C,MAAM,iBAAiB,GAAG,SAAS,CAAC;AACpC,MAAM,qBAAqB,GAAG,IAAI,iBAAiB,KAAK,eAAe,IAAI,CAAC;AAC5E,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAC1C,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,cAAc,UAAU,CAAC;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,aAAa,eAAe,qBAAqB,IAAI,aAAa,GAAG,CAAC;AAQvH,MAAM,eAAe,GAAG,UAAU,CAAwB,CAAC,EACzD,QAAQ,EACR,KAAK,GAAG,EAAE,EACV,cAAc,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,EAClE,aAAa,GAAG,KAAK,EACrB,cAAc,GAAG,KAAK,EACtB,OAAO,GAAG,KAAK,EACf,WAAW,GAAG,qBAAqB,GACpC,EAAE,GAAG,EAAE,EAAE;IACR,MAAM,YAAY,GAAG,MAAM,CAAqB,IAAI,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IAC1D,MAAM,sBAAsB,GAAG,MAAM,CAAS,EAAE,CAAC,CAAC;IAClD,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEhE,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;IAC3F,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IACtC,QAAQ,CAAC,OAAO,GAAG,YAAY,CAAC;IAEhC,MAAM,UAAU,GAAG;QACjB,UAAU,EAAE,CAAC,IAAU,EAAE,EAAE;YACzB,IAAI,CAAC,YAAY,CAAC,OAAO;gBAAE,OAAO,UAAU,CAAC,WAAW,CAAC;YAEzD,OAAO,WAAW,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,qBAAqB,CAAC;gBACnE,CAAC,CAAC,UAAU,CAAC,aAAa;gBAC1B,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC;QAC7B,CAAC;KACF,CAAA;IAED,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,OAAoB,EAAU,EAAE;;QACpE,gEAAgE;QAChE,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CACtC,OAAO,EACP,UAAU,CAAC,SAAS,EACpB,UAAU,CACX,CAAC;QAEF,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC;QACT,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,CAAA,MAAC,IAAa,CAAC,aAAa,0CAAE,YAAY,CAAC,cAAc,CAAC,CAAA,EAAE,CAAC;gBAChE,SAAS,CAAC,IAAI,CAAE,IAAa,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,sBAAsB,GAAG,WAAW,CAAC,CAAC,OAAoB,EAAE,EAAE;QAClE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC;QAC/F,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAE1D,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,UAAU,CAAC,CAAC;QAE/D,6BAA6B;QAC7B,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC;QACxF,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;;YAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YACtD,MAAA,IAAI,CAAC,UAAU,0CAAE,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,+BAA+B;QAEpD,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,sBAAsB,CAAC,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QAErE,4CAA4C;QAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CACtC,OAAO,EACP,UAAU,CAAC,SAAS,EACpB,UAAU,CACX,CAAC;QAEF,MAAM,SAAS,GAAW,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC;QACT,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;YAClC,SAAS,CAAC,IAAI,CAAC,IAAY,CAAC,CAAC;QAC/B,CAAC;QAED,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;;YAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,MAAM,OAAO,GAAG,UAAU;iBACzB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;iBACpB,GAAG,CAAC,IAAI,CAAC,EAAE;gBACV,IAAI,IAAI,YAAY,MAAM,EAAE,CAAC;oBAC3B,OAAO,IAAI,CAAC,MAAM,CAAC;gBACrB,CAAC;gBACD,IAAI,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAClE,IAAI,cAAc,EAAE,CAAC;oBACnB,IAAI,GAAG,MAAM,IAAI,KAAK,CAAC;gBACzB,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC,CAAC;iBACD,IAAI,CAAC,GAAG,CAAC,CAAC;YAEX,IAAI,CAAC,OAAO;gBAAE,OAAO;YAErB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAEhC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,EAAE,CAAC;gBAEnD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;oBACnB,IAAI,CAAC,IAAI;wBAAE,OAAO;oBAElB,MAAM,eAAe,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;wBAC7C,IAAI,IAAI,YAAY,MAAM,EAAE,CAAC;4BAC3B,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;4BACjG,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;4BACjD,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;wBAC9B,CAAC;wBACD,OAAO,aAAa;4BAClB,CAAC,CAAC,IAAI,KAAK,IAAI;4BACf,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;oBAChD,CAAC,CAAC,CAAC;oBAEH,IAAI,eAAe,EAAE,CAAC;wBACpB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;wBAC5C,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;wBAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;wBAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;wBACxB,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;oBAC7B,CAAC;yBAAM,CAAC;wBACN,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;oBACtD,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,MAAA,QAAQ,CAAC,UAAU,0CAAE,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACxD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,sCAAsC;QACtC,sBAAsB,CAAC,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7D,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAEvB,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,UAAU,GAAG,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC;QAChF,IAAI,CAAC,UAAU;YAAE,OAAO;QAExB,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;QAEjE,sBAAsB,CAAC,UAAU,CAAC,CAAC;QACnC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAE1B,WAAW,CAAC,OAAO,GAAG,IAAI,gBAAgB,CAAC,CAAC,SAAS,EAAE,EAAE;YACvD,IAAI,CAAC,UAAU;gBAAE,OAAO;YAExB,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAChD,IAAI,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC,EAAE,CAAC;oBACpE,4CAA4C;oBAC5C,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,kCAAkC,EAAE,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC;oBACvG,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,kEAAkE;gBAClE,OAAO,KAAK,CAAC;YACf,CAAC,CAAC,CAAC;YAEH,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YAED,mDAAmD;YACnD,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAEtD,IAAI,gBAAgB,KAAK,sBAAsB,CAAC,OAAO,EAAE,CAAC;gBACxD,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;gBAClE,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC;gBAC/D,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;gBAEjD,sBAAsB,CAAC,UAAU,CAAC,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;YACxE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE;YACtC,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;gBACxB,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACnC,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAEvH,OAAO,CACL,6BACQ,CAAC,iBAAiB,CAAC,EAAE,eAAe,EAC1C,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE;YACZ,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;YAC5B,IAAI,OAAO,GAAG,KAAK,UAAU,EAAE,CAAC;gBAC9B,GAAG,CAAC,IAAI,CAAC,CAAC;YACZ,CAAC;iBAAM,IAAI,GAAG,EAAE,CAAC;gBACf,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC,EACD,KAAK,EAAE;YACL,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YACnD,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;YAC5C,OAAO,EAAE,UAAU;SACpB,IAEA,QAAQ,CACL,CACP,CAAC;AACJ,CAAC,CAA6B,CAAC;AAE/B,eAAe,CAAC,aAAa,GAAG,aAAa,CAAC;AAC9C,eAAe,CAAC,qBAAqB,GAAG,qBAAqB,CAAC;AAC9D,eAAe,CAAC,uBAAuB,GAAG,uBAAuB,CAAC;AAElE,eAAe,eAAe,CAAC;AAE/B,SAAS,WAAW,CAAC,QAAiB,EAAE,MAAY,EAAE,YAAoB;IACxE,uCAAuC;IACvC,MAAM,aAAa,GAAG,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;IAChF,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,0CAA0C;IAC1C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,+CAA+C;IAC/C,MAAM,yBAAyB,GAAG,UAAU,YAAY,eAAe,YAAY,IAAI,YAAY,GAAG,CAAC;IACvG,MAAM,iBAAiB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC;IAE/E,uDAAuD;IACvD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CACtD,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CACnC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -7,6 +7,7 @@ type Props = {
|
|
|
7
7
|
caseSensitive?: boolean;
|
|
8
8
|
isWordBoundary?: boolean;
|
|
9
9
|
isDebug?: boolean;
|
|
10
|
+
escapeRegex?: RegExp;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const ROOT_ELEMENT_ID = 'react-highlight-me';
|
|
@@ -16,7 +17,11 @@ const MARK_ATTRIBUTE = 'data-highlighter';
|
|
|
16
17
|
export const MARK_SELECTOR = `mark[${MARK_ATTRIBUTE}="true"]`;
|
|
17
18
|
export const MARKS_IN_SCOPE_SELECTOR = `:scope ${MARK_SELECTOR}:not(:scope ${ROOT_ELEMENT_SELECTOR} ${MARK_SELECTOR})`;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
interface TextHighlighterComponent extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>> {
|
|
21
|
+
MARKS_IN_SCOPE_SELECTOR: string;
|
|
22
|
+
MARK_SELECTOR: string;
|
|
23
|
+
ROOT_ELEMENT_SELECTOR: string;
|
|
24
|
+
}
|
|
20
25
|
|
|
21
26
|
const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
22
27
|
children,
|
|
@@ -25,23 +30,24 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
25
30
|
caseSensitive = false,
|
|
26
31
|
isWordBoundary = false,
|
|
27
32
|
isDebug = false,
|
|
33
|
+
escapeRegex = /[.*+?^${}()|[\]\\]/g,
|
|
28
34
|
}, ref) => {
|
|
29
|
-
const containerRef = useRef<
|
|
35
|
+
const containerRef = useRef<HTMLElement | null>(null);
|
|
30
36
|
const observerRef = useRef<MutationObserver | null>(null);
|
|
31
37
|
const lastHighlightSignature = useRef<string>('');
|
|
32
38
|
const [isInitiallyReady, setIsInitiallyReady] = useState(false);
|
|
33
39
|
|
|
34
|
-
const
|
|
35
|
-
propsRef
|
|
40
|
+
const currentProps = { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex };
|
|
41
|
+
const propsRef = useRef(currentProps);
|
|
42
|
+
propsRef.current = currentProps;
|
|
36
43
|
|
|
37
44
|
const nodeFilter = {
|
|
38
|
-
// Custom filter to ignore text nodes inside nested data-id elements
|
|
39
|
-
// This prevents highlighting text inside nested highlighter elements
|
|
40
45
|
acceptNode: (node: Node) => {
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
if (!containerRef.current) return NodeFilter.FILTER_SKIP;
|
|
47
|
+
|
|
48
|
+
return isInMyScope(containerRef.current, node, ROOT_ELEMENT_SELECTOR)
|
|
49
|
+
? NodeFilter.FILTER_ACCEPT
|
|
50
|
+
: NodeFilter.FILTER_SKIP;
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -65,13 +71,13 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
65
71
|
}, []);
|
|
66
72
|
|
|
67
73
|
const highlightTextInElement = useCallback((element: HTMLElement) => {
|
|
68
|
-
const { words, highlightStyle, caseSensitive, isWordBoundary } = propsRef.current;
|
|
74
|
+
const { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex } = propsRef.current;
|
|
69
75
|
const wordsArray = Array.isArray(words) ? words : [words];
|
|
70
76
|
|
|
71
77
|
isDebug && console.log('Highlighting with words:', wordsArray);
|
|
72
78
|
|
|
73
79
|
// Remove existing highlights
|
|
74
|
-
const existingMarks = element.querySelectorAll(MARKS_IN_SCOPE_SELECTOR);
|
|
80
|
+
const existingMarks = element.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR);
|
|
75
81
|
existingMarks.forEach(mark => {
|
|
76
82
|
const textContent = mark.textContent || '';
|
|
77
83
|
const textNode = document.createTextNode(textContent);
|
|
@@ -102,7 +108,7 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
102
108
|
|
|
103
109
|
textNodes.forEach(textNode => {
|
|
104
110
|
const text = textNode.textContent || '';
|
|
105
|
-
if (!text
|
|
111
|
+
if (!text) return;
|
|
106
112
|
|
|
107
113
|
const pattern = wordsArray
|
|
108
114
|
.filter(word => word)
|
|
@@ -110,7 +116,7 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
110
116
|
if (word instanceof RegExp) {
|
|
111
117
|
return word.source;
|
|
112
118
|
}
|
|
113
|
-
let term = word.replace(
|
|
119
|
+
let term = escapeRegex ? word.replace(escapeRegex, '\\$&') : word;
|
|
114
120
|
if (isWordBoundary) {
|
|
115
121
|
term = `\\b${term}\\b`;
|
|
116
122
|
}
|
|
@@ -136,8 +142,8 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
136
142
|
return testRegex.test(part);
|
|
137
143
|
}
|
|
138
144
|
return caseSensitive
|
|
139
|
-
? part === word
|
|
140
|
-
: part.toLowerCase() === word.
|
|
145
|
+
? part === word
|
|
146
|
+
: part.toLowerCase() === word.toLowerCase();
|
|
141
147
|
});
|
|
142
148
|
|
|
143
149
|
if (shouldHighlight) {
|
|
@@ -211,7 +217,7 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
211
217
|
observerRef.current.disconnect();
|
|
212
218
|
}
|
|
213
219
|
};
|
|
214
|
-
}, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref]);
|
|
220
|
+
}, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref, escapeRegex]);
|
|
215
221
|
|
|
216
222
|
return (
|
|
217
223
|
<div
|
|
@@ -226,13 +232,18 @@ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
|
|
|
226
232
|
}}
|
|
227
233
|
style={{
|
|
228
234
|
visibility: isInitiallyReady ? 'visible' : 'hidden',
|
|
229
|
-
minHeight: isInitiallyReady ? 'auto' : '1em'
|
|
235
|
+
minHeight: isInitiallyReady ? 'auto' : '1em',
|
|
236
|
+
display: 'contents',
|
|
230
237
|
}}
|
|
231
238
|
>
|
|
232
239
|
{children}
|
|
233
240
|
</div>
|
|
234
241
|
);
|
|
235
|
-
});
|
|
242
|
+
}) as TextHighlighterComponent;
|
|
243
|
+
|
|
244
|
+
TextHighlighter.MARK_SELECTOR = MARK_SELECTOR;
|
|
245
|
+
TextHighlighter.ROOT_ELEMENT_SELECTOR = ROOT_ELEMENT_SELECTOR;
|
|
246
|
+
TextHighlighter.MARKS_IN_SCOPE_SELECTOR = MARKS_IN_SCOPE_SELECTOR;
|
|
236
247
|
|
|
237
248
|
export default TextHighlighter;
|
|
238
249
|
|