mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 20:33:32 +08:00
fix(datetime): time picker uses new iOS 15 style (#23996)
resolves #23768
This commit is contained in:
@ -139,57 +139,12 @@
|
|||||||
@include padding($datetime-ios-padding / 2, $datetime-ios-padding, $datetime-ios-padding, $datetime-ios-padding);
|
@include padding($datetime-ios-padding / 2, $datetime-ios-padding, $datetime-ios-padding, $datetime-ios-padding);
|
||||||
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host .datetime-time .time-header {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .time-base {
|
|
||||||
@include border-radius($datetime-ios-time-border-radius, $datetime-ios-time-border-radius, $datetime-ios-time-border-radius, $datetime-ios-time-border-radius);
|
|
||||||
@include margin(0, $datetime-ios-padding / 2, 0, 0);
|
|
||||||
|
|
||||||
width: $datetime-ios-time-width;
|
|
||||||
height: $datetime-ios-time-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column {
|
|
||||||
@include border-radius($datetime-ios-time-border-radius, $datetime-ios-time-border-radius, $datetime-ios-time-border-radius, $datetime-ios-time-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-item {
|
|
||||||
line-height: $datetime-ios-time-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Month and Year Picker
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
:host .datetime-year-body .datetime-picker-col {
|
|
||||||
@include padding(0, $datetime-ios-padding, 0, $datetime-ios-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-before {
|
|
||||||
background: linear-gradient(to bottom, var(--background, var(--ion-background-color, #fff)) 20%, rgba(var(--background-rgb, var(--ion-background-color-rgb, 255, 255, 255)), 0.8) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-after {
|
|
||||||
background: linear-gradient(to top, var(--background, var(--ion-background-color, #fff)) 30%, rgba(var(--background-rgb, var(--ion-background-color-rgb, 255, 255, 255)), 0.8) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-highlight {
|
|
||||||
@include border-radius($datetime-ios-time-border-radius, $datetime-ios-time-border-radius, $datetime-ios-time-border-radius, $datetime-ios-time-border-radius);
|
|
||||||
@include position(50%, 0, 0, 0);
|
|
||||||
@include margin(0, auto, 0, auto);
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
width: calc(100% - #{$datetime-ios-padding * 2});
|
|
||||||
height: 34px;
|
|
||||||
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
background: var(--ion-color-step-150, #eeeeef);
|
|
||||||
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
:host .datetime-buttons {
|
:host .datetime-buttons {
|
||||||
|
@ -117,57 +117,8 @@
|
|||||||
color: #{$text-color-step-350};
|
color: #{$text-color-step-350};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .time-base {
|
|
||||||
@include border-radius($datetime-md-time-border-radius, $datetime-md-time-border-radius, $datetime-md-time-border-radius, $datetime-md-time-border-radius);
|
|
||||||
@include margin(0, $datetime-md-padding / 2, 0, 0);
|
|
||||||
|
|
||||||
width: $datetime-md-time-width;
|
|
||||||
height: $datetime-md-time-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column {
|
|
||||||
@include border-radius($datetime-md-time-border-radius, $datetime-md-time-border-radius, $datetime-md-time-border-radius, $datetime-md-time-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-item {
|
|
||||||
line-height: $datetime-md-time-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-ampm ion-segment {
|
|
||||||
@include border-radius($datetime-md-time-border-radius, $datetime-md-time-border-radius, $datetime-md-time-border-radius, $datetime-md-time-border-radius);
|
|
||||||
|
|
||||||
border: 1px solid rgba($text-color-rgb, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-ampm ion-segment-button {
|
|
||||||
--indicator-height: 0px;
|
|
||||||
--background-checked: #{current-color(base, 0.1)};
|
|
||||||
|
|
||||||
min-height: $datetime-md-time-height + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-ampm ion-segment-button.segment-button-checked {
|
|
||||||
background: var(--background-checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Month and Year
|
// Month and Year
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
:host .datetime-picker-col {
|
|
||||||
@include border-radius($datetime-md-wheel-border-radius, $datetime-md-wheel-border-radius, $datetime-md-wheel-border-radius, $datetime-md-wheel-border-radius);
|
|
||||||
@include padding(null, $datetime-md-wheel-padding, null, $datetime-md-wheel-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .picker-col-item {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-before {
|
|
||||||
background: linear-gradient(to bottom, var(--background, var(--ion-background-color, #fff)) 20%, rgba(var(--background-rgb, var(--ion-background-color-rgb, 255, 255, 255)), 0) 90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-after {
|
|
||||||
background: linear-gradient(to top, var(--background, var(--ion-background-color, #fff)) 30%, rgba(var(--background-rgb, var(--ion-background-color-rgb, 255, 255, 255)), 0) 90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add some margin when only selecting month/year
|
* Add some margin when only selecting month/year
|
||||||
@ -194,7 +145,3 @@
|
|||||||
:host .datetime-view-buttons ion-button {
|
:host .datetime-view-buttons ion-button {
|
||||||
color: $text-color-step-200;
|
color: $text-color-step-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .picker-col-item-active {
|
|
||||||
color: current-color(base);
|
|
||||||
}
|
|
||||||
|
@ -15,18 +15,3 @@ $datetime-md-header-padding: 20px !default;
|
|||||||
|
|
||||||
/// @prop - Padding for content
|
/// @prop - Padding for content
|
||||||
$datetime-md-padding: 16px !default;
|
$datetime-md-padding: 16px !default;
|
||||||
|
|
||||||
/// @prop - Height of the time picker
|
|
||||||
$datetime-md-time-height: 28px !default;
|
|
||||||
|
|
||||||
/// @prop - Width of the time picker
|
|
||||||
$datetime-md-time-width: 68px !default;
|
|
||||||
|
|
||||||
/// @prop - Border radius of the time picker
|
|
||||||
$datetime-md-time-border-radius: 4px !default;
|
|
||||||
|
|
||||||
/// @prop - Border radius of the month and year wheel
|
|
||||||
$datetime-md-wheel-border-radius: 8px !default;
|
|
||||||
|
|
||||||
/// @prop - Padding of the month and year wheel
|
|
||||||
$datetime-md-wheel-padding: 8px !default;
|
|
||||||
|
@ -35,7 +35,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host .calendar-body,
|
:host .calendar-body,
|
||||||
:host .time-column,
|
|
||||||
:host .datetime-year {
|
:host .datetime-year {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@ -45,8 +44,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.datetime-ready) .calendar-body,
|
:host(.datetime-ready) .calendar-body {
|
||||||
:host(.datetime-ready) .time-column {
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,93 +287,12 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .time-base {
|
:host(.datetime-presentation-time) .datetime-time {
|
||||||
display: flex;
|
@include padding(0);
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border: 2px solid transparent;
|
|
||||||
|
|
||||||
background: rgba($text-color-rgb, 0.065);
|
|
||||||
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .time-base.time-base-active {
|
:host ion-popover {
|
||||||
border: 2px solid current-color(base);
|
--height: 200px;
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-wrapper {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
outline: none;
|
|
||||||
scroll-snap-type: y mandatory;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (any-hover: hover) {
|
|
||||||
:host .time-column:focus {
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
background: current-color(base, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column.time-column-active {
|
|
||||||
background: transparent;
|
|
||||||
color: current-color(base);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-base.time-base-active .time-column:not(.time-column-active),
|
|
||||||
:host .time-base.time-base-active .time-separator {
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column-hours .time-item {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-column-minutes .time-item {
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-item {
|
|
||||||
scroll-snap-align: center;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-separator {
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .time-header {
|
:host .time-header {
|
||||||
@ -385,15 +302,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host .time-body {
|
:host .time-body {
|
||||||
|
@include border-radius(8px);
|
||||||
|
@include padding(6px, 12px, 6px, 12px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
background: var(--ion-color-step-300, #edeef0);
|
||||||
|
|
||||||
|
color: $text-color;
|
||||||
|
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .time-ampm {
|
:host .time-body-active {
|
||||||
width: 100px;
|
color: current-color(base);
|
||||||
}
|
|
||||||
|
|
||||||
:host .time-ampm ion-segment-button {
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.in-item) {
|
:host(.in-item) {
|
||||||
@ -405,119 +334,3 @@
|
|||||||
:host(.show-month-and-year) .calendar-action-buttons ion-item {
|
:host(.show-month-and-year) .calendar-action-buttons ion-item {
|
||||||
--color: #{current-color(base)};
|
--color: #{current-color(base)};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host .datetime-year-body .datetime-picker-col {
|
|
||||||
@include margin(0, 10px, 0, 10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-before {
|
|
||||||
@include position(0, null, null, 0);
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
height: 82px;
|
|
||||||
|
|
||||||
background: linear-gradient(to bottom, var(--background, var(--ion-background-color, #fff)) 20%, rgba(var(--background-rgb, var(--ion-background-color-rgb, 255, 255, 255)), 0.7) 100%);
|
|
||||||
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-after {
|
|
||||||
@include position(116px, null, null, 0);
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
height: 115px;
|
|
||||||
|
|
||||||
background: linear-gradient(to top, var(--background, var(--ion-background-color, #fff)) 30%, rgba(var(--background-rgb, var(--ion-background-color-rgb, 255, 255, 255)), 0.7) 100%);
|
|
||||||
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-year-body {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
font-size: 22px;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is required otherwise the
|
|
||||||
* highlight will appear behind
|
|
||||||
* the datetime.
|
|
||||||
*/
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-picker-col {
|
|
||||||
scroll-snap-type: y mandatory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Need to explicitly set overflow-x: hidden
|
|
||||||
* for older implementations of scroll snapping.
|
|
||||||
*/
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
// Hide scrollbars on Firefox
|
|
||||||
scrollbar-width: none;
|
|
||||||
|
|
||||||
height: 200px;
|
|
||||||
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (any-hover: hover) {
|
|
||||||
:host .datetime-picker-col:focus {
|
|
||||||
background: current-color(base, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide scrollbars on Chrome and Safari
|
|
||||||
*/
|
|
||||||
:host .datetime-picker-col::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .picker-col-item {
|
|
||||||
height: 38px;
|
|
||||||
|
|
||||||
line-height: 38px;
|
|
||||||
|
|
||||||
scroll-snap-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .picker-col-item-empty {
|
|
||||||
scroll-snap-align: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-year-body .datetime-picker-col:first-of-type {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host .datetime-year-body .datetime-picker-col:last-of-type {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding :last-of-type is needed here so that
|
|
||||||
* we can achieve higher specificity than the
|
|
||||||
* previous selectors and avoid using !important.
|
|
||||||
*/
|
|
||||||
:host(.datetime-presentation-year) .datetime-picker-col:last-of-type,
|
|
||||||
:host(.datetime-presentation-month) .datetime-picker-col:last-of-type {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
@ -10,8 +10,9 @@ import {
|
|||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { Color, DatetimeChangeEventDetail, DatetimeParts, Mode, StyleEventDetail } from '../../interface';
|
import { Color, DatetimeChangeEventDetail, DatetimeParts, Mode, StyleEventDetail } from '../../interface';
|
||||||
import { startFocusVisible } from '../../utils/focus-visible';
|
import { startFocusVisible } from '../../utils/focus-visible';
|
||||||
import { getElementRoot, raf, renderHiddenInput } from '../../utils/helpers';
|
import { getElementRoot, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { createColorClasses } from '../../utils/theme';
|
import { createColorClasses } from '../../utils/theme';
|
||||||
|
import { PickerColumnItem } from '../picker-column-internal/picker-column-internal-interfaces';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
generateMonths,
|
generateMonths,
|
||||||
@ -25,6 +26,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
addTimePadding,
|
addTimePadding,
|
||||||
getFormattedHour,
|
getFormattedHour,
|
||||||
|
getFormattedTime,
|
||||||
getMonthAndDay,
|
getMonthAndDay,
|
||||||
getMonthAndYear
|
getMonthAndYear
|
||||||
} from './utils/format';
|
} from './utils/format';
|
||||||
@ -75,11 +77,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
private inputId = `ion-dt-${datetimeIds++}`;
|
private inputId = `ion-dt-${datetimeIds++}`;
|
||||||
private calendarBodyRef?: HTMLElement;
|
private calendarBodyRef?: HTMLElement;
|
||||||
private timeBaseRef?: HTMLElement;
|
private popoverRef?: HTMLIonPopoverElement;
|
||||||
private timeHourRef?: HTMLElement;
|
|
||||||
private timeMinuteRef?: HTMLElement;
|
|
||||||
private monthRef?: HTMLElement;
|
|
||||||
private yearRef?: HTMLElement;
|
|
||||||
private clearFocusVisible?: () => void;
|
private clearFocusVisible?: () => void;
|
||||||
private overlayIsPresenting = false;
|
private overlayIsPresenting = false;
|
||||||
|
|
||||||
@ -91,8 +89,6 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
private destroyCalendarIO?: () => void;
|
private destroyCalendarIO?: () => void;
|
||||||
private destroyKeyboardMO?: () => void;
|
private destroyKeyboardMO?: () => void;
|
||||||
private destroyTimeScroll?: () => void;
|
|
||||||
private destroyMonthAndYearScroll?: () => void;
|
|
||||||
|
|
||||||
private minParts?: any;
|
private minParts?: any;
|
||||||
private maxParts?: any;
|
private maxParts?: any;
|
||||||
@ -122,6 +118,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
@Element() el!: HTMLIonDatetimeElement;
|
@Element() el!: HTMLIonDatetimeElement;
|
||||||
|
|
||||||
@State() isPresented = false;
|
@State() isPresented = false;
|
||||||
|
@State() isTimePopoverOpen = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The color to use from your application's color palette.
|
* The color to use from your application's color palette.
|
||||||
@ -807,7 +804,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
* if the datetime has been hidden/presented by a modal or popover.
|
* if the datetime has been hidden/presented by a modal or popover.
|
||||||
*/
|
*/
|
||||||
private destroyListeners = () => {
|
private destroyListeners = () => {
|
||||||
const { destroyCalendarIO, destroyKeyboardMO, destroyTimeScroll, destroyMonthAndYearScroll } = this;
|
const { destroyCalendarIO, destroyKeyboardMO } = this;
|
||||||
|
|
||||||
if (destroyCalendarIO !== undefined) {
|
if (destroyCalendarIO !== undefined) {
|
||||||
destroyCalendarIO();
|
destroyCalendarIO();
|
||||||
@ -816,14 +813,6 @@ export class Datetime implements ComponentInterface {
|
|||||||
if (destroyKeyboardMO !== undefined) {
|
if (destroyKeyboardMO !== undefined) {
|
||||||
destroyKeyboardMO();
|
destroyKeyboardMO();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destroyTimeScroll !== undefined) {
|
|
||||||
destroyTimeScroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destroyMonthAndYearScroll !== undefined) {
|
|
||||||
destroyMonthAndYearScroll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
componentDidLoad() {
|
||||||
@ -841,9 +830,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
this.initializeCalendarIOListeners();
|
this.initializeCalendarIOListeners();
|
||||||
this.initializeKeyboardListeners();
|
this.initializeKeyboardListeners();
|
||||||
this.initializeTimeScrollListener();
|
|
||||||
this.initializeOverlayListener();
|
this.initializeOverlayListener();
|
||||||
this.initializeMonthAndYearScrollListeners();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Datetime needs a frame to ensure that it
|
* TODO: Datetime needs a frame to ensure that it
|
||||||
@ -911,253 +898,6 @@ export class Datetime implements ComponentInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeMonthAndYearScrollListeners = () => {
|
|
||||||
const { monthRef, yearRef, workingParts } = this;
|
|
||||||
const { year, month } = workingParts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll initial month and year into view.
|
|
||||||
* scrollIntoView() will scroll entire page
|
|
||||||
* if element is not in viewport. Use scrollTop instead.
|
|
||||||
*/
|
|
||||||
let activeYearEl = yearRef?.querySelector(`.picker-col-item[data-value="${year}"]`) as HTMLElement | null;
|
|
||||||
if (activeYearEl) {
|
|
||||||
yearRef!.scrollTop = activeYearEl.offsetTop - (activeYearEl.clientHeight * 2);
|
|
||||||
activeYearEl.classList.add(PICKER_COL_ACTIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeMonthEl = monthRef?.querySelector(`.picker-col-item[data-value="${month}"]`) as HTMLElement | null;
|
|
||||||
if (activeMonthEl) {
|
|
||||||
monthRef!.scrollTop = activeMonthEl.offsetTop - (activeMonthEl.clientHeight * 2);
|
|
||||||
activeMonthEl.classList.add(PICKER_COL_ACTIVE)
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeout: any;
|
|
||||||
const scrollCallback = (colType: string) => {
|
|
||||||
raf(() => {
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeCol = colType === 'month' ? monthRef : yearRef;
|
|
||||||
if (!activeCol) { return; }
|
|
||||||
|
|
||||||
const bbox = activeCol.getBoundingClientRect();
|
|
||||||
/**
|
|
||||||
* Select item in the center of the column
|
|
||||||
* which is the month/year that we want to select
|
|
||||||
*/
|
|
||||||
const centerX = bbox.x + (bbox.width / 2);
|
|
||||||
const centerY = bbox.y + (bbox.height / 2);
|
|
||||||
|
|
||||||
const activeElement = this.el!.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLElement;
|
|
||||||
const prevActiveEl = colType === 'month' ? activeMonthEl : activeYearEl;
|
|
||||||
if (prevActiveEl !== null) {
|
|
||||||
prevActiveEl.classList.remove(PICKER_COL_ACTIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colType === 'month') {
|
|
||||||
activeMonthEl = activeElement;
|
|
||||||
} else if (colType === 'year') {
|
|
||||||
activeYearEl = activeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
activeElement.classList.add(PICKER_COL_ACTIVE);
|
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
const dataValue = activeElement.getAttribute('data-value');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If no value it is
|
|
||||||
* possible we hit one of the
|
|
||||||
* empty padding columns.
|
|
||||||
*/
|
|
||||||
if (dataValue === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = parseInt(dataValue, 10);
|
|
||||||
const { presentation } = this;
|
|
||||||
if (colType === 'month') {
|
|
||||||
this.setWorkingParts({
|
|
||||||
...this.workingParts,
|
|
||||||
month: value
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If developers are only selecting month/month-year
|
|
||||||
* then we need to call ionChange as they will
|
|
||||||
* not be selecting dates too.
|
|
||||||
*/
|
|
||||||
if (presentation === 'month' || presentation === 'month-year') {
|
|
||||||
this.setActiveParts({
|
|
||||||
...this.activeParts,
|
|
||||||
month: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setWorkingParts({
|
|
||||||
...this.workingParts,
|
|
||||||
year: value
|
|
||||||
});
|
|
||||||
if (presentation === 'year' || presentation === 'month-year') {
|
|
||||||
this.setActiveParts({
|
|
||||||
...this.activeParts,
|
|
||||||
year: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the year changed, it is possible that
|
|
||||||
* the allowed month values have changed and the scroll
|
|
||||||
* position got reset
|
|
||||||
*/
|
|
||||||
raf(() => {
|
|
||||||
const { month: workingMonth, year: workingYear } = this.workingParts;
|
|
||||||
const monthEl = monthRef?.querySelector(`.picker-col-item[data-value='${workingMonth}']`);
|
|
||||||
const yearEl = yearRef?.querySelector(`.picker-col-item[data-value='${workingYear}']`);
|
|
||||||
|
|
||||||
if (monthEl && monthRef) {
|
|
||||||
this.centerPickerItemInView(monthEl as HTMLElement, monthRef, 'auto');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yearEl && yearRef) {
|
|
||||||
this.centerPickerItemInView(yearEl as HTMLElement, yearRef, 'auto');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 250);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add scroll listeners to the month and year containers.
|
|
||||||
* Wrap this in an raf so that the scroll callback
|
|
||||||
* does not fire when we do our initial scrollIntoView above.
|
|
||||||
*/
|
|
||||||
raf(() => {
|
|
||||||
const monthScroll = () => scrollCallback('month');
|
|
||||||
const yearScroll = () => scrollCallback('year');
|
|
||||||
monthRef?.addEventListener('scroll', monthScroll);
|
|
||||||
yearRef?.addEventListener('scroll', yearScroll);
|
|
||||||
|
|
||||||
this.destroyMonthAndYearScroll = () => {
|
|
||||||
monthRef?.removeEventListener('scroll', monthScroll);
|
|
||||||
yearRef?.removeEventListener('scroll', yearScroll);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeTimeScrollListener = () => {
|
|
||||||
const { timeBaseRef, timeHourRef, timeMinuteRef } = this;
|
|
||||||
if (!timeBaseRef || !timeHourRef || !timeMinuteRef) { return; }
|
|
||||||
|
|
||||||
const { hour, minute } = this.workingParts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll initial hour and minute into view.
|
|
||||||
* scrollIntoView() will scroll entire page
|
|
||||||
* if element is not in viewport. Use scrollTop instead.
|
|
||||||
*/
|
|
||||||
raf(() => {
|
|
||||||
const initialHour = timeHourRef.querySelector(`.time-item[data-value="${hour}"]`) as HTMLElement | null;
|
|
||||||
if (initialHour) {
|
|
||||||
timeHourRef.scrollTop = initialHour.offsetTop;
|
|
||||||
}
|
|
||||||
const initialMinute = timeMinuteRef.querySelector(`.time-item[data-value="${minute}"]`) as HTMLElement | null;
|
|
||||||
if (initialMinute) {
|
|
||||||
timeMinuteRef.scrollTop = initialMinute.offsetTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight the container and
|
|
||||||
* appropriate column when scrolling.
|
|
||||||
*/
|
|
||||||
let timeout: any;
|
|
||||||
const scrollCallback = (colType: string) => {
|
|
||||||
raf(() => {
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeCol = colType === 'hour' ? timeHourRef : timeMinuteRef;
|
|
||||||
const otherCol = colType === 'hour' ? timeMinuteRef : timeHourRef;
|
|
||||||
|
|
||||||
timeBaseRef.classList.add('time-base-active');
|
|
||||||
activeCol.classList.add('time-column-active');
|
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
timeBaseRef.classList.remove('time-base-active');
|
|
||||||
activeCol.classList.remove('time-column-active');
|
|
||||||
otherCol.classList.remove('time-column-active');
|
|
||||||
|
|
||||||
const bbox = activeCol.getBoundingClientRect();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not use floating point
|
|
||||||
* here as some browsers may clamp
|
|
||||||
* or round down.
|
|
||||||
*/
|
|
||||||
const x = Math.ceil(bbox.x + 1);
|
|
||||||
const y = Math.ceil(bbox.y + 1);
|
|
||||||
const activeElement = this.el!.shadowRoot!.elementFromPoint(x, y)!;
|
|
||||||
const value = parseInt(activeElement.getAttribute('data-value')!, 10);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When scrolling to a month that is out of
|
|
||||||
* bounds, the hour/minute column values may
|
|
||||||
* be updated, triggering a scroll callback.
|
|
||||||
* Check to make sure there is a valid
|
|
||||||
* hour/minute element so we do not emit NaN.
|
|
||||||
*/
|
|
||||||
if (Number.isNaN(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colType === 'hour') {
|
|
||||||
this.setWorkingParts({
|
|
||||||
...this.workingParts,
|
|
||||||
hour: value
|
|
||||||
});
|
|
||||||
this.setActiveParts({
|
|
||||||
...this.activeParts,
|
|
||||||
hour: value
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setWorkingParts({
|
|
||||||
...this.workingParts,
|
|
||||||
minute: value
|
|
||||||
});
|
|
||||||
this.setActiveParts({
|
|
||||||
...this.activeParts,
|
|
||||||
minute: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 250);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add scroll listeners to the hour and minute containers.
|
|
||||||
* Wrap this in an raf so that the scroll callback
|
|
||||||
* does not fire when we do our initial scrollIntoView above.
|
|
||||||
*/
|
|
||||||
raf(() => {
|
|
||||||
const hourScroll = () => scrollCallback('hour');
|
|
||||||
const minuteScroll = () => scrollCallback('minute');
|
|
||||||
|
|
||||||
timeHourRef.addEventListener('scroll', hourScroll);
|
|
||||||
timeMinuteRef.addEventListener('scroll', minuteScroll);
|
|
||||||
|
|
||||||
this.destroyTimeScroll = () => {
|
|
||||||
timeHourRef.removeEventListener('scroll', hourScroll);
|
|
||||||
timeMinuteRef.removeEventListener('scroll', minuteScroll);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private processValue = (value?: string | null) => {
|
private processValue = (value?: string | null) => {
|
||||||
const valueToProcess = value || getToday();
|
const valueToProcess = value || getToday();
|
||||||
const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess);
|
const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess);
|
||||||
@ -1281,61 +1021,70 @@ export class Datetime implements ComponentInterface {
|
|||||||
this.showMonthAndYear = !this.showMonthAndYear;
|
this.showMonthAndYear = !this.showMonthAndYear;
|
||||||
}
|
}
|
||||||
|
|
||||||
private centerPickerItemInView(target: HTMLElement, container: HTMLElement, behavior: ScrollBehavior = 'smooth') {
|
|
||||||
container.scroll({
|
|
||||||
// (Vertical offset from parent) - (three empty picker rows) + (half the height of the target to ensure the scroll triggers)
|
|
||||||
top: target.offsetTop - (3 * target.clientHeight) + (target.clientHeight / 2),
|
|
||||||
left: 0,
|
|
||||||
behavior
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderYearView() {
|
private renderYearView() {
|
||||||
const { presentation } = this;
|
const { presentation, workingParts } = this;
|
||||||
const calendarYears = getCalendarYears(this.todayParts, this.minParts, this.maxParts, this.parsedYearValues);
|
const calendarYears = getCalendarYears(this.todayParts, this.minParts, this.maxParts, this.parsedYearValues);
|
||||||
const showMonth = presentation !== 'year';
|
const showMonth = presentation !== 'year';
|
||||||
const showYear = presentation !== 'month';
|
const showYear = presentation !== 'month';
|
||||||
|
|
||||||
|
const months = getPickerMonths(this.locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues);
|
||||||
|
const years = calendarYears.map(year => {
|
||||||
|
return {
|
||||||
|
text: `${year}`,
|
||||||
|
value: year
|
||||||
|
}
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<div class="datetime-year">
|
<div class="datetime-year">
|
||||||
<div class="datetime-year-body">
|
<div class="datetime-year-body">
|
||||||
<div class="datetime-picker-before"></div>
|
<ion-picker-internal>
|
||||||
<div class="datetime-picker-after"></div>
|
{
|
||||||
<div class="datetime-picker-highlight"></div>
|
showMonth &&
|
||||||
{showMonth && <div class="datetime-picker-col month-col" ref={el => this.monthRef = el} tabindex="0">
|
<ion-picker-column-internal
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
color={this.color}
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
items={months}
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
value={workingParts.month}
|
||||||
{getPickerMonths(this.locale, this.workingParts, this.minParts, this.maxParts, this.parsedMonthValues).map(month => {
|
onIonChange={(ev: CustomEvent) => {
|
||||||
return (
|
this.setWorkingParts({
|
||||||
<div
|
...this.workingParts,
|
||||||
class="picker-col-item"
|
month: ev.detail.value
|
||||||
data-value={month.value}
|
});
|
||||||
onClick={(ev: Event) => this.centerPickerItemInView(ev.target as HTMLElement, this.monthRef as HTMLElement)}
|
|
||||||
>{month.text}</div>
|
if (presentation === 'month' || presentation === 'month-year') {
|
||||||
)
|
this.setActiveParts({
|
||||||
})}
|
...this.activeParts,
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
month: ev.detail.value
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
});
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
}
|
||||||
</div>}
|
|
||||||
{showYear && <div class="datetime-picker-col year-col" ref={el => this.yearRef = el} tabindex="0">
|
ev.stopPropagation();
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
}}
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
></ion-picker-column-internal>
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
}
|
||||||
{calendarYears.map(year => {
|
{
|
||||||
return (
|
showYear &&
|
||||||
<div
|
<ion-picker-column-internal
|
||||||
class="picker-col-item"
|
color={this.color}
|
||||||
data-value={year}
|
items={years}
|
||||||
onClick={(ev: Event) => this.centerPickerItemInView(ev.target as HTMLElement, this.yearRef as HTMLElement)}
|
value={workingParts.year}
|
||||||
>{year}</div>
|
onIonChange={(ev: CustomEvent) => {
|
||||||
)
|
this.setWorkingParts({
|
||||||
})}
|
...this.workingParts,
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
year: ev.detail.value
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
});
|
||||||
<div class="picker-col-item picker-col-item-empty"> </div>
|
|
||||||
</div>}
|
if (presentation === 'year' || presentation === 'month-year') {
|
||||||
|
this.setActiveParts({
|
||||||
|
...this.activeParts,
|
||||||
|
year: ev.detail.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
}}
|
||||||
|
></ion-picker-column-internal>
|
||||||
|
}
|
||||||
|
</ion-picker-internal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -1457,6 +1206,125 @@ export class Datetime implements ComponentInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderTimePicker(
|
||||||
|
hoursItems: PickerColumnItem[],
|
||||||
|
minutesItems: PickerColumnItem[],
|
||||||
|
ampmItems: PickerColumnItem[],
|
||||||
|
use24Hour: boolean
|
||||||
|
) {
|
||||||
|
const { color, workingParts } = this;
|
||||||
|
return (
|
||||||
|
<ion-picker-internal>
|
||||||
|
<ion-picker-column-internal
|
||||||
|
color={color}
|
||||||
|
value={workingParts.hour}
|
||||||
|
items={hoursItems}
|
||||||
|
numericInput
|
||||||
|
onIonChange={(ev: CustomEvent) => {
|
||||||
|
this.setWorkingParts({
|
||||||
|
...this.workingParts,
|
||||||
|
hour: ev.detail.value
|
||||||
|
});
|
||||||
|
this.setActiveParts({
|
||||||
|
...this.activeParts,
|
||||||
|
hour: ev.detail.value
|
||||||
|
});
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
}}
|
||||||
|
></ion-picker-column-internal>
|
||||||
|
<ion-picker-column-internal
|
||||||
|
color={color}
|
||||||
|
value={workingParts.minute}
|
||||||
|
items={minutesItems}
|
||||||
|
numericInput
|
||||||
|
onIonChange={(ev: CustomEvent) => {
|
||||||
|
this.setWorkingParts({
|
||||||
|
...this.workingParts,
|
||||||
|
minute: ev.detail.value
|
||||||
|
});
|
||||||
|
this.setActiveParts({
|
||||||
|
...this.activeParts,
|
||||||
|
minute: ev.detail.value
|
||||||
|
});
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
}}
|
||||||
|
></ion-picker-column-internal>
|
||||||
|
{ !use24Hour && <ion-picker-column-internal
|
||||||
|
color={color}
|
||||||
|
value={workingParts.ampm}
|
||||||
|
items={ampmItems}
|
||||||
|
onIonChange={(ev: CustomEvent) => {
|
||||||
|
const hour = calculateHourFromAMPM(this.workingParts, ev.detail.value);
|
||||||
|
|
||||||
|
this.setWorkingParts({
|
||||||
|
...this.workingParts,
|
||||||
|
ampm: ev.detail.value,
|
||||||
|
hour
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setActiveParts({
|
||||||
|
...this.workingParts,
|
||||||
|
ampm: ev.detail.value,
|
||||||
|
hour
|
||||||
|
});
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
}}
|
||||||
|
></ion-picker-column-internal> }
|
||||||
|
</ion-picker-internal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderTimeOverlay(
|
||||||
|
hoursItems: PickerColumnItem[],
|
||||||
|
minutesItems: PickerColumnItem[],
|
||||||
|
ampmItems: PickerColumnItem[],
|
||||||
|
use24Hour: boolean
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
<div class="time-header">
|
||||||
|
{this.renderTimeLabel()}
|
||||||
|
</div>,
|
||||||
|
<button
|
||||||
|
class={{
|
||||||
|
'time-body': true,
|
||||||
|
'time-body-active': this.isTimePopoverOpen
|
||||||
|
}}
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={async ev => {
|
||||||
|
const { popoverRef } = this;
|
||||||
|
|
||||||
|
if (popoverRef) {
|
||||||
|
|
||||||
|
this.isTimePopoverOpen = true;
|
||||||
|
popoverRef.present(ev);
|
||||||
|
|
||||||
|
await popoverRef.onWillDismiss();
|
||||||
|
|
||||||
|
this.isTimePopoverOpen = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getFormattedTime(this.workingParts, use24Hour)}
|
||||||
|
</button>,
|
||||||
|
<ion-popover
|
||||||
|
alignment="center"
|
||||||
|
translucent
|
||||||
|
overlayIndex={1}
|
||||||
|
arrow={false}
|
||||||
|
style={{
|
||||||
|
'--offset-y': '-10px'
|
||||||
|
}}
|
||||||
|
ref={el => this.popoverRef = el}
|
||||||
|
>
|
||||||
|
{this.renderTimePicker(hoursItems, minutesItems, ampmItems, use24Hour)}
|
||||||
|
</ion-popover>
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render time picker inside of datetime.
|
* Render time picker inside of datetime.
|
||||||
* Do not pass color prop to segment on
|
* Do not pass color prop to segment on
|
||||||
@ -1464,87 +1332,44 @@ export class Datetime implements ComponentInterface {
|
|||||||
* should take on the color prop, but iOS
|
* should take on the color prop, but iOS
|
||||||
* should just be the default segment.
|
* should just be the default segment.
|
||||||
*/
|
*/
|
||||||
private renderTime(mode: Mode) {
|
private renderTime() {
|
||||||
const { hourCycle } = this;
|
const { workingParts, presentation } = this;
|
||||||
const use24Hour = is24Hour(this.locale, hourCycle);
|
const timeOnlyPresentation = presentation === 'time';
|
||||||
const { ampm } = this.workingParts;
|
const use24Hour = is24Hour(this.locale, this.hourCycle);
|
||||||
const { hours, minutes, am, pm } = generateTime(this.workingParts, use24Hour ? 'h23' : 'h12', this.minParts, this.maxParts, this.parsedHourValues, this.parsedMinuteValues);
|
const { hours, minutes, am, pm } = generateTime(this.workingParts, use24Hour ? 'h23' : 'h12', this.minParts, this.maxParts, this.parsedHourValues, this.parsedMinuteValues);
|
||||||
return (
|
|
||||||
<div class="datetime-time">
|
|
||||||
<div class="time-header">
|
|
||||||
{this.renderTimeLabel()}
|
|
||||||
</div>
|
|
||||||
<div class="time-body">
|
|
||||||
<div class="time-base" ref={el => this.timeBaseRef = el}>
|
|
||||||
<div class="time-wrapper">
|
|
||||||
<div
|
|
||||||
class="ion-focusable time-column time-column-hours"
|
|
||||||
aria-label="Hours"
|
|
||||||
role="slider"
|
|
||||||
ref={el => this.timeHourRef = el}
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
{ hours.map(hour => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class="time-item"
|
|
||||||
data-value={getInternalHourValue(hour, use24Hour, ampm)}
|
|
||||||
>{getFormattedHour(hour, use24Hour)}</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div class="time-separator">:</div>
|
|
||||||
<div
|
|
||||||
class="ion-focusable time-column time-column-minutes"
|
|
||||||
aria-label="Minutes"
|
|
||||||
role="slider"
|
|
||||||
ref={el => this.timeMinuteRef = el}
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
{ minutes.map(minute => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class="time-item"
|
|
||||||
data-value={minute}
|
|
||||||
>{addTimePadding(minute)}</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{ !use24Hour && <div class="time-ampm">
|
|
||||||
<ion-segment
|
|
||||||
color={mode === 'md' ? this.color : undefined}
|
|
||||||
value={this.workingParts.ampm}
|
|
||||||
onIonChange={(ev: CustomEvent) => {
|
|
||||||
|
|
||||||
/**
|
const hoursItems = hours.map(hour => {
|
||||||
* Since datetime uses 24-hour time internally
|
return {
|
||||||
* we need to update the working hour here as well
|
text: getFormattedHour(hour, use24Hour),
|
||||||
* if the user is using a 12-hour time format.
|
value: getInternalHourValue(hour, use24Hour, workingParts.ampm)
|
||||||
*/
|
}
|
||||||
const { value } = ev.detail;
|
|
||||||
const hour = calculateHourFromAMPM(this.workingParts, value);
|
|
||||||
|
|
||||||
this.setWorkingParts({
|
|
||||||
...this.workingParts,
|
|
||||||
ampm: value,
|
|
||||||
hour
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const minutesItems = minutes.map(minute => {
|
||||||
* Do not let this event bubble up
|
return {
|
||||||
* otherwise developers listening for ionChange
|
text: addTimePadding(minute),
|
||||||
* on the datetime will see this event.
|
value: minute
|
||||||
*/
|
}
|
||||||
ev.stopPropagation();
|
});
|
||||||
}}
|
|
||||||
>
|
const ampmItems = [];
|
||||||
<ion-segment-button disabled={!am} value="am">AM</ion-segment-button>
|
if (am) {
|
||||||
<ion-segment-button disabled={!pm} value="pm">PM</ion-segment-button>
|
ampmItems.push({
|
||||||
</ion-segment>
|
text: 'AM',
|
||||||
</div> }
|
value: 'am'
|
||||||
</div>
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pm) {
|
||||||
|
ampmItems.push({
|
||||||
|
text: 'PM',
|
||||||
|
value: 'pm'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="datetime-time">
|
||||||
|
{timeOnlyPresentation ? this.renderTimePicker(hoursItems, minutesItems, ampmItems, use24Hour) : this.renderTimeOverlay(hoursItems, minutesItems, ampmItems, use24Hour)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1573,20 +1398,20 @@ export class Datetime implements ComponentInterface {
|
|||||||
this.renderCalendarViewHeader(mode),
|
this.renderCalendarViewHeader(mode),
|
||||||
this.renderCalendar(mode),
|
this.renderCalendar(mode),
|
||||||
this.renderYearView(),
|
this.renderYearView(),
|
||||||
this.renderTime(mode),
|
this.renderTime(),
|
||||||
this.renderFooter()
|
this.renderFooter()
|
||||||
]
|
]
|
||||||
case 'time-date':
|
case 'time-date':
|
||||||
return [
|
return [
|
||||||
this.renderCalendarViewHeader(mode),
|
this.renderCalendarViewHeader(mode),
|
||||||
this.renderTime(mode),
|
this.renderTime(),
|
||||||
this.renderCalendar(mode),
|
this.renderCalendar(mode),
|
||||||
this.renderYearView(),
|
this.renderYearView(),
|
||||||
this.renderFooter()
|
this.renderFooter()
|
||||||
]
|
]
|
||||||
case 'time':
|
case 'time':
|
||||||
return [
|
return [
|
||||||
this.renderTime(mode),
|
this.renderTime(),
|
||||||
this.renderFooter()
|
this.renderFooter()
|
||||||
]
|
]
|
||||||
case 'month':
|
case 'month':
|
||||||
@ -1638,4 +1463,3 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let datetimeIds = 0;
|
let datetimeIds = 0;
|
||||||
const PICKER_COL_ACTIVE = 'picker-col-item-active';
|
|
||||||
|
@ -183,6 +183,17 @@ dates in JavaScript.
|
|||||||
| `Shift` + `PageUp` | Changes the grid of dates to the previous year. |
|
| `Shift` + `PageUp` | Changes the grid of dates to the previous year. |
|
||||||
| `Shift` + `PageDown` | Changes the grid of dates to the next year. |
|
| `Shift` + `PageDown` | Changes the grid of dates to the next year. |
|
||||||
|
|
||||||
|
#### Time, Month, and Year Wheels
|
||||||
|
|
||||||
|
When using the time wheel picker, you can use the number keys to select hour and minute values when the columns are focused.
|
||||||
|
|
||||||
|
| Key | Function |
|
||||||
|
| ------------------ | ------------------------------------------------------------ |
|
||||||
|
| `ArrowUp` | Scroll to the previous item. |
|
||||||
|
| `ArrowDown` | Scroll to the next item. |
|
||||||
|
| `Home` | Scroll to the first item. |
|
||||||
|
| `End` | Scroll to the last item. |
|
||||||
|
|
||||||
## Interfaces
|
## Interfaces
|
||||||
|
|
||||||
### DatetimeChangeEventDetail
|
### DatetimeChangeEventDetail
|
||||||
@ -776,27 +787,29 @@ Type: `Promise<void>`
|
|||||||
|
|
||||||
- [ion-buttons](../buttons)
|
- [ion-buttons](../buttons)
|
||||||
- [ion-button](../button)
|
- [ion-button](../button)
|
||||||
|
- ion-picker-internal
|
||||||
|
- ion-picker-column-internal
|
||||||
- [ion-item](../item)
|
- [ion-item](../item)
|
||||||
- [ion-label](../label)
|
- [ion-label](../label)
|
||||||
- ion-icon
|
- ion-icon
|
||||||
- [ion-segment](../segment)
|
- [ion-popover](../popover)
|
||||||
- [ion-segment-button](../segment-button)
|
|
||||||
|
|
||||||
### Graph
|
### Graph
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD;
|
graph TD;
|
||||||
ion-datetime --> ion-buttons
|
ion-datetime --> ion-buttons
|
||||||
ion-datetime --> ion-button
|
ion-datetime --> ion-button
|
||||||
|
ion-datetime --> ion-picker-internal
|
||||||
|
ion-datetime --> ion-picker-column-internal
|
||||||
ion-datetime --> ion-item
|
ion-datetime --> ion-item
|
||||||
ion-datetime --> ion-label
|
ion-datetime --> ion-label
|
||||||
ion-datetime --> ion-icon
|
ion-datetime --> ion-icon
|
||||||
ion-datetime --> ion-segment
|
ion-datetime --> ion-popover
|
||||||
ion-datetime --> ion-segment-button
|
|
||||||
ion-button --> ion-ripple-effect
|
ion-button --> ion-ripple-effect
|
||||||
ion-item --> ion-icon
|
ion-item --> ion-icon
|
||||||
ion-item --> ion-ripple-effect
|
ion-item --> ion-ripple-effect
|
||||||
ion-item --> ion-note
|
ion-item --> ion-note
|
||||||
ion-segment-button --> ion-ripple-effect
|
ion-popover --> ion-backdrop
|
||||||
style ion-datetime fill:#f9f,stroke:#333,stroke-width:4px
|
style ion-datetime fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -401,7 +401,9 @@
|
|||||||
const modalElement = Object.assign(document.createElement('ion-modal'), {
|
const modalElement = Object.assign(document.createElement('ion-modal'), {
|
||||||
component: element
|
component: element
|
||||||
});
|
});
|
||||||
document.body.appendChild(modalElement);
|
|
||||||
|
const app = document.querySelector('ion-app');
|
||||||
|
app.appendChild(modalElement);
|
||||||
return modalElement;
|
return modalElement;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -57,6 +57,15 @@
|
|||||||
value="2021-06-20"
|
value="2021-06-20"
|
||||||
></ion-datetime>
|
></ion-datetime>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>AM/PM Min/Max</h2>
|
||||||
|
<ion-datetime
|
||||||
|
presentation="time"
|
||||||
|
min="09:30"
|
||||||
|
max="14:50"
|
||||||
|
value="10:30"
|
||||||
|
></ion-datetime>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
presentation="year"
|
presentation="year"
|
||||||
></ion-datetime>
|
></ion-datetime>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,5 +1,28 @@
|
|||||||
import { DatetimeParts } from '../datetime-interface';
|
import { DatetimeParts } from '../datetime-interface';
|
||||||
|
|
||||||
|
const get12HourTime = (hour: number) => {
|
||||||
|
return hour % 12 || 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFormattedAMPM = (ampm?: string) => {
|
||||||
|
if (ampm === undefined) { return ''; }
|
||||||
|
|
||||||
|
return ampm.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFormattedTime = (refParts: DatetimeParts, use24Hour: boolean): string => {
|
||||||
|
if (refParts.hour === undefined || refParts.minute === undefined) { return 'Invalid Time'; }
|
||||||
|
|
||||||
|
const hour = use24Hour ? getFormattedHour(refParts.hour, use24Hour) : get12HourTime(refParts.hour);
|
||||||
|
const minute = addTimePadding(refParts.minute);
|
||||||
|
|
||||||
|
if (use24Hour) {
|
||||||
|
return `${hour}:${minute}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${hour}:${minute} ${getFormattedAMPM(refParts.ampm)}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds padding to a time value so
|
* Adds padding to a time value so
|
||||||
* that it is always 2 digits.
|
* that it is always 2 digits.
|
||||||
|
@ -111,7 +111,6 @@
|
|||||||
column.addEventListener('ionChange', (ev) => {
|
column.addEventListener('ionChange', (ev) => {
|
||||||
console.log('Column change', ev.detail);
|
console.log('Column change', ev.detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
const setPickerColumn = (selector, items, value) => {
|
const setPickerColumn = (selector, items, value) => {
|
||||||
const picker = document.querySelector(selector);
|
const picker = document.querySelector(selector);
|
||||||
|
|
||||||
|
@ -633,6 +633,10 @@ Type: `Promise<void>`
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
### Used by
|
||||||
|
|
||||||
|
- [ion-datetime](../datetime)
|
||||||
|
|
||||||
### Depends on
|
### Depends on
|
||||||
|
|
||||||
- [ion-backdrop](../backdrop)
|
- [ion-backdrop](../backdrop)
|
||||||
@ -641,6 +645,7 @@ Type: `Promise<void>`
|
|||||||
```mermaid
|
```mermaid
|
||||||
graph TD;
|
graph TD;
|
||||||
ion-popover --> ion-backdrop
|
ion-popover --> ion-backdrop
|
||||||
|
ion-datetime --> ion-popover
|
||||||
style ion-popover fill:#f9f,stroke:#333,stroke-width:4px
|
style ion-popover fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -852,10 +852,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
### Used by
|
|
||||||
|
|
||||||
- [ion-datetime](../datetime)
|
|
||||||
|
|
||||||
### Depends on
|
### Depends on
|
||||||
|
|
||||||
- [ion-ripple-effect](../ripple-effect)
|
- [ion-ripple-effect](../ripple-effect)
|
||||||
@ -864,7 +860,6 @@ export default defineComponent({
|
|||||||
```mermaid
|
```mermaid
|
||||||
graph TD;
|
graph TD;
|
||||||
ion-segment-button --> ion-ripple-effect
|
ion-segment-button --> ion-ripple-effect
|
||||||
ion-datetime --> ion-segment-button
|
|
||||||
style ion-segment-button fill:#f9f,stroke:#333,stroke-width:4px
|
style ion-segment-button fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -626,19 +626,6 @@ export default defineComponent({
|
|||||||
| `--background` | Background of the segment button |
|
| `--background` | Background of the segment button |
|
||||||
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
### Used by
|
|
||||||
|
|
||||||
- [ion-datetime](../datetime)
|
|
||||||
|
|
||||||
### Graph
|
|
||||||
```mermaid
|
|
||||||
graph TD;
|
|
||||||
ion-datetime --> ion-segment
|
|
||||||
style ion-segment fill:#f9f,stroke:#333,stroke-width:4px
|
|
||||||
```
|
|
||||||
|
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
*Built with [StencilJS](https://stenciljs.com/)*
|
*Built with [StencilJS](https://stenciljs.com/)*
|
||||||
|
Reference in New Issue
Block a user