You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							684 lines
						
					
					
						
							19 KiB
						
					
					
				
			
		
		
	
	
							684 lines
						
					
					
						
							19 KiB
						
					
					
				////////////////////////////////////////////////////////////////////////////////
 | 
						|
//#region Types and Constants
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
/**
 | 
						|
 * Actions represent the type of change to a location value.
 | 
						|
 */
 | 
						|
export enum Action {
 | 
						|
  /**
 | 
						|
   * A POP indicates a change to an arbitrary index in the history stack, such
 | 
						|
   * as a back or forward navigation. It does not describe the direction of the
 | 
						|
   * navigation, only that the current index changed.
 | 
						|
   *
 | 
						|
   * Note: This is the default action for newly created history objects.
 | 
						|
   */
 | 
						|
  Pop = "POP",
 | 
						|
 | 
						|
  /**
 | 
						|
   * A PUSH indicates a new entry being added to the history stack, such as when
 | 
						|
   * a link is clicked and a new page loads. When this happens, all subsequent
 | 
						|
   * entries in the stack are lost.
 | 
						|
   */
 | 
						|
  Push = "PUSH",
 | 
						|
 | 
						|
  /**
 | 
						|
   * A REPLACE indicates the entry at the current index in the history stack
 | 
						|
   * being replaced by a new one.
 | 
						|
   */
 | 
						|
  Replace = "REPLACE",
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The pathname, search, and hash values of a URL.
 | 
						|
 */
 | 
						|
export interface Path {
 | 
						|
  /**
 | 
						|
   * A URL pathname, beginning with a /.
 | 
						|
   */
 | 
						|
  pathname: string;
 | 
						|
 | 
						|
  /**
 | 
						|
   * A URL search string, beginning with a ?.
 | 
						|
   */
 | 
						|
  search: string;
 | 
						|
 | 
						|
  /**
 | 
						|
   * A URL fragment identifier, beginning with a #.
 | 
						|
   */
 | 
						|
  hash: string;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * An entry in a history stack. A location contains information about the
 | 
						|
 * URL path, as well as possibly some arbitrary state and a key.
 | 
						|
 */
 | 
						|
export interface Location extends Path {
 | 
						|
  /**
 | 
						|
   * A value of arbitrary data associated with this location.
 | 
						|
   */
 | 
						|
  state: any;
 | 
						|
 | 
						|
  /**
 | 
						|
   * A unique string associated with this location. May be used to safely store
 | 
						|
   * and retrieve data in some other storage API, like `localStorage`.
 | 
						|
   *
 | 
						|
   * Note: This value is always "default" on the initial location.
 | 
						|
   */
 | 
						|
  key: string;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * A change to the current location.
 | 
						|
 */
 | 
						|
export interface Update {
 | 
						|
  /**
 | 
						|
   * The action that triggered the change.
 | 
						|
   */
 | 
						|
  action: Action;
 | 
						|
 | 
						|
  /**
 | 
						|
   * The new location.
 | 
						|
   */
 | 
						|
  location: Location;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * A function that receives notifications about location changes.
 | 
						|
 */
 | 
						|
export interface Listener {
 | 
						|
  (update: Update): void;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Describes a location that is the destination of some navigation, either via
 | 
						|
 * `history.push` or `history.replace`. May be either a URL or the pieces of a
 | 
						|
 * URL path.
 | 
						|
 */
 | 
						|
export type To = string | Partial<Path>;
 | 
						|
 | 
						|
/**
 | 
						|
 * A history is an interface to the navigation stack. The history serves as the
 | 
						|
 * source of truth for the current location, as well as provides a set of
 | 
						|
 * methods that may be used to change it.
 | 
						|
 *
 | 
						|
 * It is similar to the DOM's `window.history` object, but with a smaller, more
 | 
						|
 * focused API.
 | 
						|
 */
 | 
						|
export interface History {
 | 
						|
  /**
 | 
						|
   * The last action that modified the current location. This will always be
 | 
						|
   * Action.Pop when a history instance is first created. This value is mutable.
 | 
						|
   */
 | 
						|
  readonly action: Action;
 | 
						|
 | 
						|
  /**
 | 
						|
   * The current location. This value is mutable.
 | 
						|
   */
 | 
						|
  readonly location: Location;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns a valid href for the given `to` value that may be used as
 | 
						|
   * the value of an <a href> attribute.
 | 
						|
   *
 | 
						|
   * @param to - The destination URL
 | 
						|
   */
 | 
						|
  createHref(to: To): string;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Encode a location the same way window.history would do (no-op for memory
 | 
						|
   * history) so we ensure our PUSH/REPLACE navigations for data routers
 | 
						|
   * behave the same as POP
 | 
						|
   *
 | 
						|
   * @param to Unencoded path
 | 
						|
   */
 | 
						|
  encodeLocation(to: To): Path;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Pushes a new location onto the history stack, increasing its length by one.
 | 
						|
   * If there were any entries in the stack after the current one, they are
 | 
						|
   * lost.
 | 
						|
   *
 | 
						|
   * @param to - The new URL
 | 
						|
   * @param state - Data to associate with the new location
 | 
						|
   */
 | 
						|
  push(to: To, state?: any): void;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Replaces the current location in the history stack with a new one.  The
 | 
						|
   * location that was replaced will no longer be available.
 | 
						|
   *
 | 
						|
   * @param to - The new URL
 | 
						|
   * @param state - Data to associate with the new location
 | 
						|
   */
 | 
						|
  replace(to: To, state?: any): void;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Navigates `n` entries backward/forward in the history stack relative to the
 | 
						|
   * current index. For example, a "back" navigation would use go(-1).
 | 
						|
   *
 | 
						|
   * @param delta - The delta in the stack index
 | 
						|
   */
 | 
						|
  go(delta: number): void;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets up a listener that will be called whenever the current location
 | 
						|
   * changes.
 | 
						|
   *
 | 
						|
   * @param listener - A function that will be called when the location changes
 | 
						|
   * @returns unlisten - A function that may be used to stop listening
 | 
						|
   */
 | 
						|
  listen(listener: Listener): () => void;
 | 
						|
}
 | 
						|
 | 
						|
type HistoryState = {
 | 
						|
  usr: any;
 | 
						|
  key?: string;
 | 
						|
};
 | 
						|
 | 
						|
const PopStateEventType = "popstate";
 | 
						|
//#endregion
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
//#region Memory History
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
/**
 | 
						|
 * A user-supplied object that describes a location. Used when providing
 | 
						|
 * entries to `createMemoryHistory` via its `initialEntries` option.
 | 
						|
 */
 | 
						|
export type InitialEntry = string | Partial<Location>;
 | 
						|
 | 
						|
export type MemoryHistoryOptions = {
 | 
						|
  initialEntries?: InitialEntry[];
 | 
						|
  initialIndex?: number;
 | 
						|
  v5Compat?: boolean;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * A memory history stores locations in memory. This is useful in stateful
 | 
						|
 * environments where there is no web browser, such as node tests or React
 | 
						|
 * Native.
 | 
						|
 */
 | 
						|
export interface MemoryHistory extends History {
 | 
						|
  /**
 | 
						|
   * The current index in the history stack.
 | 
						|
   */
 | 
						|
  readonly index: number;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Memory history stores the current location in memory. It is designed for use
 | 
						|
 * in stateful non-browser environments like tests and React Native.
 | 
						|
 */
 | 
						|
export function createMemoryHistory(
 | 
						|
  options: MemoryHistoryOptions = {}
 | 
						|
): MemoryHistory {
 | 
						|
  let { initialEntries = ["/"], initialIndex, v5Compat = false } = options;
 | 
						|
  let entries: Location[]; // Declare so we can access from createMemoryLocation
 | 
						|
  entries = initialEntries.map((entry, index) =>
 | 
						|
    createMemoryLocation(
 | 
						|
      entry,
 | 
						|
      typeof entry === "string" ? null : entry.state,
 | 
						|
      index === 0 ? "default" : undefined
 | 
						|
    )
 | 
						|
  );
 | 
						|
  let index = clampIndex(
 | 
						|
    initialIndex == null ? entries.length - 1 : initialIndex
 | 
						|
  );
 | 
						|
  let action = Action.Pop;
 | 
						|
  let listener: Listener | null = null;
 | 
						|
 | 
						|
  function clampIndex(n: number): number {
 | 
						|
    return Math.min(Math.max(n, 0), entries.length - 1);
 | 
						|
  }
 | 
						|
  function getCurrentLocation(): Location {
 | 
						|
    return entries[index];
 | 
						|
  }
 | 
						|
  function createMemoryLocation(
 | 
						|
    to: To,
 | 
						|
    state: any = null,
 | 
						|
    key?: string
 | 
						|
  ): Location {
 | 
						|
    let location = createLocation(
 | 
						|
      entries ? getCurrentLocation().pathname : "/",
 | 
						|
      to,
 | 
						|
      state,
 | 
						|
      key
 | 
						|
    );
 | 
						|
    warning(
 | 
						|
      location.pathname.charAt(0) === "/",
 | 
						|
      `relative pathnames are not supported in memory history: ${JSON.stringify(
 | 
						|
        to
 | 
						|
      )}`
 | 
						|
    );
 | 
						|
    return location;
 | 
						|
  }
 | 
						|
 | 
						|
  let history: MemoryHistory = {
 | 
						|
    get index() {
 | 
						|
      return index;
 | 
						|
    },
 | 
						|
    get action() {
 | 
						|
      return action;
 | 
						|
    },
 | 
						|
    get location() {
 | 
						|
      return getCurrentLocation();
 | 
						|
    },
 | 
						|
    createHref(to) {
 | 
						|
      return typeof to === "string" ? to : createPath(to);
 | 
						|
    },
 | 
						|
    encodeLocation(to: To) {
 | 
						|
      let path = typeof to === "string" ? parsePath(to) : to;
 | 
						|
      return {
 | 
						|
        pathname: path.pathname || "",
 | 
						|
        search: path.search || "",
 | 
						|
        hash: path.hash || "",
 | 
						|
      };
 | 
						|
    },
 | 
						|
    push(to, state) {
 | 
						|
      action = Action.Push;
 | 
						|
      let nextLocation = createMemoryLocation(to, state);
 | 
						|
      index += 1;
 | 
						|
      entries.splice(index, entries.length, nextLocation);
 | 
						|
      if (v5Compat && listener) {
 | 
						|
        listener({ action, location: nextLocation });
 | 
						|
      }
 | 
						|
    },
 | 
						|
    replace(to, state) {
 | 
						|
      action = Action.Replace;
 | 
						|
      let nextLocation = createMemoryLocation(to, state);
 | 
						|
      entries[index] = nextLocation;
 | 
						|
      if (v5Compat && listener) {
 | 
						|
        listener({ action, location: nextLocation });
 | 
						|
      }
 | 
						|
    },
 | 
						|
    go(delta) {
 | 
						|
      action = Action.Pop;
 | 
						|
      index = clampIndex(index + delta);
 | 
						|
      if (listener) {
 | 
						|
        listener({ action, location: getCurrentLocation() });
 | 
						|
      }
 | 
						|
    },
 | 
						|
    listen(fn: Listener) {
 | 
						|
      listener = fn;
 | 
						|
      return () => {
 | 
						|
        listener = null;
 | 
						|
      };
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  return history;
 | 
						|
}
 | 
						|
//#endregion
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
//#region Browser History
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
/**
 | 
						|
 * A browser history stores the current location in regular URLs in a web
 | 
						|
 * browser environment. This is the standard for most web apps and provides the
 | 
						|
 * cleanest URLs the browser's address bar.
 | 
						|
 *
 | 
						|
 * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
 | 
						|
 */
 | 
						|
export interface BrowserHistory extends UrlHistory {}
 | 
						|
 | 
						|
export type BrowserHistoryOptions = UrlHistoryOptions;
 | 
						|
 | 
						|
/**
 | 
						|
 * Browser history stores the location in regular URLs. This is the standard for
 | 
						|
 * most web apps, but it requires some configuration on the server to ensure you
 | 
						|
 * serve the same app at multiple URLs.
 | 
						|
 *
 | 
						|
 * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
 | 
						|
 */
 | 
						|
export function createBrowserHistory(
 | 
						|
  options: BrowserHistoryOptions = {}
 | 
						|
): BrowserHistory {
 | 
						|
  function createBrowserLocation(
 | 
						|
    window: Window,
 | 
						|
    globalHistory: Window["history"]
 | 
						|
  ) {
 | 
						|
    let { pathname, search, hash } = window.location;
 | 
						|
    return createLocation(
 | 
						|
      "",
 | 
						|
      { pathname, search, hash },
 | 
						|
      // state defaults to `null` because `window.history.state` does
 | 
						|
      (globalHistory.state && globalHistory.state.usr) || null,
 | 
						|
      (globalHistory.state && globalHistory.state.key) || "default"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  function createBrowserHref(window: Window, to: To) {
 | 
						|
    return typeof to === "string" ? to : createPath(to);
 | 
						|
  }
 | 
						|
 | 
						|
  return getUrlBasedHistory(
 | 
						|
    createBrowserLocation,
 | 
						|
    createBrowserHref,
 | 
						|
    null,
 | 
						|
    options
 | 
						|
  );
 | 
						|
}
 | 
						|
//#endregion
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
//#region Hash History
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
/**
 | 
						|
 * A hash history stores the current location in the fragment identifier portion
 | 
						|
 * of the URL in a web browser environment.
 | 
						|
 *
 | 
						|
 * This is ideal for apps that do not control the server for some reason
 | 
						|
 * (because the fragment identifier is never sent to the server), including some
 | 
						|
 * shared hosting environments that do not provide fine-grained controls over
 | 
						|
 * which pages are served at which URLs.
 | 
						|
 *
 | 
						|
 * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
 | 
						|
 */
 | 
						|
export interface HashHistory extends UrlHistory {}
 | 
						|
 | 
						|
export type HashHistoryOptions = UrlHistoryOptions;
 | 
						|
 | 
						|
/**
 | 
						|
 * Hash history stores the location in window.location.hash. This makes it ideal
 | 
						|
 * for situations where you don't want to send the location to the server for
 | 
						|
 * some reason, either because you do cannot configure it or the URL space is
 | 
						|
 * reserved for something else.
 | 
						|
 *
 | 
						|
 * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
 | 
						|
 */
 | 
						|
export function createHashHistory(
 | 
						|
  options: HashHistoryOptions = {}
 | 
						|
): HashHistory {
 | 
						|
  function createHashLocation(
 | 
						|
    window: Window,
 | 
						|
    globalHistory: Window["history"]
 | 
						|
  ) {
 | 
						|
    let {
 | 
						|
      pathname = "/",
 | 
						|
      search = "",
 | 
						|
      hash = "",
 | 
						|
    } = parsePath(window.location.hash.substr(1));
 | 
						|
    return createLocation(
 | 
						|
      "",
 | 
						|
      { pathname, search, hash },
 | 
						|
      // state defaults to `null` because `window.history.state` does
 | 
						|
      (globalHistory.state && globalHistory.state.usr) || null,
 | 
						|
      (globalHistory.state && globalHistory.state.key) || "default"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  function createHashHref(window: Window, to: To) {
 | 
						|
    let base = window.document.querySelector("base");
 | 
						|
    let href = "";
 | 
						|
 | 
						|
    if (base && base.getAttribute("href")) {
 | 
						|
      let url = window.location.href;
 | 
						|
      let hashIndex = url.indexOf("#");
 | 
						|
      href = hashIndex === -1 ? url : url.slice(0, hashIndex);
 | 
						|
    }
 | 
						|
 | 
						|
    return href + "#" + (typeof to === "string" ? to : createPath(to));
 | 
						|
  }
 | 
						|
 | 
						|
  function validateHashLocation(location: Location, to: To) {
 | 
						|
    warning(
 | 
						|
      location.pathname.charAt(0) === "/",
 | 
						|
      `relative pathnames are not supported in hash history.push(${JSON.stringify(
 | 
						|
        to
 | 
						|
      )})`
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  return getUrlBasedHistory(
 | 
						|
    createHashLocation,
 | 
						|
    createHashHref,
 | 
						|
    validateHashLocation,
 | 
						|
    options
 | 
						|
  );
 | 
						|
}
 | 
						|
//#endregion
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
//#region UTILS
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
export function invariant(value: boolean, message?: string): asserts value;
 | 
						|
export function invariant<T>(
 | 
						|
  value: T | null | undefined,
 | 
						|
  message?: string
 | 
						|
): asserts value is T;
 | 
						|
export function invariant(value: any, message?: string) {
 | 
						|
  if (value === false || value === null || typeof value === "undefined") {
 | 
						|
    throw new Error(message);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function warning(cond: any, message: string) {
 | 
						|
  if (!cond) {
 | 
						|
    // eslint-disable-next-line no-console
 | 
						|
    if (typeof console !== "undefined") console.warn(message);
 | 
						|
 | 
						|
    try {
 | 
						|
      // Welcome to debugging history!
 | 
						|
      //
 | 
						|
      // This error is thrown as a convenience so you can more easily
 | 
						|
      // find the source for a warning that appears in the console by
 | 
						|
      // enabling "pause on exceptions" in your JavaScript debugger.
 | 
						|
      throw new Error(message);
 | 
						|
      // eslint-disable-next-line no-empty
 | 
						|
    } catch (e) {}
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function createKey() {
 | 
						|
  return Math.random().toString(36).substr(2, 8);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * For browser-based histories, we combine the state and key into an object
 | 
						|
 */
 | 
						|
function getHistoryState(location: Location): HistoryState {
 | 
						|
  return {
 | 
						|
    usr: location.state,
 | 
						|
    key: location.key,
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a Location object with a unique key from the given Path
 | 
						|
 */
 | 
						|
export function createLocation(
 | 
						|
  current: string | Location,
 | 
						|
  to: To,
 | 
						|
  state: any = null,
 | 
						|
  key?: string
 | 
						|
): Readonly<Location> {
 | 
						|
  let location: Readonly<Location> = {
 | 
						|
    pathname: typeof current === "string" ? current : current.pathname,
 | 
						|
    search: "",
 | 
						|
    hash: "",
 | 
						|
    ...(typeof to === "string" ? parsePath(to) : to),
 | 
						|
    state,
 | 
						|
    // TODO: This could be cleaned up.  push/replace should probably just take
 | 
						|
    // full Locations now and avoid the need to run through this flow at all
 | 
						|
    // But that's a pretty big refactor to the current test suite so going to
 | 
						|
    // keep as is for the time being and just let any incoming keys take precedence
 | 
						|
    key: (to && (to as Location).key) || key || createKey(),
 | 
						|
  };
 | 
						|
  return location;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a string URL path from the given pathname, search, and hash components.
 | 
						|
 */
 | 
						|
export function createPath({
 | 
						|
  pathname = "/",
 | 
						|
  search = "",
 | 
						|
  hash = "",
 | 
						|
}: Partial<Path>) {
 | 
						|
  if (search && search !== "?")
 | 
						|
    pathname += search.charAt(0) === "?" ? search : "?" + search;
 | 
						|
  if (hash && hash !== "#")
 | 
						|
    pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
 | 
						|
  return pathname;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Parses a string URL path into its separate pathname, search, and hash components.
 | 
						|
 */
 | 
						|
export function parsePath(path: string): Partial<Path> {
 | 
						|
  let parsedPath: Partial<Path> = {};
 | 
						|
 | 
						|
  if (path) {
 | 
						|
    let hashIndex = path.indexOf("#");
 | 
						|
    if (hashIndex >= 0) {
 | 
						|
      parsedPath.hash = path.substr(hashIndex);
 | 
						|
      path = path.substr(0, hashIndex);
 | 
						|
    }
 | 
						|
 | 
						|
    let searchIndex = path.indexOf("?");
 | 
						|
    if (searchIndex >= 0) {
 | 
						|
      parsedPath.search = path.substr(searchIndex);
 | 
						|
      path = path.substr(0, searchIndex);
 | 
						|
    }
 | 
						|
 | 
						|
    if (path) {
 | 
						|
      parsedPath.pathname = path;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return parsedPath;
 | 
						|
}
 | 
						|
 | 
						|
export function createClientSideURL(location: Location | string): URL {
 | 
						|
  // window.location.origin is "null" (the literal string value) in Firefox
 | 
						|
  // under certain conditions, notably when serving from a local HTML file
 | 
						|
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
 | 
						|
  let base =
 | 
						|
    typeof window !== "undefined" &&
 | 
						|
    typeof window.location !== "undefined" &&
 | 
						|
    window.location.origin !== "null"
 | 
						|
      ? window.location.origin
 | 
						|
      : window.location.href;
 | 
						|
  let href = typeof location === "string" ? location : createPath(location);
 | 
						|
  invariant(
 | 
						|
    base,
 | 
						|
    `No window.location.(origin|href) available to create URL for href: ${href}`
 | 
						|
  );
 | 
						|
  return new URL(href, base);
 | 
						|
}
 | 
						|
 | 
						|
export interface UrlHistory extends History {}
 | 
						|
 | 
						|
export type UrlHistoryOptions = {
 | 
						|
  window?: Window;
 | 
						|
  v5Compat?: boolean;
 | 
						|
};
 | 
						|
 | 
						|
function getUrlBasedHistory(
 | 
						|
  getLocation: (window: Window, globalHistory: Window["history"]) => Location,
 | 
						|
  createHref: (window: Window, to: To) => string,
 | 
						|
  validateLocation: ((location: Location, to: To) => void) | null,
 | 
						|
  options: UrlHistoryOptions = {}
 | 
						|
): UrlHistory {
 | 
						|
  let { window = document.defaultView!, v5Compat = false } = options;
 | 
						|
  let globalHistory = window.history;
 | 
						|
  let action = Action.Pop;
 | 
						|
  let listener: Listener | null = null;
 | 
						|
 | 
						|
  function handlePop() {
 | 
						|
    action = Action.Pop;
 | 
						|
    if (listener) {
 | 
						|
      listener({ action, location: history.location });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function push(to: To, state?: any) {
 | 
						|
    action = Action.Push;
 | 
						|
    let location = createLocation(history.location, to, state);
 | 
						|
    if (validateLocation) validateLocation(location, to);
 | 
						|
 | 
						|
    let historyState = getHistoryState(location);
 | 
						|
    let url = history.createHref(location);
 | 
						|
 | 
						|
    // try...catch because iOS limits us to 100 pushState calls :/
 | 
						|
    try {
 | 
						|
      globalHistory.pushState(historyState, "", url);
 | 
						|
    } catch (error) {
 | 
						|
      // They are going to lose state here, but there is no real
 | 
						|
      // way to warn them about it since the page will refresh...
 | 
						|
      window.location.assign(url);
 | 
						|
    }
 | 
						|
 | 
						|
    if (v5Compat && listener) {
 | 
						|
      listener({ action, location: history.location });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function replace(to: To, state?: any) {
 | 
						|
    action = Action.Replace;
 | 
						|
    let location = createLocation(history.location, to, state);
 | 
						|
    if (validateLocation) validateLocation(location, to);
 | 
						|
 | 
						|
    let historyState = getHistoryState(location);
 | 
						|
    let url = history.createHref(location);
 | 
						|
    globalHistory.replaceState(historyState, "", url);
 | 
						|
 | 
						|
    if (v5Compat && listener) {
 | 
						|
      listener({ action, location: history.location });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  let history: History = {
 | 
						|
    get action() {
 | 
						|
      return action;
 | 
						|
    },
 | 
						|
    get location() {
 | 
						|
      return getLocation(window, globalHistory);
 | 
						|
    },
 | 
						|
    listen(fn: Listener) {
 | 
						|
      if (listener) {
 | 
						|
        throw new Error("A history only accepts one active listener");
 | 
						|
      }
 | 
						|
      window.addEventListener(PopStateEventType, handlePop);
 | 
						|
      listener = fn;
 | 
						|
 | 
						|
      return () => {
 | 
						|
        window.removeEventListener(PopStateEventType, handlePop);
 | 
						|
        listener = null;
 | 
						|
      };
 | 
						|
    },
 | 
						|
    createHref(to) {
 | 
						|
      return createHref(window, to);
 | 
						|
    },
 | 
						|
    encodeLocation(to) {
 | 
						|
      // Encode a Location the same way window.location would
 | 
						|
      let url = createClientSideURL(
 | 
						|
        typeof to === "string" ? to : createPath(to)
 | 
						|
      );
 | 
						|
      return {
 | 
						|
        pathname: url.pathname,
 | 
						|
        search: url.search,
 | 
						|
        hash: url.hash,
 | 
						|
      };
 | 
						|
    },
 | 
						|
    push,
 | 
						|
    replace,
 | 
						|
    go(n) {
 | 
						|
      return globalHistory.go(n);
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  return history;
 | 
						|
}
 | 
						|
 | 
						|
//#endregion
 |