fix(datetime): time wheel input mode is dismissed on user scroll (#26567)

This commit is contained in:
Liam DeBeasi
2023-01-06 12:08:38 -05:00
committed by GitHub
parent dd71a3b5f3
commit d13a14658d
3 changed files with 73 additions and 7 deletions

View File

@ -1918,6 +1918,7 @@ export namespace Components {
"value"?: string | number;
}
interface IonPickerInternal {
"exitInputMode": () => Promise<void>;
/**
* The mode determines which platform styles to use.
*/

View File

@ -27,6 +27,8 @@ export class PickerColumnInternal implements ComponentInterface {
private isScrolling = false;
private scrollEndCallback?: () => void;
private isColumnVisible = false;
private parentEl?: HTMLIonPickerInternalElement | null;
private canExitInputMode = true;
@State() isActive = false;
@ -109,7 +111,7 @@ export class PickerColumnInternal implements ComponentInterface {
};
new IntersectionObserver(visibleCallback, { threshold: 0.001 }).observe(this.el);
const parentEl = this.el.closest('ion-picker-internal') as HTMLIonPickerInternalElement | null;
const parentEl = (this.parentEl = this.el.closest('ion-picker-internal') as HTMLIonPickerInternalElement | null);
if (parentEl !== null) {
parentEl.addEventListener('ionInputModeChange', (ev: any) => this.inputModeChange(ev));
}
@ -140,7 +142,7 @@ export class PickerColumnInternal implements ComponentInterface {
const activeEl = this.activeItem;
if (activeEl) {
this.centerPickerItemInView(activeEl, false);
this.centerPickerItemInView(activeEl, false, false);
}
}
@ -161,13 +163,21 @@ export class PickerColumnInternal implements ComponentInterface {
}
}
private centerPickerItemInView = (target: HTMLElement, smooth = true) => {
private centerPickerItemInView = (target: HTMLElement, smooth = true, canExitInputMode = true) => {
const { el, isColumnVisible } = this;
if (isColumnVisible) {
// (Vertical offset from parent) - (three empty picker rows) + (half the height of the target to ensure the scroll triggers)
const top = target.offsetTop - 3 * target.clientHeight + target.clientHeight / 2;
if (el.scrollTop !== top) {
/**
* Setting this flag prevents input
* mode from exiting in the picker column's
* scroll callback. This is useful when the user manually
* taps an item or types on the keyboard as both
* of these can cause a scroll to occur.
*/
this.canExitInputMode = canExitInputMode;
el.scroll({
top,
left: 0,
@ -270,6 +280,21 @@ export class PickerColumnInternal implements ComponentInterface {
*/
if (activeElement !== activeEl) {
hapticSelectionChanged();
if (this.canExitInputMode) {
/**
* The native iOS wheel picker
* only dismisses the keyboard
* once the selected item has changed
* as a result of a swipe
* from the user. If `canExitInputMode` is
* `false` then this means that the
* scroll is happening as a result of
* the `value` property programmatically changing
* either by an application or by the user via the keyboard.
*/
this.exitInputMode();
}
}
activeEl = activeElement;
@ -291,6 +316,14 @@ export class PickerColumnInternal implements ComponentInterface {
this.scrollEndCallback = undefined;
}
/**
* Reset this flag as the
* next scroll interaction could
* be a scroll from the user. In this
* case, we should exit input mode.
*/
this.canExitInputMode = true;
const dataIndex = activeElement.getAttribute('data-index');
/**
@ -325,6 +358,31 @@ export class PickerColumnInternal implements ComponentInterface {
});
};
/**
* Tells the parent picker to
* exit text entry mode. This is only called
* when the selected item changes during scroll, so
* we know that the user likely wants to scroll
* instead of type.
*/
private exitInputMode = () => {
const { parentEl } = this;
if (parentEl == null) return;
parentEl.exitInputMode();
/**
* setInputModeActive only takes
* effect once scrolling stops to avoid
* a component re-render while scrolling.
* However, we want the visual active
* indicator to go away immediately, so
* we call classList.remove here.
*/
this.el.classList.remove('picker-column-active');
};
get activeItem() {
return getElementRoot(this.el).querySelector(
`.picker-item[data-value="${this.value}"]:not([disabled])`
@ -368,7 +426,7 @@ export class PickerColumnInternal implements ComponentInterface {
data-value={item.value}
data-index={index}
onClick={(ev: Event) => {
this.centerPickerItemInView(ev.target as HTMLElement);
this.centerPickerItemInView(ev.target as HTMLElement, true);
}}
disabled={item.disabled}
>

View File

@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Listen, Host, h } from '@stencil/core';
import { Component, Element, Event, Listen, Method, Host, h } from '@stencil/core';
import { getElementRoot } from '../../utils/helpers';
@ -273,7 +273,14 @@ export class PickerInternal implements ComponentInterface {
this.emitInputModeChange();
};
private exitInputMode = () => {
/**
* @internal
* Exits text entry mode for the picker
* This method blurs the hidden input
* and cause the keyboard to dismiss.
*/
@Method()
async exitInputMode() {
const { inputEl, useInputMode } = this;
if (!useInputMode || !inputEl) {
return;
@ -290,7 +297,7 @@ export class PickerInternal implements ComponentInterface {
}
this.emitInputModeChange();
};
}
private onKeyPress = (ev: KeyboardEvent) => {
const { inputEl } = this;