diff --git a/packages/elements/src/time-picker/__test__/time-picker.test.js b/packages/elements/src/time-picker/__test__/time-picker.test.js
index 9b6e24ab7f..8115cb1ac8 100644
--- a/packages/elements/src/time-picker/__test__/time-picker.test.js
+++ b/packages/elements/src/time-picker/__test__/time-picker.test.js
@@ -174,6 +174,45 @@ describe('time-picker/TimePicker', function () {
const el = await fixture(timePickerRoleNone);
expect(el).shadowDom.to.equalSnapshot();
});
+
+ it('should not pre-populate other segments value when hours value changes', async function () {
+ const el = await fixture('');
+ const hoursInput = el.hoursInput;
+ hoursInput.value = '12';
+ hoursInput.dispatchEvent(
+ new CustomEvent('focused-changed', { bubbles: true, detail: { value: false } })
+ );
+ await elementUpdated(el);
+ expect(el.hours).to.equal(12);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(null);
+ });
+
+ it('should not pre-populate other segments value when minutes value change', async function () {
+ const el = await fixture('');
+ const minutesInput = el.minutesInput;
+ minutesInput.value = '30';
+ minutesInput.dispatchEvent(
+ new CustomEvent('focused-changed', { bubbles: true, detail: { value: false } })
+ );
+ await elementUpdated(el);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(30);
+ expect(el.seconds).to.equal(null);
+ });
+
+ it('should not pre-populate other segments value when seconds value change', async function () {
+ const el = await fixture('');
+ const secondsInput = el.secondsInput;
+ secondsInput.value = '45';
+ secondsInput.dispatchEvent(
+ new CustomEvent('focused-changed', { bubbles: true, detail: { value: false } })
+ );
+ await elementUpdated(el);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(45);
+ });
});
describe('Defaults', function () {
@@ -613,5 +652,59 @@ describe('time-picker/TimePicker', function () {
expect(el.minutes).to.equal(0);
expect(el.hours).to.equal(0);
});
+
+ it('Cycling through seconds should not pre-populate other segments value', async function () {
+ el = await fixture('');
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(null);
+ createKeyboardEvent(el.secondsInput, InputKey.arrowDown);
+ await elementUpdated(el);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(0);
+
+ createKeyboardEvent(el.secondsInput, InputKey.arrowDown);
+ await elementUpdated(el);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(59);
+ });
+
+ it('Cycling through minutes should not pre-populate other segments value', async function () {
+ el = await fixture(timePickerDefaults);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(null);
+ createKeyboardEvent(el.minutesInput, InputKey.arrowDown);
+ await elementUpdated(el);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(0);
+ expect(el.seconds).to.equal(null);
+
+ createKeyboardEvent(el.minutesInput, InputKey.arrowDown);
+ await elementUpdated(el);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(59);
+ expect(el.seconds).to.equal(null);
+ });
+
+ it('Cycling through hours should not pre-populate other segments value', async function () {
+ el = await fixture(timePickerDefaults);
+ expect(el.hours).to.equal(null);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(null);
+ createKeyboardEvent(el.hoursInput, InputKey.arrowDown);
+ await elementUpdated(el);
+ expect(el.hours).to.equal(0);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(null);
+
+ createKeyboardEvent(el.hoursInput, InputKey.arrowDown);
+ await elementUpdated(el);
+ expect(el.hours).to.equal(23);
+ expect(el.minutes).to.equal(null);
+ expect(el.seconds).to.equal(null);
+ });
});
});
diff --git a/packages/elements/src/time-picker/index.ts b/packages/elements/src/time-picker/index.ts
index dae0fc07bf..f4affc18ca 100644
--- a/packages/elements/src/time-picker/index.ts
+++ b/packages/elements/src/time-picker/index.ts
@@ -1,3 +1,6 @@
+import type { FocusedChangedEvent, ValueChangedEvent } from '../events';
+import type { NumberField } from '../number-field';
+
import {
CSSResultGroup,
ControlElement,
@@ -20,6 +23,8 @@ import {
MILLISECONDS_IN_HOUR,
MILLISECONDS_IN_MINUTE,
MILLISECONDS_IN_SECOND,
+ MINUTES_IN_HOUR,
+ SECONDS_IN_MINUTE,
TimeFormat,
addOffset,
format,
@@ -32,8 +37,6 @@ import {
toTimeSegment
} from '@refinitiv-ui/utils/date.js';
-import type { FocusedChangedEvent, ValueChangedEvent } from '../events';
-import type { NumberField } from '../number-field';
import '../number-field/index.js';
import { VERSION } from '../version.js';
@@ -50,6 +53,21 @@ const MAX_SECONDS = 59;
const HOURS_IN_DAY = 24;
const HOURS_OF_NOON = 12;
+const SegmentMap = {
+ [Segment.HOURS]: {
+ milliseconds: MILLISECONDS_IN_HOUR,
+ cycle: HOURS_IN_DAY
+ },
+ [Segment.MINUTES]: {
+ milliseconds: MILLISECONDS_IN_MINUTE,
+ cycle: MINUTES_IN_HOUR
+ },
+ [Segment.SECONDS]: {
+ milliseconds: MILLISECONDS_IN_SECOND,
+ cycle: SECONDS_IN_MINUTE
+ }
+};
+
const Placeholder = {
HOURS: '--',
MINUTES: '--',
@@ -231,7 +249,7 @@ export class TimePicker extends ControlElement {
}
}
public override get value(): string {
- if (this.hours === null || this.minutes === null || (this.isShowSeconds && this.seconds === null)) {
+ if (!this.isCompleteValue) {
return '';
}
return this.currentTimeString;
@@ -318,6 +336,13 @@ export class TimePicker extends ControlElement {
return this.showSeconds || this.valueWithSeconds;
}
+ /**
+ * True if time value is complete, that is having all the required time segment
+ */
+ private get isCompleteValue(): boolean {
+ return !(this.hours === null || this.minutes === null || (this.isShowSeconds && this.seconds === null));
+ }
+
/**
* Get hours taking into account AM/PM placeholder
*/
@@ -464,20 +489,6 @@ export class TimePicker extends ControlElement {
// no default
}
- // Pre-populate empty segments
- if (value !== null) {
- if (segment === Segment.HOURS && this.minutes === null) {
- this.minutes = 0;
- }
- if (
- this.isShowSeconds &&
- this.seconds === null &&
- (segment === Segment.HOURS || segment === Segment.MINUTES)
- ) {
- this.seconds = 0;
- }
- }
-
// verify value again, as time segment validation
// might fail in setter and previous value returned
if (oldValue !== this.value) {
@@ -722,28 +733,24 @@ export class TimePicker extends ControlElement {
/**
* Changes a time segment value by a specified amount.
* Also updates parent values when rolling through cycles.
+ * Incomplete value will update only segment without pre-populate value.
* @param amount Amount to change by
* @param segment Segment id
* @returns {void}
*/
private changeValueBy(amount: number, segment: Segment): void {
- let offset = 0;
- switch (segment) {
- case Segment.HOURS:
- offset = this.hours === null ? 0 : amount * MILLISECONDS_IN_HOUR;
- break;
- case Segment.MINUTES:
- offset = this.minutes === null ? 0 : amount * MILLISECONDS_IN_MINUTE;
- break;
- case Segment.SECONDS:
- offset = this.seconds === null ? 0 : amount * MILLISECONDS_IN_SECOND;
- break;
- // no default
+ const segmentValue = this[segment];
+ const { milliseconds, cycle } = SegmentMap[segment];
+
+ if (this.isCompleteValue) {
+ const offset = segmentValue === null ? 0 : amount * milliseconds;
+ const value = addOffset(this.currentTimeString, offset);
+ this.setValueAndNotify(value);
+ this.selectedSegment = segment;
+ } else {
+ // a segment cycle is added to support wrapping of amount with negative value
+ this[segment] = segmentValue === null ? 0 : (segmentValue + amount + cycle) % cycle;
}
-
- const value = addOffset(this.currentTimeString, offset);
- this.setValueAndNotify(value);
- this.selectedSegment = segment;
}
/**