history.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  4. var resolvePathname = _interopDefault(require('resolve-pathname'));
  5. var valueEqual = _interopDefault(require('value-equal'));
  6. var warning = _interopDefault(require('tiny-warning'));
  7. var invariant = _interopDefault(require('tiny-invariant'));
  8. function _extends() {
  9. _extends = Object.assign || function (target) {
  10. for (var i = 1; i < arguments.length; i++) {
  11. var source = arguments[i];
  12. for (var key in source) {
  13. if (Object.prototype.hasOwnProperty.call(source, key)) {
  14. target[key] = source[key];
  15. }
  16. }
  17. }
  18. return target;
  19. };
  20. return _extends.apply(this, arguments);
  21. }
  22. function addLeadingSlash(path) {
  23. return path.charAt(0) === '/' ? path : '/' + path;
  24. }
  25. function stripLeadingSlash(path) {
  26. return path.charAt(0) === '/' ? path.substr(1) : path;
  27. }
  28. function hasBasename(path, prefix) {
  29. return path.toLowerCase().indexOf(prefix.toLowerCase()) === 0 && '/?#'.indexOf(path.charAt(prefix.length)) !== -1;
  30. }
  31. function stripBasename(path, prefix) {
  32. return hasBasename(path, prefix) ? path.substr(prefix.length) : path;
  33. }
  34. function stripTrailingSlash(path) {
  35. return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path;
  36. }
  37. function parsePath(path) {
  38. var pathname = path || '/';
  39. var search = '';
  40. var hash = '';
  41. var hashIndex = pathname.indexOf('#');
  42. if (hashIndex !== -1) {
  43. hash = pathname.substr(hashIndex);
  44. pathname = pathname.substr(0, hashIndex);
  45. }
  46. var searchIndex = pathname.indexOf('?');
  47. if (searchIndex !== -1) {
  48. search = pathname.substr(searchIndex);
  49. pathname = pathname.substr(0, searchIndex);
  50. }
  51. return {
  52. pathname: pathname,
  53. search: search === '?' ? '' : search,
  54. hash: hash === '#' ? '' : hash
  55. };
  56. }
  57. function createPath(location) {
  58. var pathname = location.pathname,
  59. search = location.search,
  60. hash = location.hash;
  61. var path = pathname || '/';
  62. if (search && search !== '?') path += search.charAt(0) === '?' ? search : "?" + search;
  63. if (hash && hash !== '#') path += hash.charAt(0) === '#' ? hash : "#" + hash;
  64. return path;
  65. }
  66. function createLocation(path, state, key, currentLocation) {
  67. var location;
  68. if (typeof path === 'string') {
  69. // Two-arg form: push(path, state)
  70. location = parsePath(path);
  71. location.state = state;
  72. } else {
  73. // One-arg form: push(location)
  74. location = _extends({}, path);
  75. if (location.pathname === undefined) location.pathname = '';
  76. if (location.search) {
  77. if (location.search.charAt(0) !== '?') location.search = '?' + location.search;
  78. } else {
  79. location.search = '';
  80. }
  81. if (location.hash) {
  82. if (location.hash.charAt(0) !== '#') location.hash = '#' + location.hash;
  83. } else {
  84. location.hash = '';
  85. }
  86. if (state !== undefined && location.state === undefined) location.state = state;
  87. }
  88. try {
  89. location.pathname = decodeURI(location.pathname);
  90. } catch (e) {
  91. if (e instanceof URIError) {
  92. throw new URIError('Pathname "' + location.pathname + '" could not be decoded. ' + 'This is likely caused by an invalid percent-encoding.');
  93. } else {
  94. throw e;
  95. }
  96. }
  97. if (key) location.key = key;
  98. if (currentLocation) {
  99. // Resolve incomplete/relative pathname relative to current location.
  100. if (!location.pathname) {
  101. location.pathname = currentLocation.pathname;
  102. } else if (location.pathname.charAt(0) !== '/') {
  103. location.pathname = resolvePathname(location.pathname, currentLocation.pathname);
  104. }
  105. } else {
  106. // When there is no prior location and pathname is empty, set it to /
  107. if (!location.pathname) {
  108. location.pathname = '/';
  109. }
  110. }
  111. return location;
  112. }
  113. function locationsAreEqual(a, b) {
  114. return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash && a.key === b.key && valueEqual(a.state, b.state);
  115. }
  116. function createTransitionManager() {
  117. var prompt = null;
  118. function setPrompt(nextPrompt) {
  119. warning(prompt == null, 'A history supports only one prompt at a time');
  120. prompt = nextPrompt;
  121. return function () {
  122. if (prompt === nextPrompt) prompt = null;
  123. };
  124. }
  125. function confirmTransitionTo(location, action, getUserConfirmation, callback) {
  126. // TODO: If another transition starts while we're still confirming
  127. // the previous one, we may end up in a weird state. Figure out the
  128. // best way to handle this.
  129. if (prompt != null) {
  130. var result = typeof prompt === 'function' ? prompt(location, action) : prompt;
  131. if (typeof result === 'string') {
  132. if (typeof getUserConfirmation === 'function') {
  133. getUserConfirmation(result, callback);
  134. } else {
  135. warning(false, 'A history needs a getUserConfirmation function in order to use a prompt message');
  136. callback(true);
  137. }
  138. } else {
  139. // Return false from a transition hook to cancel the transition.
  140. callback(result !== false);
  141. }
  142. } else {
  143. callback(true);
  144. }
  145. }
  146. var listeners = [];
  147. function appendListener(fn) {
  148. var isActive = true;
  149. function listener() {
  150. if (isActive) fn.apply(void 0, arguments);
  151. }
  152. listeners.push(listener);
  153. return function () {
  154. isActive = false;
  155. listeners = listeners.filter(function (item) {
  156. return item !== listener;
  157. });
  158. };
  159. }
  160. function notifyListeners() {
  161. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  162. args[_key] = arguments[_key];
  163. }
  164. listeners.forEach(function (listener) {
  165. return listener.apply(void 0, args);
  166. });
  167. }
  168. return {
  169. setPrompt: setPrompt,
  170. confirmTransitionTo: confirmTransitionTo,
  171. appendListener: appendListener,
  172. notifyListeners: notifyListeners
  173. };
  174. }
  175. var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
  176. function getConfirmation(message, callback) {
  177. callback(window.confirm(message)); // eslint-disable-line no-alert
  178. }
  179. /**
  180. * Returns true if the HTML5 history API is supported. Taken from Modernizr.
  181. *
  182. * https://github.com/Modernizr/Modernizr/blob/master/LICENSE
  183. * https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js
  184. * changed to avoid false negatives for Windows Phones: https://github.com/reactjs/react-router/issues/586
  185. */
  186. function supportsHistory() {
  187. var ua = window.navigator.userAgent;
  188. if ((ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && ua.indexOf('Mobile Safari') !== -1 && ua.indexOf('Chrome') === -1 && ua.indexOf('Windows Phone') === -1) return false;
  189. return window.history && 'pushState' in window.history;
  190. }
  191. /**
  192. * Returns true if browser fires popstate on hash change.
  193. * IE10 and IE11 do not.
  194. */
  195. function supportsPopStateOnHashChange() {
  196. return window.navigator.userAgent.indexOf('Trident') === -1;
  197. }
  198. /**
  199. * Returns false if using go(n) with hash history causes a full page reload.
  200. */
  201. function supportsGoWithoutReloadUsingHash() {
  202. return window.navigator.userAgent.indexOf('Firefox') === -1;
  203. }
  204. /**
  205. * Returns true if a given popstate event is an extraneous WebKit event.
  206. * Accounts for the fact that Chrome on iOS fires real popstate events
  207. * containing undefined state when pressing the back button.
  208. */
  209. function isExtraneousPopstateEvent(event) {
  210. return event.state === undefined && navigator.userAgent.indexOf('CriOS') === -1;
  211. }
  212. var PopStateEvent = 'popstate';
  213. var HashChangeEvent = 'hashchange';
  214. function getHistoryState() {
  215. try {
  216. return window.history.state || {};
  217. } catch (e) {
  218. // IE 11 sometimes throws when accessing window.history.state
  219. // See https://github.com/ReactTraining/history/pull/289
  220. return {};
  221. }
  222. }
  223. /**
  224. * Creates a history object that uses the HTML5 history API including
  225. * pushState, replaceState, and the popstate event.
  226. */
  227. function createBrowserHistory(props) {
  228. if (props === void 0) {
  229. props = {};
  230. }
  231. !canUseDOM ? invariant(false, 'Browser history needs a DOM') : void 0;
  232. var globalHistory = window.history;
  233. var canUseHistory = supportsHistory();
  234. var needsHashChangeListener = !supportsPopStateOnHashChange();
  235. var _props = props,
  236. _props$forceRefresh = _props.forceRefresh,
  237. forceRefresh = _props$forceRefresh === void 0 ? false : _props$forceRefresh,
  238. _props$getUserConfirm = _props.getUserConfirmation,
  239. getUserConfirmation = _props$getUserConfirm === void 0 ? getConfirmation : _props$getUserConfirm,
  240. _props$keyLength = _props.keyLength,
  241. keyLength = _props$keyLength === void 0 ? 6 : _props$keyLength;
  242. var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : '';
  243. function getDOMLocation(historyState) {
  244. var _ref = historyState || {},
  245. key = _ref.key,
  246. state = _ref.state;
  247. var _window$location = window.location,
  248. pathname = _window$location.pathname,
  249. search = _window$location.search,
  250. hash = _window$location.hash;
  251. var path = pathname + search + hash;
  252. warning(!basename || 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 + '".');
  253. if (basename) path = stripBasename(path, basename);
  254. return createLocation(path, state, key);
  255. }
  256. function createKey() {
  257. return Math.random().toString(36).substr(2, keyLength);
  258. }
  259. var transitionManager = createTransitionManager();
  260. function setState(nextState) {
  261. _extends(history, nextState);
  262. history.length = globalHistory.length;
  263. transitionManager.notifyListeners(history.location, history.action);
  264. }
  265. function handlePopState(event) {
  266. // Ignore extraneous popstate events in WebKit.
  267. if (isExtraneousPopstateEvent(event)) return;
  268. handlePop(getDOMLocation(event.state));
  269. }
  270. function handleHashChange() {
  271. handlePop(getDOMLocation(getHistoryState()));
  272. }
  273. var forceNextPop = false;
  274. function handlePop(location) {
  275. if (forceNextPop) {
  276. forceNextPop = false;
  277. setState();
  278. } else {
  279. var action = 'POP';
  280. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  281. if (ok) {
  282. setState({
  283. action: action,
  284. location: location
  285. });
  286. } else {
  287. revertPop(location);
  288. }
  289. });
  290. }
  291. }
  292. function revertPop(fromLocation) {
  293. var toLocation = history.location; // TODO: We could probably make this more reliable by
  294. // keeping a list of keys we've seen in sessionStorage.
  295. // Instead, we just default to 0 for keys we don't know.
  296. var toIndex = allKeys.indexOf(toLocation.key);
  297. if (toIndex === -1) toIndex = 0;
  298. var fromIndex = allKeys.indexOf(fromLocation.key);
  299. if (fromIndex === -1) fromIndex = 0;
  300. var delta = toIndex - fromIndex;
  301. if (delta) {
  302. forceNextPop = true;
  303. go(delta);
  304. }
  305. }
  306. var initialLocation = getDOMLocation(getHistoryState());
  307. var allKeys = [initialLocation.key]; // Public interface
  308. function createHref(location) {
  309. return basename + createPath(location);
  310. }
  311. function push(path, state) {
  312. warning(!(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');
  313. var action = 'PUSH';
  314. var location = createLocation(path, state, createKey(), history.location);
  315. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  316. if (!ok) return;
  317. var href = createHref(location);
  318. var key = location.key,
  319. state = location.state;
  320. if (canUseHistory) {
  321. globalHistory.pushState({
  322. key: key,
  323. state: state
  324. }, null, href);
  325. if (forceRefresh) {
  326. window.location.href = href;
  327. } else {
  328. var prevIndex = allKeys.indexOf(history.location.key);
  329. var nextKeys = allKeys.slice(0, prevIndex + 1);
  330. nextKeys.push(location.key);
  331. allKeys = nextKeys;
  332. setState({
  333. action: action,
  334. location: location
  335. });
  336. }
  337. } else {
  338. warning(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history');
  339. window.location.href = href;
  340. }
  341. });
  342. }
  343. function replace(path, state) {
  344. warning(!(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');
  345. var action = 'REPLACE';
  346. var location = createLocation(path, state, createKey(), history.location);
  347. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  348. if (!ok) return;
  349. var href = createHref(location);
  350. var key = location.key,
  351. state = location.state;
  352. if (canUseHistory) {
  353. globalHistory.replaceState({
  354. key: key,
  355. state: state
  356. }, null, href);
  357. if (forceRefresh) {
  358. window.location.replace(href);
  359. } else {
  360. var prevIndex = allKeys.indexOf(history.location.key);
  361. if (prevIndex !== -1) allKeys[prevIndex] = location.key;
  362. setState({
  363. action: action,
  364. location: location
  365. });
  366. }
  367. } else {
  368. warning(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history');
  369. window.location.replace(href);
  370. }
  371. });
  372. }
  373. function go(n) {
  374. globalHistory.go(n);
  375. }
  376. function goBack() {
  377. go(-1);
  378. }
  379. function goForward() {
  380. go(1);
  381. }
  382. var listenerCount = 0;
  383. function checkDOMListeners(delta) {
  384. listenerCount += delta;
  385. if (listenerCount === 1 && delta === 1) {
  386. window.addEventListener(PopStateEvent, handlePopState);
  387. if (needsHashChangeListener) window.addEventListener(HashChangeEvent, handleHashChange);
  388. } else if (listenerCount === 0) {
  389. window.removeEventListener(PopStateEvent, handlePopState);
  390. if (needsHashChangeListener) window.removeEventListener(HashChangeEvent, handleHashChange);
  391. }
  392. }
  393. var isBlocked = false;
  394. function block(prompt) {
  395. if (prompt === void 0) {
  396. prompt = false;
  397. }
  398. var unblock = transitionManager.setPrompt(prompt);
  399. if (!isBlocked) {
  400. checkDOMListeners(1);
  401. isBlocked = true;
  402. }
  403. return function () {
  404. if (isBlocked) {
  405. isBlocked = false;
  406. checkDOMListeners(-1);
  407. }
  408. return unblock();
  409. };
  410. }
  411. function listen(listener) {
  412. var unlisten = transitionManager.appendListener(listener);
  413. checkDOMListeners(1);
  414. return function () {
  415. checkDOMListeners(-1);
  416. unlisten();
  417. };
  418. }
  419. var history = {
  420. length: globalHistory.length,
  421. action: 'POP',
  422. location: initialLocation,
  423. createHref: createHref,
  424. push: push,
  425. replace: replace,
  426. go: go,
  427. goBack: goBack,
  428. goForward: goForward,
  429. block: block,
  430. listen: listen
  431. };
  432. return history;
  433. }
  434. var HashChangeEvent$1 = 'hashchange';
  435. var HashPathCoders = {
  436. hashbang: {
  437. encodePath: function encodePath(path) {
  438. return path.charAt(0) === '!' ? path : '!/' + stripLeadingSlash(path);
  439. },
  440. decodePath: function decodePath(path) {
  441. return path.charAt(0) === '!' ? path.substr(1) : path;
  442. }
  443. },
  444. noslash: {
  445. encodePath: stripLeadingSlash,
  446. decodePath: addLeadingSlash
  447. },
  448. slash: {
  449. encodePath: addLeadingSlash,
  450. decodePath: addLeadingSlash
  451. }
  452. };
  453. function stripHash(url) {
  454. var hashIndex = url.indexOf('#');
  455. return hashIndex === -1 ? url : url.slice(0, hashIndex);
  456. }
  457. function getHashPath() {
  458. // We can't use window.location.hash here because it's not
  459. // consistent across browsers - Firefox will pre-decode it!
  460. var href = window.location.href;
  461. var hashIndex = href.indexOf('#');
  462. return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
  463. }
  464. function pushHashPath(path) {
  465. window.location.hash = path;
  466. }
  467. function replaceHashPath(path) {
  468. window.location.replace(stripHash(window.location.href) + '#' + path);
  469. }
  470. function createHashHistory(props) {
  471. if (props === void 0) {
  472. props = {};
  473. }
  474. !canUseDOM ? invariant(false, 'Hash history needs a DOM') : void 0;
  475. var globalHistory = window.history;
  476. var canGoWithoutReload = supportsGoWithoutReloadUsingHash();
  477. var _props = props,
  478. _props$getUserConfirm = _props.getUserConfirmation,
  479. getUserConfirmation = _props$getUserConfirm === void 0 ? getConfirmation : _props$getUserConfirm,
  480. _props$hashType = _props.hashType,
  481. hashType = _props$hashType === void 0 ? 'slash' : _props$hashType;
  482. var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : '';
  483. var _HashPathCoders$hashT = HashPathCoders[hashType],
  484. encodePath = _HashPathCoders$hashT.encodePath,
  485. decodePath = _HashPathCoders$hashT.decodePath;
  486. function getDOMLocation() {
  487. var path = decodePath(getHashPath());
  488. warning(!basename || 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 + '".');
  489. if (basename) path = stripBasename(path, basename);
  490. return createLocation(path);
  491. }
  492. var transitionManager = createTransitionManager();
  493. function setState(nextState) {
  494. _extends(history, nextState);
  495. history.length = globalHistory.length;
  496. transitionManager.notifyListeners(history.location, history.action);
  497. }
  498. var forceNextPop = false;
  499. var ignorePath = null;
  500. function locationsAreEqual$$1(a, b) {
  501. return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
  502. }
  503. function handleHashChange() {
  504. var path = getHashPath();
  505. var encodedPath = encodePath(path);
  506. if (path !== encodedPath) {
  507. // Ensure we always have a properly-encoded hash.
  508. replaceHashPath(encodedPath);
  509. } else {
  510. var location = getDOMLocation();
  511. var prevLocation = history.location;
  512. if (!forceNextPop && locationsAreEqual$$1(prevLocation, location)) return; // A hashchange doesn't always == location change.
  513. if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.
  514. ignorePath = null;
  515. handlePop(location);
  516. }
  517. }
  518. function handlePop(location) {
  519. if (forceNextPop) {
  520. forceNextPop = false;
  521. setState();
  522. } else {
  523. var action = 'POP';
  524. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  525. if (ok) {
  526. setState({
  527. action: action,
  528. location: location
  529. });
  530. } else {
  531. revertPop(location);
  532. }
  533. });
  534. }
  535. }
  536. function revertPop(fromLocation) {
  537. var toLocation = history.location; // TODO: We could probably make this more reliable by
  538. // keeping a list of paths we've seen in sessionStorage.
  539. // Instead, we just default to 0 for paths we don't know.
  540. var toIndex = allPaths.lastIndexOf(createPath(toLocation));
  541. if (toIndex === -1) toIndex = 0;
  542. var fromIndex = allPaths.lastIndexOf(createPath(fromLocation));
  543. if (fromIndex === -1) fromIndex = 0;
  544. var delta = toIndex - fromIndex;
  545. if (delta) {
  546. forceNextPop = true;
  547. go(delta);
  548. }
  549. } // Ensure the hash is encoded properly before doing anything else.
  550. var path = getHashPath();
  551. var encodedPath = encodePath(path);
  552. if (path !== encodedPath) replaceHashPath(encodedPath);
  553. var initialLocation = getDOMLocation();
  554. var allPaths = [createPath(initialLocation)]; // Public interface
  555. function createHref(location) {
  556. var baseTag = document.querySelector('base');
  557. var href = '';
  558. if (baseTag && baseTag.getAttribute('href')) {
  559. href = stripHash(window.location.href);
  560. }
  561. return href + '#' + encodePath(basename + createPath(location));
  562. }
  563. function push(path, state) {
  564. warning(state === undefined, 'Hash history cannot push state; it is ignored');
  565. var action = 'PUSH';
  566. var location = createLocation(path, undefined, undefined, history.location);
  567. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  568. if (!ok) return;
  569. var path = createPath(location);
  570. var encodedPath = encodePath(basename + path);
  571. var hashChanged = getHashPath() !== encodedPath;
  572. if (hashChanged) {
  573. // We cannot tell if a hashchange was caused by a PUSH, so we'd
  574. // rather setState here and ignore the hashchange. The caveat here
  575. // is that other hash histories in the page will consider it a POP.
  576. ignorePath = path;
  577. pushHashPath(encodedPath);
  578. var prevIndex = allPaths.lastIndexOf(createPath(history.location));
  579. var nextPaths = allPaths.slice(0, prevIndex + 1);
  580. nextPaths.push(path);
  581. allPaths = nextPaths;
  582. setState({
  583. action: action,
  584. location: location
  585. });
  586. } else {
  587. warning(false, 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack');
  588. setState();
  589. }
  590. });
  591. }
  592. function replace(path, state) {
  593. warning(state === undefined, 'Hash history cannot replace state; it is ignored');
  594. var action = 'REPLACE';
  595. var location = createLocation(path, undefined, undefined, history.location);
  596. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  597. if (!ok) return;
  598. var path = createPath(location);
  599. var encodedPath = encodePath(basename + path);
  600. var hashChanged = getHashPath() !== encodedPath;
  601. if (hashChanged) {
  602. // We cannot tell if a hashchange was caused by a REPLACE, so we'd
  603. // rather setState here and ignore the hashchange. The caveat here
  604. // is that other hash histories in the page will consider it a POP.
  605. ignorePath = path;
  606. replaceHashPath(encodedPath);
  607. }
  608. var prevIndex = allPaths.indexOf(createPath(history.location));
  609. if (prevIndex !== -1) allPaths[prevIndex] = path;
  610. setState({
  611. action: action,
  612. location: location
  613. });
  614. });
  615. }
  616. function go(n) {
  617. warning(canGoWithoutReload, 'Hash history go(n) causes a full page reload in this browser');
  618. globalHistory.go(n);
  619. }
  620. function goBack() {
  621. go(-1);
  622. }
  623. function goForward() {
  624. go(1);
  625. }
  626. var listenerCount = 0;
  627. function checkDOMListeners(delta) {
  628. listenerCount += delta;
  629. if (listenerCount === 1 && delta === 1) {
  630. window.addEventListener(HashChangeEvent$1, handleHashChange);
  631. } else if (listenerCount === 0) {
  632. window.removeEventListener(HashChangeEvent$1, handleHashChange);
  633. }
  634. }
  635. var isBlocked = false;
  636. function block(prompt) {
  637. if (prompt === void 0) {
  638. prompt = false;
  639. }
  640. var unblock = transitionManager.setPrompt(prompt);
  641. if (!isBlocked) {
  642. checkDOMListeners(1);
  643. isBlocked = true;
  644. }
  645. return function () {
  646. if (isBlocked) {
  647. isBlocked = false;
  648. checkDOMListeners(-1);
  649. }
  650. return unblock();
  651. };
  652. }
  653. function listen(listener) {
  654. var unlisten = transitionManager.appendListener(listener);
  655. checkDOMListeners(1);
  656. return function () {
  657. checkDOMListeners(-1);
  658. unlisten();
  659. };
  660. }
  661. var history = {
  662. length: globalHistory.length,
  663. action: 'POP',
  664. location: initialLocation,
  665. createHref: createHref,
  666. push: push,
  667. replace: replace,
  668. go: go,
  669. goBack: goBack,
  670. goForward: goForward,
  671. block: block,
  672. listen: listen
  673. };
  674. return history;
  675. }
  676. function clamp(n, lowerBound, upperBound) {
  677. return Math.min(Math.max(n, lowerBound), upperBound);
  678. }
  679. /**
  680. * Creates a history object that stores locations in memory.
  681. */
  682. function createMemoryHistory(props) {
  683. if (props === void 0) {
  684. props = {};
  685. }
  686. var _props = props,
  687. getUserConfirmation = _props.getUserConfirmation,
  688. _props$initialEntries = _props.initialEntries,
  689. initialEntries = _props$initialEntries === void 0 ? ['/'] : _props$initialEntries,
  690. _props$initialIndex = _props.initialIndex,
  691. initialIndex = _props$initialIndex === void 0 ? 0 : _props$initialIndex,
  692. _props$keyLength = _props.keyLength,
  693. keyLength = _props$keyLength === void 0 ? 6 : _props$keyLength;
  694. var transitionManager = createTransitionManager();
  695. function setState(nextState) {
  696. _extends(history, nextState);
  697. history.length = history.entries.length;
  698. transitionManager.notifyListeners(history.location, history.action);
  699. }
  700. function createKey() {
  701. return Math.random().toString(36).substr(2, keyLength);
  702. }
  703. var index = clamp(initialIndex, 0, initialEntries.length - 1);
  704. var entries = initialEntries.map(function (entry) {
  705. return typeof entry === 'string' ? createLocation(entry, undefined, createKey()) : createLocation(entry, undefined, entry.key || createKey());
  706. }); // Public interface
  707. var createHref = createPath;
  708. function push(path, state) {
  709. warning(!(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');
  710. var action = 'PUSH';
  711. var location = createLocation(path, state, createKey(), history.location);
  712. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  713. if (!ok) return;
  714. var prevIndex = history.index;
  715. var nextIndex = prevIndex + 1;
  716. var nextEntries = history.entries.slice(0);
  717. if (nextEntries.length > nextIndex) {
  718. nextEntries.splice(nextIndex, nextEntries.length - nextIndex, location);
  719. } else {
  720. nextEntries.push(location);
  721. }
  722. setState({
  723. action: action,
  724. location: location,
  725. index: nextIndex,
  726. entries: nextEntries
  727. });
  728. });
  729. }
  730. function replace(path, state) {
  731. warning(!(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');
  732. var action = 'REPLACE';
  733. var location = createLocation(path, state, createKey(), history.location);
  734. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  735. if (!ok) return;
  736. history.entries[history.index] = location;
  737. setState({
  738. action: action,
  739. location: location
  740. });
  741. });
  742. }
  743. function go(n) {
  744. var nextIndex = clamp(history.index + n, 0, history.entries.length - 1);
  745. var action = 'POP';
  746. var location = history.entries[nextIndex];
  747. transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
  748. if (ok) {
  749. setState({
  750. action: action,
  751. location: location,
  752. index: nextIndex
  753. });
  754. } else {
  755. // Mimic the behavior of DOM histories by
  756. // causing a render after a cancelled POP.
  757. setState();
  758. }
  759. });
  760. }
  761. function goBack() {
  762. go(-1);
  763. }
  764. function goForward() {
  765. go(1);
  766. }
  767. function canGo(n) {
  768. var nextIndex = history.index + n;
  769. return nextIndex >= 0 && nextIndex < history.entries.length;
  770. }
  771. function block(prompt) {
  772. if (prompt === void 0) {
  773. prompt = false;
  774. }
  775. return transitionManager.setPrompt(prompt);
  776. }
  777. function listen(listener) {
  778. return transitionManager.appendListener(listener);
  779. }
  780. var history = {
  781. length: entries.length,
  782. action: 'POP',
  783. location: entries[index],
  784. index: index,
  785. entries: entries,
  786. createHref: createHref,
  787. push: push,
  788. replace: replace,
  789. go: go,
  790. goBack: goBack,
  791. goForward: goForward,
  792. canGo: canGo,
  793. block: block,
  794. listen: listen
  795. };
  796. return history;
  797. }
  798. exports.createBrowserHistory = createBrowserHistory;
  799. exports.createHashHistory = createHashHistory;
  800. exports.createMemoryHistory = createMemoryHistory;
  801. exports.createLocation = createLocation;
  802. exports.locationsAreEqual = locationsAreEqual;
  803. exports.parsePath = parsePath;
  804. exports.createPath = createPath;