react-highlight-me 2.0.4 → 2.2.0

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 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
 
@@ -6,6 +6,14 @@ type Props = {
6
6
  caseSensitive?: boolean;
7
7
  isWordBoundary?: boolean;
8
8
  isDebug?: boolean;
9
+ escapeRegex?: RegExp;
9
10
  };
10
- declare const TextHighlighter: ({ children, words, highlightStyle, caseSensitive, isWordBoundary, isDebug, }: Props) => React.JSX.Element;
11
+ export declare const MARK_SELECTOR = "mark[data-highlighter=\"true\"]";
12
+ export declare const MARKS_IN_SCOPE_SELECTOR = ":scope mark[data-highlighter=\"true\"]:not(:scope [data-id=\"react-highlight-me\"] mark[data-highlighter=\"true\"])";
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;
11
19
  export default TextHighlighter;
package/dist/cjs/index.js CHANGED
@@ -33,34 +33,50 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MARKS_IN_SCOPE_SELECTOR = exports.MARK_SELECTOR = void 0;
36
37
  const react_1 = __importStar(require("react"));
37
- // Alternative approach: Use a single observer that never disconnects
38
- const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, }) => {
38
+ const ROOT_ELEMENT_ID = 'react-highlight-me';
39
+ const ROOT_ELEMENT_ATTR = 'data-id';
40
+ const ROOT_ELEMENT_SELECTOR = `[${ROOT_ELEMENT_ATTR}="${ROOT_ELEMENT_ID}"]`;
41
+ const MARK_ATTRIBUTE = 'data-highlighter';
42
+ exports.MARK_SELECTOR = `mark[${MARK_ATTRIBUTE}="true"]`;
43
+ exports.MARKS_IN_SCOPE_SELECTOR = `:scope ${exports.MARK_SELECTOR}:not(:scope ${ROOT_ELEMENT_SELECTOR} ${exports.MARK_SELECTOR})`;
44
+ const TextHighlighter = (0, react_1.forwardRef)(({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, escapeRegex = /[.*+?^${}()|[\]\\]/g, }, ref) => {
39
45
  const containerRef = (0, react_1.useRef)(null);
40
46
  const observerRef = (0, react_1.useRef)(null);
41
47
  const lastHighlightSignature = (0, react_1.useRef)('');
42
48
  const [isInitiallyReady, setIsInitiallyReady] = (0, react_1.useState)(false);
43
- const propsRef = (0, react_1.useRef)({ words, highlightStyle, caseSensitive, isWordBoundary });
44
- propsRef.current = { words, highlightStyle, caseSensitive, isWordBoundary };
49
+ const currentProps = { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex };
50
+ const propsRef = (0, react_1.useRef)(currentProps);
51
+ propsRef.current = currentProps;
52
+ const nodeFilter = {
53
+ acceptNode: (node) => {
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;
59
+ }
60
+ };
45
61
  const getTextSignature = (0, react_1.useCallback)((element) => {
46
62
  var _a;
47
63
  // Create a signature of all text content to detect real changes
48
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
64
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, nodeFilter);
49
65
  const textParts = [];
50
66
  let node;
51
67
  while ((node = walker.nextNode())) {
52
- if (!((_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-highlighter'))) {
68
+ if (!((_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.hasAttribute(MARK_ATTRIBUTE))) {
53
69
  textParts.push(node.textContent || '');
54
70
  }
55
71
  }
56
72
  return textParts.join('|');
57
73
  }, []);
58
74
  const highlightTextInElement = (0, react_1.useCallback)((element) => {
59
- const { words, highlightStyle, caseSensitive, isWordBoundary } = propsRef.current;
75
+ const { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex } = propsRef.current;
60
76
  const wordsArray = Array.isArray(words) ? words : [words];
61
77
  isDebug && console.log('Highlighting with words:', wordsArray);
62
78
  // Remove existing highlights
63
- const existingMarks = element.querySelectorAll('mark[data-highlighter="true"]');
79
+ const existingMarks = element.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR);
64
80
  existingMarks.forEach(mark => {
65
81
  var _a;
66
82
  const textContent = mark.textContent || '';
@@ -72,8 +88,9 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
72
88
  lastHighlightSignature.current = getTextSignature(element);
73
89
  return;
74
90
  }
91
+ isDebug && console.log('Text signature:', getTextSignature(element));
75
92
  // Apply highlighting (same logic as before)
76
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
93
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, nodeFilter);
77
94
  const textNodes = [];
78
95
  let node;
79
96
  while ((node = walker.nextNode())) {
@@ -82,7 +99,7 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
82
99
  textNodes.forEach(textNode => {
83
100
  var _a;
84
101
  const text = textNode.textContent || '';
85
- if (!text.trim())
102
+ if (!text)
86
103
  return;
87
104
  const pattern = wordsArray
88
105
  .filter(word => word)
@@ -90,7 +107,7 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
90
107
  if (word instanceof RegExp) {
91
108
  return word.source;
92
109
  }
93
- let term = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
110
+ let term = escapeRegex ? word.replace(escapeRegex, '\\$&') : word;
94
111
  if (isWordBoundary) {
95
112
  term = `\\b${term}\\b`;
96
113
  }
@@ -113,12 +130,12 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
113
130
  return testRegex.test(part);
114
131
  }
115
132
  return caseSensitive
116
- ? part === word.trim()
117
- : part.toLowerCase() === word.trim().toLowerCase();
133
+ ? part === word
134
+ : part.toLowerCase() === word.toLowerCase();
118
135
  });
119
136
  if (shouldHighlight) {
120
137
  const mark = document.createElement('mark');
121
- mark.setAttribute('data-highlighter', 'true');
138
+ mark.setAttribute(MARK_ATTRIBUTE, 'true');
122
139
  Object.assign(mark.style, highlightStyle);
123
140
  mark.textContent = part;
124
141
  fragment.appendChild(mark);
@@ -134,27 +151,41 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
134
151
  lastHighlightSignature.current = getTextSignature(element);
135
152
  }, [getTextSignature]);
136
153
  (0, react_1.useLayoutEffect)(() => {
137
- if (!containerRef.current)
154
+ const currentRef = ref && 'current' in ref ? ref.current : containerRef.current;
155
+ if (!currentRef)
138
156
  return;
139
157
  isDebug && console.log('Setting up persistent MutationObserver');
140
- highlightTextInElement(containerRef.current);
158
+ highlightTextInElement(currentRef);
141
159
  setIsInitiallyReady(true);
142
160
  observerRef.current = new MutationObserver((mutations) => {
143
- if (!containerRef.current)
161
+ if (!currentRef)
162
+ return;
163
+ const myMutations = mutations.filter((mutation) => {
164
+ if (isInMyScope(currentRef, mutation.target, ROOT_ELEMENT_SELECTOR)) {
165
+ // This mutation is in my scope - process it
166
+ isDebug && console.log('Processing mutation in my scope:', currentRef.getAttribute(ROOT_ELEMENT_ATTR));
167
+ return true;
168
+ }
169
+ // Otherwise ignore - it's either from outer scope or nested scope
170
+ return false;
171
+ });
172
+ if (myMutations.length === 0) {
173
+ isDebug && console.log('No relevant mutations in my scope, skipping');
144
174
  return;
175
+ }
145
176
  // Check if the text signature has actually changed
146
- const currentSignature = getTextSignature(containerRef.current);
177
+ const currentSignature = getTextSignature(currentRef);
147
178
  if (currentSignature !== lastHighlightSignature.current) {
148
179
  isDebug && console.log('Text signature changed, re-highlighting');
149
180
  isDebug && console.log('Old:', lastHighlightSignature.current);
150
181
  isDebug && console.log('New:', currentSignature);
151
- highlightTextInElement(containerRef.current);
182
+ highlightTextInElement(currentRef);
152
183
  }
153
184
  else {
154
185
  isDebug && console.log('Text signature unchanged, ignoring mutation');
155
186
  }
156
187
  });
157
- observerRef.current.observe(containerRef.current, {
188
+ observerRef.current.observe(currentRef, {
158
189
  childList: true,
159
190
  subtree: true,
160
191
  characterData: true
@@ -164,11 +195,38 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
164
195
  observerRef.current.disconnect();
165
196
  }
166
197
  };
167
- }, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature]);
168
- return (react_1.default.createElement("div", { ref: containerRef, style: {
198
+ }, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref, escapeRegex]);
199
+ return (react_1.default.createElement("div", { [ROOT_ELEMENT_ATTR]: ROOT_ELEMENT_ID, ref: (node) => {
200
+ containerRef.current = node;
201
+ if (typeof ref === 'function') {
202
+ ref(node);
203
+ }
204
+ else if (ref) {
205
+ ref.current = node;
206
+ }
207
+ }, style: {
169
208
  visibility: isInitiallyReady ? 'visible' : 'hidden',
170
209
  minHeight: isInitiallyReady ? 'auto' : '1em'
171
210
  } }, children));
172
- };
211
+ });
212
+ TextHighlighter.MARK_SELECTOR = exports.MARK_SELECTOR;
213
+ TextHighlighter.ROOT_ELEMENT_SELECTOR = ROOT_ELEMENT_SELECTOR;
214
+ TextHighlighter.MARKS_IN_SCOPE_SELECTOR = exports.MARKS_IN_SCOPE_SELECTOR;
173
215
  exports.default = TextHighlighter;
216
+ function isInMyScope(rootElem, target, rootSelector) {
217
+ // Type guard: handle non-Element nodes
218
+ const targetElement = target instanceof Element ? target : target.parentElement;
219
+ if (!targetElement) {
220
+ return false;
221
+ }
222
+ // First check if target is within my root
223
+ if (!rootElem.contains(targetElement)) {
224
+ return false;
225
+ }
226
+ // Select only direct nested roots using :scope
227
+ const directNestedRootsSelector = `:scope ${rootSelector}:not(:scope ${rootSelector} ${rootSelector})`;
228
+ const directNestedRoots = rootElem.querySelectorAll(directNestedRootsSelector);
229
+ // Check if target is NOT within any direct nested root
230
+ return !Array.from(directNestedRoots).some(nestedRoot => nestedRoot.contains(targetElement));
231
+ }
174
232
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
@@ -6,6 +6,14 @@ type Props = {
6
6
  caseSensitive?: boolean;
7
7
  isWordBoundary?: boolean;
8
8
  isDebug?: boolean;
9
+ escapeRegex?: RegExp;
9
10
  };
10
- declare const TextHighlighter: ({ children, words, highlightStyle, caseSensitive, isWordBoundary, isDebug, }: Props) => React.JSX.Element;
11
+ export declare const MARK_SELECTOR = "mark[data-highlighter=\"true\"]";
12
+ export declare const MARKS_IN_SCOPE_SELECTOR = ":scope mark[data-highlighter=\"true\"]:not(:scope [data-id=\"react-highlight-me\"] mark[data-highlighter=\"true\"])";
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;
11
19
  export default TextHighlighter;
package/dist/esm/index.js CHANGED
@@ -1,31 +1,46 @@
1
- import React, { useLayoutEffect, useRef, useCallback, useState } from 'react';
2
- // Alternative approach: Use a single observer that never disconnects
3
- const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, }) => {
1
+ import React, { useLayoutEffect, useRef, useCallback, useState, forwardRef } from 'react';
2
+ const ROOT_ELEMENT_ID = 'react-highlight-me';
3
+ const ROOT_ELEMENT_ATTR = 'data-id';
4
+ const ROOT_ELEMENT_SELECTOR = `[${ROOT_ELEMENT_ATTR}="${ROOT_ELEMENT_ID}"]`;
5
+ const MARK_ATTRIBUTE = 'data-highlighter';
6
+ export const MARK_SELECTOR = `mark[${MARK_ATTRIBUTE}="true"]`;
7
+ export const MARKS_IN_SCOPE_SELECTOR = `:scope ${MARK_SELECTOR}:not(:scope ${ROOT_ELEMENT_SELECTOR} ${MARK_SELECTOR})`;
8
+ const TextHighlighter = forwardRef(({ children, words = [], highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' }, caseSensitive = false, isWordBoundary = false, isDebug = false, escapeRegex = /[.*+?^${}()|[\]\\]/g, }, ref) => {
4
9
  const containerRef = useRef(null);
5
10
  const observerRef = useRef(null);
6
11
  const lastHighlightSignature = useRef('');
7
12
  const [isInitiallyReady, setIsInitiallyReady] = useState(false);
8
- const propsRef = useRef({ words, highlightStyle, caseSensitive, isWordBoundary });
9
- propsRef.current = { words, highlightStyle, caseSensitive, isWordBoundary };
13
+ const currentProps = { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex };
14
+ const propsRef = useRef(currentProps);
15
+ propsRef.current = currentProps;
16
+ const nodeFilter = {
17
+ acceptNode: (node) => {
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;
23
+ }
24
+ };
10
25
  const getTextSignature = useCallback((element) => {
11
26
  var _a;
12
27
  // Create a signature of all text content to detect real changes
13
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
28
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, nodeFilter);
14
29
  const textParts = [];
15
30
  let node;
16
31
  while ((node = walker.nextNode())) {
17
- if (!((_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-highlighter'))) {
32
+ if (!((_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.hasAttribute(MARK_ATTRIBUTE))) {
18
33
  textParts.push(node.textContent || '');
19
34
  }
20
35
  }
21
36
  return textParts.join('|');
22
37
  }, []);
23
38
  const highlightTextInElement = useCallback((element) => {
24
- const { words, highlightStyle, caseSensitive, isWordBoundary } = propsRef.current;
39
+ const { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex } = propsRef.current;
25
40
  const wordsArray = Array.isArray(words) ? words : [words];
26
41
  isDebug && console.log('Highlighting with words:', wordsArray);
27
42
  // Remove existing highlights
28
- const existingMarks = element.querySelectorAll('mark[data-highlighter="true"]');
43
+ const existingMarks = element.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR);
29
44
  existingMarks.forEach(mark => {
30
45
  var _a;
31
46
  const textContent = mark.textContent || '';
@@ -37,8 +52,9 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
37
52
  lastHighlightSignature.current = getTextSignature(element);
38
53
  return;
39
54
  }
55
+ isDebug && console.log('Text signature:', getTextSignature(element));
40
56
  // Apply highlighting (same logic as before)
41
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
57
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, nodeFilter);
42
58
  const textNodes = [];
43
59
  let node;
44
60
  while ((node = walker.nextNode())) {
@@ -47,7 +63,7 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
47
63
  textNodes.forEach(textNode => {
48
64
  var _a;
49
65
  const text = textNode.textContent || '';
50
- if (!text.trim())
66
+ if (!text)
51
67
  return;
52
68
  const pattern = wordsArray
53
69
  .filter(word => word)
@@ -55,7 +71,7 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
55
71
  if (word instanceof RegExp) {
56
72
  return word.source;
57
73
  }
58
- let term = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
74
+ let term = escapeRegex ? word.replace(escapeRegex, '\\$&') : word;
59
75
  if (isWordBoundary) {
60
76
  term = `\\b${term}\\b`;
61
77
  }
@@ -78,12 +94,12 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
78
94
  return testRegex.test(part);
79
95
  }
80
96
  return caseSensitive
81
- ? part === word.trim()
82
- : part.toLowerCase() === word.trim().toLowerCase();
97
+ ? part === word
98
+ : part.toLowerCase() === word.toLowerCase();
83
99
  });
84
100
  if (shouldHighlight) {
85
101
  const mark = document.createElement('mark');
86
- mark.setAttribute('data-highlighter', 'true');
102
+ mark.setAttribute(MARK_ATTRIBUTE, 'true');
87
103
  Object.assign(mark.style, highlightStyle);
88
104
  mark.textContent = part;
89
105
  fragment.appendChild(mark);
@@ -99,27 +115,41 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
99
115
  lastHighlightSignature.current = getTextSignature(element);
100
116
  }, [getTextSignature]);
101
117
  useLayoutEffect(() => {
102
- if (!containerRef.current)
118
+ const currentRef = ref && 'current' in ref ? ref.current : containerRef.current;
119
+ if (!currentRef)
103
120
  return;
104
121
  isDebug && console.log('Setting up persistent MutationObserver');
105
- highlightTextInElement(containerRef.current);
122
+ highlightTextInElement(currentRef);
106
123
  setIsInitiallyReady(true);
107
124
  observerRef.current = new MutationObserver((mutations) => {
108
- if (!containerRef.current)
125
+ if (!currentRef)
126
+ return;
127
+ const myMutations = mutations.filter((mutation) => {
128
+ if (isInMyScope(currentRef, mutation.target, ROOT_ELEMENT_SELECTOR)) {
129
+ // This mutation is in my scope - process it
130
+ isDebug && console.log('Processing mutation in my scope:', currentRef.getAttribute(ROOT_ELEMENT_ATTR));
131
+ return true;
132
+ }
133
+ // Otherwise ignore - it's either from outer scope or nested scope
134
+ return false;
135
+ });
136
+ if (myMutations.length === 0) {
137
+ isDebug && console.log('No relevant mutations in my scope, skipping');
109
138
  return;
139
+ }
110
140
  // Check if the text signature has actually changed
111
- const currentSignature = getTextSignature(containerRef.current);
141
+ const currentSignature = getTextSignature(currentRef);
112
142
  if (currentSignature !== lastHighlightSignature.current) {
113
143
  isDebug && console.log('Text signature changed, re-highlighting');
114
144
  isDebug && console.log('Old:', lastHighlightSignature.current);
115
145
  isDebug && console.log('New:', currentSignature);
116
- highlightTextInElement(containerRef.current);
146
+ highlightTextInElement(currentRef);
117
147
  }
118
148
  else {
119
149
  isDebug && console.log('Text signature unchanged, ignoring mutation');
120
150
  }
121
151
  });
122
- observerRef.current.observe(containerRef.current, {
152
+ observerRef.current.observe(currentRef, {
123
153
  childList: true,
124
154
  subtree: true,
125
155
  characterData: true
@@ -129,11 +159,38 @@ const TextHighlighter = ({ children, words = [], highlightStyle = { backgroundCo
129
159
  observerRef.current.disconnect();
130
160
  }
131
161
  };
132
- }, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature]);
133
- return (React.createElement("div", { ref: containerRef, style: {
162
+ }, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref, escapeRegex]);
163
+ return (React.createElement("div", { [ROOT_ELEMENT_ATTR]: ROOT_ELEMENT_ID, ref: (node) => {
164
+ containerRef.current = node;
165
+ if (typeof ref === 'function') {
166
+ ref(node);
167
+ }
168
+ else if (ref) {
169
+ ref.current = node;
170
+ }
171
+ }, style: {
134
172
  visibility: isInitiallyReady ? 'visible' : 'hidden',
135
173
  minHeight: isInitiallyReady ? 'auto' : '1em'
136
174
  } }, children));
137
- };
175
+ });
176
+ TextHighlighter.MARK_SELECTOR = MARK_SELECTOR;
177
+ TextHighlighter.ROOT_ELEMENT_SELECTOR = ROOT_ELEMENT_SELECTOR;
178
+ TextHighlighter.MARKS_IN_SCOPE_SELECTOR = MARKS_IN_SCOPE_SELECTOR;
138
179
  export default TextHighlighter;
180
+ function isInMyScope(rootElem, target, rootSelector) {
181
+ // Type guard: handle non-Element nodes
182
+ const targetElement = target instanceof Element ? target : target.parentElement;
183
+ if (!targetElement) {
184
+ return false;
185
+ }
186
+ // First check if target is within my root
187
+ if (!rootElem.contains(targetElement)) {
188
+ return false;
189
+ }
190
+ // Select only direct nested roots using :scope
191
+ const directNestedRootsSelector = `:scope ${rootSelector}:not(:scope ${rootSelector} ${rootSelector})`;
192
+ const directNestedRoots = rootElem.querySelectorAll(directNestedRootsSelector);
193
+ // Check if target is NOT within any direct nested root
194
+ return !Array.from(directNestedRoots).some(nestedRoot => nestedRoot.contains(targetElement));
195
+ }
139
196
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-highlight-me",
3
- "version": "2.0.4",
3
+ "version": "2.2.0",
4
4
  "description": "Highlight words in React components or text",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
package/src/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useLayoutEffect, useRef, useCallback, useState } from 'react';
1
+ import React, { useLayoutEffect, useRef, useCallback, useState, forwardRef } from 'react';
2
2
 
3
3
  type Props = {
4
4
  children?: React.ReactNode;
@@ -7,36 +7,62 @@ type Props = {
7
7
  caseSensitive?: boolean;
8
8
  isWordBoundary?: boolean;
9
9
  isDebug?: boolean;
10
+ escapeRegex?: RegExp;
10
11
  }
11
- // Alternative approach: Use a single observer that never disconnects
12
- const TextHighlighter = ({
12
+
13
+ const ROOT_ELEMENT_ID = 'react-highlight-me';
14
+ const ROOT_ELEMENT_ATTR = 'data-id';
15
+ const ROOT_ELEMENT_SELECTOR = `[${ROOT_ELEMENT_ATTR}="${ROOT_ELEMENT_ID}"]`;
16
+ const MARK_ATTRIBUTE = 'data-highlighter';
17
+ export const MARK_SELECTOR = `mark[${MARK_ATTRIBUTE}="true"]`;
18
+ export const MARKS_IN_SCOPE_SELECTOR = `:scope ${MARK_SELECTOR}:not(:scope ${ROOT_ELEMENT_SELECTOR} ${MARK_SELECTOR})`;
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
+ }
25
+
26
+ const TextHighlighter = forwardRef<HTMLDivElement, Props>(({
13
27
  children,
14
28
  words = [],
15
29
  highlightStyle = { backgroundColor: 'yellow', fontWeight: 'bold' },
16
30
  caseSensitive = false,
17
31
  isWordBoundary = false,
18
32
  isDebug = false,
19
- }: Props) => {
20
- const containerRef = useRef<HTMLDivElement>(null);
33
+ escapeRegex = /[.*+?^${}()|[\]\\]/g,
34
+ }, ref) => {
35
+ const containerRef = useRef<HTMLElement | null>(null);
21
36
  const observerRef = useRef<MutationObserver | null>(null);
22
37
  const lastHighlightSignature = useRef<string>('');
23
38
  const [isInitiallyReady, setIsInitiallyReady] = useState(false);
24
39
 
25
- const propsRef = useRef({ words, highlightStyle, caseSensitive, isWordBoundary });
26
- propsRef.current = { words, highlightStyle, caseSensitive, isWordBoundary };
40
+ const currentProps = { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex };
41
+ const propsRef = useRef(currentProps);
42
+ propsRef.current = currentProps;
43
+
44
+ const nodeFilter = {
45
+ acceptNode: (node: Node) => {
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;
51
+ }
52
+ }
27
53
 
28
54
  const getTextSignature = useCallback((element: HTMLElement): string => {
29
55
  // Create a signature of all text content to detect real changes
30
56
  const walker = document.createTreeWalker(
31
57
  element,
32
58
  NodeFilter.SHOW_TEXT,
33
- null
59
+ nodeFilter,
34
60
  );
35
61
 
36
62
  const textParts: string[] = [];
37
63
  let node;
38
64
  while ((node = walker.nextNode())) {
39
- if (!(node as Text).parentElement?.hasAttribute('data-highlighter')) {
65
+ if (!(node as Text).parentElement?.hasAttribute(MARK_ATTRIBUTE)) {
40
66
  textParts.push((node as Text).textContent || '');
41
67
  }
42
68
  }
@@ -45,13 +71,13 @@ const TextHighlighter = ({
45
71
  }, []);
46
72
 
47
73
  const highlightTextInElement = useCallback((element: HTMLElement) => {
48
- const { words, highlightStyle, caseSensitive, isWordBoundary } = propsRef.current;
74
+ const { words, highlightStyle, caseSensitive, isWordBoundary, escapeRegex } = propsRef.current;
49
75
  const wordsArray = Array.isArray(words) ? words : [words];
50
76
 
51
77
  isDebug && console.log('Highlighting with words:', wordsArray);
52
78
 
53
79
  // Remove existing highlights
54
- const existingMarks = element.querySelectorAll('mark[data-highlighter="true"]');
80
+ const existingMarks = element.querySelectorAll(TextHighlighter.MARKS_IN_SCOPE_SELECTOR);
55
81
  existingMarks.forEach(mark => {
56
82
  const textContent = mark.textContent || '';
57
83
  const textNode = document.createTextNode(textContent);
@@ -65,11 +91,13 @@ const TextHighlighter = ({
65
91
  return;
66
92
  }
67
93
 
94
+ isDebug && console.log('Text signature:', getTextSignature(element));
95
+
68
96
  // Apply highlighting (same logic as before)
69
97
  const walker = document.createTreeWalker(
70
98
  element,
71
99
  NodeFilter.SHOW_TEXT,
72
- null
100
+ nodeFilter,
73
101
  );
74
102
 
75
103
  const textNodes: Text[] = [];
@@ -80,21 +108,21 @@ const TextHighlighter = ({
80
108
 
81
109
  textNodes.forEach(textNode => {
82
110
  const text = textNode.textContent || '';
83
- if (!text.trim()) return;
111
+ if (!text) return;
84
112
 
85
113
  const pattern = wordsArray
86
- .filter(word => word)
87
- .map(word => {
88
- if (word instanceof RegExp) {
89
- return word.source;
90
- }
91
- let term = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
- if (isWordBoundary) {
93
- term = `\\b${term}\\b`;
94
- }
95
- return term;
96
- })
97
- .join('|');
114
+ .filter(word => word)
115
+ .map(word => {
116
+ if (word instanceof RegExp) {
117
+ return word.source;
118
+ }
119
+ let term = escapeRegex ? word.replace(escapeRegex, '\\$&') : word;
120
+ if (isWordBoundary) {
121
+ term = `\\b${term}\\b`;
122
+ }
123
+ return term;
124
+ })
125
+ .join('|');
98
126
 
99
127
  if (!pattern) return;
100
128
 
@@ -114,13 +142,13 @@ const TextHighlighter = ({
114
142
  return testRegex.test(part);
115
143
  }
116
144
  return caseSensitive
117
- ? part === word.trim()
118
- : part.toLowerCase() === word.trim().toLowerCase();
145
+ ? part === word
146
+ : part.toLowerCase() === word.toLowerCase();
119
147
  });
120
148
 
121
149
  if (shouldHighlight) {
122
150
  const mark = document.createElement('mark');
123
- mark.setAttribute('data-highlighter', 'true');
151
+ mark.setAttribute(MARK_ATTRIBUTE, 'true');
124
152
  Object.assign(mark.style, highlightStyle);
125
153
  mark.textContent = part;
126
154
  fragment.appendChild(mark);
@@ -138,31 +166,47 @@ const TextHighlighter = ({
138
166
  }, [getTextSignature]);
139
167
 
140
168
  useLayoutEffect(() => {
141
- if (!containerRef.current) return;
169
+ const currentRef = ref && 'current' in ref ? ref.current : containerRef.current;
170
+ if (!currentRef) return;
142
171
 
143
172
  isDebug && console.log('Setting up persistent MutationObserver');
144
173
 
145
- highlightTextInElement(containerRef.current);
174
+ highlightTextInElement(currentRef);
146
175
  setIsInitiallyReady(true);
147
176
 
148
177
  observerRef.current = new MutationObserver((mutations) => {
149
- if (!containerRef.current) return;
178
+ if (!currentRef) return;
179
+
180
+ const myMutations = mutations.filter((mutation) => {
181
+ if (isInMyScope(currentRef, mutation.target, ROOT_ELEMENT_SELECTOR)) {
182
+ // This mutation is in my scope - process it
183
+ isDebug && console.log('Processing mutation in my scope:', currentRef.getAttribute(ROOT_ELEMENT_ATTR));
184
+ return true;
185
+ }
186
+ // Otherwise ignore - it's either from outer scope or nested scope
187
+ return false;
188
+ });
189
+
190
+ if (myMutations.length === 0) {
191
+ isDebug && console.log('No relevant mutations in my scope, skipping');
192
+ return;
193
+ }
150
194
 
151
195
  // Check if the text signature has actually changed
152
- const currentSignature = getTextSignature(containerRef.current);
196
+ const currentSignature = getTextSignature(currentRef);
153
197
 
154
198
  if (currentSignature !== lastHighlightSignature.current) {
155
199
  isDebug && console.log('Text signature changed, re-highlighting');
156
200
  isDebug && console.log('Old:', lastHighlightSignature.current);
157
201
  isDebug && console.log('New:', currentSignature);
158
202
 
159
- highlightTextInElement(containerRef.current);
203
+ highlightTextInElement(currentRef);
160
204
  } else {
161
205
  isDebug && console.log('Text signature unchanged, ignoring mutation');
162
206
  }
163
207
  });
164
208
 
165
- observerRef.current.observe(containerRef.current, {
209
+ observerRef.current.observe(currentRef, {
166
210
  childList: true,
167
211
  subtree: true,
168
212
  characterData: true
@@ -173,11 +217,19 @@ const TextHighlighter = ({
173
217
  observerRef.current.disconnect();
174
218
  }
175
219
  };
176
- }, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature]);
220
+ }, [words, highlightStyle, caseSensitive, isWordBoundary, highlightTextInElement, getTextSignature, ref, escapeRegex]);
177
221
 
178
222
  return (
179
223
  <div
180
- ref={containerRef}
224
+ {...{ [ROOT_ELEMENT_ATTR]: ROOT_ELEMENT_ID }}
225
+ ref={(node) => {
226
+ containerRef.current = node;
227
+ if (typeof ref === 'function') {
228
+ ref(node);
229
+ } else if (ref) {
230
+ ref.current = node;
231
+ }
232
+ }}
181
233
  style={{
182
234
  visibility: isInitiallyReady ? 'visible' : 'hidden',
183
235
  minHeight: isInitiallyReady ? 'auto' : '1em'
@@ -186,6 +238,32 @@ const TextHighlighter = ({
186
238
  {children}
187
239
  </div>
188
240
  );
189
- };
241
+ }) as TextHighlighterComponent;
242
+
243
+ TextHighlighter.MARK_SELECTOR = MARK_SELECTOR;
244
+ TextHighlighter.ROOT_ELEMENT_SELECTOR = ROOT_ELEMENT_SELECTOR;
245
+ TextHighlighter.MARKS_IN_SCOPE_SELECTOR = MARKS_IN_SCOPE_SELECTOR;
190
246
 
191
247
  export default TextHighlighter;
248
+
249
+ function isInMyScope(rootElem: Element, target: Node, rootSelector: string): boolean {
250
+ // Type guard: handle non-Element nodes
251
+ const targetElement = target instanceof Element ? target : target.parentElement;
252
+ if (!targetElement) {
253
+ return false;
254
+ }
255
+
256
+ // First check if target is within my root
257
+ if (!rootElem.contains(targetElement)) {
258
+ return false;
259
+ }
260
+
261
+ // Select only direct nested roots using :scope
262
+ const directNestedRootsSelector = `:scope ${rootSelector}:not(:scope ${rootSelector} ${rootSelector})`;
263
+ const directNestedRoots = rootElem.querySelectorAll(directNestedRootsSelector);
264
+
265
+ // Check if target is NOT within any direct nested root
266
+ return !Array.from(directNestedRoots).some(nestedRoot =>
267
+ nestedRoot.contains(targetElement)
268
+ );
269
+ }