history.js 28 KB

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