diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json
index 5531583295..0b280eb894 100644
--- a/packages/vue-router/package-lock.json
+++ b/packages/vue-router/package-lock.json
@@ -8,10 +8,6 @@
"name": "@ionic/vue-router",
"version": "5.5.2",
"license": "MIT",
- "dependencies": {
- "@ionic/core": "file:../../core",
- "path-to-regexp": "^6.2.0"
- },
"devDependencies": {
"@ionic/vue": "5.4.1",
"@types/jest": "^26.0.13",
@@ -28,6 +24,7 @@
},
"../../core": {
"version": "5.4.1",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ionicons": "^5.1.2",
@@ -5849,11 +5846,6 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
- "node_modules/path-to-regexp": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz",
- "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg=="
- },
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -11490,7 +11482,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"jest-regex-util": {
"version": "26.0.0",
@@ -12623,11 +12616,6 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
- "path-to-regexp": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz",
- "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg=="
- },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -14204,7 +14192,8 @@
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"xml-name-validator": {
"version": "3.0.0",
diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json
index 9ff2c7a980..f95d4e7279 100644
--- a/packages/vue-router/package.json
+++ b/packages/vue-router/package.json
@@ -66,8 +66,5 @@
"json",
"jsx"
]
- },
- "dependencies": {
- "path-to-regexp": "^6.2.0"
}
}
diff --git a/packages/vue-router/src/index.ts b/packages/vue-router/src/index.ts
index dd9c3980e1..f59720b6e9 100644
--- a/packages/vue-router/src/index.ts
+++ b/packages/vue-router/src/index.ts
@@ -15,7 +15,7 @@ export const createRouter = (opts: IonicVueRouterOptions) => {
const router = createVueRouter(routerOptions);
const ionRouter = createIonRouter(opts, router);
- const viewStacks = createViewStacks();
+ const viewStacks = createViewStacks(router);
const oldInstall = router.install.bind(router);
router.install = (app: App) => {
diff --git a/packages/vue-router/src/regexp.ts b/packages/vue-router/src/regexp.ts
deleted file mode 100644
index f828d4cf90..0000000000
--- a/packages/vue-router/src/regexp.ts
+++ /dev/null
@@ -1,622 +0,0 @@
-// @ts-nocheck
-// https://github.com/pillarjs/path-to-regexp
-// Included here so we do not need to add additional package.json dependency
-
-/**
- * Tokenizer results.
- */
-interface LexToken {
- type:
- | "OPEN"
- | "CLOSE"
- | "PATTERN"
- | "NAME"
- | "CHAR"
- | "ESCAPED_CHAR"
- | "MODIFIER"
- | "END";
- index: number;
- value: string;
-}
-
-/**
- * Tokenize input string.
- */
-function lexer(str: string): LexToken[] {
- const tokens: LexToken[] = [];
- let i = 0;
-
- while (i < str.length) {
- const char = str[i];
-
- if (char === "*" || char === "+" || char === "?") {
- tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
- continue;
- }
-
- if (char === "\\") {
- tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
- continue;
- }
-
- if (char === "{") {
- tokens.push({ type: "OPEN", index: i, value: str[i++] });
- continue;
- }
-
- if (char === "}") {
- tokens.push({ type: "CLOSE", index: i, value: str[i++] });
- continue;
- }
-
- if (char === ":") {
- let name = "";
- let j = i + 1;
-
- while (j < str.length) {
- const code = str.charCodeAt(j);
-
- if (
- // `0-9`
- (code >= 48 && code <= 57) ||
- // `A-Z`
- (code >= 65 && code <= 90) ||
- // `a-z`
- (code >= 97 && code <= 122) ||
- // `_`
- code === 95
- ) {
- name += str[j++];
- continue;
- }
-
- break;
- }
-
- if (!name) throw new TypeError(`Missing parameter name at ${i}`);
-
- tokens.push({ type: "NAME", index: i, value: name });
- i = j;
- continue;
- }
-
- if (char === "(") {
- let count = 1;
- let pattern = "";
- let j = i + 1;
-
- if (str[j] === "?") {
- throw new TypeError(`Pattern cannot start with "?" at ${j}`);
- }
-
- while (j < str.length) {
- if (str[j] === "\\") {
- pattern += str[j++] + str[j++];
- continue;
- }
-
- if (str[j] === ")") {
- count--;
- if (count === 0) {
- j++;
- break;
- }
- } else if (str[j] === "(") {
- count++;
- if (str[j + 1] !== "?") {
- throw new TypeError(`Capturing groups are not allowed at ${j}`);
- }
- }
-
- pattern += str[j++];
- }
-
- if (count) throw new TypeError(`Unbalanced pattern at ${i}`);
- if (!pattern) throw new TypeError(`Missing pattern at ${i}`);
-
- tokens.push({ type: "PATTERN", index: i, value: pattern });
- i = j;
- continue;
- }
-
- tokens.push({ type: "CHAR", index: i, value: str[i++] });
- }
-
- tokens.push({ type: "END", index: i, value: "" });
-
- return tokens;
-}
-
-export interface ParseOptions {
- /**
- * Set the default delimiter for repeat parameters. (default: `'/'`)
- */
- delimiter?: string;
- /**
- * List of characters to automatically consider prefixes when parsing.
- */
- prefixes?: string;
-}
-
-/**
- * Parse a string for the raw tokens.
- */
-export function parse(str: string, options: ParseOptions = {}): Token[] {
- const tokens = lexer(str);
- const { prefixes = "./" } = options;
- const defaultPattern = `[^${escapeString(options.delimiter || "/#?")}]+?`;
- const result: Token[] = [];
- let key = 0;
- let i = 0;
- let path = "";
-
- const tryConsume = (type: LexToken["type"]): string | undefined => {
- if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;
- };
-
- const mustConsume = (type: LexToken["type"]): string => {
- const value = tryConsume(type);
- if (value !== undefined) return value;
- const { type: nextType, index } = tokens[i];
- throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);
- };
-
- const consumeText = (): string => {
- let result = "";
- let value: string | undefined;
- // tslint:disable-next-line
- while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) {
- result += value;
- }
- return result;
- };
-
- while (i < tokens.length) {
- const char = tryConsume("CHAR");
- const name = tryConsume("NAME");
- const pattern = tryConsume("PATTERN");
-
- if (name || pattern) {
- let prefix = char || "";
-
- if (prefixes.indexOf(prefix) === -1) {
- path += prefix;
- prefix = "";
- }
-
- if (path) {
- result.push(path);
- path = "";
- }
-
- result.push({
- name: name || key++,
- prefix,
- suffix: "",
- pattern: pattern || defaultPattern,
- modifier: tryConsume("MODIFIER") || ""
- });
- continue;
- }
-
- const value = char || tryConsume("ESCAPED_CHAR");
- if (value) {
- path += value;
- continue;
- }
-
- if (path) {
- result.push(path);
- path = "";
- }
-
- const open = tryConsume("OPEN");
- if (open) {
- const prefix = consumeText();
- const name = tryConsume("NAME") || "";
- const pattern = tryConsume("PATTERN") || "";
- const suffix = consumeText();
-
- mustConsume("CLOSE");
-
- result.push({
- name: name || (pattern ? key++ : ""),
- pattern: name && !pattern ? defaultPattern : pattern,
- prefix,
- suffix,
- modifier: tryConsume("MODIFIER") || ""
- });
- continue;
- }
-
- mustConsume("END");
- }
-
- return result;
-}
-
-export interface TokensToFunctionOptions {
- /**
- * When `true` the regexp will be case sensitive. (default: `false`)
- */
- sensitive?: boolean;
- /**
- * Function for encoding input strings for output.
- */
- encode?: (value: string, token: Key) => string;
- /**
- * When `false` the function can produce an invalid (unmatched) path. (default: `true`)
- */
- validate?: boolean;
-}
-
-/**
- * Compile a string to a template function for the path.
- */
-export function compile
(
- str: string,
- options?: ParseOptions & TokensToFunctionOptions
-) {
- return tokensToFunction
(parse(str, options), options);
-}
-
-export type PathFunction
= (data?: P) => string;
-
-/**
- * Expose a method for transforming tokens into the path function.
- */
-export function tokensToFunction
(
- tokens: Token[],
- options: TokensToFunctionOptions = {}
-): PathFunction
{
- const reFlags = flags(options);
- const { encode = (x: string) => x, validate = true } = options;
-
- // Compile all the tokens into regexps.
- const matches = tokens.map(token => {
- if (typeof token === "object") {
- return new RegExp(`^(?:${token.pattern})$`, reFlags);
- }
- });
-
- return (data: Record | null | undefined) => {
- let path = "";
-
- for (let i = 0; i < tokens.length; i++) {
- const token = tokens[i];
-
- if (typeof token === "string") {
- path += token;
- continue;
- }
-
- const value = data ? data[token.name] : undefined;
- const optional = token.modifier === "?" || token.modifier === "*";
- const repeat = token.modifier === "*" || token.modifier === "+";
-
- if (Array.isArray(value)) {
- if (!repeat) {
- throw new TypeError(
- `Expected "${token.name}" to not repeat, but got an array`
- );
- }
-
- if (value.length === 0) {
- if (optional) continue;
-
- throw new TypeError(`Expected "${token.name}" to not be empty`);
- }
-
- for (let j = 0; j < value.length; j++) {
- const segment = encode(value[j], token);
-
- if (validate && !(matches[i] as RegExp).test(segment)) {
- throw new TypeError(
- `Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"`
- );
- }
-
- path += token.prefix + segment + token.suffix;
- }
-
- continue;
- }
-
- if (typeof value === "string" || typeof value === "number") {
- const segment = encode(String(value), token);
-
- if (validate && !(matches[i] as RegExp).test(segment)) {
- throw new TypeError(
- `Expected "${token.name}" to match "${token.pattern}", but got "${segment}"`
- );
- }
-
- path += token.prefix + segment + token.suffix;
- continue;
- }
-
- if (optional) continue;
-
- const typeOfMessage = repeat ? "an array" : "a string";
- throw new TypeError(`Expected "${token.name}" to be ${typeOfMessage}`);
- }
-
- return path;
- };
-}
-
-export interface RegexpToFunctionOptions {
- /**
- * Function for decoding strings for params.
- */
- decode?: (value: string, token: Key) => string;
-}
-
-/**
- * A match result contains data about the path match.
- */
-export interface MatchResult {
- path: string;
- index: number;
- params: P;
-}
-
-/**
- * A match is either `false` (no match) or a match result.
- */
-export type Match
= false | MatchResult
;
-
-/**
- * The match function takes a string and returns whether it matched the path.
- */
-export type MatchFunction
= (
- path: string
-) => Match
;
-
-/**
- * Create path match function from `path-to-regexp` spec.
- */
-export function match
(
- str: Path,
- options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions
-) {
- const keys: Key[] = [];
- const re = pathToRegexp(str, keys, options);
- return regexpToFunction
(re, keys, options);
-}
-
-/**
- * Create a path match function from `path-to-regexp` output.
- */
-export function regexpToFunction
(
- re: RegExp,
- keys: Key[],
- options: RegexpToFunctionOptions = {}
-): MatchFunction
{
- const { decode = (x: string) => x } = options;
-
- return function(pathname: string) {
- const m = re.exec(pathname);
- if (!m) return false;
-
- const { 0: path, index } = m;
- const params = Object.create(null);
-
- for (let i = 1; i < m.length; i++) {
- // tslint:disable-next-line
- if (m[i] === undefined) continue;
-
- const key = keys[i - 1];
-
- if (key.modifier === "*" || key.modifier === "+") {
- params[key.name] = m[i].split(key.prefix + key.suffix).map(value => {
- return decode(value, key);
- });
- } else {
- params[key.name] = decode(m[i], key);
- }
- }
-
- return { path, index, params };
- };
-}
-
-/**
- * Escape a regular expression string.
- */
-function escapeString(str: string) {
- return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
-}
-
-/**
- * Get the flags for a regexp from the options.
- */
-function flags(options?: { sensitive?: boolean }) {
- return options && options.sensitive ? "" : "i";
-}
-
-/**
- * Metadata about a key.
- */
-export interface Key {
- name: string | number;
- prefix: string;
- suffix: string;
- pattern: string;
- modifier: string;
-}
-
-/**
- * A token is a string (nothing special) or key metadata (capture group).
- */
-export type Token = string | Key;
-
-/**
- * Pull out keys from a regexp.
- */
-function regexpToRegexp(path: RegExp, keys?: Key[]): RegExp {
- if (!keys) return path;
-
- const groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
-
- let index = 0;
- let execResult = groupsRegex.exec(path.source);
- while (execResult) {
- keys.push({
- // Use parenthesized substring match if available, index otherwise
- name: execResult[1] || index++,
- prefix: "",
- suffix: "",
- modifier: "",
- pattern: ""
- });
- execResult = groupsRegex.exec(path.source);
- }
-
- return path;
-}
-
-/**
- * Transform an array into a regexp.
- */
-function arrayToRegexp(
- paths: Array,
- keys?: Key[],
- options?: TokensToRegexpOptions & ParseOptions
-): RegExp {
- const parts = paths.map(path => pathToRegexp(path, keys, options).source);
- return new RegExp(`(?:${parts.join("|")})`, flags(options));
-}
-
-/**
- * Create a path regexp from string input.
- */
-function stringToRegexp(
- path: string,
- keys?: Key[],
- options?: TokensToRegexpOptions & ParseOptions
-) {
- return tokensToRegexp(parse(path, options), keys, options);
-}
-
-export interface TokensToRegexpOptions {
- /**
- * When `true` the regexp will be case sensitive. (default: `false`)
- */
- sensitive?: boolean;
- /**
- * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`)
- */
- strict?: boolean;
- /**
- * When `true` the regexp will match to the end of the string. (default: `true`)
- */
- end?: boolean;
- /**
- * When `true` the regexp will match from the beginning of the string. (default: `true`)
- */
- start?: boolean;
- /**
- * Sets the final character for non-ending optimistic matches. (default: `/`)
- */
- delimiter?: string;
- /**
- * List of characters that can also be "end" characters.
- */
- endsWith?: string;
- /**
- * Encode path tokens for use in the `RegExp`.
- */
- encode?: (value: string) => string;
-}
-
-/**
- * Expose a function for taking tokens and returning a RegExp.
- */
-export function tokensToRegexp(
- tokens: Token[],
- keys?: Key[],
- options: TokensToRegexpOptions = {}
-) {
- const {
- strict = false,
- start = true,
- end = true,
- encode = (x: string) => x
- } = options;
- const endsWith = `[${escapeString(options.endsWith || "")}]|$`;
- const delimiter = `[${escapeString(options.delimiter || "/#?")}]`;
- let route = start ? "^" : "";
-
- // Iterate over the tokens and create our regexp string.
- for (const token of tokens) {
- if (typeof token === "string") {
- route += escapeString(encode(token));
- } else {
- const prefix = escapeString(encode(token.prefix));
- const suffix = escapeString(encode(token.suffix));
-
- if (token.pattern) {
- if (keys) keys.push(token);
-
- if (prefix || suffix) {
- if (token.modifier === "+" || token.modifier === "*") {
- const mod = token.modifier === "*" ? "?" : "";
- route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`;
- } else {
- route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;
- }
- } else {
- route += `(${token.pattern})${token.modifier}`;
- }
- } else {
- route += `(?:${prefix}${suffix})${token.modifier}`;
- }
- }
- }
-
- if (end) {
- if (!strict) route += `${delimiter}?`;
-
- route += !options.endsWith ? "$" : `(?=${endsWith})`;
- } else {
- const endToken = tokens[tokens.length - 1];
- const isEndDelimited =
- typeof endToken === "string"
- ? delimiter.indexOf(endToken[endToken.length - 1]) > -1
- : // tslint:disable-next-line
- endToken === undefined;
-
- if (!strict) {
- route += `(?:${delimiter}(?=${endsWith}))?`;
- }
-
- if (!isEndDelimited) {
- route += `(?=${delimiter}|${endsWith})`;
- }
- }
-
- return new RegExp(route, flags(options));
-}
-
-/**
- * Supported `path-to-regexp` input types.
- */
-export type Path = string | RegExp | Array;
-
-/**
- * Normalize the given path string, returning a regular expression.
- *
- * An empty array can be passed in for the keys, which will hold the
- * placeholder key descriptions. For example, using `/user/:id`, `keys` will
- * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
- */
-export function pathToRegexp(
- path: Path,
- keys?: Key[],
- options?: TokensToRegexpOptions & ParseOptions
-) {
- if (path instanceof RegExp) return regexpToRegexp(path, keys);
- if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
- return stringToRegexp(path, keys, options);
-}
diff --git a/packages/vue-router/src/router.ts b/packages/vue-router/src/router.ts
index ba2118296a..756fd95eba 100644
--- a/packages/vue-router/src/router.ts
+++ b/packages/vue-router/src/router.ts
@@ -188,6 +188,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
pathname: location.path,
search: location.fullPath && location.fullPath.split('?')[1] || '',
params: location.params && location.params,
+ prevRouteLastPathname: leavingLocationInfo.lastPathname
}
if (isPushed) {
@@ -205,6 +206,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
routeInfo.pushedByRoute = currentRouteInfo?.pushedByRoute || routeInfo.pushedByRoute;
routeInfo.routerDirection = currentRouteInfo?.routerDirection || routeInfo.routerDirection;
routeInfo.routerAnimation = currentRouteInfo?.routerAnimation || routeInfo.routerAnimation;
+ routeInfo.prevRouteLastPathname = currentRouteInfo?.lastPathname;
}
locationHistory.add(routeInfo);
diff --git a/packages/vue-router/src/types.ts b/packages/vue-router/src/types.ts
index 13e8d8ae71..74d41e5e68 100644
--- a/packages/vue-router/src/types.ts
+++ b/packages/vue-router/src/types.ts
@@ -20,6 +20,7 @@ export interface RouteInfo {
routerDirection?: RouteDirection;
routerAnimation?: AnimationBuilder;
lastPathname?: string;
+ prevRouteLastPathname?: string;
pathname?: string;
search?: string;
params?: { [k: string]: any };
diff --git a/packages/vue-router/src/viewStacks.ts b/packages/vue-router/src/viewStacks.ts
index 1f83ee81b4..3688d193eb 100644
--- a/packages/vue-router/src/viewStacks.ts
+++ b/packages/vue-router/src/viewStacks.ts
@@ -1,13 +1,12 @@
import { generateId } from './utils';
-import { pathToRegexp } from './regexp';
import { RouteInfo,
ViewItem,
ViewStacks,
} from './types';
-import { RouteLocationMatched } from 'vue-router';
+import { RouteLocationMatched, Router } from 'vue-router';
import { shallowRef } from 'vue';
-export const createViewStacks = () => {
+export const createViewStacks = (router: Router) => {
let viewStacks: ViewStacks = {};
const clear = (outletId: number) => {
@@ -20,14 +19,19 @@ export const createViewStacks = () => {
const registerIonPage = (viewItem: ViewItem, ionPage: HTMLElement) => {
viewItem.ionPageElement = ionPage;
+ viewItem.ionRoute = true;
}
const findViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
return findViewItemByPath(routeInfo.pathname, outletId);
}
- const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
- return findViewItemByPath(routeInfo.lastPathname, outletId, false);
+ const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number, mustBeIonRoute: boolean = true) => {
+ return findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute);
+ }
+
+ const findViewItemByPathname = (pathname: string, outletId?: number) => {
+ return findViewItemByPath(pathname, outletId);
}
const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => {
@@ -40,30 +44,31 @@ export const createViewStacks = () => {
})
}
- const findViewItemByPath = (path: string, outletId?: number, strict: boolean = true): ViewItem | undefined => {
+ const findViewItemByPath = (path: string, outletId?: number, mustBeIonRoute: boolean = false): ViewItem | undefined => {
const matchView = (viewItem: ViewItem) => {
- const pathname = path;
- const viewItemPath = viewItem.matchedRoute.path;
+ if (
+ (mustBeIonRoute && !viewItem.ionRoute) ||
+ path === ''
+ ) {
+ return false;
+ }
- const regexp = pathToRegexp(viewItemPath, [], {
- end: viewItem.exact,
- strict: viewItem.exact,
- sensitive: false
- });
- return (regexp.exec(pathname)) ? viewItem : undefined;
+ const resolvedPath = router.resolve(path);
+ const findMatchedRoute = resolvedPath.matched.find((matchedRoute: RouteLocationMatched) => matchedRoute === viewItem.matchedRoute && (path === viewItem.pathname || matchedRoute.path.includes(':')));
+
+ if (findMatchedRoute) {
+ return viewItem;
+ }
+
+ return undefined;
}
if (outletId) {
const stack = viewStacks[outletId];
if (!stack) return undefined;
- const quickMatch = findViewItemInStack(path, stack);
- if (quickMatch) return quickMatch;
-
- if (!strict) {
- const match = stack.find(matchView);
- if (match) return match;
- }
+ const match = (router) ? stack.find(matchView) : findViewItemInStack(path, stack)
+ if (match) return match;
} else {
for (let outletId in viewStacks) {
const stack = viewStacks[outletId];
@@ -125,6 +130,7 @@ export const createViewStacks = () => {
clear,
findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo,
+ findViewItemByPathname,
createViewItem,
getChildrenToRender,
add,
diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts
index 66a207c488..9530f22a6a 100644
--- a/packages/vue/src/components/IonRouterOutlet.ts
+++ b/packages/vue/src/components/IonRouterOutlet.ts
@@ -76,14 +76,14 @@ export const IonRouterOutlet = defineComponent({
* to make sure the view is in the outlet we want.
*/
const routeInfo = ionRouter.getCurrentRouteInfo();
- const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
+ const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
return !!enteringViewItem;
}
const onStart = async () => {
const routeInfo = ionRouter.getCurrentRouteInfo();
const { routerAnimation } = routeInfo;
- const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
+ const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
if (leavingViewItem) {
@@ -139,7 +139,7 @@ export const IonRouterOutlet = defineComponent({
* re-hide the page that was going to enter.
*/
const routeInfo = ionRouter.getCurrentRouteInfo();
- const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
+ const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
enteringViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
enteringViewItem.ionPageElement.classList.add('ion-page-hidden');
}
@@ -192,17 +192,21 @@ export const IonRouterOutlet = defineComponent({
const handlePageTransition = async () => {
const routeInfo = ionRouter.getCurrentRouteInfo();
- const { routerDirection, routerAction, routerAnimation } = routeInfo;
+ const { routerDirection, routerAction, routerAnimation, prevRouteLastPathname } = routeInfo;
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
- const leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
+ let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
const enteringEl = enteringViewItem.ionPageElement;
if (enteringViewItem === leavingViewItem) return;
+ if (!leavingViewItem && prevRouteLastPathname) {
+ leavingViewItem = viewStacks.findViewItemByPathname(prevRouteLastPathname, id);
+ }
+
fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER);
- if (leavingViewItem) {
+ if (leavingViewItem && enteringViewItem !== leavingViewItem) {
let animationBuilder = routerAnimation;
const leavingEl = leavingViewItem.ionPageElement;
diff --git a/packages/vue/test-app/src/router/index.ts b/packages/vue/test-app/src/router/index.ts
index 764a7925c3..5ec99223b9 100644
--- a/packages/vue/test-app/src/router/index.ts
+++ b/packages/vue/test-app/src/router/index.ts
@@ -68,6 +68,10 @@ const routes: Array = [
{
path: 'two',
component: () => import('@/views/NestedChildTwo.vue')
+ },
+ {
+ path: ':id',
+ component: () => import('@/views/Folder.vue')
}
]
},
@@ -135,4 +139,6 @@ const router = createRouter({
routes
});
+(window as any).debugRouter = router;
+
export default router
diff --git a/packages/vue/test-app/src/views/Folder.vue b/packages/vue/test-app/src/views/Folder.vue
new file mode 100644
index 0000000000..98e011e881
--- /dev/null
+++ b/packages/vue/test-app/src/views/Folder.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+ {{ folder }}
+
+
+
+
+
+
+ {{ folder }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vue/test-app/src/views/Home.vue b/packages/vue/test-app/src/views/Home.vue
index 82db17ee0f..3366c171d9 100644
--- a/packages/vue/test-app/src/views/Home.vue
+++ b/packages/vue/test-app/src/views/Home.vue
@@ -14,37 +14,37 @@
-
+
Overlays
-
+
Inputs
-
+
Slides
-
+
Navigation
-
+
Routing
-
+
Default Href
-
+
Nested Router Outlet
-
+
Tabs
-
+
Tabs Secondary
-
+
Lifecycle
-
+
Delayed Redirect
diff --git a/packages/vue/test-app/src/views/RouterOutlet.vue b/packages/vue/test-app/src/views/RouterOutlet.vue
index 5057301adf..2d179c9865 100644
--- a/packages/vue/test-app/src/views/RouterOutlet.vue
+++ b/packages/vue/test-app/src/views/RouterOutlet.vue
@@ -1,5 +1,16 @@
+
+
+
+ Inbox
+ Trash
+ Outbox
+ Spam
+ Other
+
+
+
@@ -8,6 +19,10 @@
diff --git a/packages/vue/test-app/src/views/Tab3.vue b/packages/vue/test-app/src/views/Tab3.vue
index e811acc054..30a58bb309 100644
--- a/packages/vue/test-app/src/views/Tab3.vue
+++ b/packages/vue/test-app/src/views/Tab3.vue
@@ -11,7 +11,7 @@
Tab 3
-
+
@@ -25,4 +25,4 @@ export default {
name: 'Tab3',
components: { ExploreContainer, IonHeader, IonToolbar, IonTitle, IonContent, IonPage }
}
-
\ No newline at end of file
+
diff --git a/packages/vue/test-app/tests/e2e/specs/routing.js b/packages/vue/test-app/tests/e2e/specs/routing.js
index fa14197da0..2e885661c2 100644
--- a/packages/vue/test-app/tests/e2e/specs/routing.js
+++ b/packages/vue/test-app/tests/e2e/specs/routing.js
@@ -119,6 +119,63 @@ describe('Routing', () => {
cy.ionSwipeToGoBack(true);
cy.ionPageVisible('navigation');
});
+
+ // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22654
+ it('should show correct view when navigating between parameter urls', () => {
+ cy.visit('http://localhost:8080/nested');
+
+ cy.ionPageVisible('nestedchild');
+
+ cy.get('[data-pageid="routeroutlet"] #trash').click();
+ cy.ionPageVisible('folder');
+
+ cy.get('[data-pageid="routeroutlet"] #outbox').click();
+ cy.ionPageVisible('folder');
+
+ cy.get('[data-pageid="routeroutlet"] #other').click();
+ cy.ionPageVisible('nestedchildtwo');
+ cy.ionPageDoesNotExist('folder');
+
+ cy.get('[data-pageid="routeroutlet"] #spam').click();
+ cy.ionPageVisible('folder');
+ cy.ionPageDoesNotExist('nestedchildtwo');
+
+ cy.get('[data-pageid="routeroutlet"] #outbox').click();
+ cy.ionPageVisible('folder');
+
+ cy.get('[data-pageid="routeroutlet"] #other').click();
+ cy.ionPageVisible('nestedchildtwo');
+ cy.ionPageDoesNotExist('folder');
+ });
+
+ // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22658
+ it('should select correct leaving view when navigating between paramter urls', () => {
+ cy.visit('http://localhost:8080');
+
+ cy.routerPush('/routing/123');
+ cy.ionPageVisible('routingparameter');
+ cy.ionPageHidden('home');
+
+ cy.routerPush('/routing/456');
+ cy.ionPageVisible('routingparameter');
+ cy.ionPageHidden('home');
+
+ cy.routerPush('/navigation');
+ cy.ionPageVisible('navigation');
+ cy.ionPageHidden('routingparameter');
+
+ cy.routerPush('/routing/789');
+ cy.ionPageVisible('routingparameter');
+ cy.ionPageHidden('home');
+
+ cy.routerPush('/routing/000');
+ cy.ionPageVisible('routingparameter');
+ cy.ionPageHidden('home');
+
+ cy.routerPush('/navigation');
+ cy.ionPageVisible('navigation');
+ cy.ionPageHidden('routingparameter');
+ });
});
describe('Routing - Swipe to Go Back', () => {
diff --git a/packages/vue/test-app/tests/e2e/specs/tabs.js b/packages/vue/test-app/tests/e2e/specs/tabs.js
index 63eef2114b..0af23bad18 100644
--- a/packages/vue/test-app/tests/e2e/specs/tabs.js
+++ b/packages/vue/test-app/tests/e2e/specs/tabs.js
@@ -85,6 +85,8 @@ describe('Tabs', () => {
});
it('should go back from a tabs page to a non-tabs page using ion-back-button', () => {
+ cy.visit('http://localhost:8080');
+
cy.get('#tabs').click();
cy.ionPageVisible('tab1');
@@ -97,6 +99,8 @@ describe('Tabs', () => {
});
it('should properly clear stack when leaving tabs', () => {
+ cy.visit('http://localhost:8080/');
+
cy.get('#tabs').click();
cy.ionPageVisible('tab1');
@@ -147,6 +151,28 @@ describe('Tabs', () => {
cy.ionPageVisible('tab3-secondary');
cy.ionPageHidden('tab1-secondary');
});
+
+ // Verifies 1 of 2 fixes for https://github.com/ionic-team/ionic-framework/issues/22519
+ it('should show correct tab when switching between tabbed and non-tabbed contexts', () => {
+ cy.visit('http://localhost:8080/routing');
+
+ cy.get('[data-pageid="routing"] #tab1').click();
+ cy.ionPageHidden('routing');
+ cy.ionPageVisible('tab1');
+
+ cy.get('ion-tab-button#tab-button-tab2').click();
+ cy.ionPageHidden('tab1');
+ cy.ionPageVisible('tab2');
+
+ cy.get('[data-pageid="tab2"] #routing').click();
+ cy.ionPageVisible('routing');
+ cy.ionPageHidden('tabs');
+
+ cy.get('[data-pageid="routing"] #tab1').click();
+ cy.ionPageVisible('tab1');
+ cy.ionPageHidden('routing');
+ cy.ionPageHidden('tab2');
+ })
})
describe('Tabs - Swipe to Go Back', () => {
diff --git a/packages/vue/test-app/tests/e2e/support/commands.js b/packages/vue/test-app/tests/e2e/support/commands.js
index 2dc864d2bb..f3a1bd86a2 100644
--- a/packages/vue/test-app/tests/e2e/support/commands.js
+++ b/packages/vue/test-app/tests/e2e/support/commands.js
@@ -71,3 +71,9 @@ Cypress.Commands.add('ionPageDoesNotExist', (pageId) => {
cy.get(`div.ion-page[data-pageid=${pageId}]`)
.should('not.exist')
});
+
+Cypress.Commands.add('routerPush', (path) => {
+ cy.window().then(win => {
+ win.debugRouter.push(path);
+ });
+})