mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(input): prevent placeholder from overlapping start slot during scroll assist (#30896)
Issue number: resolves internal --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? On iOS, when focusing an `ion-input` or `ion-textarea` that requires scrolling into view (scroll assist), the placeholder text shifts to the left and overlaps any content in the start slot (e.g., icons). This occurs because the cloned input used during scroll assist is positioned at the container's left edge rather than at the native input's actual position. Additionally, when quickly switching between inputs before scroll assist completes, focus jumps back to the original input. ## What is the new behavior? The cloned input is now positioned at the same offset as the native input, preventing the placeholder from shifting or overlapping start slot content during scroll assist. This works correctly for both LTR and RTL layouts. Also, scroll assist no longer steals focus back if the user has moved focus to another element while scrolling was in progress. ## Does this introduce a breaking change? - [ ] Yes - [X] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Current dev build: ``` 8.7.16-dev.11767042721.11309185 ```
This commit is contained in:
@@ -165,9 +165,13 @@
|
||||
// otherwise the .input-cover will not be rendered at all
|
||||
// The input cover is not clickable when the input is disabled
|
||||
.cloned-input {
|
||||
@include position(0, null, 0, 0);
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
// Reset height since absolute positioning with top/bottom handles sizing
|
||||
height: auto;
|
||||
max-height: none;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -205,9 +205,13 @@
|
||||
// otherwise the .input-cover will not be rendered at all
|
||||
// The input cover is not clickable when the input is disabled
|
||||
.cloned-input {
|
||||
@include position(0, null, 0, 0);
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
// Reset height since absolute positioning with top/bottom handles sizing
|
||||
height: auto;
|
||||
max-height: none;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -68,11 +68,26 @@ const addClone = (
|
||||
if (disabledClonedInput) {
|
||||
clonedEl.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the clone at the same horizontal offset as the native input
|
||||
* to prevent the placeholder from overlapping start slot content (e.g., icons).
|
||||
*/
|
||||
const doc = componentEl.ownerDocument!;
|
||||
const isRTL = doc.dir === 'rtl';
|
||||
|
||||
if (isRTL) {
|
||||
const parentWidth = (parentEl as HTMLElement).offsetWidth;
|
||||
const startOffset = parentWidth - inputEl.offsetLeft - inputEl.offsetWidth;
|
||||
clonedEl.style.insetInlineStart = `${startOffset}px`;
|
||||
} else {
|
||||
clonedEl.style.insetInlineStart = `${inputEl.offsetLeft}px`;
|
||||
}
|
||||
|
||||
parentEl.appendChild(clonedEl);
|
||||
cloneMap.set(componentEl, clonedEl);
|
||||
|
||||
const doc = componentEl.ownerDocument!;
|
||||
const tx = doc.dir === 'rtl' ? 9999 : -9999;
|
||||
const tx = isRTL ? 9999 : -9999;
|
||||
componentEl.style.pointerEvents = 'none';
|
||||
inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0) scale(0)`;
|
||||
};
|
||||
|
||||
@@ -291,8 +291,14 @@ const jsSetFocus = async (
|
||||
// give the native text input focus
|
||||
relocateInput(componentEl, inputEl, false, scrollData.inputSafeY);
|
||||
|
||||
// ensure this is the focused input
|
||||
setManualFocus(inputEl);
|
||||
/**
|
||||
* If focus has moved to another element while scroll assist was running,
|
||||
* don't steal focus back. This prevents focus jumping when users
|
||||
* quickly switch between inputs or tap other elements.
|
||||
*/
|
||||
if (document.activeElement === inputEl) {
|
||||
setManualFocus(inputEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the input is about to be blurred
|
||||
|
||||
Reference in New Issue
Block a user