@koordinates/xstate-tree
Version:
Build UIs with Actors using xstate and React
186 lines (185 loc) • 8.28 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildCreateRoute = void 0;
const path_to_regexp_1 = require("path-to-regexp");
const query_string_1 = require("query-string");
const joinRoutes_1 = require("../joinRoutes");
/**
* @public
*
* Creates a route factory
*
* @param history - the history object to use for this route factory, this needs to be the same one used in the trees root component
* @param basePath - the base path for this route factory
*/
function buildCreateRoute(history, basePath) {
function navigate({ history, url, meta, }) {
const method = meta?.replace ? history.replace : history.push;
method(url, {
meta,
previousUrl: `${window.location.pathname}${window.location.search}`,
});
}
return {
simpleRoute(baseRoute) {
return ({ url, paramsSchema, querySchema, ...args }) => {
const matcher = (0, path_to_regexp_1.match)(url, { end: false });
const reverser = (0, path_to_regexp_1.compile)(url);
return this.route(baseRoute)({
...args,
paramsSchema,
querySchema,
// @ts-ignore :cry:
matcher: (url, query) => {
const match = matcher(url);
if (match === false) {
return false;
}
const params = match.params;
if (params && paramsSchema) {
paramsSchema.parse(params);
}
if (query && querySchema) {
querySchema.parse(query);
}
return {
matchLength: match.path.length,
params,
query,
};
},
// @ts-ignore :cry:
reverser: (args) => {
const url = reverser(args.params);
if (args.query) {
return `${url}?${(0, query_string_1.stringify)(args.query)}`;
}
return url;
},
});
};
},
route(baseRoute) {
function getParentArray() {
const parentRoutes = [];
let currentParent = baseRoute;
while (currentParent) {
parentRoutes.unshift(currentParent);
currentParent = currentParent.parent;
}
return parentRoutes;
}
return ({ event, matcher, reverser, paramsSchema, querySchema, redirect, preload, canMatch, }) => {
let fullParamsSchema = paramsSchema;
let parentRoute = baseRoute;
while (fullParamsSchema && parentRoute) {
if (parentRoute.paramsSchema) {
fullParamsSchema = fullParamsSchema.merge(parentRoute.paramsSchema);
}
parentRoute = parentRoute.parent;
}
return {
basePath,
event,
history,
paramsSchema,
querySchema,
parent: baseRoute,
redirect,
canMatch,
matcher: matcher,
reverser: reverser,
// @ts-ignore :cry:
getEvent(args) {
const { params, query, meta } = args ?? {};
return { type: event, params, query, meta };
},
// @ts-ignore :cry:
matches(suppliedUrl, search) {
const fullUrl = suppliedUrl.endsWith("/")
? suppliedUrl
: suppliedUrl + "/";
let url = fullUrl;
const parentRoutes = getParentArray();
let params = {};
while (parentRoutes.length) {
const parentRoute = parentRoutes.shift();
const parentMatch = parentRoute.matcher(url, undefined);
if (parentMatch === false) {
return false;
}
url = url.slice(parentMatch.matchLength);
// All routes assume the url starts with a /
// so if the parent route matches the / in the url, which consumes it
// need to re-add it for the next route to match against
if (!url.startsWith("/")) {
url = "/" + url;
}
params = { ...params, ...(parentMatch.params ?? {}) };
}
const matches = matcher(url, (0, query_string_1.parse)(search));
// if there is any URL left after matching this route, the last to match
// that means the match isn't actually a match
if (matches === false || matches.matchLength !== url.length) {
return false;
}
const fullParams = {
...params,
...(matches.params ?? {}),
};
if (fullParamsSchema) {
fullParamsSchema.parse(fullParams);
}
if (querySchema) {
querySchema.parse(matches.query);
}
// Check canMatch predicate if provided
if (canMatch) {
const canMatchResult = canMatch({
params: fullParams,
query: matches.query ?? {},
});
if (!canMatchResult) {
return false;
}
}
return {
originalUrl: `${fullUrl}${search}`,
type: event,
params: fullParams,
query: matches.query ?? {},
};
},
// @ts-ignore :cry:
reverse(args) {
const { params, query } = args ?? {};
const parentRoutes = getParentArray();
const baseUrl = parentRoutes
.map((route) => route.reverser({ params }))
.reduce((fullUrl, urlPartial) => (0, joinRoutes_1.joinRoutes)(fullUrl, urlPartial), "");
return `${(0, joinRoutes_1.joinRoutes)(baseUrl, reverser({ params, query }))}`;
},
// @ts-ignore :cry:
navigate(args) {
const { params, query, meta } = args ?? {};
const url = this.reverse({ params, query });
navigate({
url: (0, joinRoutes_1.joinRoutes)(this.basePath, url),
meta,
history: this.history(),
});
},
// @ts-ignore :cry:
preload(args) {
const parentRoutes = getParentArray();
parentRoutes.forEach((route) => {
route?.preload(args);
});
preload?.(args);
},
};
};
},
};
}
exports.buildCreateRoute = buildCreateRoute;