notistack-mod
Version:
Notistack with ability to modify active snackbars.
584 lines (480 loc) • 23.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _Slide = require('@material-ui/core/Slide');
var _Slide2 = _interopRequireDefault(_Slide);
var _SnackbarContext = require('./SnackbarContext');
var _SnackbarContext2 = _interopRequireDefault(_SnackbarContext);
var _constants = require('./utils/constants');
var _SnackbarItem = require('./SnackbarItem');
var _SnackbarItem2 = _interopRequireDefault(_SnackbarItem);
var _SnackbarContainer = require('./SnackbarContainer');
var _SnackbarContainer2 = _interopRequireDefault(_SnackbarContainer);
var _warning = require('./utils/warning');
var _warning2 = _interopRequireDefault(_warning);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* Omit SnackbarContainer class keys that are not needed for SnakcbarItem
*/
var getClasses = function getClasses(classes) {
return Object.keys(classes).filter(function (key) {
return !_constants.allClasses.container[key];
}).reduce(function (obj, key) {
return _extends({}, obj, _defineProperty({}, key, classes[key]));
}, {});
};
var SnackbarProvider = function (_Component) {
_inherits(SnackbarProvider, _Component);
function SnackbarProvider(props) {
_classCallCheck(this, SnackbarProvider);
var _this = _possibleConstructorReturn(this, (SnackbarProvider.__proto__ || Object.getPrototypeOf(SnackbarProvider)).call(this, props));
_this.enqueueSnackbar = function (message) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var key = _ref.key,
preventDuplicate = _ref.preventDuplicate,
options = _objectWithoutProperties(_ref, ['key', 'preventDuplicate']);
var userSpecifiedKey = key || key === 0;
var id = userSpecifiedKey ? key : new Date().getTime() + Math.random();
var snack = _extends({
key: id
}, options, {
message: message,
open: true,
entered: false,
requestClose: false,
anchorOrigin: options.anchorOrigin || _this.props.anchorOrigin
});
if (options.persist) {
snack.autoHideDuration = undefined;
}
_this.setState(function (state) {
if (preventDuplicate === undefined && _this.props.preventDuplicate || preventDuplicate) {
var compareFunction = function compareFunction(item) {
return userSpecifiedKey ? item.key === key : item.message === message;
};
var inQueue = state.queue.findIndex(compareFunction) > -1;
var inView = state.snacks.findIndex(compareFunction) > -1;
if (inQueue || inView) {
return state;
}
}
return _this.handleDisplaySnack(_extends({}, state, {
queue: [].concat(_toConsumableArray(state.queue), [snack])
}));
});
return id;
};
_this.modifySnackbar = function (key) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var state = _extends({}, _this.state);
var snack = null;
for (var i = 0; i < state.snacks.length; i++) {
if (state.snacks[i].key === key) {
snack = Object.assign(state.snacks[i], options);
break;
}
}
if (snack == null) {
for (var _i = 0; _i < state.queue.length; _i++) {
if (state.queue[_i].key === key) {
snack = Object.assign(state.queue[_i], options);
break;
}
}
}
if (snack == null) return;
if (options.persist) {
snack.autoHideDuration = undefined;
}
_this.setState(state);
return key;
};
_this.handleDisplaySnack = function (state) {
var snacks = state.snacks;
if (snacks.length >= _this.props.maxSnack) {
return _this.handleDismissOldest(state);
}
return _this.processQueue(state);
};
_this.processQueue = function (state) {
var queue = state.queue,
snacks = state.snacks;
if (queue.length > 0) {
return _extends({}, state, {
snacks: [].concat(_toConsumableArray(snacks), [queue[0]]),
queue: queue.slice(1, queue.length)
});
}
return state;
};
_this.handleDismissOldest = function (state) {
if (state.snacks.some(function (item) {
return !item.open || item.requestClose;
})) {
return state;
}
var popped = false;
var ignore = false;
var persistentCount = state.snacks.reduce(function (acc, current) {
return acc + (current.open && current.persist ? 1 : 0);
}, 0);
if (persistentCount === _this.props.maxSnack) {
(0, _warning2.default)(_constants.MESSAGES.NO_PERSIST_ALL);
ignore = true;
}
var snacks = state.snacks.map(function (item) {
if (!popped && (!item.persist || ignore)) {
popped = true;
if (!item.entered) {
return _extends({}, item, {
requestClose: true
});
}
if (item.onClose) item.onClose(null, _constants.REASONS.MAXSNACK, item.key);
if (_this.props.onClose) _this.props.onClose(null, _constants.REASONS.MAXSNACK, item.key);
return _extends({}, item, {
open: false
});
}
return _extends({}, item);
});
return _extends({}, state, { snacks: snacks });
};
_this.handleEnteredSnack = function (node, isAppearing, key) {
if (_this.props.onEntered) {
_this.props.onEntered(node, isAppearing, key);
}
_this.setState(function (_ref2) {
var snacks = _ref2.snacks;
return {
snacks: snacks.map(function (item) {
return item.key === key ? _extends({}, item, { entered: true }) : _extends({}, item);
})
};
});
};
_this.handleCloseSnack = function (event, reason, key) {
if (_this.props.onClose) {
_this.props.onClose(event, reason, key);
}
if (reason === _constants.REASONS.CLICKAWAY) return;
var shouldCloseAll = key === undefined;
_this.setState(function (_ref3) {
var snacks = _ref3.snacks,
queue = _ref3.queue;
return {
snacks: snacks.map(function (item) {
if (!shouldCloseAll && item.key !== key) {
return _extends({}, item);
}
return item.entered ? _extends({}, item, { open: false }) : _extends({}, item, { requestClose: true });
}),
queue: queue.filter(function (item) {
return item.key !== key;
}) // eslint-disable-line react/no-unused-state
};
});
};
_this.closeSnackbar = function (key) {
// call individual snackbar onClose callback passed through options parameter
var toBeClosed = _this.state.snacks.find(function (item) {
return item.key === key;
});
if (toBeClosed && toBeClosed.onClose) {
toBeClosed.onClose(null, _constants.REASONS.INSTRUCTED, key);
}
_this.handleCloseSnack(null, _constants.REASONS.INSTRUCTED, key);
};
_this.handleExitedSnack = function (event, key) {
_this.setState(function (state) {
var newState = _this.processQueue(_extends({}, state, {
snacks: state.snacks.filter(function (item) {
return item.key !== key;
})
}));
if (newState.queue.length === 0) {
return newState;
}
return _this.handleDismissOldest(newState);
});
if (_this.props.onExited) {
_this.props.onExited(event, key);
}
};
_this.state = {
snacks: [],
queue: [], // eslint-disable-line react/no-unused-state
contextValue: {
enqueueSnackbar: _this.enqueueSnackbar,
closeSnackbar: _this.closeSnackbar,
modifySnackbar: _this.modifySnackbar
}
};
return _this;
}
/**
* Adds a new snackbar to the queue to be presented.
* @param {string} message - text of the notification
* @param {object} options - additional options for the snackbar we want to enqueue.
* We can pass Material-ui Snackbar props for individual customisation.
* @param {string} options.key
* @param {string} options.variant - type of the snackbar. default value is 'default'.
* can be: (default, success, error, warning, info)
* @param {bool} options.persist
* @param {bool} options.preventDuplicate
* @returns generated or user defined key referencing the new snackbar or null
*/
/**
* Reducer: Display snack if there's space for it. Otherwise, immediately
* begin dismissing the oldest message to start showing the new one.
*/
/**
* Reducer: Display items (notifications) in the queue if there's space for them.
*/
/**
* Reducer: Hide oldest snackbar on the screen because there exists a new one which we have to display.
* (ignoring the one with 'persist' flag. i.e. explicitly told by user not to get dismissed).
*
* Note 1: If there is already a message leaving the screen, no new messages are dismissed.
* Note 2: If the oldest message has not yet entered the screen, only a request to close the
* snackbar is made. Once it entered the screen, it will be immediately dismissed.
*/
/**
* Set the entered state of the snackbar with the given key.
*/
/**
* Hide a snackbar after its timeout.
* @param {object} event - The event source of the callback
* @param {string} reason - can be timeout, clickaway
* @param {number} key - id of the snackbar we want to hide
*/
/**
* Close snackbar with the given key
* @param {number} key - id of the snackbar we want to hide
*/
/**
* When we set open attribute of a snackbar to false (i.e. after we hide a snackbar),
* it leaves the screen and immediately after leaving animation is done, this method
* gets called. We remove the hidden snackbar from state and then display notifications
* waiting in the queue (if any). If after this process the queue is not empty, the
* oldest message is dismissed.
* @param {number} key - id of the snackbar we want to remove
* @param {object} event - The event source of the callback
*/
_createClass(SnackbarProvider, [{
key: 'render',
value: function render() {
var _this2 = this;
var _props = this.props,
classes = _props.classes,
children = _props.children,
maxSnack = _props.maxSnack,
dense = _props.dense,
domRoot = _props.domRoot,
props = _objectWithoutProperties(_props, ['classes', 'children', 'maxSnack', 'dense', 'domRoot']);
var contextValue = this.state.contextValue;
var categ = this.state.snacks.reduce(function (acc, current) {
var category = (0, _constants.originKeyExtractor)(current.anchorOrigin);
var existingOfCategory = acc[category] || [];
return _extends({}, acc, _defineProperty({}, category, [].concat(_toConsumableArray(existingOfCategory), [current])));
}, {});
var iconVariant = Object.assign(_extends({}, _constants.defaultIconVariant), _extends({}, this.props.iconVariant));
var snackbars = Object.entries(categ).map(function (_ref4) {
var _ref5 = _slicedToArray(_ref4, 2),
origin = _ref5[0],
snacks = _ref5[1];
return _react2.default.createElement(
_SnackbarContainer2.default,
{
key: origin,
dense: dense,
anchorOrigin: snacks[0].anchorOrigin,
className: classes['containerAnchorOrigin' + origin]
},
snacks.map(function (snack) {
return _react2.default.createElement(_SnackbarItem2.default, _extends({}, props, {
key: snack.key,
dense: dense,
snack: snack,
iconVariant: iconVariant,
classes: getClasses(classes),
onClose: _this2.handleCloseSnack,
onExited: _this2.handleExitedSnack,
onEntered: _this2.handleEnteredSnack
}));
})
);
});
return _react2.default.createElement(
_SnackbarContext2.default.Provider,
{ value: contextValue },
children,
domRoot ? (0, _reactDom.createPortal)(snackbars, domRoot) : snackbars
);
}
}]);
return SnackbarProvider;
}(_react.Component);
// polyfill for Node
var Element = typeof Element === 'undefined' ? function () {} : Element;
process.env.NODE_ENV !== "production" ? SnackbarProvider.propTypes = {
/**
* Most of the time, this is your App. every component from this point onward
* will be able to show snackbars.
*/
children: _propTypes2.default.node.isRequired,
/**
* Override or extend the styles applied to the container component or Snackbars.
*/
classes: _propTypes2.default.object,
/**
* Maximum snackbars that can be stacked on top of one another.
*/
maxSnack: _propTypes2.default.number,
/**
* Denser margins for snackbars. Recommended to be used on mobile devices
*/
dense: _propTypes2.default.bool,
/**
* Ignores displaying multiple snackbars with the same `message`
*/
preventDuplicate: _propTypes2.default.bool,
/**
* Hides iconVariant if set to `true`.
*/
hideIconVariant: _propTypes2.default.bool,
/**
* Little icon that is displayed at left corner of a snackbar.
*/
iconVariant: _propTypes2.default.shape({
/**
* Icon displayed when variant of a snackbar is set to `success`.
*/
success: _propTypes2.default.any,
/**
* Icon displayed when variant of a snackbar is set to `warning`.
*/
warning: _propTypes2.default.any,
/**
* Icon displayed when variant of a snackbar is set to `error`.
*/
error: _propTypes2.default.any,
/**
* Icon displayed when variant of a snackbar is set to `info`.
*/
info: _propTypes2.default.any
}),
/**
* Callback used for getting action(s). actions are mostly buttons displayed in Snackbar.
* @param {string|number} key key of a snackbar
*/
action: _propTypes2.default.oneOfType([_propTypes2.default.func, _propTypes2.default.object]),
/**
* Replace the snackbar. Callback used for displaying entirely customized snackbar.
* @param {string|number} key key of a snackbar
*/
content: _propTypes2.default.oneOfType([_propTypes2.default.func, _propTypes2.default.object]),
/**
* The anchor of the `Snackbar`.
*/
anchorOrigin: _propTypes2.default.shape({
horizontal: _propTypes2.default.oneOf(['left', 'center', 'right']).isRequired,
vertical: _propTypes2.default.oneOf(['top', 'bottom']).isRequired
}),
/**
* The number of milliseconds to wait before automatically calling the
* `onClose` function. By default snackbars get closed after 5000 milliseconds.
* Set autoHideDuration to 'undefined' if you don't want snackbars to automatically close.
* Alternatively pass `persist: true` in the options parameter of enqueueSnackbar.
*/
autoHideDuration: _propTypes2.default.number,
/**
* If `true`, the `autoHideDuration` timer will expire even if the window is not focused.
*/
disableWindowBlurListener: _propTypes2.default.bool,
/**
* Callback fired when the component is gets closed.
* The `reason` parameter can optionally be used to control the response to `onClose`.
*
* @param {object} event The event source of the callback
* @param {string} reason Can be:`"timeout"` (`autoHideDuration` expired) or: `"clickaway"`
* or: `"maxsnack"` (snackbar was closed because `maxSnack` has reached) or: `"instructed"`
* (snackbar was closed programmatically)
* @param {string|number} key key of a Snackbar
*/
onClose: _propTypes2.default.func,
/**
* Callback fired before the transition is entering.
*/
onEnter: _propTypes2.default.func,
/**
* Callback fired when the transition has entered.
*/
onEntered: _propTypes2.default.func,
/**
* Callback fired when the transition is entering.
*/
onEntering: _propTypes2.default.func,
/**
* Callback fired before the transition is exiting.
*/
onExit: _propTypes2.default.func,
/**
* Callback fired when the transition has exited.
*/
onExited: _propTypes2.default.func,
/**
* Callback fired when the transition is exiting.
*/
onExiting: _propTypes2.default.func,
/**
* The number of milliseconds to wait before dismissing after user interaction.
* If `autoHideDuration` property isn't specified, it does nothing.
* If `autoHideDuration` property is specified but `resumeHideDuration` isn't,
* we default to `autoHideDuration / 2` ms.
*/
resumeHideDuration: _propTypes2.default.number,
/**
* The component used for the transition.
*/
TransitionComponent: _propTypes2.default.elementType,
/**
* The duration for the transition, in milliseconds.
* You may specify a single timeout for all transitions, or individually with an object.
*/
transitionDuration: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.shape({ enter: _propTypes2.default.number, exit: _propTypes2.default.number })]),
/**
* Valid and exist HTML Node element, used to target `ReactDOM.createPortal`
*/
domRoot: _propTypes2.default.instanceOf(Element)
} : void 0;
SnackbarProvider.defaultProps = {
maxSnack: 3,
dense: false,
preventDuplicate: false,
hideIconVariant: false,
classes: {},
iconVariant: {},
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left'
},
autoHideDuration: 5000,
TransitionComponent: _Slide2.default
};
exports.default = SnackbarProvider;