@ionic/core
Version:
Base components for Ionic
228 lines (227 loc) • 7.45 kB
JavaScript
/*!
* (C) Ionic http://ionicframework.com - MIT License
*/
/**
* Returns whether the given redirect matches the given path segments.
*
* A redirect matches when the segments of the path and redirect.from are equal.
* Note that segments are only checked until redirect.from contains a '*' which matches any path segment.
* The path ['some', 'path', 'to', 'page'] matches both ['some', 'path', 'to', 'page'] and ['some', 'path', '*'].
*/
export const matchesRedirect = (segments, redirect) => {
const { from, to } = redirect;
if (to === undefined) {
return false;
}
if (from.length > segments.length) {
return false;
}
for (let i = 0; i < from.length; i++) {
const expected = from[i];
if (expected === '*') {
return true;
}
if (expected !== segments[i]) {
return false;
}
}
return from.length === segments.length;
};
/** Returns the first redirect matching the path segments or undefined when no match found. */
export const findRouteRedirect = (segments, redirects) => {
return redirects.find((redirect) => matchesRedirect(segments, redirect));
};
export const matchesIDs = (ids, chain) => {
const len = Math.min(ids.length, chain.length);
let score = 0;
for (let i = 0; i < len; i++) {
const routeId = ids[i];
const routeChain = chain[i];
// Skip results where the route id does not match the chain at the same index
if (routeId.id.toLowerCase() !== routeChain.id) {
break;
}
if (routeId.params) {
const routeIdParams = Object.keys(routeId.params);
// Only compare routes with the chain that have the same number of parameters.
if (routeIdParams.length === routeChain.segments.length) {
// Maps the route's params into a path based on the path variable names,
// to compare against the route chain format.
//
// Before:
// ```ts
// {
// params: {
// s1: 'a',
// s2: 'b'
// }
// }
// ```
//
// After:
// ```ts
// [':s1',':s2']
// ```
//
const pathWithParams = routeIdParams.map((key) => `:${key}`);
for (let j = 0; j < pathWithParams.length; j++) {
// Skip results where the path variable is not a match
if (pathWithParams[j].toLowerCase() !== routeChain.segments[j]) {
break;
}
// Weight path matches for the same index higher.
score++;
}
}
}
// Weight id matches
score++;
}
return score;
};
/**
* Matches the segments against the chain.
*
* Returns:
* - null when there is no match,
* - a chain with the params properties updated with the parameter segments on match.
*/
export const matchesSegments = (segments, chain) => {
const inputSegments = new RouterSegments(segments);
let matchesDefault = false;
let allparams;
for (let i = 0; i < chain.length; i++) {
const chainSegments = chain[i].segments;
if (chainSegments[0] === '') {
matchesDefault = true;
}
else {
for (const segment of chainSegments) {
const data = inputSegments.next();
// data param
if (segment[0] === ':') {
if (data === '') {
return null;
}
allparams = allparams || [];
const params = allparams[i] || (allparams[i] = {});
params[segment.slice(1)] = data;
}
else if (data !== segment) {
return null;
}
}
matchesDefault = false;
}
}
const matches = matchesDefault ? matchesDefault === (inputSegments.next() === '') : true;
if (!matches) {
return null;
}
if (allparams) {
return chain.map((route, i) => ({
id: route.id,
segments: route.segments,
params: mergeParams(route.params, allparams[i]),
beforeEnter: route.beforeEnter,
beforeLeave: route.beforeLeave,
}));
}
return chain;
};
/**
* Merges the route parameter objects.
* Returns undefined when both parameters are undefined.
*/
export const mergeParams = (a, b) => {
return a || b ? Object.assign(Object.assign({}, a), b) : undefined;
};
/**
* Finds the best match for the ids in the chains.
*
* Returns the best match or null when no match is found.
* When a chain is returned the parameters are updated from the RouteIDs.
* That is they contain both the componentProps of the <ion-route> and the parameter segment.
*/
export const findChainForIDs = (ids, chains) => {
let match = null;
let maxMatches = 0;
for (const chain of chains) {
const score = matchesIDs(ids, chain);
if (score > maxMatches) {
match = chain;
maxMatches = score;
}
}
if (match) {
return match.map((route, i) => {
var _a;
return ({
id: route.id,
segments: route.segments,
params: mergeParams(route.params, (_a = ids[i]) === null || _a === void 0 ? void 0 : _a.params),
});
});
}
return null;
};
/**
* Finds the best match for the segments in the chains.
*
* Returns the best match or null when no match is found.
* When a chain is returned the parameters are updated from the segments.
* That is they contain both the componentProps of the <ion-route> and the parameter segments.
*/
export const findChainForSegments = (segments, chains) => {
let match = null;
let bestScore = 0;
for (const chain of chains) {
const matchedChain = matchesSegments(segments, chain);
if (matchedChain !== null) {
const score = computePriority(matchedChain);
if (score > bestScore) {
bestScore = score;
match = matchedChain;
}
}
}
return match;
};
/**
* Computes the priority of a chain.
*
* Parameter segments are given a lower priority over fixed segments.
*
* Considering the following 2 chains matching the path /path/to/page:
* - /path/to/:where
* - /path/to/page
*
* The second one will be given a higher priority because "page" is a fixed segment (vs ":where", a parameter segment).
*/
export const computePriority = (chain) => {
let score = 1;
let level = 1;
for (const route of chain) {
for (const segment of route.segments) {
if (segment[0] === ':') {
score += Math.pow(1, level);
}
else if (segment !== '') {
score += Math.pow(2, level);
}
level++;
}
}
return score;
};
export class RouterSegments {
constructor(segments) {
this.segments = segments.slice();
}
next() {
if (this.segments.length > 0) {
return this.segments.shift();
}
return '';
}
}