history.js 33 KB

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