From 4091b430c68a5be0b00cd606469aebc202dae866 Mon Sep 17 00:00:00 2001 From: droc101 <37421449+droc101@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:14:49 -0500 Subject: [PATCH 1/3] fix(searchbar): fix search icon sometimes being offset incorrectly change the positionPlaceholder in searchbar.tsx to check if iconEl.clientWidth is zero and retry up to ten times if it is, fixing the search icon being incorrectly positioned in some cases closes #30434 --- core/src/components/searchbar/searchbar.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx index 2830d75b73c..9ece8c04382 100644 --- a/core/src/components/searchbar/searchbar.tsx +++ b/core/src/components/searchbar/searchbar.tsx @@ -506,7 +506,7 @@ export class Searchbar implements ComponentInterface { /** * Positions the input placeholder */ - private positionPlaceholder() { + private positionPlaceholder(numTries = 0) { const inputEl = this.nativeInput; if (!inputEl) { return; @@ -539,7 +539,17 @@ export class Searchbar implements ComponentInterface { * such as Dynamic Type on iOS as well as 8px * of padding. */ - const iconLeft = 'calc(50% - ' + (textWidth / 2 + iconEl.clientWidth + 8) + 'px)'; + const iconWidth = iconEl.clientWidth; + if (iconWidth == 0 && numTries < 10) { + /* + * fix for https://github.com/ionic-team/ionic-framework/issues/30434 + * iconEl.clientWidth can very briefly be 0 when this is called from componentDidLoad + * this will try again until it is nonzero + */ + this.positionPlaceholder(numTries + 1); + return; + } + const iconLeft = 'calc(50% - ' + (textWidth / 2 + iconWidth + 8) + 'px)'; // Set the input padding start and icon margin start if (rtl) { From 09d9e451caf7004c1f640a4165c0d1007c7aa50e Mon Sep 17 00:00:00 2001 From: droc101 <37421449+droc101@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:27:09 -0500 Subject: [PATCH 2/3] refactor(searchbar): use ResizeObserver for iconWidth check change the fix for search icon positioning to use a ResizeObserver waiting until width is greater than zero instead of only checking over 10 frames, which could be problematic on slower devices or connections --- core/src/components/searchbar/searchbar.tsx | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx index 9ece8c04382..f2b983ffa3f 100644 --- a/core/src/components/searchbar/searchbar.tsx +++ b/core/src/components/searchbar/searchbar.tsx @@ -506,7 +506,7 @@ export class Searchbar implements ComponentInterface { /** * Positions the input placeholder */ - private positionPlaceholder(numTries = 0) { + private positionPlaceholder() { const inputEl = this.nativeInput; if (!inputEl) { return; @@ -540,15 +540,23 @@ export class Searchbar implements ComponentInterface { * of padding. */ const iconWidth = iconEl.clientWidth; - if (iconWidth == 0 && numTries < 10) { - /* - * fix for https://github.com/ionic-team/ionic-framework/issues/30434 - * iconEl.clientWidth can very briefly be 0 when this is called from componentDidLoad - * this will try again until it is nonzero - */ - this.positionPlaceholder(numTries + 1); + + /** + * Fix for https://github.com/ionic-team/ionic-framework/issues/30434: + * iconEl.clientWidth can very briefly be 0 when this is called from componentDidLoad. + * If it is zero, set up a ResizeObserver and call this function when it observes a width greater than 0. + */ + if (iconWidth === 0) { + const observer = new ResizeObserver((entries) => { + if (entries[0].contentRect.width > 0) { + observer.disconnect(); + this.positionPlaceholder(); + } + }); + observer.observe(iconEl); return; } + const iconLeft = 'calc(50% - ' + (textWidth / 2 + iconWidth + 8) + 'px)'; // Set the input padding start and icon margin start From 4f8e9ac29b1505379ba94f40e73ea9e17ebb057c Mon Sep 17 00:00:00 2001 From: droc101 <37421449+droc101@users.noreply.github.com> Date: Fri, 12 Jun 2026 11:18:30 -0500 Subject: [PATCH 3/3] fix(searchbar): prevent leaking and duplicating ResizeObserver move the ResizeObserver variable for the search icon into the class scope, disconnect it in the disconnectedCallback, and disconnect any existing one before creating a new one --- core/src/components/searchbar/searchbar.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx index f2b983ffa3f..d031db55884 100644 --- a/core/src/components/searchbar/searchbar.tsx +++ b/core/src/components/searchbar/searchbar.tsx @@ -32,6 +32,7 @@ export class Searchbar implements ComponentInterface { private inheritedAttributes: Attributes = {}; private loadTimeout: ReturnType | undefined; private clearTimeout: ReturnType | undefined; + private searchIconResizeObserver?: ResizeObserver; /** * The value of the input when the textarea is focused. @@ -302,6 +303,7 @@ export class Searchbar implements ComponentInterface { if (this.clearTimeout) { clearTimeout(this.clearTimeout); } + this.searchIconResizeObserver?.disconnect(); } private emitStyle() { @@ -547,13 +549,14 @@ export class Searchbar implements ComponentInterface { * If it is zero, set up a ResizeObserver and call this function when it observes a width greater than 0. */ if (iconWidth === 0) { - const observer = new ResizeObserver((entries) => { + this.searchIconResizeObserver?.disconnect(); + this.searchIconResizeObserver = new ResizeObserver((entries) => { if (entries[0].contentRect.width > 0) { - observer.disconnect(); + this.searchIconResizeObserver?.disconnect(); this.positionPlaceholder(); } }); - observer.observe(iconEl); + this.searchIconResizeObserver?.observe(iconEl); return; }