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 @@ + + + + + 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 @@