CoolFace
Apppublic

AK-21/Graphite-Industrial-Intelligence

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
index.js375 linesDownload Raw Back to src
1import { parse as parsePattern } from "regexparam";2 3import {4  useBrowserLocation,5  useSearch as useBrowserSearch,6} from "./use-browser-location.js";7 8import {9  useRef,10  useContext,11  createContext,12  isValidElement,13  cloneElement,14  createElement as h,15  Fragment,16  forwardRef,17  useIsomorphicLayoutEffect,18  useEvent,19  useMemo,20} from "./react-deps.js";21import { absolutePath, relativePath, sanitizeSearch } from "./paths.js";22 23/*24 * Router and router context. Router is a lightweight object that represents the current25 * routing options: how location is managed, base path etc.26 *27 * There is a default router present for most of the use cases, however it can be overridden28 * via the <Router /> component.29 */30 31const defaultRouter = {32  hook: useBrowserLocation,33  searchHook: useBrowserSearch,34  parser: parsePattern,35  base: "",36  // this option is used to override the current location during SSR37  ssrPath: undefined,38  ssrSearch: undefined,39  // optional context to track render state during SSR40  ssrContext: undefined,41  // customizes how `href` props are transformed for <Link />42  hrefs: (x) => x,43  // wraps navigate calls, useful for view transitions44  aroundNav: (n, t, o) => n(t, o),45};46 47const RouterCtx = createContext(defaultRouter);48 49// gets the closest parent router from the context50export const useRouter = () => useContext(RouterCtx);51 52/**53 * Parameters context. Used by `useParams()` to get the54 * matched params from the innermost `Route` component.55 */56 57const Params0 = {},58  ParamsCtx = createContext(Params0);59 60export const useParams = () => useContext(ParamsCtx);61 62/*63 * Part 1, Hooks API: useRoute and useLocation64 */65 66// Internal version of useLocation to avoid redundant useRouter calls67 68const useLocationFromRouter = (router) => {69  const [location, navigate] = router.hook(router);70 71  // the function reference should stay the same between re-renders, so that72  // it can be passed down as an element prop without any performance concerns.73  // (This is achieved via `useEvent`.)74  return [75    relativePath(router.base, location),76    useEvent((to, opts) =>77      router.aroundNav(navigate, absolutePath(to, router.base), opts)78    ),79  ];80};81 82export const useLocation = () => useLocationFromRouter(useRouter());83 84export const useSearch = () => {85  const router = useRouter();86  return sanitizeSearch(router.searchHook(router));87};88 89export const matchRoute = (parser, route, path, loose) => {90  // if the input is a regexp, skip parsing91  const { pattern, keys } =92    route instanceof RegExp93      ? { keys: false, pattern: route }94      : parser(route || "*", loose);95 96  // array destructuring loses keys, so this is done in two steps97  const result = pattern.exec(path) || [];98 99  // when parser is in "loose" mode, `$base` is equal to the100  // first part of the route that matches the pattern101  // (e.g. for pattern `/a/:b` and path `/a/1/2/3` the `$base` is `a/1`)102  // we use this for route nesting103  const [$base, ...matches] = result;104 105  return $base !== undefined106    ? [107        true,108 109        (() => {110          // for regex paths, `keys` will always be false111 112          // an object with parameters matched, e.g. { foo: "bar" } for "/:foo"113          // we "zip" two arrays here to construct the object114          // ["foo"], ["bar"] โ†’ { foo: "bar" }115          const groups =116            keys !== false117              ? Object.fromEntries(keys.map((key, i) => [key, matches[i]]))118              : result.groups;119 120          // convert the array to an instance of object121          // this makes it easier to integrate with the existing param implementation122          let obj = { ...matches };123 124          // merge named capture groups with matches array125          groups && Object.assign(obj, groups);126 127          return obj;128        })(),129 130        // the third value if only present when parser is in "loose" mode,131        // so that we can extract the base path for nested routes132        ...(loose ? [$base] : []),133      ]134    : [false, null];135};136 137export const useRoute = (pattern) =>138  matchRoute(useRouter().parser, pattern, useLocation()[0]);139 140/*141 * Part 2, Low Carb Router API: Router, Route, Link, Switch142 */143 144export const Router = ({ children, ...props }) => {145  // the router we will inherit from - it is the closest router in the tree,146  // unless the custom `hook` is provided (in that case it's the default one)147  const parent_ = useRouter();148  const parent = props.hook ? defaultRouter : parent_;149 150  // holds to the context value: the router object151  let value = parent;152 153  // when `ssrPath` contains a `?` character, we can extract the search from it.154  // also, ensure ssrSearch is always defined when ssrPath is provided, so that155  // useSearch behavior matches usePathname (proper SSR hydration when client156  // renders <Router> without props after server rendered with ssrPath/ssrSearch)157  const [path, search = props.ssrSearch ?? ""] =158    props.ssrPath?.split("?") ?? [];159  if (path) (props.ssrSearch = search), (props.ssrPath = path);160 161  // hooks can define their own `href` formatter (e.g. for hash location)162  props.hrefs = props.hrefs ?? props.hook?.hrefs;163 164  // hooks can define their own search hook (e.g. for memory location)165  props.searchHook = props.searchHook ?? props.hook?.searchHook;166 167  // what is happening below: to avoid unnecessary rerenders in child components,168  // we ensure that the router object reference is stable, unless there are any169  // changes that require reload (e.g. `base` prop changes -> all components that170  // get the router from the context should rerender, even if the component is memoized).171  // the expected behaviour is:172  //173  //   1) when the resulted router is no different from the parent, use parent174  //   2) if the custom `hook` prop is provided, we always inherit from the175  //      default router instead. this resets all previously overridden options.176  //   3) when the router is customized here, it should stay stable between renders177  let ref = useRef({}),178    prev = ref.current,179    next = prev;180 181  for (let k in parent) {182    const option =183      k === "base"184        ? /* base is special case, it is appended to the parent's base */185          parent[k] + (props[k] ?? "")186        : props[k] ?? parent[k];187 188    if (prev === next && option !== next[k]) {189      ref.current = next = { ...next };190    }191 192    next[k] = option;193 194    // the new router is no different from the parent or from the memoized value, use parent195    if (option !== parent[k] || option !== value[k]) value = next;196  }197 198  return h(RouterCtx.Provider, { value, children });199};200 201const h_route = ({ children, component }, params) => {202  // React-Router style `component` prop203  if (component) return h(component, { params });204 205  // support render prop or plain children206  return typeof children === "function" ? children(params) : children;207};208 209// Cache params object between renders if values are shallow equal210const useCachedParams = (value) => {211  let prev = useRef(Params0);212  const curr = prev.current;213  return (prev.current =214    // Update cache if number of params changed or any value changed215    Object.keys(value).length !== Object.keys(curr).length ||216    Object.entries(value).some(([k, v]) => v !== curr[k])217      ? value // Return new value if there are changes218      : curr); // Return cached value if nothing changed219};220 221export function useSearchParams() {222  const [location, navigate] = useLocation();223 224  const search = useSearch();225  const searchParams = useMemo(() => new URLSearchParams(search), [search]);226 227  // cached value before next render, so you can call setSearchParams multiple times228  let tempSearchParams = searchParams;229 230  const setSearchParams = useEvent((nextInit, options) => {231    tempSearchParams = new URLSearchParams(232      typeof nextInit === "function" ? nextInit(tempSearchParams) : nextInit233    );234    navigate(235      location + (tempSearchParams.size ? "?" + tempSearchParams : ""),236      options237    );238  });239 240  return [searchParams, setSearchParams];241}242 243export const Route = ({ path, nest, match, ...renderProps }) => {244  const router = useRouter();245  const [location] = useLocationFromRouter(router);246 247  const [matches, routeParams, base] =248    // `match` is a special prop to give up control to the parent,249    // it is used by the `Switch` to avoid double matching250    match ?? matchRoute(router.parser, path, location, nest);251 252  // when `routeParams` is `null` (there was no match), the argument253  // below becomes {...null} = {}, see the Object Spread specs254  // https://tc39.es/proposal-object-rest-spread/#AbstractOperations-CopyDataProperties255  const params = useCachedParams({ ...useParams(), ...routeParams });256 257  if (!matches) return null;258 259  const children = base260    ? h(Router, { base }, h_route(renderProps, params))261    : h_route(renderProps, params);262 263  return h(ParamsCtx.Provider, { value: params, children });264};265 266export const Link = forwardRef((props, ref) => {267  const router = useRouter();268  const [currentPath, navigate] = useLocationFromRouter(router);269 270  const {271    to = "",272    href: targetPath = to,273    onClick: _onClick,274    asChild,275    children,276    className: cls,277    /* eslint-disable no-unused-vars */278    replace /* ignore nav props */,279    state /* ignore nav props */,280    transition /* ignore nav props */,281    /* eslint-enable no-unused-vars */282 283    ...restProps284  } = props;285 286  const onClick = useEvent((event) => {287    // ignores the navigation when clicked using right mouse button or288    // by holding a special modifier key: ctrl, command, win, alt, shift289    if (290      event.ctrlKey ||291      event.metaKey ||292      event.altKey ||293      event.shiftKey ||294      event.button !== 0295    )296      return;297 298    _onClick?.(event);299    if (!event.defaultPrevented) {300      event.preventDefault();301      navigate(targetPath, props);302    }303  });304 305  // handle nested routers and absolute paths306  const href = router.hrefs(307    targetPath[0] === "~" ? targetPath.slice(1) : router.base + targetPath,308    router // pass router as a second argument for convinience309  );310 311  return asChild && isValidElement(children)312    ? cloneElement(children, { onClick, href })313    : h("a", {314        ...restProps,315        onClick,316        href,317        // `className` can be a function to apply the class if this link is active318        className: cls?.call ? cls(currentPath === targetPath) : cls,319        children,320        ref,321      });322});323 324const flattenChildren = (children) =>325  Array.isArray(children)326    ? children.flatMap((c) =>327        flattenChildren(c && c.type === Fragment ? c.props.children : c)328      )329    : [children];330 331export const Switch = ({ children, location }) => {332  const router = useRouter();333  const [originalLocation] = useLocationFromRouter(router);334 335  for (const element of flattenChildren(children)) {336    let match = 0;337 338    if (339      isValidElement(element) &&340      // we don't require an element to be of type Route,341      // but we do require it to contain a truthy `path` prop.342      // this allows to use different components that wrap Route343      // inside of a switch, for example <AnimatedRoute />.344      (match = matchRoute(345        router.parser,346        element.props.path,347        location || originalLocation,348        element.props.nest349      ))[0]350    )351      return cloneElement(element, { match });352  }353 354  return null;355};356 357export const Redirect = (props) => {358  const { to, href = to } = props;359  const router = useRouter();360  const [, navigate] = useLocationFromRouter(router);361  const redirect = useEvent(() => navigate(to || href, props));362  const { ssrContext } = router;363 364  // redirect is guaranteed to be stable since it is returned from useEvent365  useIsomorphicLayoutEffect(() => {366    redirect();367  }, []); // eslint-disable-line react-hooks/exhaustive-deps368 369  if (ssrContext) {370    ssrContext.redirectTo = to;371  }372 373  return null;374};375