UNPKG

bloom-layout

Version:
324 lines (234 loc) 10.1 kB
'use strict'; exports.__esModule = true; 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 _DOMUtils = require('./DOMUtils'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var HashChangeEvent = 'hashchange'; var HashPathCoders = { hashbang: { encodePath: function encodePath(path) { return path.charAt(0) === '!' ? path : '!/' + (0, _PathUtils.stripLeadingSlash)(path); }, decodePath: function decodePath(path) { return path.charAt(0) === '!' ? path.substr(1) : path; } }, noslash: { encodePath: _PathUtils.stripLeadingSlash, decodePath: _PathUtils.addLeadingSlash }, slash: { encodePath: _PathUtils.addLeadingSlash, decodePath: _PathUtils.addLeadingSlash } }; var getHashPath = function getHashPath() { // We can't use window.location.hash here because it's not // consistent across browsers - Firefox will pre-decode it! var href = window.location.href; var hashIndex = href.indexOf('#'); return hashIndex === -1 ? '' : href.substring(hashIndex + 1); }; var pushHashPath = function pushHashPath(path) { return window.location.hash = path; }; var replaceHashPath = function replaceHashPath(path) { var hashIndex = window.location.href.indexOf('#'); window.location.replace(window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path); }; var createHashHistory = function createHashHistory() { var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; (0, _invariant2.default)(_DOMUtils.canUseDOM, 'Hash history needs a DOM'); var globalHistory = window.history; var canGoWithoutReload = (0, _DOMUtils.supportsGoWithoutReloadUsingHash)(); var _props$getUserConfirm = props.getUserConfirmation, getUserConfirmation = _props$getUserConfirm === undefined ? _DOMUtils.getConfirmation : _props$getUserConfirm, _props$hashType = props.hashType, hashType = _props$hashType === undefined ? 'slash' : _props$hashType; var basename = props.basename ? (0, _PathUtils.stripTrailingSlash)((0, _PathUtils.addLeadingSlash)(props.basename)) : ''; var _HashPathCoders$hashT = HashPathCoders[hashType], encodePath = _HashPathCoders$hashT.encodePath, decodePath = _HashPathCoders$hashT.decodePath; var getDOMLocation = function getDOMLocation() { var path = decodePath(getHashPath()); (0, _warning2.default)(!basename || (0, _PathUtils.hasBasename)(path, basename), 'You are attempting to use a basename on a page whose URL path does not begin ' + 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".'); if (basename) path = (0, _PathUtils.stripBasename)(path, basename); return (0, _LocationUtils.createLocation)(path); }; var transitionManager = (0, _createTransitionManager2.default)(); var setState = function setState(nextState) { _extends(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; var forceNextPop = false; var ignorePath = null; var handleHashChange = function handleHashChange() { var path = getHashPath(); var encodedPath = encodePath(path); if (path !== encodedPath) { // Ensure we always have a properly-encoded hash. replaceHashPath(encodedPath); } else { var location = getDOMLocation(); var prevLocation = history.location; if (!forceNextPop && (0, _LocationUtils.locationsAreEqual)(prevLocation, location)) return; // A hashchange doesn't always == location change. if (ignorePath === (0, _PathUtils.createPath)(location)) return; // Ignore this change; we already setState in push/replace. ignorePath = null; handlePop(location); } }; var handlePop = function handlePop(location) { if (forceNextPop) { forceNextPop = false; setState(); } else { 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 paths we've seen in sessionStorage. // Instead, we just default to 0 for paths we don't know. var toIndex = allPaths.lastIndexOf((0, _PathUtils.createPath)(toLocation)); if (toIndex === -1) toIndex = 0; var fromIndex = allPaths.lastIndexOf((0, _PathUtils.createPath)(fromLocation)); if (fromIndex === -1) fromIndex = 0; var delta = toIndex - fromIndex; if (delta) { forceNextPop = true; go(delta); } }; // Ensure the hash is encoded properly before doing anything else. var path = getHashPath(); var encodedPath = encodePath(path); if (path !== encodedPath) replaceHashPath(encodedPath); var initialLocation = getDOMLocation(); var allPaths = [(0, _PathUtils.createPath)(initialLocation)]; // Public interface var createHref = function createHref(location) { return '#' + encodePath(basename + (0, _PathUtils.createPath)(location)); }; var push = function push(path, state) { (0, _warning2.default)(state === undefined, 'Hash history cannot push state; it is ignored'); var action = 'PUSH'; var location = (0, _LocationUtils.createLocation)(path, undefined, undefined, history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) return; var path = (0, _PathUtils.createPath)(location); var encodedPath = encodePath(basename + path); var hashChanged = getHashPath() !== encodedPath; if (hashChanged) { // We cannot tell if a hashchange was caused by a PUSH, so we'd // rather setState here and ignore the hashchange. The caveat here // is that other hash histories in the page will consider it a POP. ignorePath = path; pushHashPath(encodedPath); var prevIndex = allPaths.lastIndexOf((0, _PathUtils.createPath)(history.location)); var nextPaths = allPaths.slice(0, prevIndex === -1 ? 0 : prevIndex + 1); nextPaths.push(path); allPaths = nextPaths; setState({ action: action, location: location }); } else { (0, _warning2.default)(false, 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack'); setState(); } }); }; var replace = function replace(path, state) { (0, _warning2.default)(state === undefined, 'Hash history cannot replace state; it is ignored'); var action = 'REPLACE'; var location = (0, _LocationUtils.createLocation)(path, undefined, undefined, history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) return; var path = (0, _PathUtils.createPath)(location); var encodedPath = encodePath(basename + path); var hashChanged = getHashPath() !== encodedPath; if (hashChanged) { // We cannot tell if a hashchange was caused by a REPLACE, so we'd // rather setState here and ignore the hashchange. The caveat here // is that other hash histories in the page will consider it a POP. ignorePath = path; replaceHashPath(encodedPath); } var prevIndex = allPaths.indexOf((0, _PathUtils.createPath)(history.location)); if (prevIndex !== -1) allPaths[prevIndex] = path; setState({ action: action, location: location }); }); }; var go = function go(n) { (0, _warning2.default)(canGoWithoutReload, 'Hash history go(n) causes a full page reload in this browser'); 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, HashChangeEvent, handleHashChange); } else if (listenerCount === 0) { (0, _DOMUtils.removeEventListener)(window, HashChangeEvent, handleHashChange); } }; var isBlocked = false; var block = function block() { var prompt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 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); unlisten(); }; }; var history = { length: globalHistory.length, action: 'POP', location: initialLocation, createHref: createHref, push: push, replace: replace, go: go, goBack: goBack, goForward: goForward, block: block, listen: listen }; return history; }; exports.default = createHashHistory;