Skip to content

Commit

Permalink
feat: add useCompositionInput
Browse files Browse the repository at this point in the history
  • Loading branch information
SukkaW committed Jun 23, 2023
1 parent 0aec807 commit fe17304
Showing 1 changed file with 58 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/use-composition-input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'client-only';
import { useCallback, useRef } from 'react';

export type UseCompositionInputCallback = (value: string) => void;
export type UseCompositionInputReturnKey = 'onChange' | 'onCompositionStart' | 'onCompositionEnd';
export type UseCompositionInputReturn = Pick<JSX.IntrinsicElements['input'], UseCompositionInputReturnKey>;

/** @see https://foxact.skk.moe/use-composition-input */
export const useCompositionInput = (cb: UseCompositionInputCallback): UseCompositionInputReturn => {
// @ts-expect-error -- We are using singleton approach here, to prevent repeated initialization.
const internalState: React.MutableRefObject<{
/** is"C"ompositioning */ c: boolean,
/** is"E"mitted */ e: boolean
}> = useRef();

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- it will be undefined only on first render
if (!internalState.current) {
internalState.current = {
/** is"C"ompositioning */ c: false,
/** is"E"mitted */ e: false
};
}

const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement> | React.CompositionEvent<HTMLInputElement>) => {
if ('value' in e.target) {
const userInputValue = e.target.value;

if (!internalState.current.c) {
cb(userInputValue);
internalState.current.e = true;
} else {
internalState.current.e = false;
}
}
}, [cb]);

const onCompositionStart = useCallback<React.CompositionEventHandler<HTMLInputElement>>(() => {
internalState.current.c = true;
internalState.current.e = false;
}, []);

const onCompositionEnd = useCallback<React.CompositionEventHandler<HTMLInputElement>>((e) => {
internalState.current.c = false;
// fixed for Chrome v53+ and detect all Chrome
// https://chromium.googlesource.com/chromium/src/+/afce9d93e76f2ff81baaa088a4ea25f67d1a76b3%5E%21/
// also fixed for the native Apple keyboard which emit input event before composition event
// subscribe this issue: https://github.com/facebook/react/issues/8683
if (!internalState.current.e) {
onChange(e);
}
}, [onChange]);

return {
onChange,
onCompositionStart,
onCompositionEnd
};
};

0 comments on commit fe17304

Please sign in to comment.