@atlaskit/renderer
Version:
Renderer component
220 lines • 7.03 kB
JavaScript
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);
};