alinea
Version:
Headless git-based CMS
162 lines (160 loc) • 4.34 kB
JavaScript
import {
useAtomValue,
useSetAtom
} from "../../chunks/chunk-TOJF2G3X.js";
import {
atom
} from "../../chunks/chunk-WJ67RR7S.js";
import "../../chunks/chunk-NZLE2WMY.js";
// src/dashboard/atoms/RouterAtoms.tsx
import {
createContext,
useContext,
useEffect,
useState
} from "react";
import { locationAtom, matchAtoms } from "./LocationAtoms.js";
import { Fragment, jsx } from "react/jsx-runtime";
var Route = class {
constructor(data) {
this.data = data;
}
get component() {
return this.data.component;
}
};
var Router = class {
constructor(data) {
this.data = data;
this.matchers = this.data.routes.map((route) => ({
route,
matcher: matchAtoms({ route: route.data.path })
}));
}
matchers;
matchingRoute = atom((get) => {
for (const { route, matcher } of this.matchers) {
const params = get(matcher);
if (params) return { route, params };
}
});
matchingRouteWithData = atom(async (get) => {
const match = get(this.matchingRoute);
if (!match) return void 0;
const { loader } = match.route.data;
const data = loader ? await get(loader(match.params)) : {};
return { route: match.route, data, params: match.params };
});
currentRoute = atom(void 0);
prevLocation = atom(void 0);
blockers = atom(/* @__PURE__ */ new Set());
expected;
cancelled = atom(false);
match = atom(
(get, { setSelf }) => {
const current = get(this.currentRoute);
const next = get(this.matchingRouteWithData);
this.expected = next.then((value) => {
setSelf(this.expected, value);
return value;
});
return current ?? this.expected;
},
(get, set, forPromise, value) => {
const cancelled = get(this.cancelled);
if (cancelled) {
set(this.cancelled, false);
return;
}
const blockers = get(this.blockers);
if (forPromise !== this.expected) return;
const currentLocation = get(locationAtom);
const prevLocation = get(this.prevLocation);
const confirm = () => {
for (const block of blockers) set(block, void 0);
set(this.currentRoute, value);
set(this.prevLocation, currentLocation);
};
const cancel = () => {
set(this.cancelled, true);
for (const block of blockers) set(block, void 0);
if (prevLocation) {
const returnTo = prevLocation.pathname + prevLocation.search;
set(locationAtom, returnTo);
}
};
if (blockers.size) {
for (const block of blockers)
set(block, {
nextRoute: value,
confirm,
cancel
});
} else {
confirm();
}
}
);
};
var RouterContext = createContext(void 0);
function RouterProvider({
children,
router
}) {
return /* @__PURE__ */ jsx(RouterContext.Provider, { value: router, children });
}
function useRouter() {
const router = useContext(RouterContext);
if (!router) throw new Error("No router context found");
return router;
}
function useRouteBlocker(message, when) {
const router = useRouter();
const [blockingAtom] = useState(() => atom(void 0));
const setBlockers = useSetAtom(router.blockers);
useEffect(() => {
if (!when) return;
setBlockers((blockers) => new Set(blockers).add(blockingAtom));
window.onbeforeunload = () => true;
return () => {
window.onbeforeunload = null;
setBlockers((blockers) => {
const res = new Set(blockers);
res.delete(blockingAtom);
return res;
});
};
}, [message, when]);
const block = useAtomValue(blockingAtom);
return {
isBlocking: Boolean(block),
...block
};
}
function useRouteMatch() {
const router = useRouter();
return useAtomValue(router.match);
}
function useRouteParams() {
const match = useRouteMatch();
return match?.params ?? {};
}
function useRouteRender() {
const match = useRouteMatch();
return match ? /* @__PURE__ */ jsx(match.route.component, { ...match.data }) : null;
}
function RouteView({ fallback }) {
const view = useRouteRender();
return /* @__PURE__ */ jsx(Fragment, { children: view ?? fallback ?? null });
}
export {
Route,
RouteView,
Router,
RouterProvider,
useRouteBlocker,
useRouteMatch,
useRouteParams,
useRouteRender,
useRouter
};