Skip to content

Commit 10d6096

Browse files
committed
feat: update debounced spellcheck hook to toggle decorations instead of the real spellcheck
1 parent a60f7bd commit 10d6096

File tree

1 file changed

+74
-7
lines changed

1 file changed

+74
-7
lines changed

ts/hooks/useDebuncedSpellcheck.ts

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,72 @@ interface DebouncedSpellcheckProps {
66
delay?: number;
77
}
88

9+
// Global reference counter for the hook
10+
let hookUsageCount = 0;
11+
const STYLE_ID = 'debounced-spellcheck-styles';
12+
13+
const cssStyles = `
14+
.spellcheck-hidden::-webkit-spelling-error {
15+
text-decoration: none !important;
16+
}
17+
18+
.spellcheck-hidden::-webkit-grammar-error {
19+
text-decoration: none !important;
20+
}
21+
22+
.spellcheck-hidden::-moz-spelling-error {
23+
text-decoration: none !important;
24+
}
25+
26+
.spellcheck-hidden::-moz-grammar-error {
27+
text-decoration: none !important;
28+
}
29+
30+
.spellcheck-hidden::spelling-error {
31+
text-decoration: none !important;
32+
}
33+
34+
.spellcheck-hidden::grammar-error {
35+
text-decoration: none !important;
36+
}
37+
`;
38+
939
export const useDebouncedSpellcheck = ({ delay = 300, elementRef }: DebouncedSpellcheckProps) => {
10-
const enableSpellcheck = useCallback(() => {
11-
elementRef.current?.setAttribute('spellcheck', 'true');
40+
// Inject CSS styles if they don't exist
41+
useEffect(() => {
42+
hookUsageCount++;
43+
44+
// Only inject styles on first usage
45+
if (hookUsageCount === 1 && !document.getElementById(STYLE_ID)) {
46+
const style = document.createElement('style');
47+
style.id = STYLE_ID;
48+
style.textContent = cssStyles;
49+
document.head.appendChild(style);
50+
}
51+
52+
// Remove styles only when no components are using the hook
53+
return () => {
54+
hookUsageCount--;
55+
if (hookUsageCount === 0) {
56+
const existingStyle = document.getElementById(STYLE_ID);
57+
if (existingStyle) {
58+
existingStyle.remove();
59+
}
60+
}
61+
};
62+
}, []);
63+
64+
const hideSpellcheckLines = useCallback(() => {
65+
elementRef.current?.classList.add('spellcheck-hidden');
66+
}, [elementRef]);
67+
68+
const showSpellcheckLines = useCallback(() => {
69+
elementRef.current?.classList.remove('spellcheck-hidden');
1270
}, [elementRef]);
1371

1472
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: see if we can create our own useDebounce hook
15-
const debouncedSpellcheck = useCallback(debounce(enableSpellcheck, delay), [
16-
enableSpellcheck,
73+
const debouncedShowSpellcheck = useCallback(debounce(showSpellcheckLines, delay), [
74+
showSpellcheckLines,
1775
delay,
1876
]);
1977

@@ -24,15 +82,24 @@ export const useDebouncedSpellcheck = ({ delay = 300, elementRef }: DebouncedSpe
2482
}
2583

2684
const handleInput = () => {
27-
el.setAttribute('spellcheck', 'false');
28-
debouncedSpellcheck();
85+
// Hide spellcheck lines immediately while typing
86+
hideSpellcheckLines();
87+
// Show them again after user stops typing
88+
debouncedShowSpellcheck();
2989
};
3090

3191
el.addEventListener('input', handleInput);
3292

3393
// eslint-disable-next-line consistent-return -- This return is the destructor
3494
return () => {
3595
el.removeEventListener('input', handleInput);
96+
// Clean up: show spellcheck lines when component unmounts
97+
showSpellcheckLines();
3698
};
37-
}, [debouncedSpellcheck, elementRef]);
99+
}, [debouncedShowSpellcheck, hideSpellcheckLines, showSpellcheckLines, elementRef]);
100+
101+
return {
102+
hideSpellcheckLines,
103+
showSpellcheckLines,
104+
};
38105
};

0 commit comments

Comments
 (0)