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