UNPKG

@atlaskit/renderer

Version:
220 lines • 7.03 kB
import React, { createContext, useCallback, useContext, useMemo, useReducer } from 'react'; const initialState = { type: null, selectionRange: null, hoverRange: null, selectionDraftRange: null, hoverDraftRange: null, selectionDraftDocumentPosition: null, hoverDraftDocumentPosition: null }; function reducer(state, action) { switch (action.type) { case 'clearSelection': if (state.selectionRange !== null) { return { ...state, selectionRange: null, type: state.type === 'selection' ? null : state.type }; } return state; case 'clearHover': { if (state.hoverRange !== null) { return { ...state, hoverRange: null, type: state.type === 'hover' ? null : state.type }; } return state; } case 'setSelection': if (state.selectionRange !== action.range) { return { ...state, selectionRange: action.range, type: 'selection' }; } return state; case 'setHover': if (state.hoverRange !== action.range) { return { ...state, hoverRange: action.range, type: 'hover' }; } return state; case 'promoteSelectionToDraft': // we should only promote the range to a draft if the current range type is a selection // we should also store the promotion type, so that a clear will not accidently clear the draft of a // different type if (state.selectionDraftRange !== state.selectionRange) { return { ...state, selectionRange: null, type: state.type === 'selection' ? null : state.type, selectionDraftRange: state.selectionRange, selectionDraftDocumentPosition: action.position }; } return state; case 'promoteHoverToDraft': if (state.hoverDraftRange !== state.hoverRange) { return { ...state, hoverRange: null, type: state.type === 'hover' ? null : state.type, hoverDraftRange: state.hoverRange, hoverDraftDocumentPosition: action.position }; } return state; case 'clearSelectionDraft': if (state.selectionDraftRange !== null) { return { ...state, selectionDraftRange: null, selectionDraftDocumentPosition: null }; } return state; case 'clearHoverDraft': if (state.hoverDraftRange !== null) { return { ...state, hoverDraftRange: null, hoverDraftDocumentPosition: null }; } return state; } } export const AnnotationRangeStateContext = /*#__PURE__*/createContext({ range: null, type: null, selectionDraftRange: null, hoverDraftRange: null, selectionDraftDocumentPosition: null, hoverDraftDocumentPosition: null }); export const AnnotationRangeDispatchContext = /*#__PURE__*/createContext({ clearSelectionRange: () => {}, clearHoverRange: () => {}, setSelectionRange: () => {}, promoteSelectionToDraft: () => {}, promoteHoverToDraft: () => {}, clearSelectionDraft: () => {}, clearHoverDraft: () => {} }); export const AnnotationRangeProviderInner = ({ children, allowCommentsOnMedia }) => { const [{ selectionRange, hoverRange, type, selectionDraftRange, selectionDraftDocumentPosition, hoverDraftRange, hoverDraftDocumentPosition }, dispatch] = useReducer(reducer, initialState); const clearSelectionRange = useCallback(() => dispatch({ type: 'clearSelection' }), []); const clearHoverRange = useCallback(() => dispatch({ type: 'clearHover' }), []); const setSelectionRange = useCallback(range => dispatch({ type: 'setSelection', range }), []); const setHoverTarget = useCallback(target => { // the HoverComponent expects an element deeply nested inside media, these classes work with the current implementation const mediaNode = target.querySelector('.media-card-inline-player, .media-file-card-view'); if (!mediaNode) { return; } // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- range for media hover highlight const range = document.createRange(); range.setStartBefore(mediaNode); range.setEndAfter(mediaNode); dispatch({ type: 'setHover', range }); }, []); const promoteSelectionToDraft = useCallback(position => { dispatch({ type: 'promoteSelectionToDraft', position }); }, []); const clearSelectionDraft = useCallback(() => { dispatch({ type: 'clearSelectionDraft' }); }, []); const promoteHoverToDraft = useCallback(position => { dispatch({ type: 'promoteHoverToDraft', position }); }, []); const clearHoverDraft = useCallback(() => { dispatch({ type: 'clearHoverDraft' }); }, []); const stateData = useMemo(() => { return { // We techinically have two ranges, however we only want to expose one of them at a time, because only one draft // can be active at a time. The type of range is used to determine which range is active. range: type === 'selection' ? selectionRange : hoverRange, type, selectionDraftRange, hoverDraftRange, selectionDraftDocumentPosition, hoverDraftDocumentPosition }; }, [selectionRange, hoverRange, type, selectionDraftRange, selectionDraftDocumentPosition, hoverDraftRange, hoverDraftDocumentPosition]); const dispatchData = useMemo(() => ({ clearSelectionRange, clearHoverRange, setSelectionRange, setHoverTarget: !!allowCommentsOnMedia ? setHoverTarget : undefined, promoteSelectionToDraft, promoteHoverToDraft, clearSelectionDraft, clearHoverDraft }), [allowCommentsOnMedia, clearSelectionRange, clearHoverRange, setSelectionRange, setHoverTarget, promoteSelectionToDraft, promoteHoverToDraft, clearSelectionDraft, clearHoverDraft]); return /*#__PURE__*/React.createElement(AnnotationRangeStateContext.Provider, { value: stateData }, /*#__PURE__*/React.createElement(AnnotationRangeDispatchContext.Provider, { value: dispatchData }, children)); }; export const AnnotationRangeProvider = ({ children, allowCommentsOnMedia, isNestedRender }) => { /* * If this is a nested render, we do not provide the context * because it has already been provided higher up the component tree * and we need the original context to create annotations on extensions. */ return isNestedRender ? /*#__PURE__*/React.createElement(React.Fragment, null, children) : /*#__PURE__*/React.createElement(AnnotationRangeProviderInner, { allowCommentsOnMedia: allowCommentsOnMedia }, children); }; export const useAnnotationRangeState = () => { return useContext(AnnotationRangeStateContext); }; export const useAnnotationRangeDispatch = () => { return useContext(AnnotationRangeDispatchContext); };