UNPKG

react-beautiful-dnd-next

Version:

Beautiful and accessible drag and drop for lists with React

127 lines (110 loc) 3.71 kB
// @flow import { useRef } from 'react'; import memoizeOne from 'memoize-one'; import { useMemo, useCallback } from 'use-memo-one'; import invariant from 'tiny-invariant'; import type { StyleMarshal } from './style-marshal-types'; import type { DropReason } from '../../types'; import getStyles, { type Styles } from './get-styles'; import { prefix } from '../data-attributes'; import useLayoutEffect from '../use-isomorphic-layout-effect'; const getHead = (): HTMLHeadElement => { const head: ?HTMLHeadElement = document.querySelector('head'); invariant(head, 'Cannot find the head to append a style to'); return head; }; const createStyleEl = (): HTMLStyleElement => { const el: HTMLStyleElement = document.createElement('style'); el.type = 'text/css'; return el; }; export default function useStyleMarshal(uniqueId: number) { const uniqueContext: string = useMemo(() => `${uniqueId}`, [uniqueId]); const styles: Styles = useMemo(() => getStyles(uniqueContext), [ uniqueContext, ]); const alwaysRef = useRef<?HTMLStyleElement>(null); const dynamicRef = useRef<?HTMLStyleElement>(null); const setDynamicStyle = useCallback( // Using memoizeOne to prevent frequent updates to textContext memoizeOne((proposed: string) => { const el: ?HTMLStyleElement = dynamicRef.current; invariant(el, 'Cannot set dynamic style element if it is not set'); el.textContent = proposed; }), [], ); const setAlwaysStyle = useCallback((proposed: string) => { const el: ?HTMLStyleElement = alwaysRef.current; invariant(el, 'Cannot set dynamic style element if it is not set'); el.textContent = proposed; }, []); // using layout effect as programatic dragging might start straight away (such as for cypress) useLayoutEffect(() => { invariant( !alwaysRef.current && !dynamicRef.current, 'style elements already mounted', ); const always: HTMLStyleElement = createStyleEl(); const dynamic: HTMLStyleElement = createStyleEl(); // store their refs alwaysRef.current = always; dynamicRef.current = dynamic; // for easy identification always.setAttribute(`${prefix}-always`, uniqueContext); dynamic.setAttribute(`${prefix}-dynamic`, uniqueContext); // add style tags to head getHead().appendChild(always); getHead().appendChild(dynamic); // set initial style setAlwaysStyle(styles.always); setDynamicStyle(styles.resting); return () => { const remove = ref => { const current: ?HTMLStyleElement = ref.current; invariant(current, 'Cannot unmount ref as it is not set'); getHead().removeChild(current); ref.current = null; }; remove(alwaysRef); remove(dynamicRef); }; }, [ setAlwaysStyle, setDynamicStyle, styles.always, styles.resting, uniqueContext, ]); const dragging = useCallback(() => setDynamicStyle(styles.dragging), [ setDynamicStyle, styles.dragging, ]); const dropping = useCallback( (reason: DropReason) => { if (reason === 'DROP') { setDynamicStyle(styles.dropAnimating); return; } setDynamicStyle(styles.userCancel); }, [setDynamicStyle, styles.dropAnimating, styles.userCancel], ); const resting = useCallback(() => { // Can be called defensively if (!dynamicRef.current) { return; } setDynamicStyle(styles.resting); }, [setDynamicStyle, styles.resting]); const marshal: StyleMarshal = useMemo( () => ({ dragging, dropping, resting, styleContext: uniqueContext, }), [dragging, dropping, resting, uniqueContext], ); return marshal; }