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
			| 
								 
											3 years ago
										 
									 | 
							
								////////////////////////////////////////////////////////////////////////////////
							 | 
						||
| 
								 | 
							
								//#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
							 |