Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(datetime-picker): replace input with datetimeField #408

Draft
wants to merge 9 commits into
base: v7
Choose a base branch
from
309 changes: 95 additions & 214 deletions documents/src/pages/elements/datetime-picker.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@import 'element:ef-icon';
@import 'element:ef-overlay';
@import 'element:ef-time-picker';
@import 'element:ef-text-field';
@import 'element:ef-datetime-field';

@import (reference) 'ef-text-field';

Expand Down Expand Up @@ -32,6 +32,23 @@
}
// #endregion

padding: 0;

[part~=button] {
height: 100%;
width: @button-height;
display: flex;
justify-content: center;
align-items: center;
flex: none;
padding: 0;
margin: 0;
border: 0;
background: none;
color: inherit;
font-size: inherit;
}

[part=calendar] {
width: @calendar-width;
padding: 0 calc((@input-width - @calendar-width) / 2);
Expand All @@ -48,7 +65,7 @@
}

&[range] {
ef-text-field {
ef-datetime-field {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better if we style this with part selector ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should follow other elements and use part selector.

text-align: center;
}
}
Expand All @@ -57,10 +74,6 @@
color: inherit;
}

[part='icon'] {
color: @control-text-color;
}

[part=input-separator] {
line-height: @control-height;
background: @global-text-color;
Expand Down
146 changes: 25 additions & 121 deletions packages/elements/src/datetime-field/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
} from '@refinitiv-ui/utils/date.js';
import {
translate,
getLocale,
TranslateDirective,
TranslatePropertyKey
} from '@refinitiv-ui/translate';
Expand All @@ -31,6 +30,7 @@ import type {
DateTimeFormatPart,
InputSelection
} from './types';
import { resolvedLocale } from './resolvedLocale.js';
import { TextField } from '../text-field/index.js';
import {
getSelectedPartIndex,
Expand Down Expand Up @@ -111,101 +111,45 @@ export class DatetimeField extends TextField {
@property({ type: String, reflect: true })
public max: string | null = null;

private _timepicker = false;
/**
* Toggle to display the time picker
* @param timepicker true to set timepicker mode
* @default false
*/
@property({ type: Boolean, reflect: true })
public set timepicker (timepicker: boolean) {
const oldTimepicker = this._timepicker;
if (timepicker !== oldTimepicker) {
this._timepicker = timepicker;
this._locale = null;
this.requestUpdate('timepicker', oldTimepicker);
}
}
public get timepicker (): boolean {
return this._timepicker;
}
public timepicker = false;

private _showSeconds = false;
/**
* Toggle to display the seconds
* @param showSeconds true to show seconds
* @default false
*/
@property({ type: Boolean, attribute: 'show-seconds', reflect: true })
public set showSeconds (showSeconds: boolean) {
const oldShowSeconds = this._showSeconds;
if (oldShowSeconds !== showSeconds) {
this._showSeconds = showSeconds;
this._locale = null;
this.requestUpdate('showSeconds', oldShowSeconds);
}
}
public get showSeconds (): boolean {
return this._showSeconds;
}
public showSeconds = false;

private _amPm = false;
/**
* Overrides 12hr time display format
* @param amPm true to show 12hr time format
* @default false
*/
@property({ type: Boolean, attribute: 'am-pm', reflect: true })
public set amPm (amPm: boolean) {
const oldAmPm = this._amPm;
if (oldAmPm !== amPm) {
this._amPm = amPm;
this._locale = null;
this.requestUpdate('amPm', oldAmPm);
}
}
public get amPm (): boolean {
return this._amPm;
}
public amPm = false;

private _formatOptions: Intl.DateTimeFormatOptions | null = null;
/**
* Set the datetime format options based on
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
* `formatOptions` overrides `timepicker` and `showSeconds` properties.
* Note: time-zone is not supported
* @param formatOptions Format options
* @default - null
*/
@property({ attribute: false })
public set formatOptions (formatOptions: Intl.DateTimeFormatOptions | null) {
const oldFormatOptions = this._formatOptions;
if (oldFormatOptions !== formatOptions) {
this._formatOptions = formatOptions;
this._locale = null;
this.requestUpdate('formatOptions', oldFormatOptions);
}
}
public get formatOptions (): Intl.DateTimeFormatOptions | null {
return this._formatOptions;
}
public formatOptions: Intl.DateTimeFormatOptions | null = null;

/**
* Used for translations
* Set the Locale object.
* `Locale` overrides `formatOptions`, `timepicker` and `showSeconds` properties.
*/
@translate({ mode: 'directive', scope: 'ef-datetime-field' })
protected t!: TranslateDirective;
@property({ attribute: false })
public locale: Locale | null = null;

/**
* Format, which is based on locale
* Used for translations
*/
private _locale: Locale | null = null;
protected get locale (): Locale {
if (!this._locale) {
this._locale = this.resolveLocale();
}
return this._locale;
}
@translate({ mode: 'directive', scope: 'ef-datetime-field' })
protected t!: TranslateDirective;

private interimValueState = false; // make sure that internal input field value is updated only on external value change
/**
Expand Down Expand Up @@ -265,37 +209,19 @@ export class DatetimeField extends TextField {
protected partLabel = '';

/**
* Transform Date object to date string
* @param value Date
* @returns dateSting
*/
protected dateToString (value: Date): string {
return isNaN(value.getTime()) ? '' : utcFormat(value, this.locale.isoFormat);
}

/**
* Returns true if the datetime field has timepicker
* @returns hasTimePicker
*/
protected get hasTimePicker (): boolean {
// need to check for attribute to resolve the value correctly until the first lifecycle is run
return this.timepicker || this.hasAttribute('timepicker') || this.hasAmPm || this.hasSeconds;
}

/**
* Returns true if the datetime field has seconds
* @returns hasSeconds
* Get resolved locale for current element
*/
protected get hasSeconds (): boolean {
return this.showSeconds || this.hasAttribute('show-seconds');
protected get resolvedLocale (): Locale {
return resolvedLocale(this);
}

/**
* Returns true if the datetime field has am-pm
* @returns hasAmPm
* Transform Date object to date string
* @param value Date
* @returns dateSting
*/
protected get hasAmPm (): boolean {
return this.amPm || this.hasAttribute('am-pm');
protected dateToString (value: Date): string {
return isNaN(value.getTime()) ? '' : utcFormat(value, this.resolvedLocale.isoFormat);
}

/**
Expand All @@ -320,10 +246,6 @@ export class DatetimeField extends TextField {
public willUpdate (changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);

if (changedProperties.has(TranslatePropertyKey)) {
this._locale = null; // Locale is updated on next call via getter
}

if (changedProperties.has(FocusedPropertyKey) && !this.focused) {
this.partLabel = '';
}
Expand All @@ -343,6 +265,7 @@ export class DatetimeField extends TextField {
|| changedProperties.has('timepicker')
|| changedProperties.has('showSeconds')
|| changedProperties.has('amPm')
|| changedProperties.has('locale')
|| (changedProperties.has(FocusedPropertyKey) && this.value !== '' && !this.focused);
}

Expand Down Expand Up @@ -392,7 +315,7 @@ export class DatetimeField extends TextField {
return true;
}
// value format depends on locale.
return getFormat(value) === this.locale.isoFormat;
return getFormat(value) === this.resolvedLocale.isoFormat;
}

/**
Expand All @@ -401,33 +324,14 @@ export class DatetimeField extends TextField {
* @returns {void}
*/
protected override warnInvalidValue (value: string): void {
new WarningNotice(`${this.localName}: the specified value "${value}" does not conform to the required format. The format is '${this.locale.isoFormat}'.`).show();
}

/**
* Resolve locale based on element parameters
* @returns locale Resolved locale
*/
protected resolveLocale (): Locale {
const hasTimePicker = this.hasTimePicker;

// TODO: Do not use dateStyle and timeStyle as these are supported only in modern browsers
return Locale.fromOptions(this.formatOptions || {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: hasTimePicker ? 'numeric' : undefined,
minute: hasTimePicker ? 'numeric' : undefined,
second: this.hasSeconds ? 'numeric' : undefined,
hour12: this.hasAmPm ? true : undefined // force am-pm if provided, otherwise rely on locale
}, `${getLocale(this)}`);
new WarningNotice(`${this.localName}: the specified value "${value}" does not conform to the required format. The format is '${this.resolvedLocale.isoFormat}'.`).show();
}

/**
* Get Intl.DateTimeFormat object from locale
*/
protected get formatter (): Intl.DateTimeFormat {
return this.locale.formatter;
return this.resolvedLocale.formatter;
}

/**
Expand Down Expand Up @@ -458,7 +362,7 @@ export class DatetimeField extends TextField {
protected toValue (inputValue: string): string {
let value = '';
try {
value = inputValue ? this.locale.parse(inputValue, this.value || this.startDate) : '';
value = inputValue ? this.resolvedLocale.parse(inputValue, this.value || this.startDate) : '';
}
catch (error) {
// do nothing
Expand Down
Loading