You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			135 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			135 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
'use client';
 | 
						|
 | 
						|
import _extends from "@babel/runtime/helpers/esm/extends";
 | 
						|
import * as React from 'react';
 | 
						|
function areEqual(a, b) {
 | 
						|
  return a === b;
 | 
						|
}
 | 
						|
var EMPTY_OBJECT = {};
 | 
						|
var NOOP = function NOOP() {};
 | 
						|
 | 
						|
/**
 | 
						|
 * Gets the current state augmented with controlled values from the outside.
 | 
						|
 * If a state item has a corresponding controlled value, it will be used instead of the internal state.
 | 
						|
 */
 | 
						|
function getControlledState(internalState, controlledProps) {
 | 
						|
  var augmentedState = _extends({}, internalState);
 | 
						|
  Object.keys(controlledProps).forEach(function (key) {
 | 
						|
    if (controlledProps[key] !== undefined) {
 | 
						|
      augmentedState[key] = controlledProps[key];
 | 
						|
    }
 | 
						|
  });
 | 
						|
  return augmentedState;
 | 
						|
}
 | 
						|
/**
 | 
						|
 * Defines an effect that compares the next state with the previous state and calls
 | 
						|
 * the `onStateChange` callback if the state has changed.
 | 
						|
 * The comparison is done based on the `stateComparers` parameter.
 | 
						|
 */
 | 
						|
function useStateChangeDetection(parameters) {
 | 
						|
  var nextState = parameters.nextState,
 | 
						|
    initialState = parameters.initialState,
 | 
						|
    stateComparers = parameters.stateComparers,
 | 
						|
    onStateChange = parameters.onStateChange,
 | 
						|
    controlledProps = parameters.controlledProps,
 | 
						|
    lastActionRef = parameters.lastActionRef;
 | 
						|
  var internalPreviousStateRef = React.useRef(initialState);
 | 
						|
  React.useEffect(function () {
 | 
						|
    if (lastActionRef.current === null) {
 | 
						|
      // Detect changes only if an action has been dispatched.
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    var previousState = getControlledState(internalPreviousStateRef.current, controlledProps);
 | 
						|
    Object.keys(nextState).forEach(function (key) {
 | 
						|
      var _stateComparers$key;
 | 
						|
      // go through all state keys and compare them with the previous state
 | 
						|
      var stateComparer = (_stateComparers$key = stateComparers[key]) != null ? _stateComparers$key : areEqual;
 | 
						|
      var nextStateItem = nextState[key];
 | 
						|
      var previousStateItem = previousState[key];
 | 
						|
      if (previousStateItem == null && nextStateItem != null || previousStateItem != null && nextStateItem == null || previousStateItem != null && nextStateItem != null && !stateComparer(nextStateItem, previousStateItem)) {
 | 
						|
        var _event, _type;
 | 
						|
        onStateChange == null || onStateChange((_event = lastActionRef.current.event) != null ? _event : null, key, nextStateItem, (_type = lastActionRef.current.type) != null ? _type : '', nextState);
 | 
						|
      }
 | 
						|
    });
 | 
						|
    internalPreviousStateRef.current = nextState;
 | 
						|
    lastActionRef.current = null;
 | 
						|
  }, [internalPreviousStateRef, nextState, lastActionRef, onStateChange, stateComparers, controlledProps]);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The alternative to `React.useReducer` that lets you control the state from the outside.
 | 
						|
 *
 | 
						|
 * It can be used in an uncontrolled mode, similar to `React.useReducer`, or in a controlled mode, when the state is controlled by the props.
 | 
						|
 * It also supports partially controlled state, when some state items are controlled and some are not.
 | 
						|
 *
 | 
						|
 * The controlled state items are provided via the `controlledProps` parameter.
 | 
						|
 * When a reducer action is dispatched, the internal state is updated with the new values.
 | 
						|
 * A change event (`onStateChange`) is then triggered (for each changed state item) if the new state is different from the previous state.
 | 
						|
 * This event can be used to update the controlled values.
 | 
						|
 *
 | 
						|
 * The comparison of the previous and next states is done using the `stateComparers` parameter.
 | 
						|
 * If a state item has a corresponding comparer, it will be used to determine if the state has changed.
 | 
						|
 * This is useful when the state item is an object and you want to compare only a subset of its properties or if it's an array and you want to compare its contents.
 | 
						|
 *
 | 
						|
 * An additional feature is the `actionContext` parameter. It allows you to add additional properties to every action object,
 | 
						|
 * similarly to how React context is implicitly available to every component.
 | 
						|
 *
 | 
						|
 * @template State - The type of the state calculated by the reducer.
 | 
						|
 * @template Action - The type of the actions that can be dispatched.
 | 
						|
 * @template ActionContext - The type of the additional properties that will be added to every action object.
 | 
						|
 *
 | 
						|
 * @ignore - internal hook.
 | 
						|
 */
 | 
						|
export function useControllableReducer(parameters) {
 | 
						|
  var lastActionRef = React.useRef(null);
 | 
						|
  var reducer = parameters.reducer,
 | 
						|
    initialState = parameters.initialState,
 | 
						|
    _parameters$controlle = parameters.controlledProps,
 | 
						|
    controlledProps = _parameters$controlle === void 0 ? EMPTY_OBJECT : _parameters$controlle,
 | 
						|
    _parameters$stateComp = parameters.stateComparers,
 | 
						|
    stateComparers = _parameters$stateComp === void 0 ? EMPTY_OBJECT : _parameters$stateComp,
 | 
						|
    _parameters$onStateCh = parameters.onStateChange,
 | 
						|
    onStateChange = _parameters$onStateCh === void 0 ? NOOP : _parameters$onStateCh,
 | 
						|
    actionContext = parameters.actionContext,
 | 
						|
    _parameters$component = parameters.componentName,
 | 
						|
    componentName = _parameters$component === void 0 ? '' : _parameters$component;
 | 
						|
  var controlledPropsRef = React.useRef(controlledProps);
 | 
						|
  if (process.env.NODE_ENV !== 'production') {
 | 
						|
    // eslint-disable-next-line react-hooks/rules-of-hooks
 | 
						|
    React.useEffect(function () {
 | 
						|
      Object.keys(controlledProps).forEach(function (key) {
 | 
						|
        if (controlledPropsRef.current[key] !== undefined && controlledProps[key] === undefined) {
 | 
						|
          console.error("useControllableReducer: ".concat(componentName ? "The ".concat(componentName, " component") : 'A component', " is changing a controlled prop to be uncontrolled: ").concat(key));
 | 
						|
        }
 | 
						|
        if (controlledPropsRef.current[key] === undefined && controlledProps[key] !== undefined) {
 | 
						|
          console.error("useControllableReducer: ".concat(componentName ? "The ".concat(componentName, " component") : 'A component', " is changing an uncontrolled prop to be controlled: ").concat(key));
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }, [controlledProps, componentName]);
 | 
						|
  }
 | 
						|
 | 
						|
  // The reducer that is passed to React.useReducer is wrapped with a function that augments the state with controlled values.
 | 
						|
  var reducerWithControlledState = React.useCallback(function (state, action) {
 | 
						|
    lastActionRef.current = action;
 | 
						|
    var controlledState = getControlledState(state, controlledProps);
 | 
						|
    var newState = reducer(controlledState, action);
 | 
						|
    return newState;
 | 
						|
  }, [controlledProps, reducer]);
 | 
						|
  var _React$useReducer = React.useReducer(reducerWithControlledState, initialState),
 | 
						|
    nextState = _React$useReducer[0],
 | 
						|
    dispatch = _React$useReducer[1]; // The action that is passed to dispatch is augmented with the actionContext.
 | 
						|
  var dispatchWithContext = React.useCallback(function (action) {
 | 
						|
    dispatch(_extends({}, action, {
 | 
						|
      context: actionContext
 | 
						|
    }));
 | 
						|
  }, [actionContext]);
 | 
						|
  useStateChangeDetection({
 | 
						|
    nextState: nextState,
 | 
						|
    initialState: initialState,
 | 
						|
    stateComparers: stateComparers != null ? stateComparers : EMPTY_OBJECT,
 | 
						|
    onStateChange: onStateChange != null ? onStateChange : NOOP,
 | 
						|
    controlledProps: controlledProps,
 | 
						|
    lastActionRef: lastActionRef
 | 
						|
  });
 | 
						|
  return [getControlledState(nextState, controlledProps), dispatchWithContext];
 | 
						|
} |