mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
7 Commits
patch-test
...
Fw-4558
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12cae824b1 | ||
|
|
504c90beae | ||
|
|
f663b41644 | ||
|
|
1a99036fd1 | ||
|
|
578981b2d0 | ||
|
|
875091b9ad | ||
|
|
c782c91185 |
28
core/package-lock.json
generated
28
core/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "7.3.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.2.0",
|
||||
"@stencil/core": "^4.2.1",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.7.1",
|
||||
"@stencil/angular-output-target": "^0.8.2",
|
||||
"@stencil/react-output-target": "^0.5.3",
|
||||
"@stencil/sass": "^3.0.5",
|
||||
"@stencil/vue-output-target": "^0.8.6",
|
||||
@@ -1625,18 +1625,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/angular-output-target": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.7.1.tgz",
|
||||
"integrity": "sha512-lxJbCAbyAQVAKGgEpNTjSF7GZZszbrJnNdNVgzuD1hLRFJyElA6kUSL0GQrZMbiPG5lC/cYdbQwpyWHX4xN8mw==",
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.8.2.tgz",
|
||||
"integrity": "sha512-i2Oxq2VPQTo1OoP3iDN39N2f/CDO9crS8oUfGEtjwzMgMNuYSMB2VprFoVDUTwqaCP6N409M8+/wJK3oApTDuQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.0.tgz",
|
||||
"integrity": "sha512-HhxRs/b/VHTCM35lunFCzYajRQeYezsJQGgalENNpkrKUOPMvzv0dalXe8Yn/8p9eyn+GZVZuWLd0CAR4VWBbA==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.1.tgz",
|
||||
"integrity": "sha512-alYwqVwxfD0n6HKRVJqJoTzQNnf44n/sddvjNu3JMEn3sfY/Ag7rpmwUntYjtJmRut+or+9gPPgIJviCuKi4yQ==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -11517,16 +11517,16 @@
|
||||
}
|
||||
},
|
||||
"@stencil/angular-output-target": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.7.1.tgz",
|
||||
"integrity": "sha512-lxJbCAbyAQVAKGgEpNTjSF7GZZszbrJnNdNVgzuD1hLRFJyElA6kUSL0GQrZMbiPG5lC/cYdbQwpyWHX4xN8mw==",
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.8.2.tgz",
|
||||
"integrity": "sha512-i2Oxq2VPQTo1OoP3iDN39N2f/CDO9crS8oUfGEtjwzMgMNuYSMB2VprFoVDUTwqaCP6N409M8+/wJK3oApTDuQ==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.0.tgz",
|
||||
"integrity": "sha512-HhxRs/b/VHTCM35lunFCzYajRQeYezsJQGgalENNpkrKUOPMvzv0dalXe8Yn/8p9eyn+GZVZuWLd0CAR4VWBbA=="
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.1.tgz",
|
||||
"integrity": "sha512-alYwqVwxfD0n6HKRVJqJoTzQNnf44n/sddvjNu3JMEn3sfY/Ag7rpmwUntYjtJmRut+or+9gPPgIJviCuKi4yQ=="
|
||||
},
|
||||
"@stencil/react-output-target": {
|
||||
"version": "0.5.3",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.2.0",
|
||||
"@stencil/core": "^4.2.1",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -47,7 +47,7 @@
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.7.1",
|
||||
"@stencil/angular-output-target": "^0.8.2",
|
||||
"@stencil/react-output-target": "^0.5.3",
|
||||
"@stencil/sass": "^3.0.5",
|
||||
"@stencil/vue-output-target": "^0.8.6",
|
||||
|
||||
@@ -20,6 +20,26 @@
|
||||
* Note: Code inside of this if-block will
|
||||
* not run in an SSR environment.
|
||||
*/
|
||||
export const win: Window | undefined = typeof window !== 'undefined' ? window : undefined;
|
||||
|
||||
/**
|
||||
* Even listeners on the window typically expect
|
||||
* Event types for the listener parameter. If you want to listen
|
||||
* on the window for certain CustomEvent types you can add that definition
|
||||
* here as long as you are using the "win" utility below.
|
||||
*/
|
||||
type IonicWindow = Window & {
|
||||
addEventListener(
|
||||
type: 'ionKeyboardDidShow',
|
||||
listener: (ev: CustomEvent<{ keyboardHeight: number }>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: 'ionKeyboardDidShow',
|
||||
listener: (ev: CustomEvent<{ keyboardHeight: number }>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
};
|
||||
|
||||
export const win: IonicWindow | undefined = typeof window !== 'undefined' ? window : undefined;
|
||||
|
||||
export const doc: Document | undefined = typeof document !== 'undefined' ? document : undefined;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { KeyboardResizeOptions } from '@capacitor/keyboard';
|
||||
import { win } from '@utils/browser';
|
||||
|
||||
import { getScrollElement, scrollByPoint } from '../../content';
|
||||
import { raf } from '../../helpers';
|
||||
@@ -34,6 +35,98 @@ export const enableScrollAssist = (
|
||||
const addScrollPadding =
|
||||
enableScrollPadding && (keyboardResize === undefined || keyboardResize.mode === KeyboardResize.None);
|
||||
|
||||
/**
|
||||
* This tracks whether or not the keyboard has been
|
||||
* presented for a single focused text field. Note
|
||||
* that it does not track if the keyboard is open
|
||||
* in general such as if the keyboard is open for
|
||||
* a different focused text field.
|
||||
*/
|
||||
let hasKeyboardBeenPresentedForTextField = false;
|
||||
|
||||
/**
|
||||
* When adding scroll padding we need to know
|
||||
* how much of the viewport the keyboard obscures.
|
||||
* We do this by subtracting the keyboard height
|
||||
* from the platform height.
|
||||
*
|
||||
* If we compute this value when switching between
|
||||
* inputs then the webview may already be resized.
|
||||
* At this point, `win.innerHeight` has already accounted
|
||||
* for the keyboard meaning we would then subtract
|
||||
* the keyboard height again. This will result in the input
|
||||
* being scrolled more than it needs to.
|
||||
*/
|
||||
const platformHeight = win !== undefined ? win.innerHeight : 0;
|
||||
|
||||
/**
|
||||
* Scroll assist is run when a text field
|
||||
* is focused. However, it may need to
|
||||
* re-run when the keyboard size changes
|
||||
* such that the text field is now hidden
|
||||
* underneath the keyboard.
|
||||
* This function re-runs scroll assist
|
||||
* when that happens.
|
||||
*
|
||||
* One limitation of this is on a web browser
|
||||
* where native keyboard APIs do not have cross-browser
|
||||
* support. `ionKeyboardDidShow` relies on the Visual Viewport API.
|
||||
* This means that if the keyboard changes but does not change
|
||||
* geometry, then scroll assist will not re-run even if
|
||||
* the user has scrolled the text field under the keyboard.
|
||||
* This is not a problem when running in Cordova/Capacitor
|
||||
* because `ionKeyboardDidShow` uses the native events
|
||||
* which fire every time the keyboard changes.
|
||||
*/
|
||||
const keyboardShow = (ev: CustomEvent<{ keyboardHeight: number }>) => {
|
||||
/**
|
||||
* If the keyboard has not yet been presented
|
||||
* for this text field then the text field has just
|
||||
* received focus. In that case, the focusin listener
|
||||
* will run scroll assist.
|
||||
*/
|
||||
if (hasKeyboardBeenPresentedForTextField === false) {
|
||||
hasKeyboardBeenPresentedForTextField = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Otherwise, the keyboard has already been presented
|
||||
* for the focused text field.
|
||||
* This means that the keyboard likely changed
|
||||
* geometry, and we need to re-run scroll assist.
|
||||
* This can happen when the user rotates their device
|
||||
* or when they switch keyboards.
|
||||
*
|
||||
* Make sure we pass in the computed keyboard height
|
||||
* rather than the estimated keyboard height.
|
||||
*
|
||||
* Since the keyboard is already open then we do not
|
||||
* need to wait for the webview to resize, so we pass
|
||||
* "waitForResize: false".
|
||||
*/
|
||||
jsSetFocus(
|
||||
componentEl,
|
||||
inputEl,
|
||||
contentEl,
|
||||
footerEl,
|
||||
ev.detail.keyboardHeight,
|
||||
addScrollPadding,
|
||||
disableClonedInput,
|
||||
platformHeight,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the internal state when the text field loses focus.
|
||||
*/
|
||||
const focusOut = () => {
|
||||
hasKeyboardBeenPresentedForTextField = false;
|
||||
win?.removeEventListener('ionKeyboardDidShow', keyboardShow);
|
||||
componentEl.removeEventListener('focusout', focusOut, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* When the input is about to receive
|
||||
* focus, we need to move it to prevent
|
||||
@@ -50,12 +143,27 @@ export const enableScrollAssist = (
|
||||
inputEl.removeAttribute(SKIP_SCROLL_ASSIST);
|
||||
return;
|
||||
}
|
||||
jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight, addScrollPadding, disableClonedInput);
|
||||
jsSetFocus(
|
||||
componentEl,
|
||||
inputEl,
|
||||
contentEl,
|
||||
footerEl,
|
||||
keyboardHeight,
|
||||
addScrollPadding,
|
||||
disableClonedInput,
|
||||
platformHeight
|
||||
);
|
||||
|
||||
win?.addEventListener('ionKeyboardDidShow', keyboardShow);
|
||||
componentEl.addEventListener('focusout', focusOut, true);
|
||||
};
|
||||
|
||||
componentEl.addEventListener('focusin', focusIn, true);
|
||||
|
||||
return () => {
|
||||
componentEl.removeEventListener('focusin', focusIn, true);
|
||||
win?.removeEventListener('ionKeyboardDidShow', keyboardShow);
|
||||
componentEl.removeEventListener('focusout', focusOut, true);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -84,12 +192,14 @@ const jsSetFocus = async (
|
||||
footerEl: HTMLIonFooterElement | null,
|
||||
keyboardHeight: number,
|
||||
enableScrollPadding: boolean,
|
||||
disableClonedInput = false
|
||||
disableClonedInput = false,
|
||||
platformHeight = 0,
|
||||
waitForResize = true
|
||||
) => {
|
||||
if (!contentEl && !footerEl) {
|
||||
return;
|
||||
}
|
||||
const scrollData = getScrollData(componentEl, (contentEl || footerEl)!, keyboardHeight);
|
||||
const scrollData = getScrollData(componentEl, (contentEl || footerEl)!, keyboardHeight, platformHeight);
|
||||
|
||||
if (contentEl && Math.abs(scrollData.scrollAmount) < 4) {
|
||||
// the text input is in a safe position that doesn't
|
||||
@@ -191,7 +301,7 @@ const jsSetFocus = async (
|
||||
* bandwidth to become available.
|
||||
*/
|
||||
const totalScrollAmount = scrollEl.scrollHeight - scrollEl.clientHeight;
|
||||
if (scrollData.scrollAmount > totalScrollAmount - scrollEl.scrollTop) {
|
||||
if (waitForResize && scrollData.scrollAmount > totalScrollAmount - scrollEl.scrollTop) {
|
||||
/**
|
||||
* On iOS devices, the system will show a "Passwords" bar above the keyboard
|
||||
* after the initial keyboard is shown. This prevents the webview from resizing
|
||||
|
||||
@@ -9,13 +9,18 @@ export interface ScrollData {
|
||||
inputSafeY: number;
|
||||
}
|
||||
|
||||
export const getScrollData = (componentEl: HTMLElement, contentEl: HTMLElement, keyboardHeight: number): ScrollData => {
|
||||
export const getScrollData = (
|
||||
componentEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
keyboardHeight: number,
|
||||
platformHeight: number
|
||||
): ScrollData => {
|
||||
const itemEl = (componentEl.closest('ion-item,[ion-item]') as HTMLElement) ?? componentEl;
|
||||
return calcScrollData(
|
||||
itemEl.getBoundingClientRect(),
|
||||
contentEl.getBoundingClientRect(),
|
||||
keyboardHeight,
|
||||
(componentEl as any).ownerDocument.defaultView.innerHeight // TODO(FW-2832): type
|
||||
platformHeight
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user