From 0435f13669e09c9756dc456d0b049135a6f5cd96 Mon Sep 17 00:00:00 2001 From: huajiqaq Date: Sat, 24 Jan 2026 13:28:33 +0800 Subject: [PATCH 1/3] Fix router history restoration logic and improve findElement with max depth limits --- src/core/components/app/app-class.js | 14 ++++++ src/core/modules/router/component-loader.js | 2 + src/core/modules/router/router-class.js | 43 ++++++++++++++++--- src/react/shared/components-router.js | 2 + .../shared/get-router-initial-component.js | 11 +++-- src/svelte/shared/components-router.js | 15 +++---- .../shared/get-router-initial-component.js | 13 +++--- src/vue/shared/components-router.js | 2 + .../shared/get-router-initial-component.js | 11 +++-- 9 files changed, 85 insertions(+), 28 deletions(-) diff --git a/src/core/components/app/app-class.js b/src/core/components/app/app-class.js index 0d55cada48..77c94117b2 100644 --- a/src/core/components/app/app-class.js +++ b/src/core/components/app/app-class.js @@ -14,6 +14,20 @@ import $jsx from '../../shared/$jsx.js'; class Framework7 extends Framework7Class { constructor(params = {}) { + const routes = params.routes; + if (routes) { + params.routes = routes.map(route => { + const newRoute = { ...route }; + const options = newRoute.options ||= {}; + const props = options.props ||= {}; + Object.defineProperty(props, 'routeId', { + get: () => `${Date.now()}_${Math.random().toString(36).slice(2)}`, + enumerable: true, + configurable: true + }); + return newRoute; + }); + } super(params); // eslint-disable-next-line if (Framework7.instance && typeof window !== 'undefined') { diff --git a/src/core/modules/router/component-loader.js b/src/core/modules/router/component-loader.js index 3e511c0407..9318123a87 100644 --- a/src/core/modules/router/component-loader.js +++ b/src/core/modules/router/component-loader.js @@ -106,6 +106,7 @@ export default { if (options.componentOptions && options.componentOptions.root) { componentRoot = options.componentOptions.root; } + const routeId = componentProps.routeId; app.component .create(componentFunction, componentProps, { context: componentContext, @@ -113,6 +114,7 @@ export default { root: componentRoot, }) .then((createdComponent) => { + if (routeId) createdComponent.el.routeId = routeId; resolve(createdComponent.el); }) .catch((err) => { diff --git a/src/core/modules/router/router-class.js b/src/core/modules/router/router-class.js index 5dab0ee0b5..ffeeb6d3c3 100644 --- a/src/core/modules/router/router-class.js +++ b/src/core/modules/router/router-class.js @@ -208,11 +208,17 @@ class Router extends Framework7Class { return router.findElement('.page', router.tempDom); } - findElement(stringSelector, container) { + findElement(stringSelector, container, depth = 0) { const router = this; const view = router.view; const app = router.app; + const MAX_DEPTH = 10; + if (depth > MAX_DEPTH) { + console.error('Framework7: findElement recursion depth exceeded when searching for "' + stringSelector + '". Please check the container parameter:', container); + throw new Error('Framework7: findElement recursion depth exceeded when searching for "' + stringSelector + '"'); + } + // Modals Selector const modalsSelector = '.popup, .dialog, .popover, .actions-modal, .sheet-modal, .login-screen, .page'; @@ -236,7 +242,7 @@ class Router extends Framework7Class { } if (found.length === 1) return found; - found = router.findElement(selector, $container); + found = router.findElement(selector, $container, depth + 1); if (found && found.length === 1) return found; if (found && found.length > 1) return $(found[0]); return undefined; @@ -730,12 +736,14 @@ class Router extends Framework7Class { if (callback === 'mounted') { attachEvents(); } + + const routeId = page.routeId || page.route.url; if (callback === 'init') { if ( restoreScrollTopOnBack && (from === 'previous' || !from) && to === 'current' && - router.scrollHistory[page.route.url] && + router.scrollHistory[routeId] && !$pageEl.hasClass('no-restore-scroll') ) { let $pageContent = $pageEl.find('.page-content'); @@ -748,7 +756,7 @@ class Router extends Framework7Class { ); }); } - $pageContent.scrollTop(router.scrollHistory[page.route.url]); + $pageContent.scrollTop(router.scrollHistory[routeId]); } attachEvents(); if ($pageEl[0].f7PageInitialized) { @@ -775,11 +783,11 @@ class Router extends Framework7Class { ); }); } - router.scrollHistory[page.route.url] = $pageContent.scrollTop(); + router.scrollHistory[routeId] = $pageContent.scrollTop(); } if (restoreScrollTopOnBack && callback === 'beforeOut' && from === 'current' && to === 'next') { // Delete scroll position - delete router.scrollHistory[page.route.url]; + delete router.scrollHistory[routeId]; } $pageEl.trigger(colonName, page); @@ -904,6 +912,11 @@ class Router extends Framework7Class { 'Framework7: wrong or not complete browserHistory configuration, trying to guess browserHistoryRoot', ); browserHistoryRoot = location.pathname.split('index.html')[0]; + } else if (browserHistory && !browserHistoryRoot) { + if (documentUrl.includes('index.html')) { + history.replaceState(history.state, document.title, location.pathname.replace(/\/index\.html/gi, '/') + location.search + location.hash); + documentUrl = documentUrl.replace(/\/index\.html/gi, '/'); + } } if (!browserHistory || !browserHistoryOnLoad) { if (!initialUrl) { @@ -930,6 +943,13 @@ class Router extends Framework7Class { initialUrl = documentUrl; } router.restoreHistory(); + + const initialRoute = router.findMatchingRoute(initialUrl); + const anotherViewName = initialRoute.route.viewName; + if (initialRoute && anotherViewName && app.view[anotherViewName] !== view) { + initialUrl = router.params.url || documentUrl.split(browserHistorySeparator)[0] || '/'; + } + if (router.history.indexOf(initialUrl) >= 0) { router.history = router.history.slice(0, router.history.indexOf(initialUrl) + 1); } else if (router.params.url === initialUrl) { @@ -941,13 +961,22 @@ class Router extends Framework7Class { ) { initialUrl = router.history[router.history.length - 1]; } else { - router.history = [documentUrl.split(browserHistorySeparator)[0] || '/', initialUrl]; + router.history = [router.params.url || documentUrl.split(browserHistorySeparator)[0] || '/', initialUrl]; } if (router.history.length > 1) { historyRestored = true; } else { router.history = []; } + + router.propsHistory = []; + for (let i = 0; i < router.history.length; i++) { + const navigateUrl = router.history[i]; + const route = router.findMatchingRoute(navigateUrl); + const anotherViewName = route.route.viewName; + if (anotherViewName && router.view !== app.views[anotherViewName]) continue; + router.propsHistory.push({ ...(route?.route?.options?.props || {}) }); + } router.saveHistory(); } diff --git a/src/react/shared/components-router.js b/src/react/shared/components-router.js index f2701b2aa0..4ed9d9529a 100644 --- a/src/react/shared/components-router.js +++ b/src/react/shared/components-router.js @@ -54,6 +54,7 @@ export default { let resolved; const childrenBefore = getChildrenArray(el); + const routeId = pageData.props.routeId function onDidUpdate(componentRouterData) { if (componentRouterData !== viewRouter || resolved) return; const childrenAfter = getChildrenArray(el); @@ -62,6 +63,7 @@ export default { const pageEl = el.children[el.children.length - 1]; pageData.el = pageEl; + if (routeId) pageEl.routeId = routeId; resolve(pageEl); resolved = true; diff --git a/src/react/shared/get-router-initial-component.js b/src/react/shared/get-router-initial-component.js index 53d5c2b65b..2e347eb046 100644 --- a/src/react/shared/get-router-initial-component.js +++ b/src/react/shared/get-router-initial-component.js @@ -4,10 +4,13 @@ export const getRouterInitialComponent = (router, initialComponent) => { let initialComponentData; const { initialUrl } = router.getInitialUrl(); const initialRoute = router.findMatchingRoute(initialUrl); - let routeProps = {}; - - if (initialRoute && initialRoute.route && initialRoute.route.options) { - routeProps = initialRoute.route.options.props; + let routeProps = routeProps = router.propsHistory[router.propsHistory.length - 1]; + // Store route props first, if not present, fallback to route options + if (!routeProps) { + if (initialRoute && initialRoute.route && initialRoute.route.options) { + routeProps = { ...initialRoute.route.options.props }; + router.propsHistory.push(routeProps); + } } const isMasterRoute = (route) => { diff --git a/src/svelte/shared/components-router.js b/src/svelte/shared/components-router.js index c1d42cd63e..436dc52be6 100644 --- a/src/svelte/shared/components-router.js +++ b/src/svelte/shared/components-router.js @@ -22,7 +22,7 @@ export default { openIn(router, navigateUrl, options) { return routerOpenIn(router, navigateUrl, options); }, - pageComponentLoader({ routerEl, component, options, resolve, reject, direction }) { + pageComponentLoader({ routerEl, component, options, resolve, reject }) { const router = this; const routerId = router.id; const el = routerEl; @@ -54,25 +54,24 @@ export default { let resolved; const childrenBefore = getChildrenArray(el); + const routeId = pageData.props.routeId function onDidUpdate(componentRouterData) { if (componentRouterData !== viewRouter || resolved) return; const childrenAfter = getChildrenArray(el); if (hasSameChildren(childrenBefore, childrenAfter)) return; app.f7events.off('viewRouterDidUpdate', onDidUpdate); - const pageEl = el.children[direction === 'backward' ? 0 : el.children.length - 1]; + const pageEl = el.children[el.children.length - 1]; pageData.el = pageEl; + if (routeId) pageEl.routeId = routeId; + resolve(pageEl); - viewRouter.setPages(viewRouter.pages); resolved = true; } app.f7events.on('viewRouterDidUpdate', onDidUpdate); - if (direction === 'backward') { - viewRouter.pages.unshift(pageData); - } else { - viewRouter.pages.push(pageData); - } + + viewRouter.pages.push(pageData); viewRouter.setPages(viewRouter.pages); }, removePage($pageEl) { diff --git a/src/svelte/shared/get-router-initial-component.js b/src/svelte/shared/get-router-initial-component.js index ac3ea31564..2e347eb046 100644 --- a/src/svelte/shared/get-router-initial-component.js +++ b/src/svelte/shared/get-router-initial-component.js @@ -4,10 +4,13 @@ export const getRouterInitialComponent = (router, initialComponent) => { let initialComponentData; const { initialUrl } = router.getInitialUrl(); const initialRoute = router.findMatchingRoute(initialUrl); - let routeProps = {}; - - if (initialRoute && initialRoute.route && initialRoute.route.options) { - routeProps = initialRoute.route.options.props; + let routeProps = routeProps = router.propsHistory[router.propsHistory.length - 1]; + // Store route props first, if not present, fallback to route options + if (!routeProps) { + if (initialRoute && initialRoute.route && initialRoute.route.options) { + routeProps = { ...initialRoute.route.options.props }; + router.propsHistory.push(routeProps); + } } const isMasterRoute = (route) => { @@ -30,8 +33,8 @@ export const getRouterInitialComponent = (router, initialComponent) => { props: { f7route: initialRoute, f7router: router, - ...initialRoute.params, ...routeProps, + ...initialRoute.params, }, }; } diff --git a/src/vue/shared/components-router.js b/src/vue/shared/components-router.js index 3bb0c9763b..b78785a2e9 100644 --- a/src/vue/shared/components-router.js +++ b/src/vue/shared/components-router.js @@ -54,6 +54,7 @@ export default { let resolved; const childrenBefore = getChildrenArray(el); + const routeId = pageData.props.routeId function onDidUpdate(componentRouterData) { if (componentRouterData !== viewRouter || resolved) return; const childrenAfter = getChildrenArray(el); @@ -62,6 +63,7 @@ export default { const pageEl = el.children[el.children.length - 1]; pageData.el = pageEl; + if (routeId) pageEl.routeId = routeId; resolve(pageEl); resolved = true; diff --git a/src/vue/shared/get-router-initial-component.js b/src/vue/shared/get-router-initial-component.js index 53d5c2b65b..2e347eb046 100644 --- a/src/vue/shared/get-router-initial-component.js +++ b/src/vue/shared/get-router-initial-component.js @@ -4,10 +4,13 @@ export const getRouterInitialComponent = (router, initialComponent) => { let initialComponentData; const { initialUrl } = router.getInitialUrl(); const initialRoute = router.findMatchingRoute(initialUrl); - let routeProps = {}; - - if (initialRoute && initialRoute.route && initialRoute.route.options) { - routeProps = initialRoute.route.options.props; + let routeProps = routeProps = router.propsHistory[router.propsHistory.length - 1]; + // Store route props first, if not present, fallback to route options + if (!routeProps) { + if (initialRoute && initialRoute.route && initialRoute.route.options) { + routeProps = { ...initialRoute.route.options.props }; + router.propsHistory.push(routeProps); + } } const isMasterRoute = (route) => { From 2b6dfc2c9b61b3dd86e69ba25d03c3894ce04beb Mon Sep 17 00:00:00 2001 From: huajiqaq Date: Sat, 24 Jan 2026 13:29:01 +0800 Subject: [PATCH 2/3] Fix router history restoration to only restore visible pages and prevent unnecessary navigation --- src/core/shared/history.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/shared/history.js b/src/core/shared/history.js index 1d0d5db06f..79e71c4e2b 100644 --- a/src/core/shared/history.js +++ b/src/core/shared/history.js @@ -40,6 +40,10 @@ const History = { if (!state) state = {}; app.views.forEach((view) => { + const pageEl = view.el; + const pageElstyle = window.getComputedStyle(pageEl); + if (pageElstyle.display === 'none') return; + const router = view.router; let viewState = state[view.id]; if (!viewState && view.params.browserHistory) { From af19da86c0a2fe6dbfa566d7e76e0607657aa97a Mon Sep 17 00:00:00 2001 From: huajiqaq Date: Sat, 24 Jan 2026 21:59:39 +0800 Subject: [PATCH 3/3] Fix initial page routeId and improve router handling Assigns routeId to the initial page element in React, Svelte, and Vue view components if available. Fixes a typo in get-router-initial-component.js for all frameworks. Improves router-class.js to handle /index.html more robustly and adds a warning when the initial page is not found, prompting the user to set the data-url attribute. --- src/core/modules/router/router-class.js | 16 ++++++++++++---- src/react/components/view.jsx | 4 ++++ src/react/shared/get-router-initial-component.js | 2 +- src/svelte/components/view.svelte | 4 ++++ .../shared/get-router-initial-component.js | 2 +- src/vue/components/view.vue | 4 ++++ src/vue/shared/get-router-initial-component.js | 2 +- 7 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/core/modules/router/router-class.js b/src/core/modules/router/router-class.js index ffeeb6d3c3..f9989cfe1d 100644 --- a/src/core/modules/router/router-class.js +++ b/src/core/modules/router/router-class.js @@ -913,9 +913,13 @@ class Router extends Framework7Class { ); browserHistoryRoot = location.pathname.split('index.html')[0]; } else if (browserHistory && !browserHistoryRoot) { - if (documentUrl.includes('index.html')) { - history.replaceState(history.state, document.title, location.pathname.replace(/\/index\.html/gi, '/') + location.search + location.hash); - documentUrl = documentUrl.replace(/\/index\.html/gi, '/'); + if (documentUrl.startsWith('/index.html')) { + window.history.replaceState( + window.history.state, + document.title, + location.pathname.replace(/^\/index\.html/, '/') + location.search + location.hash + ); + documentUrl = documentUrl.replace(/^\/index\.html/, '/'); } } if (!browserHistory || !browserHistoryOnLoad) { @@ -947,7 +951,11 @@ class Router extends Framework7Class { const initialRoute = router.findMatchingRoute(initialUrl); const anotherViewName = initialRoute.route.viewName; if (initialRoute && anotherViewName && app.view[anotherViewName] !== view) { - initialUrl = router.params.url || documentUrl.split(browserHistorySeparator)[0] || '/'; + if (router.params.url) { + initialUrl = router.params.url; + } else { + console.error('Framework7: Initial page not found, using default URL (null). Please set the data-url attribute on your view element.', view); + } } if (router.history.indexOf(initialUrl) >= 0) { diff --git a/src/react/components/view.jsx b/src/react/components/view.jsx index 9a92a6948d..bda28bd28e 100644 --- a/src/react/components/view.jsx +++ b/src/react/components/view.jsx @@ -182,6 +182,8 @@ const View = (props) => { f7View.current.init(elRef.current); if (initialPage) { initialPage.el = f7View.current.router.currentPageEl; + const routeId = pages?.[0]?.props?.routeId; + if (routeId) initialPage.el.routeId = routeId; if (initialRoute && initialRoute.route && initialRoute.route.keepAlive) { initialRoute.route.keepAliveData = { pageEl: initialPage.el }; } @@ -192,6 +194,8 @@ const View = (props) => { f7View.current.init(elRef.current); if (initialPage) { initialPage.el = f7View.current.router.currentPageEl; + const routeId = pages?.[0]?.props?.routeId; + if (routeId) initialPage.el.routeId = routeId; if (initialRoute && initialRoute.route && initialRoute.route.keepAlive) { initialRoute.route.keepAliveData = { pageEl: initialPage.el }; } diff --git a/src/react/shared/get-router-initial-component.js b/src/react/shared/get-router-initial-component.js index 2e347eb046..7a665a412e 100644 --- a/src/react/shared/get-router-initial-component.js +++ b/src/react/shared/get-router-initial-component.js @@ -4,7 +4,7 @@ export const getRouterInitialComponent = (router, initialComponent) => { let initialComponentData; const { initialUrl } = router.getInitialUrl(); const initialRoute = router.findMatchingRoute(initialUrl); - let routeProps = routeProps = router.propsHistory[router.propsHistory.length - 1]; + let routeProps = router.propsHistory[router.propsHistory.length - 1]; // Store route props first, if not present, fallback to route options if (!routeProps) { if (initialRoute && initialRoute.route && initialRoute.route.options) { diff --git a/src/svelte/components/view.svelte b/src/svelte/components/view.svelte index 7b79746633..0553bcbcce 100644 --- a/src/svelte/components/view.svelte +++ b/src/svelte/components/view.svelte @@ -132,6 +132,8 @@ f7View.init(el); if (initialPage) { initialPage.el = f7View.router.currentPageEl; + const routeId = pages?.[0]?.props?.routeId; + if (routeId) initialPage.el.routeId = routeId; if (initialRoute && initialRoute.route && initialRoute.route.keepAlive) { initialRoute.route.keepAliveData = { pageEl: initialPage.el }; } @@ -142,6 +144,8 @@ f7View.init(el); if (initialPage) { initialPage.el = f7View.router.currentPageEl; + const routeId = pages?.[0]?.props?.routeId; + if (routeId) initialPage.el.routeId = routeId; if (initialRoute && initialRoute.route && initialRoute.route.keepAlive) { initialRoute.route.keepAliveData = { pageEl: initialPage.el }; } diff --git a/src/svelte/shared/get-router-initial-component.js b/src/svelte/shared/get-router-initial-component.js index 2e347eb046..7a665a412e 100644 --- a/src/svelte/shared/get-router-initial-component.js +++ b/src/svelte/shared/get-router-initial-component.js @@ -4,7 +4,7 @@ export const getRouterInitialComponent = (router, initialComponent) => { let initialComponentData; const { initialUrl } = router.getInitialUrl(); const initialRoute = router.findMatchingRoute(initialUrl); - let routeProps = routeProps = router.propsHistory[router.propsHistory.length - 1]; + let routeProps = router.propsHistory[router.propsHistory.length - 1]; // Store route props first, if not present, fallback to route options if (!routeProps) { if (initialRoute && initialRoute.route && initialRoute.route.options) { diff --git a/src/vue/components/view.vue b/src/vue/components/view.vue index a9a3d1127d..6e231f59e4 100644 --- a/src/vue/components/view.vue +++ b/src/vue/components/view.vue @@ -275,6 +275,8 @@ export default { f7View.init(elRef.value); if (initialPage) { initialPage.el = f7View.router.currentPageEl; + const routeId = pages?.value[0]?.props?.routeId; + if (routeId) initialPage.el.routeId = routeId; if (initialRoute && initialRoute.route && initialRoute.route.keepAlive) { initialRoute.route.keepAliveData = { pageEl: initialPage.el }; } @@ -285,6 +287,8 @@ export default { f7View.init(elRef.value); if (initialPage) { initialPage.el = f7View.router.currentPageEl; + const routeId = pages?.value[0]?.props?.routeId; + if (routeId) initialPage.el.routeId = routeId; if (initialRoute && initialRoute.route && initialRoute.route.keepAlive) { initialRoute.route.keepAliveData = { pageEl: initialPage.el }; } diff --git a/src/vue/shared/get-router-initial-component.js b/src/vue/shared/get-router-initial-component.js index 2e347eb046..7a665a412e 100644 --- a/src/vue/shared/get-router-initial-component.js +++ b/src/vue/shared/get-router-initial-component.js @@ -4,7 +4,7 @@ export const getRouterInitialComponent = (router, initialComponent) => { let initialComponentData; const { initialUrl } = router.getInitialUrl(); const initialRoute = router.findMatchingRoute(initialUrl); - let routeProps = routeProps = router.propsHistory[router.propsHistory.length - 1]; + let routeProps = router.propsHistory[router.propsHistory.length - 1]; // Store route props first, if not present, fallback to route options if (!routeProps) { if (initialRoute && initialRoute.route && initialRoute.route.options) {