UNPKG

react-beautiful-dnd

Version:

Beautiful, accessible drag and drop for lists with React.js

196 lines (171 loc) 5.75 kB
// @flow import React, { type Node } from 'react'; import { bindActionCreators } from 'redux'; import PropTypes from 'prop-types'; import createStore from '../../state/create-store'; import createDimensionMarshal from '../../state/dimension-marshal/dimension-marshal'; import createStyleMarshal, { resetStyleContext, } from '../style-marshal/style-marshal'; import canStartDrag from '../../state/can-start-drag'; import scrollWindow from '../window/scroll-window'; import createAnnouncer from '../announcer/announcer'; import createAutoScroller from '../../state/auto-scroller'; import type { Announcer } from '../announcer/announcer-types'; import type { AutoScroller } from '../../state/auto-scroller/auto-scroller-types'; import type { StyleMarshal } from '../style-marshal/style-marshal-types'; import type { DimensionMarshal, Callbacks as DimensionMarshalCallbacks, } from '../../state/dimension-marshal/dimension-marshal-types'; import type { DraggableId, State, Hooks } from '../../types'; import type { Store } from '../../state/store-types'; import { storeKey, dimensionMarshalKey, styleContextKey, canLiftContextKey, } from '../context-keys'; import { clean, move, publish, updateDroppableScroll, updateDroppableIsEnabled, collectionStarting, } from '../../state/action-creators'; type Props = {| ...Hooks, children: ?Node, |}; type Context = { [string]: Store, }; // Reset any context that gets persisted across server side renders export const resetServerContext = () => { resetStyleContext(); }; const printFatalDevError = (error: Error) => { if (process.env.NODE_ENV === 'production') { return; } console.warn(` An error has occurred while a drag is occurring. Any existing drag will be cancelled. Raw error: `); console.error(error); }; export default class DragDropContext extends React.Component<Props> { /* eslint-disable react/sort-comp */ store: Store; dimensionMarshal: DimensionMarshal; styleMarshal: StyleMarshal; autoScroller: AutoScroller; announcer: Announcer; unsubscribe: Function; constructor(props: Props, context: mixed) { super(props, context); this.announcer = createAnnouncer(); // create the style marshal this.styleMarshal = createStyleMarshal(); this.store = createStore({ // Lazy reference to dimension marshal get around circular dependency getDimensionMarshal: (): DimensionMarshal => this.dimensionMarshal, styleMarshal: this.styleMarshal, // This is a function as users are allowed to change their hook functions // at any time getHooks: (): Hooks => ({ onBeforeDragStart: this.props.onBeforeDragStart, onDragStart: this.props.onDragStart, onDragEnd: this.props.onDragEnd, onDragUpdate: this.props.onDragUpdate, }), announce: this.announcer.announce, getScroller: () => this.autoScroller, }); const callbacks: DimensionMarshalCallbacks = bindActionCreators( { collectionStarting, publish, updateDroppableScroll, updateDroppableIsEnabled, }, this.store.dispatch, ); this.dimensionMarshal = createDimensionMarshal(callbacks); this.autoScroller = createAutoScroller({ scrollWindow, scrollDroppable: this.dimensionMarshal.scrollDroppable, ...bindActionCreators( { move, }, this.store.dispatch, ), }); } // Need to declare childContextTypes without flow // https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/22 static childContextTypes = { [storeKey]: PropTypes.shape({ dispatch: PropTypes.func.isRequired, subscribe: PropTypes.func.isRequired, getState: PropTypes.func.isRequired, }).isRequired, [dimensionMarshalKey]: PropTypes.object.isRequired, [styleContextKey]: PropTypes.string.isRequired, [canLiftContextKey]: PropTypes.func.isRequired, }; getChildContext(): Context { return { [storeKey]: this.store, [dimensionMarshalKey]: this.dimensionMarshal, [styleContextKey]: this.styleMarshal.styleContext, [canLiftContextKey]: this.canLift, }; } // Providing function on the context for drag handles to use to // let them know if they can start a drag or not. This is done // rather than mapping a prop onto the drag handle so that we // do not need to re-render a connected drag handle in order to // pull this state off. It would cause a re-render of all items // on drag start which is too expensive. // This is useful when the user canLift = (id: DraggableId) => canStartDrag(this.store.getState(), id); componentDidMount() { window.addEventListener('error', this.onWindowError); this.styleMarshal.mount(); this.announcer.mount(); } componentDidCatch(error: Error) { this.onFatalError(error); // If the failure was due to an invariant failure - then we handle the error if (error.message.indexOf('Invariant failed') !== -1) { this.setState({}); return; } // Error is more serious and we throw it throw error; } componentWillUnmount() { window.addEventListener('error', this.onWindowError); const state: State = this.store.getState(); if (state.phase !== 'IDLE') { this.store.dispatch(clean()); } this.styleMarshal.unmount(); this.announcer.unmount(); } onFatalError = (error: Error) => { printFatalDevError(error); const state: State = this.store.getState(); if (state.phase !== 'IDLE') { this.store.dispatch(clean()); } }; onWindowError = (error: Error) => this.onFatalError(error); render() { return this.props.children; } }