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..f9989cfe1d 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,15 @@ 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.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) { if (!initialUrl) { @@ -930,6 +947,17 @@ 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) { + 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) { router.history = router.history.slice(0, router.history.indexOf(initialUrl) + 1); } else if (router.params.url === initialUrl) { @@ -941,13 +969,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/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) { 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/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..7a665a412e 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 = 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/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/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..7a665a412e 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 = 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/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/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..7a665a412e 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 = 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) => {