diff --git a/src/core/components/data-table/data-table-class.js b/src/core/components/data-table/data-table-class.js index 7f6d812a8d..99d348df2e 100644 --- a/src/core/components/data-table/data-table-class.js +++ b/src/core/components/data-table/data-table-class.js @@ -4,6 +4,11 @@ import Framework7Class from '../../shared/class.js'; class DataTable extends Framework7Class { constructor(app, params = {}) { + // El + const $el = $(params.el); + // Because temporarily does not support virtual lists, so if the element does not exist, we can return directly. + if ($el[0].f7DataTable) return $el[0].f7DataTable; + super(params, [app]); const table = this; @@ -15,19 +20,11 @@ class DataTable extends Framework7Class { table.params = extend(defaults, params); - // El - const $el = $(table.params.el); if ($el.length === 0) return undefined; table.$el = $el; table.el = $el[0]; - if (table.$el[0].f7DataTable) { - const instance = table.$el[0].f7DataTable; - table.destroy(); - return instance; - } - table.$el[0].f7DataTable = table; extend(table, { diff --git a/src/core/components/form/form.js b/src/core/components/form/form.js index f111ae7c3d..5fb7081c70 100644 --- a/src/core/components/form/form.js +++ b/src/core/components/form/form.js @@ -68,6 +68,10 @@ const FormStorage = { const $formEl = $(formEl); const formId = $formEl.attr('id'); if (!formId) return; + // Check using data attribute + if ($formEl[0]._f7FormStorageInitialized) { + return; + } const initialData = app.form.getFormData(formId); if (initialData) { app.form.fillFromData($formEl, initialData); @@ -80,10 +84,12 @@ const FormStorage = { app.emit('formStoreData', $formEl[0], data); } $formEl.on('change submit', store); + $formEl[0]._f7FormStorageInitialized = true; // Set flag }, destroy(formEl) { const $formEl = $(formEl); $formEl.off('change submit'); + delete $formEl[0]._f7FormStorageInitialized; // Clear flag }, }; diff --git a/src/core/components/infinite-scroll/infinite-scroll.js b/src/core/components/infinite-scroll/infinite-scroll.js index 59a177f55c..230afe69a8 100644 --- a/src/core/components/infinite-scroll/infinite-scroll.js +++ b/src/core/components/infinite-scroll/infinite-scroll.js @@ -42,6 +42,8 @@ const InfiniteScroll = { app.infiniteScroll.handle(this, e); } $el.each((element) => { + // skip if already initialized + if (element.f7InfiniteScrollHandler) return; element.f7InfiniteScrollHandler = scrollHandler; element.addEventListener('scroll', element.f7InfiniteScrollHandler); }); diff --git a/src/core/components/pull-to-refresh/pull-to-refresh-class.js b/src/core/components/pull-to-refresh/pull-to-refresh-class.js index 1b37deb982..104f2f338f 100644 --- a/src/core/components/pull-to-refresh/pull-to-refresh-class.js +++ b/src/core/components/pull-to-refresh/pull-to-refresh-class.js @@ -5,10 +5,12 @@ import { getDevice } from '../../shared/get-device.js'; class PullToRefresh extends Framework7Class { constructor(app, el) { + const $el = $(el); + if ($el[0].f7PullToRefresh) return $el[0].f7PullToRefresh; + super({}, [app]); const ptr = this; const device = getDevice(); - const $el = $(el); const $preloaderEl = $el.find('.ptr-preloader'); ptr.$el = $el; @@ -27,7 +29,8 @@ class PullToRefresh extends Framework7Class { ptr.done = function done() { const $transitionTarget = isMaterial ? $preloaderEl : $el; const onTranstionEnd = (e) => { - if ($(e.target).closest($preloaderEl).length) return; + // Material Design platform currently does not support bottom preloader animation. + if (!isMaterial && $(e.target).closest($preloaderEl).length) return; $el.removeClass('ptr-transitioning ptr-pull-up ptr-pull-down ptr-closing'); $el.trigger('ptr:done'); ptr.emit('local::done ptrDone', $el[0]); @@ -204,7 +207,7 @@ class PullToRefresh extends Framework7Class { ((!ptr.bottom && ptrScrollableEl.scrollTop > 0) || (ptr.bottom && ptrScrollableEl.scrollTop < - ptrScrollableEl.scrollHeight - ptrScrollableEl.offsetHeight)) + ptrScrollableEl.scrollHeight - ptrScrollableEl.offsetHeight)) ) { targetIsScrollable = true; } @@ -453,7 +456,7 @@ class PullToRefresh extends Framework7Class { ((!ptr.bottom && ptrScrollableEl.scrollTop > 0) || (ptr.bottom && ptrScrollableEl.scrollTop < - ptrScrollableEl.scrollHeight - ptrScrollableEl.offsetHeight)) + ptrScrollableEl.scrollHeight - ptrScrollableEl.offsetHeight)) ) { targetIsScrollable = true; } diff --git a/src/core/components/swiper/swiper.js b/src/core/components/swiper/swiper.js index 5c72e8f435..f35d8b616e 100644 --- a/src/core/components/swiper/swiper.js +++ b/src/core/components/swiper/swiper.js @@ -4,6 +4,7 @@ import Swiper from 'swiper/bundle'; import { register } from 'swiper/element/bundle'; import $ from '../../shared/dom7.js'; import ConstructorMethods from '../../shared/constructor-methods.js'; +import { extend } from '../../shared/utils.js'; register(); @@ -19,8 +20,14 @@ function initSwiper(swiperEl) { const app = this; const $swiperEl = $(swiperEl); if ($swiperEl.length === 0) return; - const isElement = $swiperEl[0].swiper && $swiperEl[0].swiper.isElement; + if (!$swiperEl[0].swiper) return; if ($swiperEl[0].swiper && !$swiperEl[0].swiper.isElement) return; + + // skip if already initialized + if ($swiperEl[0]._f7SwiperInitialized) return; + $swiperEl[0]._f7SwiperInitialized = true; + + const isElement = $swiperEl[0].swiper && $swiperEl[0].swiper.isElement; let initialSlide; let params = {}; let isTabs; @@ -113,11 +120,13 @@ export default { }, create() { const app = this; - app.swiper = ConstructorMethods({ - defaultSelector: '.swiper', + app.swiper = extend(ConstructorMethods({ + defaultSelector: ".swiper", constructor: Swiper, - domProp: 'swiper', - }); + domProp: "swiper" + }), { + init: initSwiper.bind(app) + }) }, on: { pageMounted(page) { diff --git a/src/core/components/toolbar/toolbar.js b/src/core/components/toolbar/toolbar.js index ca129d6a75..97c7691fe0 100644 --- a/src/core/components/toolbar/toolbar.js +++ b/src/core/components/toolbar/toolbar.js @@ -45,6 +45,9 @@ const Toolbar = { let highlightTranslate; if ($tabbarEl.hasClass('tabbar-scrollable') && $activeLink && $activeLink[0]) { + if ($activeLink[0].offsetWidth == 0) { + console.warn("ToolBar: ToolBar's scrollable indicator width is 0 because the first active tab width measured as 0."); + }; highlightWidth = `${$activeLink[0].offsetWidth}px`; highlightTranslate = `${$activeLink[0].offsetLeft}px`; } else { diff --git a/src/react/components/page-content.jsx b/src/react/components/page-content.jsx index 192be45db1..e3d4edbfca 100644 --- a/src/react/components/page-content.jsx +++ b/src/react/components/page-content.jsx @@ -104,9 +104,15 @@ const PageContent = (props) => { f7.on('ptrPullEnd', onPtrPullEnd); f7.on('ptrRefresh', onPtrRefresh); f7.on('ptrDone', onPtrDone); + // PTR only initializes in pageInit callback. + // If page-content is added after page initialization, check if PTR exists. + // If not, create it manually. + f7.ptr.create(elRef.current); } if (infinite) { f7.on('infinite', onInfinite); + // Same logic applies to infinite scroll + f7.infiniteScroll.create(elRef.current); } }); }; diff --git a/src/react/components/tabs.jsx b/src/react/components/tabs.jsx index b0620e3733..7f814c2b67 100644 --- a/src/react/components/tabs.jsx +++ b/src/react/components/tabs.jsx @@ -3,6 +3,7 @@ import { useIsomorphicLayoutEffect } from '../shared/use-isomorphic-layout-effec import { classNames, getExtraAttrs } from '../shared/utils.js'; import { colorClasses } from '../shared/mixins.js'; import { TabsSwipeableContext } from '../shared/tabs-swipeable-context.js'; +import { f7ready, f7 } from '../shared/f7.js'; import { setRef } from '../shared/set-ref.js'; /* dts-imports import { SwiperOptions } from 'swiper'; @@ -28,6 +29,13 @@ const Tabs = (props) => { const elRef = useRef(null); useIsomorphicLayoutEffect(() => { + if (swipeable) { + f7ready(() => { + // It only initializes in pageInit callback + // We may need to manually call init() to update the instance + f7.swiper.init(elRef.current) + }); + } if (!swipeable || !swiperParams) return; if (!elRef.current) return; Object.assign(elRef.current, swiperParams); diff --git a/src/svelte/components/page-content.svelte b/src/svelte/components/page-content.svelte index ba56d759d0..85f2ed5155 100644 --- a/src/svelte/components/page-content.svelte +++ b/src/svelte/components/page-content.svelte @@ -91,9 +91,15 @@ app.f7.on('ptrPullEnd', onPtrPullEnd); app.f7.on('ptrRefresh', onPtrRefresh); app.f7.on('ptrDone', onPtrDone); + // PTR only initializes in pageInit callback. + // If page-content is added after page initialization, check if PTR exists. + // If not, create it manually. + app.f7.ptr.create(ptrEl); } if (infinite) { app.f7.on('infinite', onInfinite); + // Same logic applies to infinite scroll + app.f7.infiniteScroll.create(ptrEl); } } function destroyPageContent() { diff --git a/src/svelte/components/tabs.svelte b/src/svelte/components/tabs.svelte index 1a44e247ad..38c0dd84e8 100644 --- a/src/svelte/components/tabs.svelte +++ b/src/svelte/components/tabs.svelte @@ -1,6 +1,7 @@ diff --git a/src/vue/components/page-content.vue b/src/vue/components/page-content.vue index 5305a73273..fbe60ade76 100644 --- a/src/vue/components/page-content.vue +++ b/src/vue/components/page-content.vue @@ -71,11 +71,6 @@ export default { 'ptr:refresh', 'ptr:done', 'infinite', - 'ptrPullStart', - 'ptrPullMove', - 'ptrPullEnd', - 'ptrRefresh', - 'ptrDone', 'tab:hide', 'tab:show', ], @@ -85,27 +80,22 @@ export default { const onPtrPullStart = (el) => { if (elRef.value !== el) return; emit('ptr:pullstart'); - emit('ptrPullStart'); }; const onPtrPullMove = (el) => { if (elRef.value !== el) return; emit('ptr:pullmove'); - emit('ptrPullMove'); }; const onPtrPullEnd = (el) => { if (elRef.value !== el) return; emit('ptr:pullend'); - emit('ptrPullEnd'); }; const onPtrRefresh = (el, done) => { if (elRef.value !== el) return; emit('ptr:refresh', done); - emit('ptrRefresh', done); }; const onPtrDone = (el) => { if (elRef.value !== el) return; emit('ptr:done'); - emit('ptrDone'); }; const onInfinite = (el) => { if (elRef.value !== el) return; @@ -122,9 +112,15 @@ export default { f7.on('ptrPullEnd', onPtrPullEnd); f7.on('ptrRefresh', onPtrRefresh); f7.on('ptrDone', onPtrDone); + // PTR only initializes in pageInit callback. + // If page-content is added after page initialization, check if PTR exists. + // If not, create it manually. + f7.ptr.create(elRef.value); } if (props.infinite) { f7.on('infinite', onInfinite); + // Same logic applies to infinite scroll + f7.infiniteScroll.create(elRef.value); } }); }); diff --git a/src/vue/components/page.vue b/src/vue/components/page.vue index 6428461480..1df9d5a39f 100644 --- a/src/vue/components/page.vue +++ b/src/vue/components/page.vue @@ -376,12 +376,12 @@ export default { hideToolbarOnScroll: props.hideToolbarOnScroll, messagesContent: props.messagesContent || hasMessages, loginScreen: props.loginScreen, - onPtrPullStart, - onPtrPullMove, - onPtrPullEnd, - onPtrRefresh, - onPtrDone, - onInfinite, + "onPtr:pullstart": onPtrPullStart, + "onPtr:pullmove": onPtrPullMove, + "onPtr:pullend": onPtrPullEnd, + "onPtr:refresh": onPtrRefresh, + "onPtr:done": onPtrDone, + "onInfinite": onInfinite }, () => [slotsStatic && slotsStatic(), staticList], ), diff --git a/src/vue/components/tabs.vue b/src/vue/components/tabs.vue index 8d8eb95453..e5bbcd495f 100644 --- a/src/vue/components/tabs.vue +++ b/src/vue/components/tabs.vue @@ -4,12 +4,8 @@ - +
@@ -20,6 +16,7 @@ import { computed, ref, onMounted, provide } from 'vue'; import { classNames } from '../shared/utils.js'; import { colorClasses, colorProps } from '../shared/mixins.js'; +import { f7ready, f7 } from '../shared/f7.js'; export default { name: 'f7-tabs', @@ -37,6 +34,13 @@ export default { const elRef = ref(null); onMounted(() => { + if (props.swipeable) { + f7ready(() => { + // It only initializes in pageInit callback + // We may need to manually call init() to update the instance + f7.swiper.init(elRef.value) + }); + } if (!props.swipeable || !props.swiperParams) return; if (!elRef.value) return; Object.assign(elRef.value, props.swiperParams);