Files
cinny/src/app/hooks/useComposingCheck.ts
T
Lotus Bot ff7c2ed941 Bug fixes, security hardening, and performance improvements
- BUG-16: Fixed pagination deadlock (fetching flag stuck on error path)
- BUG-17: Fixed absoluteIndex===0 falsy check skipping unread jump
- BUG-19: Fixed mEvt.getRoomId()! non-null assertion crash
- BUG-20: Wrapped getSettings()/setSettings() in try/catch for corrupt localStorage
- SEC: Replaced randomStr() Math.random() with crypto.getRandomValues() CSPRNG
- SEC: Fixed afterLoginRedirectPath open redirect validation
- SEC: Narrowed OSM iframe sandbox to scripts-only (removed allow-same-origin)
- Perf-2: Memoized selectAtom in useSetting (prevented new atom ref per render)
- Perf-4: Fixed typingMembers setTimeout leak (tracked timers per user/room)
- Perf-8: Memoized getChatBg() result in RoomView (not inline in JSX)
- Perf-12: Replaced body.class * font-family with body.class (inherited)
- Perf-15: Memoized typingNames array chain in RoomViewTyping
- Perf-9: Added blob URL cleanup useEffect in AudioContent
- BUG: Fixed forEach(async) -> Promise.all in useCommands join handler
- BUG: Fixed useCompositionEndTracking missing dependency array
- A11y: Fixed spoiler button aria-pressed + keyboard handler
- A11y: Added aria-label to Send message button
- Build: Set sourcemap:false, removed netlify.toml from copyFiles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 21:11:38 -04:00

48 lines
1.4 KiB
TypeScript

import { useCallback, useEffect } from 'react';
import { useAtomValue, useSetAtom } from 'jotai';
import { lastCompositionEndAtom } from '../state/lastCompositionEnd';
interface TimeStamped {
readonly timeStamp: number;
}
export function useCompositionEndTracking(): void {
const setLastCompositionEnd = useSetAtom(lastCompositionEndAtom);
const recordCompositionEnd = useCallback(
(evt: TimeStamped) => {
setLastCompositionEnd(evt.timeStamp);
},
[setLastCompositionEnd]
);
useEffect(() => {
window.addEventListener('compositionend', recordCompositionEnd, { capture: true });
return () => {
window.removeEventListener('compositionend', recordCompositionEnd, { capture: true });
};
}, [recordCompositionEnd]);
}
interface IsComposingLike {
readonly timeStamp: number;
readonly keyCode: number;
readonly nativeEvent: {
readonly isComposing?: boolean;
};
}
export function useComposingCheck({
compositionEndThreshold = 500,
}: { compositionEndThreshold?: number } = {}): (evt: IsComposingLike) => boolean {
const compositionEnd = useAtomValue(lastCompositionEndAtom);
return useCallback(
(evt: IsComposingLike): boolean =>
evt.nativeEvent.isComposing ||
(evt.keyCode === 229 &&
typeof compositionEnd !== 'undefined' &&
evt.timeStamp - compositionEnd < compositionEndThreshold),
[compositionEndThreshold, compositionEnd]
);
}