UNPKG

@gechiui/components

Version:
256 lines (220 loc) 6.37 kB
/** * External dependencies */ import { isEmpty } from 'lodash'; // eslint-disable-next-line no-restricted-imports /** * GeChiUI dependencies */ import { useReducer } from '@gechiui/element'; /** * Internal dependencies */ import { initialInputControlState, initialStateReducer } from './state'; import * as actions from './actions'; /** * Prepares initialState for the reducer. * * @param initialState The initial state. * @return Prepared initialState for the reducer */ function mergeInitialState() { let initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialInputControlState; const { value } = initialState; return { ...initialInputControlState, ...initialState, initialValue: value }; } /** * Composes multiple stateReducers into a single stateReducer, building * the pipeline to control the flow for state and actions. * * @param fns State reducers. * @return The single composed stateReducer. */ export const composeStateReducers = function () { for (var _len = arguments.length, fns = new Array(_len), _key = 0; _key < _len; _key++) { fns[_key] = arguments[_key]; } return function () { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } return fns.reduceRight((state, fn) => { const fnState = fn(...args); return isEmpty(fnState) ? state : { ...state, ...fnState }; }, {}); }; }; /** * Creates a reducer that opens the channel for external state subscription * and modification. * * This technique uses the "stateReducer" design pattern: * https://kentcdodds.com/blog/the-state-reducer-pattern/ * * @param composedStateReducers A custom reducer that can subscribe and modify state. * @return The reducer. */ function inputControlStateReducer(composedStateReducers) { return (state, action) => { const nextState = { ...state }; switch (action.type) { /** * Keyboard events */ case actions.PRESS_UP: nextState.isDirty = false; break; case actions.PRESS_DOWN: nextState.isDirty = false; break; /** * Drag events */ case actions.DRAG_START: nextState.isDragging = true; break; case actions.DRAG_END: nextState.isDragging = false; break; /** * Input events */ case actions.CHANGE: nextState.error = null; nextState.value = action.payload.value; if (state.isPressEnterToChange) { nextState.isDirty = true; } break; case actions.COMMIT: nextState.value = action.payload.value; nextState.isDirty = false; break; case actions.RESET: nextState.error = null; nextState.isDirty = false; nextState.value = action.payload.value || state.initialValue; break; case actions.UPDATE: nextState.value = action.payload.value; nextState.isDirty = false; break; /** * Validation */ case actions.INVALIDATE: nextState.error = action.payload.error; break; } if (action.payload.event) { nextState._event = action.payload.event; } /** * Send the nextState + action to the composedReducers via * this "bridge" mechanism. This allows external stateReducers * to hook into actions, and modify state if needed. */ return composedStateReducers(nextState, action); }; } /** * A custom hook that connects and external stateReducer with an internal * reducer. This hook manages the internal state of InputControl. * However, by connecting an external stateReducer function, other * components can react to actions as well as modify state before it is * applied. * * This technique uses the "stateReducer" design pattern: * https://kentcdodds.com/blog/the-state-reducer-pattern/ * * @param stateReducer An external state reducer. * @param initialState The initial state for the reducer. * @return State, dispatch, and a collection of actions. */ export function useInputControlStateReducer() { let stateReducer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialStateReducer; let initialState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : initialInputControlState; const [state, dispatch] = useReducer(inputControlStateReducer(stateReducer), mergeInitialState(initialState)); const createChangeEvent = type => (nextValue, event) => { /** * Persist allows for the (Synthetic) event to be used outside of * this function call. * https://reactjs.org/docs/events.html#event-pooling */ if (event && event.persist) { event.persist(); } dispatch({ type, payload: { value: nextValue, event } }); }; const createKeyEvent = type => event => { /** * Persist allows for the (Synthetic) event to be used outside of * this function call. * https://reactjs.org/docs/events.html#event-pooling */ if (event && event.persist) { event.persist(); } dispatch({ type, payload: { event } }); }; const createDragEvent = type => payload => { dispatch({ type, payload }); }; /** * Actions for the reducer */ const change = createChangeEvent(actions.CHANGE); const invalidate = (error, event) => dispatch({ type: actions.INVALIDATE, payload: { error, event } }); const reset = createChangeEvent(actions.RESET); const commit = createChangeEvent(actions.COMMIT); const update = createChangeEvent(actions.UPDATE); const dragStart = createDragEvent(actions.DRAG_START); const drag = createDragEvent(actions.DRAG); const dragEnd = createDragEvent(actions.DRAG_END); const pressUp = createKeyEvent(actions.PRESS_UP); const pressDown = createKeyEvent(actions.PRESS_DOWN); const pressEnter = createKeyEvent(actions.PRESS_ENTER); return { change, commit, dispatch, drag, dragEnd, dragStart, invalidate, pressDown, pressEnter, pressUp, reset, state, update }; } //# sourceMappingURL=reducer.js.map