mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 10:41:13 +08:00
fix(datetime): timepicker popover will position relative to click target (#24616)
Resolves #24531, #24415 Co-authored-by: mixalbl4 <mixalbl4.127@gmail.com>>
This commit is contained in:
@ -901,7 +901,7 @@ ion-popover,prop,triggerAction,"click" | "context-menu" | "hover",'click',false,
|
|||||||
ion-popover,method,dismiss,dismiss(data?: any, role?: string | undefined, dismissParentPopover?: boolean) => Promise<boolean>
|
ion-popover,method,dismiss,dismiss(data?: any, role?: string | undefined, dismissParentPopover?: boolean) => Promise<boolean>
|
||||||
ion-popover,method,onDidDismiss,onDidDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
ion-popover,method,onDidDismiss,onDidDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
||||||
ion-popover,method,onWillDismiss,onWillDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
ion-popover,method,onWillDismiss,onWillDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
||||||
ion-popover,method,present,present(event?: MouseEvent | TouchEvent | PointerEvent | undefined) => Promise<void>
|
ion-popover,method,present,present(event?: MouseEvent | TouchEvent | PointerEvent | CustomEvent<any> | undefined) => Promise<void>
|
||||||
ion-popover,event,didDismiss,OverlayEventDetail<any>,true
|
ion-popover,event,didDismiss,OverlayEventDetail<any>,true
|
||||||
ion-popover,event,didPresent,void,true
|
ion-popover,event,didPresent,void,true
|
||||||
ion-popover,event,ionPopoverDidDismiss,OverlayEventDetail<any>,true
|
ion-popover,event,ionPopoverDidDismiss,OverlayEventDetail<any>,true
|
||||||
|
3
core/src/components.d.ts
vendored
3
core/src/components.d.ts
vendored
@ -1823,6 +1823,7 @@ export namespace Components {
|
|||||||
* If `true`, tapping the picker will reveal a number input keyboard that lets the user type in values for each picker column. This is useful when working with time pickers.
|
* If `true`, tapping the picker will reveal a number input keyboard that lets the user type in values for each picker column. This is useful when working with time pickers.
|
||||||
*/
|
*/
|
||||||
"numericInput": boolean;
|
"numericInput": boolean;
|
||||||
|
"scrollActiveItemIntoView": () => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* The selected option in the picker.
|
* The selected option in the picker.
|
||||||
*/
|
*/
|
||||||
@ -1918,7 +1919,7 @@ export namespace Components {
|
|||||||
/**
|
/**
|
||||||
* Present the popover overlay after it has been created. Developers can pass a mouse, touch, or pointer event to position the popover relative to where that event was dispatched.
|
* Present the popover overlay after it has been created. Developers can pass a mouse, touch, or pointer event to position the popover relative to where that event was dispatched.
|
||||||
*/
|
*/
|
||||||
"present": (event?: MouseEvent | TouchEvent | PointerEvent | undefined) => Promise<void>;
|
"present": (event?: MouseEvent | TouchEvent | PointerEvent | CustomEvent<any> | undefined) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* When opening a popover from a trigger, we should not be modifying the `event` prop from inside the component. Additionally, when pressing the "Right" arrow key, we need to shift focus to the first descendant in the newly presented popover.
|
* When opening a popover from a trigger, we should not be modifying the `event` prop from inside the component. Additionally, when pressing the "Right" arrow key, we need to shift focus to the first descendant in the newly presented popover.
|
||||||
*/
|
*/
|
||||||
|
@ -1345,9 +1345,13 @@ export class Datetime implements ComponentInterface {
|
|||||||
const { popoverRef } = this;
|
const { popoverRef } = this;
|
||||||
|
|
||||||
if (popoverRef) {
|
if (popoverRef) {
|
||||||
|
|
||||||
this.isTimePopoverOpen = true;
|
this.isTimePopoverOpen = true;
|
||||||
popoverRef.present(ev);
|
|
||||||
|
popoverRef.present(new CustomEvent('ionShadowTarget', {
|
||||||
|
detail: {
|
||||||
|
ionShadowTarget: ev.target
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
await popoverRef.onWillDismiss();
|
await popoverRef.onWillDismiss();
|
||||||
|
|
||||||
@ -1362,6 +1366,19 @@ export class Datetime implements ComponentInterface {
|
|||||||
translucent
|
translucent
|
||||||
overlayIndex={1}
|
overlayIndex={1}
|
||||||
arrow={false}
|
arrow={false}
|
||||||
|
onWillPresent={ev => {
|
||||||
|
/**
|
||||||
|
* Intersection Observers do not consistently fire between Blink and Webkit
|
||||||
|
* when toggling the visibility of the popover and trying to scroll the picker
|
||||||
|
* column to the correct time value.
|
||||||
|
*
|
||||||
|
* This will correctly scroll the element position to the correct time value,
|
||||||
|
* before the popover is fully presented.
|
||||||
|
*/
|
||||||
|
const cols = (ev.target! as HTMLElement).querySelectorAll('ion-picker-column-internal');
|
||||||
|
// TODO (FW-615): Potentially remove this when intersection observers are fixed in picker column
|
||||||
|
cols.forEach(col => col.scrollActiveItemIntoView());
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
'--offset-y': '-10px'
|
'--offset-y': '-10px'
|
||||||
}}
|
}}
|
||||||
|
27
core/src/components/datetime/test/position/e2e.ts
Normal file
27
core/src/components/datetime/test/position/e2e.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { newE2EPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
|
describe('datetime: position', () => {
|
||||||
|
|
||||||
|
it('should position the time picker relative to the click target', async () => {
|
||||||
|
const page = await newE2EPage({
|
||||||
|
url: '/src/components/datetime/test/position?ionic:_testing=true'
|
||||||
|
});
|
||||||
|
const screenshotCompares = [];
|
||||||
|
|
||||||
|
const openDateTimeBtn = await page.find('ion-button');
|
||||||
|
await openDateTimeBtn.click();
|
||||||
|
|
||||||
|
screenshotCompares.push(await page.compareScreenshot());
|
||||||
|
|
||||||
|
const timepickerBtn = await page.find('ion-datetime >>> .time-body');
|
||||||
|
await timepickerBtn.click();
|
||||||
|
|
||||||
|
screenshotCompares.push(await page.compareScreenshot());
|
||||||
|
|
||||||
|
for (const screenshotCompare of screenshotCompares) {
|
||||||
|
expect(screenshotCompare).toMatchScreenshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
51
core/src/components/datetime/test/position/index.html
Normal file
51
core/src/components/datetime/test/position/index.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Datetime - Position</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Datetime - Position</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
|
||||||
|
<ion-button onclick="presentPopover(defaultPopover, event)">Present Popover</ion-button>
|
||||||
|
<ion-popover class="datetime-popover" id="default-popover">
|
||||||
|
<ion-datetime></ion-datetime>
|
||||||
|
</ion-popover>
|
||||||
|
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const defaultPopover = document.querySelector('ion-popover#default-popover');
|
||||||
|
const presentPopover = (popover, ev) => {
|
||||||
|
popover.event = ev;
|
||||||
|
popover.showBackdrop = false;
|
||||||
|
popover.isOpen = true;
|
||||||
|
|
||||||
|
const dismiss = () => {
|
||||||
|
popover.isOpen = false;
|
||||||
|
popover.event = undefined;
|
||||||
|
|
||||||
|
popover.removeEventListener('didDismiss', dismiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
popover.addEventListener('didDismiss', dismiss);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, State, Watch, h } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { Color } from '../../interface';
|
import { Color } from '../../interface';
|
||||||
@ -118,7 +118,9 @@ export class PickerColumnInternal implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollActiveItemIntoView() {
|
/** @internal */
|
||||||
|
@Method()
|
||||||
|
async scrollActiveItemIntoView() {
|
||||||
const activeEl = this.activeItem;
|
const activeEl = this.activeItem;
|
||||||
|
|
||||||
if (activeEl) {
|
if (activeEl) {
|
||||||
|
@ -372,7 +372,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
|
|||||||
* was dispatched.
|
* was dispatched.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
async present(event?: MouseEvent | TouchEvent | PointerEvent): Promise<void> {
|
async present(event?: MouseEvent | TouchEvent | PointerEvent | CustomEvent): Promise<void> {
|
||||||
if (this.presented) {
|
if (this.presented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1022,7 +1022,7 @@ Type: `Promise<OverlayEventDetail<T>>`
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `present(event?: MouseEvent | TouchEvent | PointerEvent | undefined) => Promise<void>`
|
### `present(event?: MouseEvent | TouchEvent | PointerEvent | CustomEvent<any> | undefined) => Promise<void>`
|
||||||
|
|
||||||
Present the popover overlay after it has been created.
|
Present the popover overlay after it has been created.
|
||||||
Developers can pass a mouse, touch, or pointer event
|
Developers can pass a mouse, touch, or pointer event
|
||||||
|
Reference in New Issue
Block a user