From dccc74d9cd1cd36f87e52875e37a18f03fc75c04 Mon Sep 17 00:00:00 2001 From: smikhalevski Date: Fri, 19 Jul 2024 22:47:02 +0300 Subject: [PATCH] Added createHashHistory --- src/main/history/Link.tsx | 44 ++++++++------ src/main/history/createHashHistory.ts | 82 +++++++++++++++++++++++++++ src/main/index.ts | 1 + 3 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 src/main/history/createHashHistory.ts diff --git a/src/main/history/Link.tsx b/src/main/history/Link.tsx index 07048f4..d5ac7f3 100644 --- a/src/main/history/Link.tsx +++ b/src/main/history/Link.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, HTMLAttributes, useContext, useEffect } from 'react'; +import React, { forwardRef, HTMLAttributes, useContext, useEffect, MouseEvent } from 'react'; import { LocationOptions, To } from '../types'; import { useNavigation } from '../useNavigation'; import { toLocation } from '../utils'; @@ -43,28 +43,36 @@ export const Link = forwardRef((props, ref) => { } }, []); + const handleClick = (event: MouseEvent) => { + if (typeof onClick === 'function') { + onClick(event); + } + + if ( + event.isDefaultPrevented() || + event.getModifierState('Alt') || + event.getModifierState('Control') || + event.getModifierState('Shift') || + event.getModifierState('Meta') + ) { + return; + } + + event.preventDefault(); + + if (replace) { + navigation.replace(to); + } else { + navigation.push(to); + } + }; + return ( { - if (typeof onClick === 'function') { - onClick(event); - } - - if (event.isDefaultPrevented()) { - return; - } - - event.preventDefault(); - - if (replace) { - navigation.replace(to); - } else { - navigation.push(to); - } - }} + onClick={handleClick} /> ); }); diff --git a/src/main/history/createHashHistory.ts b/src/main/history/createHashHistory.ts new file mode 100644 index 0000000..1efd6dc --- /dev/null +++ b/src/main/history/createHashHistory.ts @@ -0,0 +1,82 @@ +import { PubSub } from 'parallel-universe'; +import { toLocation } from '../utils'; +import { History, SearchParamsAdapter } from './types'; +import { Location } from '../types'; +import { urlSearchParamsAdapter } from './urlSearchParamsAdapter'; +import { parseURL, toURL } from './utils'; + +/** + * Options of {@link createHashHistory}. + */ +export interface HashHistoryOptions { + /** + * A default URL base used by {@link History.toURL}. + */ + base?: URL | string; + + /** + * An adapter that extracts params from a URL search string and stringifies them back. By default, an adapter that + * relies on {@link !URLSearchParams} is used. + */ + searchParamsAdapter?: SearchParamsAdapter; +} + +/** + * Create the history adapter that reads and writes location to a browser's session history. + * + * @param options History options. + */ +export function createHashHistory(options: HashHistoryOptions = {}): History { + const { base: defaultBase, searchParamsAdapter = urlSearchParamsAdapter } = options; + const pubSub = new PubSub(); + const handlePopstate = () => pubSub.publish(); + + let prevHref: string; + let location: Location; + + return { + get location() { + const href = decodeURIComponent(window.location.hash.substring(1)); + + return prevHref === href ? location : (location = parseURL((prevHref = href), searchParamsAdapter)); + }, + + toURL(location, base = defaultBase) { + const url = '#' + encodeURIComponent(toURL(location, searchParamsAdapter)); + + return base === undefined ? url : new URL(url, base).toString(); + }, + + push(to) { + location = toLocation(to); + history.pushState(location.state, '', '#' + encodeURIComponent(toURL(location, searchParamsAdapter))); + pubSub.publish(); + }, + + replace(to) { + location = toLocation(to); + history.replaceState(location.state, '', '#' + encodeURIComponent(toURL(location, searchParamsAdapter))); + pubSub.publish(); + }, + + back() { + history.back(); + }, + + subscribe(listener) { + if (pubSub.listenerCount === 0) { + window.addEventListener('popstate', handlePopstate); + } + + const unsubscribe = pubSub.subscribe(listener); + + return () => { + unsubscribe(); + + if (pubSub.listenerCount === 0) { + window.removeEventListener('popstate', handlePopstate); + } + }; + }, + }; +} diff --git a/src/main/index.ts b/src/main/index.ts index d541385..0912abf 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,5 @@ export { createBrowserHistory } from './history/createBrowserHistory'; +export { createHashHistory } from './history/createHashHistory'; export { createMemoryHistory } from './history/createMemoryHistory'; export { Link } from './history/Link'; export { useHistorySubscription } from './history/useHistorySubscription';