From 0668f629aee5dd4dca063c4f2e2e6babf4629db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bengt=20Wei=C3=9Fe?= Date: Tue, 9 Dec 2025 17:01:45 +0100 Subject: [PATCH] fix(timeouts): clear timeouts --- core/src/components/app/app.tsx | 25 +++++++++++++-------- core/src/components/content/content.tsx | 5 +++++ core/src/components/datetime/datetime.tsx | 6 ++++- core/src/components/fab-list/fab-list.tsx | 11 ++++++++- core/src/components/img/img.tsx | 13 ++++++++++- core/src/components/label/label.tsx | 9 +++++++- core/src/components/searchbar/searchbar.tsx | 18 +++++++++++++-- 7 files changed, 72 insertions(+), 15 deletions(-) diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index f7aba5fb152..c1c7e5366d9 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -14,12 +14,13 @@ import { getIonMode } from '../../global/ionic-global'; }) export class App implements ComponentInterface { private focusVisible?: FocusVisibleUtility; + private loadTimeout?: ReturnType | undefined; @Element() el!: HTMLElement; componentDidLoad() { if (Build.isBrowser) { - rIC(async () => { + this.rIC(async () => { const isHybrid = isPlatform(window, 'hybrid'); if (!config.getBoolean('_testing')) { import('../../utils/tap-click').then((module) => module.startTapClick(config)); @@ -60,6 +61,12 @@ export class App implements ComponentInterface { } } + disconnectedCallback() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } + } + /** * Used to set focus on an element that uses `ion-focusable`. * Do not use this if focusing the element as a result of a keyboard @@ -78,6 +85,14 @@ export class App implements ComponentInterface { } } + private rIC(callback: () => void) { + if ('requestIdleCallback' in window) { + (window as any).requestIdleCallback(callback); + } else { + this.loadTimeout = setTimeout(callback, 32); + } + } + render() { const mode = getIonMode(this); return ( @@ -113,11 +128,3 @@ const needInputShims = () => { return false; }; - -const rIC = (callback: () => void) => { - if ('requestIdleCallback' in window) { - (window as any).requestIdleCallback(callback); - } else { - setTimeout(callback, 32); - } -}; diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index 74e44c63597..0575fca7c86 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -188,6 +188,11 @@ export class Content implements ComponentInterface { this.tabsElement = null; this.tabsLoadCallback = undefined; } + + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + this.resizeTimeout = null; + } } /** diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 3411f28eb57..37d8a490723 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -124,6 +124,7 @@ export class Datetime implements ComponentInterface { private maxParts?: any; private todayParts!: DatetimeParts; private defaultParts!: DatetimeParts; + private loadTimeout: ReturnType | undefined; private prevPresentation: string | null = null; @@ -1077,6 +1078,9 @@ export class Datetime implements ComponentInterface { this.clearFocusVisible(); this.clearFocusVisible = undefined; } + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } } /** @@ -1175,7 +1179,7 @@ export class Datetime implements ComponentInterface { * * We schedule this after everything has had a chance to run. */ - setTimeout(() => { + this.loadTimeout = setTimeout(() => { this.ensureReadyIfVisible(); }, 100); diff --git a/core/src/components/fab-list/fab-list.tsx b/core/src/components/fab-list/fab-list.tsx index 1273673a917..1803f7f2c30 100644 --- a/core/src/components/fab-list/fab-list.tsx +++ b/core/src/components/fab-list/fab-list.tsx @@ -10,6 +10,7 @@ import { getIonMode } from '../../global/ionic-global'; }) export class FabList implements ComponentInterface { @Element() el!: HTMLIonFabElement; + private activateTimeouts: ReturnType[] = []; /** * If `true`, the fab list will show all fab buttons in the list. @@ -18,12 +19,15 @@ export class FabList implements ComponentInterface { @Watch('activated') protected activatedChanged(activated: boolean) { + this.activateTimeouts.forEach(clearTimeout); + this.activateTimeouts = []; + const fabs = Array.from(this.el.querySelectorAll('ion-fab-button')); // if showing the fabs add a timeout, else show immediately const timeout = activated ? 30 : 0; fabs.forEach((fab, i) => { - setTimeout(() => (fab.show = activated), i * timeout); + this.activateTimeouts.push(setTimeout(() => (fab.show = activated), i * timeout)); }); } @@ -32,6 +36,11 @@ export class FabList implements ComponentInterface { */ @Prop() side: 'start' | 'end' | 'top' | 'bottom' = 'bottom'; + disconnectedCallback() { + this.activateTimeouts.forEach(clearTimeout); + this.activateTimeouts = []; + } + render() { const mode = getIonMode(this); return ( diff --git a/core/src/components/img/img.tsx b/core/src/components/img/img.tsx index c83f0fab3cf..0afce19d1d2 100644 --- a/core/src/components/img/img.tsx +++ b/core/src/components/img/img.tsx @@ -16,6 +16,7 @@ import { getIonMode } from '../../global/ionic-global'; export class Img implements ComponentInterface { private io?: IntersectionObserver; private inheritedAttributes: Attributes = {}; + private loadTimeout: ReturnType | undefined; @Element() el!: HTMLElement; @@ -56,7 +57,17 @@ export class Img implements ComponentInterface { this.addIO(); } + disconnectedCallback() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } + } + private addIO() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + this.loadTimeout = undefined; + } if (this.src === undefined) { return; } @@ -82,7 +93,7 @@ export class Img implements ComponentInterface { this.io.observe(this.el); } else { // fall back to setTimeout for Safari and IE - setTimeout(() => this.load(), 200); + this.loadTimeout = setTimeout(() => this.load(), 200); } } diff --git a/core/src/components/label/label.tsx b/core/src/components/label/label.tsx index 6f8ad05a4a7..cea7a844ec4 100644 --- a/core/src/components/label/label.tsx +++ b/core/src/components/label/label.tsx @@ -18,6 +18,7 @@ import type { Color, StyleEventDetail } from '../../interface'; }) export class Label implements ComponentInterface { private inRange = false; + private loadTimeout: ReturnType | undefined; @Element() el!: HTMLElement; @@ -56,12 +57,18 @@ export class Label implements ComponentInterface { componentDidLoad() { if (this.noAnimate) { - setTimeout(() => { + this.loadTimeout = setTimeout(() => { this.noAnimate = false; }, 1000); } } + disconnectedCallback() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } + } + @Watch('color') colorChanged() { this.emitColor(); diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx index 0c038962a53..2830d75b73c 100644 --- a/core/src/components/searchbar/searchbar.tsx +++ b/core/src/components/searchbar/searchbar.tsx @@ -30,6 +30,8 @@ export class Searchbar implements ComponentInterface { private originalIonInput?: EventEmitter; private inputId = `ion-searchbar-${searchbarIds++}`; private inheritedAttributes: Attributes = {}; + private loadTimeout: ReturnType | undefined; + private clearTimeout: ReturnType | undefined; /** * The value of the input when the textarea is focused. @@ -288,11 +290,20 @@ export class Searchbar implements ComponentInterface { this.positionElements(); this.debounceChanged(); - setTimeout(() => { + this.loadTimeout = setTimeout(() => { this.noAnimate = false; }, 300); } + disconnectedCallback() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } + if (this.clearTimeout) { + clearTimeout(this.clearTimeout); + } + } + private emitStyle() { this.ionStyle.emit({ searchbar: true, @@ -358,12 +369,15 @@ export class Searchbar implements ComponentInterface { * Clears the input field and triggers the control change. */ private onClearInput = async (shouldFocus?: boolean) => { + if (this.clearTimeout) { + clearTimeout(this.clearTimeout); + } this.ionClear.emit(); return new Promise((resolve) => { // setTimeout() fixes https://github.com/ionic-team/ionic-framework/issues/7527 // wait for 4 frames - setTimeout(() => { + this.clearTimeout = setTimeout(() => { const value = this.getValue(); if (value !== '') { this.value = '';