@chayns-components/emoji-input
Version:
Input field that supports HTML elements and emojis
103 lines • 3.3 kB
JavaScript
import { useCallback, useEffect, useRef, useState } from 'react';
import { getCurrentCursorPosition } from '../utils/selection';
export const useCursorPosition = (editorRef, onChange, options) => {
const {
isDisabled
} = options || {};
const [position, setPosition] = useState(null);
const rafIdRef = useRef(null);
const lastPositionRef = useRef(null);
const emit = useCallback(() => {
const el = editorRef.current;
if (!el || isDisabled) return;
const pos = getCurrentCursorPosition(el);
if (pos !== null && pos !== lastPositionRef.current) {
lastPositionRef.current = pos;
setPosition(pos);
if (typeof onChange === 'function') onChange(pos);
}
}, [editorRef, onChange, isDisabled]);
const scheduleEmit = useCallback(() => {
if (rafIdRef.current != null) cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = requestAnimationFrame(() => {
rafIdRef.current = null;
emit();
});
}, [emit]);
const trigger = useCallback(() => {
scheduleEmit();
}, [scheduleEmit]);
useEffect(() => {
const el = editorRef.current;
if (!el) return () => {};
const onKeyUp = e => {
const navKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown', 'Backspace', 'Delete'];
if (navKeys.includes(e.key)) scheduleEmit();
};
el.addEventListener('keyup', onKeyUp, {
passive: true
});
return () => el.removeEventListener('keyup', onKeyUp);
}, [editorRef, scheduleEmit]);
useEffect(() => {
const el = editorRef.current;
if (!el) return () => {};
const onMouseUp = () => scheduleEmit();
const onTouchEnd = () => scheduleEmit();
el.addEventListener('mouseup', onMouseUp, {
passive: true
});
el.addEventListener('touchend', onTouchEnd, {
passive: true
});
return () => {
el.removeEventListener('mouseup', onMouseUp);
el.removeEventListener('touchend', onTouchEnd);
};
}, [editorRef, scheduleEmit]);
useEffect(() => {
const onSelectionChange = () => {
const el = editorRef.current;
const sel = document.getSelection();
if (!el || !sel) return;
const {
anchorNode
} = sel;
if (anchorNode && el.contains(anchorNode) && document.activeElement === el) {
scheduleEmit();
}
};
document.addEventListener('selectionchange', onSelectionChange, {
passive: true
});
return () => document.removeEventListener('selectionchange', onSelectionChange);
}, [editorRef, scheduleEmit]);
useEffect(() => {
const el = editorRef.current;
if (!el) return () => {};
const onFocus = () => scheduleEmit();
const onBlur = () => {
lastPositionRef.current = null;
setPosition(null);
};
el.addEventListener('focus', onFocus, {
passive: true
});
el.addEventListener('blur', onBlur, {
passive: true
});
return () => {
el.removeEventListener('focus', onFocus);
el.removeEventListener('blur', onBlur);
};
}, [editorRef, scheduleEmit]);
useEffect(() => () => {
if (rafIdRef.current != null) cancelAnimationFrame(rafIdRef.current);
}, []);
return {
position,
trigger,
lastPositionRef
};
};
//# sourceMappingURL=cursor.js.map