react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
307 lines (220 loc) • 9.75 kB
JavaScript
;
exports.__esModule = true;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
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 _warning = require('warning');
var _warning2 = _interopRequireDefault(_warning);
var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var _LocationUtils = require('./LocationUtils');
var _PathUtils = require('./PathUtils');
var _createTransitionManager = require('./createTransitionManager');
var _createTransitionManager2 = _interopRequireDefault(_createTransitionManager);
var _ExecutionEnvironment = require('./ExecutionEnvironment');
var _DOMUtils = require('./DOMUtils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var PopStateEvent = 'popstate';
var HashChangeEvent = 'hashchange';
var getHistoryState = function getHistoryState() {
try {
return window.history.state || {};
} catch (e) {
// IE 11 sometimes throws when accessing window.history.state
// See https://github.com/mjackson/history/pull/289
return {};
}
};
/**
* Creates a history object that uses the HTML5 history API including
* pushState, replaceState, and the popstate event.
*/
var createBrowserHistory = function createBrowserHistory() {
var props = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
!_ExecutionEnvironment.canUseDOM ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'Browser history needs a DOM') : (0, _invariant2.default)(false) : void 0;
var globalHistory = window.history;
var canUseHistory = (0, _DOMUtils.supportsHistory)();
var needsHashChangeListener = !(0, _DOMUtils.supportsPopStateOnHashChange)();
var _props$basename = props.basename;
var basename = _props$basename === undefined ? '' : _props$basename;
var _props$forceRefresh = props.forceRefresh;
var forceRefresh = _props$forceRefresh === undefined ? false : _props$forceRefresh;
var _props$getUserConfirm = props.getUserConfirmation;
var getUserConfirmation = _props$getUserConfirm === undefined ? _DOMUtils.getConfirmation : _props$getUserConfirm;
var _props$keyLength = props.keyLength;
var keyLength = _props$keyLength === undefined ? 6 : _props$keyLength;
var getDOMLocation = function getDOMLocation(historyState) {
var _ref = historyState || {};
var key = _ref.key;
var state = _ref.state;
var _window$location = window.location;
var pathname = _window$location.pathname;
var search = _window$location.search;
var hash = _window$location.hash;
var path = pathname + search + hash;
if (basename) path = (0, _PathUtils.stripPrefix)(path, basename);
return _extends({}, (0, _PathUtils.parsePath)(path), {
state: state,
key: key
});
};
var createKey = function createKey() {
return Math.random().toString(36).substr(2, keyLength);
};
var transitionManager = (0, _createTransitionManager2.default)();
var setState = function setState(nextState) {
_extends(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.location, history.action);
};
var handlePopState = function handlePopState(event) {
if (event.state === undefined) return; // Ignore extraneous popstate events in WebKit.
handlePop(getDOMLocation(event.state));
};
var handleHashChange = function handleHashChange() {
handlePop(getDOMLocation(getHistoryState()));
};
var forceNextPop = false;
var handlePop = function handlePop(location) {
if (forceNextPop) {
forceNextPop = false;
setState();
} else {
(function () {
var action = 'POP';
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if (ok) {
setState({ action: action, location: location });
} else {
revertPop(location);
}
});
})();
}
};
var revertPop = function revertPop(fromLocation) {
var toLocation = history.location;
// TODO: We could probably make this more reliable by
// keeping a list of keys we've seen in sessionStorage.
// Instead, we just default to 0 for keys we don't know.
var toIndex = allKeys.indexOf(toLocation.key);
if (toIndex === -1) toIndex = 0;
var fromIndex = allKeys.indexOf(fromLocation.key);
if (fromIndex === -1) fromIndex = 0;
var delta = toIndex - fromIndex;
if (delta) {
forceNextPop = true;
go(delta);
}
};
var initialLocation = getDOMLocation(getHistoryState());
var allKeys = [initialLocation.key];
// Public interface
var push = function push(path, state) {
process.env.NODE_ENV !== 'production' ? (0, _warning2.default)(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to push when the 1st ' + 'argument is a location-like object that already has state; it is ignored') : void 0;
var action = 'PUSH';
var location = (0, _LocationUtils.createLocation)(path, state, createKey());
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if (!ok) return;
var url = basename + (0, _PathUtils.createPath)(location);
var key = location.key;
var state = location.state;
if (canUseHistory) {
globalHistory.pushState({ key: key, state: state }, null, url);
if (forceRefresh) {
window.location.href = url;
} else {
var prevIndex = allKeys.indexOf(history.location.key);
var nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
nextKeys.push(location.key);
allKeys = nextKeys;
setState({ action: action, location: location });
}
} else {
process.env.NODE_ENV !== 'production' ? (0, _warning2.default)(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history') : void 0;
window.location.href = url;
}
});
};
var replace = function replace(path, state) {
process.env.NODE_ENV !== 'production' ? (0, _warning2.default)(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to replace when the 1st ' + 'argument is a location-like object that already has state; it is ignored') : void 0;
var action = 'REPLACE';
var location = (0, _LocationUtils.createLocation)(path, state, createKey());
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if (!ok) return;
var url = basename + (0, _PathUtils.createPath)(location);
var key = location.key;
var state = location.state;
if (canUseHistory) {
globalHistory.replaceState({ key: key, state: state }, null, url);
if (forceRefresh) {
window.location.replace(url);
} else {
var prevIndex = allKeys.indexOf(history.location.key);
if (prevIndex !== -1) allKeys[prevIndex] = location.key;
setState({ action: action, location: location });
}
} else {
process.env.NODE_ENV !== 'production' ? (0, _warning2.default)(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history') : void 0;
window.location.replace(url);
}
});
};
var go = function go(n) {
globalHistory.go(n);
};
var goBack = function goBack() {
return go(-1);
};
var goForward = function goForward() {
return go(1);
};
var listenerCount = 0;
var checkDOMListeners = function checkDOMListeners(delta) {
listenerCount += delta;
if (listenerCount === 1) {
(0, _DOMUtils.addEventListener)(window, PopStateEvent, handlePopState);
if (needsHashChangeListener) (0, _DOMUtils.addEventListener)(window, HashChangeEvent, handleHashChange);
} else if (listenerCount === 0) {
(0, _DOMUtils.removeEventListener)(window, PopStateEvent, handlePopState);
if (needsHashChangeListener) (0, _DOMUtils.removeEventListener)(window, HashChangeEvent, handleHashChange);
}
};
var isBlocked = false;
var block = function block() {
var prompt = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0];
var unblock = transitionManager.setPrompt(prompt);
if (!isBlocked) {
checkDOMListeners(1);
isBlocked = true;
}
return function () {
if (isBlocked) {
isBlocked = false;
checkDOMListeners(-1);
}
return unblock();
};
};
var listen = function listen(listener) {
var unlisten = transitionManager.appendListener(listener);
checkDOMListeners(1);
return function () {
checkDOMListeners(-1);
return unlisten();
};
};
var history = {
length: globalHistory.length,
action: 'POP',
location: initialLocation,
push: push,
replace: replace,
go: go,
goBack: goBack,
goForward: goForward,
block: block,
listen: listen
};
return history;
};
exports.default = createBrowserHistory;