From 275ad967e114ee5cd121a8467ba7891cbf189ab7 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 24 Jul 2023 17:15:14 -0700 Subject: [PATCH 01/67] feat(time-picker): stubbing out initial boilerplate markup for showing and editing milliseconds. Displays milliseconds only when step is a decimal with 3 places or less --- .../src/components/time-picker/resources.ts | 3 + .../components/time-picker/time-picker.tsx | 90 ++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/time-picker/resources.ts b/packages/calcite-components/src/components/time-picker/resources.ts index 19679137c0d..a281ead6811 100644 --- a/packages/calcite-components/src/components/time-picker/resources.ts +++ b/packages/calcite-components/src/components/time-picker/resources.ts @@ -6,6 +6,8 @@ export const CSS = { buttonHourUp: "button--hour-up", buttonMeridiemDown: "button--meridiem-down", buttonMeridiemUp: "button--meridiem-up", + buttonMillisecondDown: "button--millisecond-down", + buttonMillisecondUp: "button--millisecond-up", buttonMinuteDown: "button--minute-down", buttonMinuteUp: "button--minute-up", buttonSecondDown: "button--second-down", @@ -17,6 +19,7 @@ export const CSS = { hour: "hour", input: "input", meridiem: "meridiem", + millisecond: "millisecond", minute: "minute", second: "second", showMeridiem: "show-meridiem", diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 28f27d93eaf..e1b23cdd271 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -161,12 +161,18 @@ export class TimePicker @State() hourCycle: HourCycle; + // TODO: set this per locale + @State() localizedDecimalSeparator: string = "."; + @State() localizedHour: string; @State() localizedHourSuffix: string; @State() localizedMeridiem: string; + // TODO: set localizedMillisecond on mount and whenever millisecond value changes + @State() localizedMillisecond: string; + @State() localizedMinute: string; @State() localizedMinuteSuffix: string; @@ -181,6 +187,9 @@ export class TimePicker @State() second: string; + // TODO: calculate this on mount and whenever step changes + @State() showMillisecond: boolean; + @State() showSecond: boolean; @State() defaultMessages: TimePickerMessages; @@ -307,7 +316,8 @@ export class TimePicker // -------------------------------------------------------------------------- private updateShowSecond(): void { - this.showSecond = this.step < 60; + this.showSecond = this.step >= 0 && this.step < 60; + this.showMillisecond = this.step >= 0.001 && this.step < 1; } private async focusPart(target: TimePart): Promise { @@ -336,6 +346,10 @@ export class TimePicker this.setValuePart("meridiem", newMeridiem); }; + private decrementMillisecond = (): void => { + // TODO: decrementMillisecond + }; + private decrementMinuteOrSecond = (key: MinuteOrSecond): void => { let newValue; if (isValidNumber(this[key])) { @@ -446,6 +460,10 @@ export class TimePicker this.incrementMinuteOrSecond("minute"); }; + private incrementMillisecond = (): void => { + // TODO: increment millisecond + }; + private incrementSecond = (): void => { this.incrementMinuteOrSecond("second"); }; @@ -488,6 +506,12 @@ export class TimePicker } }; + private millisecondUpButtonKeyDownHandler = (event: KeyboardEvent): void => { + if (this.buttonActivated(event)) { + this.incrementMillisecond(); + } + }; + private minuteDownButtonKeyDownHandler = (event: KeyboardEvent): void => { if (this.buttonActivated(event)) { this.decrementMinute(); @@ -911,6 +935,70 @@ export class TimePicker )} + {this.showMillisecond && ( + {this.localizedDecimalSeparator} + )} + {this.showMillisecond && ( +
+ + + + + {/* TODO: localizedMillisecond */} + {this.localizedMillisecond || "--"} + + + + +
+ )} {this.localizedSecondSuffix && ( {this.localizedSecondSuffix} )} From 1f4ea0f6308d9d1de7e67819b1d065437231ef97 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 24 Jul 2023 17:17:53 -0700 Subject: [PATCH 02/67] adding getDecimalPlaces number util since we're going to need this to determine how precise to round millisecond numbers to --- .../src/utils/number.spec.ts | 13 ++++++++++++ .../calcite-components/src/utils/number.ts | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/calcite-components/src/utils/number.spec.ts b/packages/calcite-components/src/utils/number.spec.ts index 0bcd61b28e3..fa230b3a659 100644 --- a/packages/calcite-components/src/utils/number.spec.ts +++ b/packages/calcite-components/src/utils/number.spec.ts @@ -6,6 +6,7 @@ import { isValidNumber, parseNumberString, sanitizeNumberString, + getDecimalPlaces, } from "./number"; describe("isValidNumber", () => { @@ -229,3 +230,15 @@ describe("addLocalizedTrailingDecimalZeros", () => { }); }); }); + +describe("getDecimalPlaces", () => { + it("returns the amount of decimal places for a string representation of a decimal", () => { + const decimalPlaces = getDecimalPlaces("0.001"); + expect(decimalPlaces).toBe(3); + }); + + it("returns the amount of decimal places for a number representation of a decimal", () => { + const decimalPlaces = getDecimalPlaces(0.001); + expect(decimalPlaces).toBe(3); + }); +}); diff --git a/packages/calcite-components/src/utils/number.ts b/packages/calcite-components/src/utils/number.ts index 388b6b0dc34..8877be9d1cc 100644 --- a/packages/calcite-components/src/utils/number.ts +++ b/packages/calcite-components/src/utils/number.ts @@ -259,3 +259,24 @@ export function addLocalizedTrailingDecimalZeros( } return localizedValue; } + +/** + * Returns the amount of decimal places for a number. + * + * @link https://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number + * @param {string | number} decimal - decimal value + * @returns {number} the amount of decimal places in a number + */ +export function getDecimalPlaces(decimal: string | number): number { + var match = ("" + decimal).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) { + return 0; + } + return Math.max( + 0, + // Number of digits right of decimal point. + (match[1] ? match[1].length : 0) - + // Adjust for scientific notation. + (match[2] ? +match[2] : 0) + ); +} From d2b6c27d849604d40b570a223654b18f584fd1e2 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 25 Jul 2023 11:39:44 -0700 Subject: [PATCH 03/67] adding millisecond entries to default messages --- .../time-picker/assets/time-picker/t9n/messages.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json index 00f28802e29..7bca55c5954 100644 --- a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json +++ b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json @@ -5,6 +5,9 @@ "meridiem": "AM/PM", "meridiemDown": "Decrease AM/PM", "meridiemUp": "Increase AM/PM", + "millisecond": "Millisecond", + "millisecondDown": "Decrease millisecond", + "millisecondUp": "Increase millisecond", "minute": "Minute", "minuteDown": "Decrease minute", "minuteUp": "Increase minute", From 187f7a2264b5dd22041bf62ed6bd3f818bbdafec Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 25 Jul 2023 11:48:28 -0700 Subject: [PATCH 04/67] adding more test cases for getDecimalPlaces --- .../calcite-components/src/utils/number.spec.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/utils/number.spec.ts b/packages/calcite-components/src/utils/number.spec.ts index fa230b3a659..76f8ea35bbb 100644 --- a/packages/calcite-components/src/utils/number.spec.ts +++ b/packages/calcite-components/src/utils/number.spec.ts @@ -233,12 +233,18 @@ describe("addLocalizedTrailingDecimalZeros", () => { describe("getDecimalPlaces", () => { it("returns the amount of decimal places for a string representation of a decimal", () => { - const decimalPlaces = getDecimalPlaces("0.001"); - expect(decimalPlaces).toBe(3); + expect(getDecimalPlaces("0")).toBe(0); + expect(getDecimalPlaces("0.1")).toBe(1); + expect(getDecimalPlaces("0.01")).toBe(2); + expect(getDecimalPlaces("0.001")).toBe(3); + expect(getDecimalPlaces("0.0001")).toBe(4); }); it("returns the amount of decimal places for a number representation of a decimal", () => { - const decimalPlaces = getDecimalPlaces(0.001); - expect(decimalPlaces).toBe(3); + expect(getDecimalPlaces(0)).toBe(0); + expect(getDecimalPlaces(0.1)).toBe(1); + expect(getDecimalPlaces(0.01)).toBe(2); + expect(getDecimalPlaces(0.001)).toBe(3); + expect(getDecimalPlaces(0.0001)).toBe(4); }); }); From b287eae8027dd11d6a784cd2b9ab0e589aad32b3 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 26 Jul 2023 13:51:32 -0700 Subject: [PATCH 05/67] displaying localized millisecond value on initial load --- .../components/time-picker/time-picker.tsx | 12 ++++++---- packages/calcite-components/src/utils/time.ts | 22 +++++++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index e1b23cdd271..13387840279 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -162,7 +162,7 @@ export class TimePicker @State() hourCycle: HourCycle; // TODO: set this per locale - @State() localizedDecimalSeparator: string = "."; + @State() localizedDecimalSeparator = "."; @State() localizedHour: string; @@ -621,16 +621,20 @@ export class TimePicker const { hour, minute, second } = parseTimeString(value); const { effectiveLocale: locale, numberingSystem } = this; const { + localizedDecimalSeparator, localizedHour, localizedHourSuffix, + localizedMillisecond, localizedMinute, localizedMinuteSuffix, localizedSecond, localizedSecondSuffix, localizedMeridiem, } = localizeTimeStringToParts({ value, locale, numberingSystem }); + this.localizedDecimalSeparator = localizedDecimalSeparator; this.localizedHour = localizedHour; this.localizedHourSuffix = localizedHourSuffix; + this.localizedMillisecond = localizedMillisecond; this.localizedMinute = localizedMinute; this.localizedMinuteSuffix = localizedMinuteSuffix; this.localizedSecond = localizedSecond; @@ -646,9 +650,11 @@ export class TimePicker } } else { this.hour = null; + this.localizedDecimalSeparator = null; this.localizedHour = null; this.localizedHourSuffix = null; this.localizedMeridiem = null; + this.localizedMillisecond = null; this.localizedMinute = null; this.localizedMinuteSuffix = null; this.localizedSecond = null; @@ -963,8 +969,7 @@ export class TimePicker // TODO: aria-valuenow // aria-valuenow={(millisecondIsNumber && parseInt(this.millisecond)) || "0"} - // TODO: this.millisecond - // aria-valuetext={this.millisecond} + aria-valuetext={this.localizedMillisecond} class={{ [CSS.input]: true, [CSS.millisecond]: true, @@ -980,7 +985,6 @@ export class TimePicker // TODO: setMillisecondEl // ref={this.setMillisecondEl} > - {/* TODO: localizedMillisecond */} {this.localizedMillisecond || "--"} 3) { + millisecond = parseFloat(second).toFixed(3); + millisecondDecimal = millisecond.split(".", 2)[1]; + numberStringFormatter.numberFormatOptions = { + locale, + numberingSystem, + }; + localizedMillisecondDecimal = numberStringFormatter.localize(millisecondDecimal); + localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; + } + return { + localizedDecimalSeparator, localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), + localizedMillisecond: localizedMillisecondDecimal, localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), From 1956cd65a1f341f1fab09b98310048690587d339 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 26 Jul 2023 16:30:52 -0700 Subject: [PATCH 06/67] updating parseTimeString to support fractional seconds, adding time tests --- .../calcite-components/src/utils/time.spec.ts | 88 ++++++++++++++++++- packages/calcite-components/src/utils/time.ts | 31 ++++++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/utils/time.spec.ts b/packages/calcite-components/src/utils/time.spec.ts index a7d01a4ac62..6ae962cbe4a 100644 --- a/packages/calcite-components/src/utils/time.spec.ts +++ b/packages/calcite-components/src/utils/time.spec.ts @@ -1,4 +1,90 @@ -import { toISOTimeString } from "./time"; +import { isValidTime, parseTimeString, toISOTimeString } from "./time"; + +describe("isValidTime", () => { + it("returns true when time string contains fractional seconds", () => { + expect(isValidTime("12:30:45.0")).toBe(true); + expect(isValidTime("12:30:45.01")).toBe(true); + expect(isValidTime("12:30:45.001")).toBe(true); + expect(isValidTime("12:30:45.1")).toBe(true); + expect(isValidTime("12:30:45.12")).toBe(true); + expect(isValidTime("12:30:45.123")).toBe(true); + expect(isValidTime("12:30:45.1234")).toBe(true); + expect(isValidTime("12:30:45.12345")).toBe(true); + expect(isValidTime("12:30:45.123456")).toBe(true); + expect(isValidTime("12:30:45.1234567")).toBe(true); + expect(isValidTime("12:30:45.12345678")).toBe(true); + expect(isValidTime("12:30:45.123456789")).toBe(true); + }); +}); + +describe("localizeTimeStringToParts", () => { + it("returns localized decimal and millisecond value", () => { + // TODO: write test + }); +}); + +describe("parseTimeString", () => { + it("returns hour, minute, and fractional second", () => { + expect(parseTimeString("12:30:45.0")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: null, + }); + expect(parseTimeString("12:30:45.01")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "01", + }); + expect(parseTimeString("12:30:45.001")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "001", + }); + expect(parseTimeString("12:30:45.0001")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "0001", + }); + expect(parseTimeString("12:30:45.1")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "1", + }); + expect(parseTimeString("12:30:45.12")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "12", + }); + expect(parseTimeString("12:30:45.123")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "123", + }); + expect(parseTimeString("12:30:45.1234")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "1234", + }); + expect(parseTimeString("12:30:45.12345")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "12345", + }); + }); + + it("returns null fractionalSecond when second is a whole number", () => { + expect(parseTimeString("12:30:45")).toEqual({ fractionalSecond: null, hour: "12", minute: "30", second: "45" }); + }); +}); describe("toISOTimeString", () => { it("returns hh:mm value when includeSeconds is false", () => { diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index b87207c9f55..913651df16c 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -1,10 +1,12 @@ -import { getDateTimeFormat, getSupportedNumberingSystem, NumberingSystem } from "./locale"; -import { isValidNumber } from "./number"; +import { getDateTimeFormat, getSupportedNumberingSystem, NumberingSystem, numberStringFormatter } from "./locale"; +import { getDecimalPlaces, isValidNumber } from "./number"; export type HourCycle = "12" | "24"; export interface LocalizedTime { + localizedDecimalSeparator: string; localizedHour: string; localizedHourSuffix: string; + localizedMillisecond: string; localizedMinute: string; localizedMinuteSuffix: string; localizedSecond: string; @@ -17,6 +19,7 @@ export type Meridiem = "AM" | "PM"; export type MinuteOrSecond = "minute" | "second"; export interface Time { + fractionalSecond: string; hour: string; minute: string; second: string; @@ -209,9 +212,25 @@ export function localizeTimeStringToParts({ if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); + + let millisecond, millisecondDecimal, localizedMillisecondDecimal, localizedDecimalSeparator; + const secondPrecision = getDecimalPlaces(second); + if (secondPrecision && secondPrecision > 3) { + millisecond = parseFloat(second).toFixed(3); + millisecondDecimal = millisecond.split(".", 2)[1]; + numberStringFormatter.numberFormatOptions = { + locale, + numberingSystem, + }; + localizedMillisecondDecimal = numberStringFormatter.localize(millisecondDecimal); + localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; + } + return { + localizedDecimalSeparator, localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), + localizedMillisecond: localizedMillisecondDecimal, localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), @@ -243,14 +262,20 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { - const [hour, minute, second] = value.split(":"); + const [hour, minute, secondDecimal] = value.split(":"); + let second, fractionalSecond; + if (secondDecimal) { + [second, fractionalSecond] = secondDecimal.split("."); + } return { + fractionalSecond: fractionalSecond && parseInt(fractionalSecond) !== 0 ? fractionalSecond : null, hour, minute, second, }; } return { + fractionalSecond: null, hour: null, minute: null, second: null, From caf04e9e0ea7edc28f472a58623b6fd7cd50daf2 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 26 Jul 2023 16:55:45 -0700 Subject: [PATCH 07/67] refactoring from millisecond to fractionalSecond --- .../assets/time-picker/t9n/messages.json | 6 +- .../src/components/time-picker/resources.ts | 6 +- .../components/time-picker/time-picker.tsx | 70 +++++++++---------- .../calcite-components/src/utils/time.spec.ts | 2 +- packages/calcite-components/src/utils/time.ts | 14 ++-- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json index 7bca55c5954..0738fa48ad4 100644 --- a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json +++ b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json @@ -5,9 +5,9 @@ "meridiem": "AM/PM", "meridiemDown": "Decrease AM/PM", "meridiemUp": "Increase AM/PM", - "millisecond": "Millisecond", - "millisecondDown": "Decrease millisecond", - "millisecondUp": "Increase millisecond", + "fractionalSecond": "Fractional second", + "fractionalSecondDown": "Decrease fractional second", + "fractionalSecondUp": "Increase fractional second", "minute": "Minute", "minuteDown": "Decrease minute", "minuteUp": "Increase minute", diff --git a/packages/calcite-components/src/components/time-picker/resources.ts b/packages/calcite-components/src/components/time-picker/resources.ts index a281ead6811..32834f0f5eb 100644 --- a/packages/calcite-components/src/components/time-picker/resources.ts +++ b/packages/calcite-components/src/components/time-picker/resources.ts @@ -2,12 +2,12 @@ export const CSS = { button: "button", buttonBottomLeft: "button--bottom-left", buttonBottomRight: "button--bottom-right", + buttonFractionalSecondDown: "button--fractionalSecond-down", + buttonFractionalSecondUp: "button--fractionalSecond-up", buttonHourDown: "button--hour-down", buttonHourUp: "button--hour-up", buttonMeridiemDown: "button--meridiem-down", buttonMeridiemUp: "button--meridiem-up", - buttonMillisecondDown: "button--millisecond-down", - buttonMillisecondUp: "button--millisecond-up", buttonMinuteDown: "button--minute-down", buttonMinuteUp: "button--minute-up", buttonSecondDown: "button--second-down", @@ -16,10 +16,10 @@ export const CSS = { buttonTopRight: "button--top-right", column: "column", delimiter: "delimiter", + fractionalSecond: "fractionalSecond", hour: "hour", input: "input", meridiem: "meridiem", - millisecond: "millisecond", minute: "minute", second: "second", showMeridiem: "show-meridiem", diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 13387840279..966491f2471 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -170,8 +170,8 @@ export class TimePicker @State() localizedMeridiem: string; - // TODO: set localizedMillisecond on mount and whenever millisecond value changes - @State() localizedMillisecond: string; + // TODO: set localizedFractionalSecond on mount and whenever fractionalSecond value changes + @State() localizedFractionalSecond: string; @State() localizedMinute: string; @@ -188,7 +188,7 @@ export class TimePicker @State() second: string; // TODO: calculate this on mount and whenever step changes - @State() showMillisecond: boolean; + @State() showFractionalSecond: boolean; @State() showSecond: boolean; @@ -317,7 +317,7 @@ export class TimePicker private updateShowSecond(): void { this.showSecond = this.step >= 0 && this.step < 60; - this.showMillisecond = this.step >= 0.001 && this.step < 1; + this.showFractionalSecond = this.step >= 0.001 && this.step < 1; } private async focusPart(target: TimePart): Promise { @@ -346,8 +346,8 @@ export class TimePicker this.setValuePart("meridiem", newMeridiem); }; - private decrementMillisecond = (): void => { - // TODO: decrementMillisecond + private decrementFractionalSecond = (): void => { + // TODO: decrementFractionalSecond }; private decrementMinuteOrSecond = (key: MinuteOrSecond): void => { @@ -460,8 +460,8 @@ export class TimePicker this.incrementMinuteOrSecond("minute"); }; - private incrementMillisecond = (): void => { - // TODO: increment millisecond + private incrementFractionalSecond = (): void => { + // TODO: increment fractionalSecond }; private incrementSecond = (): void => { @@ -506,9 +506,9 @@ export class TimePicker } }; - private millisecondUpButtonKeyDownHandler = (event: KeyboardEvent): void => { + private fractionalSecondUpButtonKeyDownHandler = (event: KeyboardEvent): void => { if (this.buttonActivated(event)) { - this.incrementMillisecond(); + this.incrementFractionalSecond(); } }; @@ -621,23 +621,23 @@ export class TimePicker const { hour, minute, second } = parseTimeString(value); const { effectiveLocale: locale, numberingSystem } = this; const { - localizedDecimalSeparator, localizedHour, localizedHourSuffix, - localizedMillisecond, localizedMinute, localizedMinuteSuffix, localizedSecond, + localizedDecimalSeparator, + localizedFractionalSecond, localizedSecondSuffix, localizedMeridiem, } = localizeTimeStringToParts({ value, locale, numberingSystem }); - this.localizedDecimalSeparator = localizedDecimalSeparator; this.localizedHour = localizedHour; this.localizedHourSuffix = localizedHourSuffix; - this.localizedMillisecond = localizedMillisecond; this.localizedMinute = localizedMinute; this.localizedMinuteSuffix = localizedMinuteSuffix; this.localizedSecond = localizedSecond; + this.localizedDecimalSeparator = localizedDecimalSeparator; + this.localizedFractionalSecond = localizedFractionalSecond; this.localizedSecondSuffix = localizedSecondSuffix; this.hour = hour; this.minute = minute; @@ -650,14 +650,14 @@ export class TimePicker } } else { this.hour = null; - this.localizedDecimalSeparator = null; this.localizedHour = null; this.localizedHourSuffix = null; this.localizedMeridiem = null; - this.localizedMillisecond = null; this.localizedMinute = null; this.localizedMinuteSuffix = null; this.localizedSecond = null; + this.localizedDecimalSeparator = null; + this.localizedFractionalSecond = null; this.localizedSecondSuffix = null; this.meridiem = null; this.minute = null; @@ -941,25 +941,25 @@ export class TimePicker )} - {this.showMillisecond && ( + {this.showFractionalSecond && ( {this.localizedDecimalSeparator} )} - {this.showMillisecond && ( + {this.showFractionalSecond && (
- {this.localizedMillisecond || "--"} + {this.localizedFractionalSecond || "--"} diff --git a/packages/calcite-components/src/utils/time.spec.ts b/packages/calcite-components/src/utils/time.spec.ts index 6ae962cbe4a..25840190d54 100644 --- a/packages/calcite-components/src/utils/time.spec.ts +++ b/packages/calcite-components/src/utils/time.spec.ts @@ -18,7 +18,7 @@ describe("isValidTime", () => { }); describe("localizeTimeStringToParts", () => { - it("returns localized decimal and millisecond value", () => { + it("returns localized decimal separator and fractional second value", () => { // TODO: write test }); }); diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 913651df16c..f86a668e5af 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -3,13 +3,13 @@ import { getDecimalPlaces, isValidNumber } from "./number"; export type HourCycle = "12" | "24"; export interface LocalizedTime { - localizedDecimalSeparator: string; localizedHour: string; localizedHourSuffix: string; - localizedMillisecond: string; localizedMinute: string; localizedMinuteSuffix: string; localizedSecond: string; + localizedDecimalSeparator: string; + localizedFractionalSecond: string; localizedSecondSuffix: string; localizedMeridiem: string; } @@ -213,11 +213,11 @@ export function localizeTimeStringToParts({ const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - let millisecond, millisecondDecimal, localizedMillisecondDecimal, localizedDecimalSeparator; + let fractionalSecond, millisecondDecimal, localizedMillisecondDecimal, localizedDecimalSeparator; const secondPrecision = getDecimalPlaces(second); - if (secondPrecision && secondPrecision > 3) { - millisecond = parseFloat(second).toFixed(3); - millisecondDecimal = millisecond.split(".", 2)[1]; + if (secondPrecision && secondPrecision > 1) { + fractionalSecond = parseFloat(second).toFixed(3); + millisecondDecimal = fractionalSecond.split(".", 2)[1]; numberStringFormatter.numberFormatOptions = { locale, numberingSystem, @@ -230,7 +230,7 @@ export function localizeTimeStringToParts({ localizedDecimalSeparator, localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), - localizedMillisecond: localizedMillisecondDecimal, + localizedFractionalSecond: localizedMillisecondDecimal, localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), From 196b452aa146e427a866d673227c1807d37c701f Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Thu, 27 Jul 2023 11:08:58 -0700 Subject: [PATCH 08/67] setting aria value ranges to accommodate any millisecond value --- .../src/components/time-picker/time-picker.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 966491f2471..1678530f7c0 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -960,12 +960,8 @@ export class TimePicker Date: Thu, 27 Jul 2023 11:09:50 -0700 Subject: [PATCH 09/67] renaming millisecond to fractionalSecond --- packages/calcite-components/src/utils/time.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index f86a668e5af..59fa1f39921 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -213,16 +213,16 @@ export function localizeTimeStringToParts({ const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - let fractionalSecond, millisecondDecimal, localizedMillisecondDecimal, localizedDecimalSeparator; + let fractionalSecond, fractionalSecondDecimal, localizedFractionalSecondDecimal, localizedDecimalSeparator; const secondPrecision = getDecimalPlaces(second); if (secondPrecision && secondPrecision > 1) { fractionalSecond = parseFloat(second).toFixed(3); - millisecondDecimal = fractionalSecond.split(".", 2)[1]; + fractionalSecondDecimal = fractionalSecond.split(".", 2)[1]; numberStringFormatter.numberFormatOptions = { locale, numberingSystem, }; - localizedMillisecondDecimal = numberStringFormatter.localize(millisecondDecimal); + localizedFractionalSecondDecimal = numberStringFormatter.localize(fractionalSecondDecimal); localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; } @@ -230,7 +230,7 @@ export function localizeTimeStringToParts({ localizedDecimalSeparator, localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), - localizedFractionalSecond: localizedMillisecondDecimal, + localizedFractionalSecond: localizedFractionalSecondDecimal, localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), From a68c02498b6e7ec691718f89856f109f57436460 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Thu, 27 Jul 2023 16:23:01 -0700 Subject: [PATCH 10/67] getRealDecimalPlaces now returns the true amount of decimal places taking into account no trailing zeros --- .../src/utils/number.spec.ts | 30 +++++++++++-------- .../calcite-components/src/utils/number.ts | 10 ++++--- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/calcite-components/src/utils/number.spec.ts b/packages/calcite-components/src/utils/number.spec.ts index 76f8ea35bbb..ecd928a971f 100644 --- a/packages/calcite-components/src/utils/number.spec.ts +++ b/packages/calcite-components/src/utils/number.spec.ts @@ -6,7 +6,7 @@ import { isValidNumber, parseNumberString, sanitizeNumberString, - getDecimalPlaces, + getRealDecimalPlaces, } from "./number"; describe("isValidNumber", () => { @@ -231,20 +231,24 @@ describe("addLocalizedTrailingDecimalZeros", () => { }); }); -describe("getDecimalPlaces", () => { - it("returns the amount of decimal places for a string representation of a decimal", () => { - expect(getDecimalPlaces("0")).toBe(0); - expect(getDecimalPlaces("0.1")).toBe(1); - expect(getDecimalPlaces("0.01")).toBe(2); - expect(getDecimalPlaces("0.001")).toBe(3); - expect(getDecimalPlaces("0.0001")).toBe(4); +describe("getRealDecimalPlaces", () => { + it("returns the amount of non-zero decimal places for a given number string", () => { + expect(getRealDecimalPlaces("0")).toBe(0); + expect(getRealDecimalPlaces("0.0")).toBe(0); + expect(getRealDecimalPlaces("0.00")).toBe(0); + expect(getRealDecimalPlaces("0.000")).toBe(0); + expect(getRealDecimalPlaces("0.1")).toBe(1); + expect(getRealDecimalPlaces("0.01")).toBe(2); + expect(getRealDecimalPlaces("0.001")).toBe(3); + expect(getRealDecimalPlaces("0.0001")).toBe(4); }); it("returns the amount of decimal places for a number representation of a decimal", () => { - expect(getDecimalPlaces(0)).toBe(0); - expect(getDecimalPlaces(0.1)).toBe(1); - expect(getDecimalPlaces(0.01)).toBe(2); - expect(getDecimalPlaces(0.001)).toBe(3); - expect(getDecimalPlaces(0.0001)).toBe(4); + expect(getRealDecimalPlaces(0)).toBe(0); + expect(getRealDecimalPlaces(0.0)).toBe(0); + expect(getRealDecimalPlaces(0.1)).toBe(1); + expect(getRealDecimalPlaces(0.01)).toBe(2); + expect(getRealDecimalPlaces(0.001)).toBe(3); + expect(getRealDecimalPlaces(0.0001)).toBe(4); }); }); diff --git a/packages/calcite-components/src/utils/number.ts b/packages/calcite-components/src/utils/number.ts index 8877be9d1cc..b1e858a6161 100644 --- a/packages/calcite-components/src/utils/number.ts +++ b/packages/calcite-components/src/utils/number.ts @@ -261,15 +261,17 @@ export function addLocalizedTrailingDecimalZeros( } /** - * Returns the amount of decimal places for a number. + * Returns the amount of real decimal places for a number, which excludes trailing zeros. + * + * Adapted from: * * @link https://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number * @param {string | number} decimal - decimal value * @returns {number} the amount of decimal places in a number */ -export function getDecimalPlaces(decimal: string | number): number { - var match = ("" + decimal).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); - if (!match) { +export function getRealDecimalPlaces(decimal: string | number): number { + const match = ("" + decimal).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match || parseInt(match[1]) === 0) { return 0; } return Math.max( From cdb3e4ec96ce7f16071b62d0dd4b271a61bf9084 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Fri, 28 Jul 2023 10:39:10 -0700 Subject: [PATCH 11/67] fixing parseTimeString to return correct fractionalSecond, fixing localizeTimeStringToParts and renaming getRealDecimalPlaces to getRealDecimalPlacesCount --- .../src/utils/number.spec.ts | 32 +++++++-------- .../calcite-components/src/utils/number.ts | 4 +- .../calcite-components/src/utils/time.spec.ts | 40 ++++++++++++++++--- packages/calcite-components/src/utils/time.ts | 39 +++++++++--------- 4 files changed, 73 insertions(+), 42 deletions(-) diff --git a/packages/calcite-components/src/utils/number.spec.ts b/packages/calcite-components/src/utils/number.spec.ts index ecd928a971f..c2b75682381 100644 --- a/packages/calcite-components/src/utils/number.spec.ts +++ b/packages/calcite-components/src/utils/number.spec.ts @@ -6,7 +6,7 @@ import { isValidNumber, parseNumberString, sanitizeNumberString, - getRealDecimalPlaces, + getRealDecimalPlacesCount, } from "./number"; describe("isValidNumber", () => { @@ -231,24 +231,24 @@ describe("addLocalizedTrailingDecimalZeros", () => { }); }); -describe("getRealDecimalPlaces", () => { +describe("getRealDecimalPlacesCount", () => { it("returns the amount of non-zero decimal places for a given number string", () => { - expect(getRealDecimalPlaces("0")).toBe(0); - expect(getRealDecimalPlaces("0.0")).toBe(0); - expect(getRealDecimalPlaces("0.00")).toBe(0); - expect(getRealDecimalPlaces("0.000")).toBe(0); - expect(getRealDecimalPlaces("0.1")).toBe(1); - expect(getRealDecimalPlaces("0.01")).toBe(2); - expect(getRealDecimalPlaces("0.001")).toBe(3); - expect(getRealDecimalPlaces("0.0001")).toBe(4); + expect(getRealDecimalPlacesCount("0")).toBe(0); + expect(getRealDecimalPlacesCount("0.0")).toBe(0); + expect(getRealDecimalPlacesCount("0.00")).toBe(0); + expect(getRealDecimalPlacesCount("0.000")).toBe(0); + expect(getRealDecimalPlacesCount("0.1")).toBe(1); + expect(getRealDecimalPlacesCount("0.01")).toBe(2); + expect(getRealDecimalPlacesCount("0.001")).toBe(3); + expect(getRealDecimalPlacesCount("0.0001")).toBe(4); }); it("returns the amount of decimal places for a number representation of a decimal", () => { - expect(getRealDecimalPlaces(0)).toBe(0); - expect(getRealDecimalPlaces(0.0)).toBe(0); - expect(getRealDecimalPlaces(0.1)).toBe(1); - expect(getRealDecimalPlaces(0.01)).toBe(2); - expect(getRealDecimalPlaces(0.001)).toBe(3); - expect(getRealDecimalPlaces(0.0001)).toBe(4); + expect(getRealDecimalPlacesCount(0)).toBe(0); + expect(getRealDecimalPlacesCount(0.0)).toBe(0); + expect(getRealDecimalPlacesCount(0.1)).toBe(1); + expect(getRealDecimalPlacesCount(0.01)).toBe(2); + expect(getRealDecimalPlacesCount(0.001)).toBe(3); + expect(getRealDecimalPlacesCount(0.0001)).toBe(4); }); }); diff --git a/packages/calcite-components/src/utils/number.ts b/packages/calcite-components/src/utils/number.ts index b1e858a6161..e131bcee984 100644 --- a/packages/calcite-components/src/utils/number.ts +++ b/packages/calcite-components/src/utils/number.ts @@ -261,7 +261,7 @@ export function addLocalizedTrailingDecimalZeros( } /** - * Returns the amount of real decimal places for a number, which excludes trailing zeros. + * Returns the quantity of real decimal places for a number, which excludes trailing zeros. * * Adapted from: * @@ -269,7 +269,7 @@ export function addLocalizedTrailingDecimalZeros( * @param {string | number} decimal - decimal value * @returns {number} the amount of decimal places in a number */ -export function getRealDecimalPlaces(decimal: string | number): number { +export function getRealDecimalPlacesCount(decimal: string | number): number { const match = ("" + decimal).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); if (!match || parseInt(match[1]) === 0) { return 0; diff --git a/packages/calcite-components/src/utils/time.spec.ts b/packages/calcite-components/src/utils/time.spec.ts index 25840190d54..c2bb9020afe 100644 --- a/packages/calcite-components/src/utils/time.spec.ts +++ b/packages/calcite-components/src/utils/time.spec.ts @@ -1,4 +1,4 @@ -import { isValidTime, parseTimeString, toISOTimeString } from "./time"; +import { isValidTime, localizeTimeStringToParts, parseTimeString, toISOTimeString } from "./time"; describe("isValidTime", () => { it("returns true when time string contains fractional seconds", () => { @@ -19,12 +19,34 @@ describe("isValidTime", () => { describe("localizeTimeStringToParts", () => { it("returns localized decimal separator and fractional second value", () => { - // TODO: write test + expect(localizeTimeStringToParts({ value: "06:45:30.12123", locale: "fr" })).toEqual({ + localizedHour: "06", + localizedHourSuffix: ":", + localizedMinute: "45", + localizedMinuteSuffix: ":", + localizedSecond: "30", + localizedDecimalSeparator: ",", + localizedFractionalSecond: "121", + localizedSecondSuffix: null, + localizedMeridiem: null, + }); + + expect(localizeTimeStringToParts({ value: "06:45:30.12123", locale: "da" })).toEqual({ + localizedHour: "06", + localizedHourSuffix: ".", + localizedMinute: "45", + localizedMinuteSuffix: ".", + localizedSecond: "30", + localizedDecimalSeparator: ",", + localizedFractionalSecond: "121", + localizedSecondSuffix: null, + localizedMeridiem: null, + }); }); }); describe("parseTimeString", () => { - it("returns hour, minute, and fractional second", () => { + it("returns real fractional second, rounded to the nearest millisecond", () => { expect(parseTimeString("12:30:45.0")).toEqual({ hour: "12", minute: "30", @@ -47,7 +69,13 @@ describe("parseTimeString", () => { hour: "12", minute: "30", second: "45", - fractionalSecond: "0001", + fractionalSecond: null, + }); + expect(parseTimeString("12:30:45.0049")).toEqual({ + hour: "12", + minute: "30", + second: "45", + fractionalSecond: "005", }); expect(parseTimeString("12:30:45.1")).toEqual({ hour: "12", @@ -71,13 +99,13 @@ describe("parseTimeString", () => { hour: "12", minute: "30", second: "45", - fractionalSecond: "1234", + fractionalSecond: "123", }); expect(parseTimeString("12:30:45.12345")).toEqual({ hour: "12", minute: "30", second: "45", - fractionalSecond: "12345", + fractionalSecond: "123", }); }); diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 59fa1f39921..e4a68516b22 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -1,5 +1,5 @@ import { getDateTimeFormat, getSupportedNumberingSystem, NumberingSystem, numberStringFormatter } from "./locale"; -import { getDecimalPlaces, isValidNumber } from "./number"; +import { getRealDecimalPlacesCount, isValidNumber } from "./number"; export type HourCycle = "12" | "24"; export interface LocalizedTime { @@ -195,45 +195,40 @@ export function localizeTimeString({ interface LocalizeTimeStringToPartsParameters { value: string; locale: string; - numberingSystem: NumberingSystem; + numberingSystem?: NumberingSystem; } export function localizeTimeStringToParts({ value, locale, - numberingSystem, + numberingSystem = "latn", }: LocalizeTimeStringToPartsParameters): LocalizedTime { if (!isValidTime(value)) { return null; } - const { hour, minute, second = "0" } = parseTimeString(value); + const { hour, minute, second = "0", fractionalSecond } = parseTimeString(value); const dateFromTimeString = new Date(Date.UTC(0, 0, 0, parseInt(hour), parseInt(minute), parseInt(second))); if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - - let fractionalSecond, fractionalSecondDecimal, localizedFractionalSecondDecimal, localizedDecimalSeparator; - const secondPrecision = getDecimalPlaces(second); - if (secondPrecision && secondPrecision > 1) { - fractionalSecond = parseFloat(second).toFixed(3); - fractionalSecondDecimal = fractionalSecond.split(".", 2)[1]; + let localizedFractionalSecond, localizedDecimalSeparator; + if (fractionalSecond) { numberStringFormatter.numberFormatOptions = { locale, numberingSystem, }; - localizedFractionalSecondDecimal = numberStringFormatter.localize(fractionalSecondDecimal); + localizedFractionalSecond = numberStringFormatter.localize(parseInt(fractionalSecond).toFixed(3)); localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; } - return { - localizedDecimalSeparator, localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), - localizedFractionalSecond: localizedFractionalSecondDecimal, localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), + localizedDecimalSeparator, + localizedFractionalSecond, localizedSecondSuffix: getLocalizedTimePart("secondSuffix", parts), localizedMeridiem: getLocalizedTimePart("meridiem", parts), }; @@ -263,12 +258,20 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { const [hour, minute, secondDecimal] = value.split(":"); - let second, fractionalSecond; - if (secondDecimal) { - [second, fractionalSecond] = secondDecimal.split("."); + const secondDecimalPlaces = getRealDecimalPlacesCount(secondDecimal); + let second, + fractionalSecond = null; + if (secondDecimalPlaces > 0) { + [second, fractionalSecond] = parseFloat(secondDecimal) + .toFixed(secondDecimalPlaces > 3 ? 3 : secondDecimalPlaces) + .toString() + .split("."); + fractionalSecond = fractionalSecond && parseInt(fractionalSecond) !== 0 ? fractionalSecond : null; + } else { + second = secondDecimal ? formatTimePart(parseInt(secondDecimal)) : "00"; } return { - fractionalSecond: fractionalSecond && parseInt(fractionalSecond) !== 0 ? fractionalSecond : null, + fractionalSecond, hour, minute, second, From d11cc4bda809d6da7d52d2ea7c9a9fac9c87c131 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Fri, 28 Jul 2023 10:44:50 -0700 Subject: [PATCH 12/67] fixing localizeTimeStringToParts to stop calling toFixed on the already processed fractionalSecond value that comes from parseTimeString --- packages/calcite-components/src/utils/time.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index e4a68516b22..5886c753083 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -218,8 +218,8 @@ export function localizeTimeStringToParts({ locale, numberingSystem, }; - localizedFractionalSecond = numberStringFormatter.localize(parseInt(fractionalSecond).toFixed(3)); localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; + localizedFractionalSecond = numberStringFormatter.localize(fractionalSecond); } return { localizedHour: getLocalizedTimePart("hour", parts), From 3ed449f97094ae4709f78f78dff952f5bcd20834 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Fri, 28 Jul 2023 10:48:49 -0700 Subject: [PATCH 13/67] initializing localized fractional second and decimal values to null for consistency --- packages/calcite-components/src/utils/time.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 5886c753083..6c1914ebd19 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -212,7 +212,8 @@ export function localizeTimeStringToParts({ if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - let localizedFractionalSecond, localizedDecimalSeparator; + let localizedFractionalSecond = null, + localizedDecimalSeparator = null; if (fractionalSecond) { numberStringFormatter.numberFormatOptions = { locale, From 94befb6f8373c9aa30cb24d9b54f4e2db749ec30 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 31 Jul 2023 16:07:21 -0700 Subject: [PATCH 14/67] updating math.decimalPlaces() to return false for numbers that have decimal portions that compute to zero so that we can switch to using this instead of getRealDecimalPlacesCount --- .../calcite-components/src/utils/math.spec.ts | 20 +++++++++++++++++++ packages/calcite-components/src/utils/math.ts | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/utils/math.spec.ts b/packages/calcite-components/src/utils/math.spec.ts index be7eee4b27a..00f1ef108c1 100644 --- a/packages/calcite-components/src/utils/math.spec.ts +++ b/packages/calcite-components/src/utils/math.spec.ts @@ -13,4 +13,24 @@ describe("decimalPlaces", () => { expect(decimalPlaces(123)).toBe(0); expect(decimalPlaces(123.123)).toBe(3); }); + + it("returns the amount of non-zero decimal places for a given number string", () => { + expect(decimalPlaces("0")).toBe(0); + expect(decimalPlaces("0.0")).toBe(0); + expect(decimalPlaces("0.00")).toBe(0); + expect(decimalPlaces("0.000")).toBe(0); + expect(decimalPlaces("0.1")).toBe(1); + expect(decimalPlaces("0.01")).toBe(2); + expect(decimalPlaces("0.001")).toBe(3); + expect(decimalPlaces("0.0001")).toBe(4); + }); + + it("returns the amount of decimal places for a number representation of a decimal", () => { + expect(decimalPlaces(0)).toBe(0); + expect(decimalPlaces(0.0)).toBe(0); + expect(decimalPlaces(0.1)).toBe(1); + expect(decimalPlaces(0.01)).toBe(2); + expect(decimalPlaces(0.001)).toBe(3); + expect(decimalPlaces(0.0001)).toBe(4); + }); }); diff --git a/packages/calcite-components/src/utils/math.ts b/packages/calcite-components/src/utils/math.ts index 3bc251556bc..51baceee736 100644 --- a/packages/calcite-components/src/utils/math.ts +++ b/packages/calcite-components/src/utils/math.ts @@ -2,9 +2,9 @@ export const clamp = (value: number, min: number, max: number): number => Math.m const decimalNumberRegex = new RegExp(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); -export const decimalPlaces = (value: number): number => { +export const decimalPlaces = (value: number | string): number => { const match = ("" + value).match(decimalNumberRegex); - if (!match) { + if (!match || parseInt(match[1]) === 0) { return 0; } return Math.max( From 4074f6a56463ea5bd4b16426b0607d4dbad8c560 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 31 Jul 2023 16:11:57 -0700 Subject: [PATCH 15/67] using decimalPlaces instead of getRealDecimalPlacesCount --- packages/calcite-components/src/utils/math.ts | 10 ++++++++ .../src/utils/number.spec.ts | 23 ------------------- .../calcite-components/src/utils/number.ts | 23 ------------------- packages/calcite-components/src/utils/time.ts | 17 +++++++++++--- 4 files changed, 24 insertions(+), 49 deletions(-) diff --git a/packages/calcite-components/src/utils/math.ts b/packages/calcite-components/src/utils/math.ts index 51baceee736..e9bbd4bb190 100644 --- a/packages/calcite-components/src/utils/math.ts +++ b/packages/calcite-components/src/utils/math.ts @@ -2,6 +2,16 @@ export const clamp = (value: number, min: number, max: number): number => Math.m const decimalNumberRegex = new RegExp(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); +/** + * Returns the quantity of real decimal places for a number, which excludes trailing zeros. + * + * Adapted from: + * + * @link https://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number + * @param value + * @param {string | number} decimal - decimal value + * @returns {number} the amount of decimal places in a number + */ export const decimalPlaces = (value: number | string): number => { const match = ("" + value).match(decimalNumberRegex); if (!match || parseInt(match[1]) === 0) { diff --git a/packages/calcite-components/src/utils/number.spec.ts b/packages/calcite-components/src/utils/number.spec.ts index c2b75682381..0bcd61b28e3 100644 --- a/packages/calcite-components/src/utils/number.spec.ts +++ b/packages/calcite-components/src/utils/number.spec.ts @@ -6,7 +6,6 @@ import { isValidNumber, parseNumberString, sanitizeNumberString, - getRealDecimalPlacesCount, } from "./number"; describe("isValidNumber", () => { @@ -230,25 +229,3 @@ describe("addLocalizedTrailingDecimalZeros", () => { }); }); }); - -describe("getRealDecimalPlacesCount", () => { - it("returns the amount of non-zero decimal places for a given number string", () => { - expect(getRealDecimalPlacesCount("0")).toBe(0); - expect(getRealDecimalPlacesCount("0.0")).toBe(0); - expect(getRealDecimalPlacesCount("0.00")).toBe(0); - expect(getRealDecimalPlacesCount("0.000")).toBe(0); - expect(getRealDecimalPlacesCount("0.1")).toBe(1); - expect(getRealDecimalPlacesCount("0.01")).toBe(2); - expect(getRealDecimalPlacesCount("0.001")).toBe(3); - expect(getRealDecimalPlacesCount("0.0001")).toBe(4); - }); - - it("returns the amount of decimal places for a number representation of a decimal", () => { - expect(getRealDecimalPlacesCount(0)).toBe(0); - expect(getRealDecimalPlacesCount(0.0)).toBe(0); - expect(getRealDecimalPlacesCount(0.1)).toBe(1); - expect(getRealDecimalPlacesCount(0.01)).toBe(2); - expect(getRealDecimalPlacesCount(0.001)).toBe(3); - expect(getRealDecimalPlacesCount(0.0001)).toBe(4); - }); -}); diff --git a/packages/calcite-components/src/utils/number.ts b/packages/calcite-components/src/utils/number.ts index e131bcee984..388b6b0dc34 100644 --- a/packages/calcite-components/src/utils/number.ts +++ b/packages/calcite-components/src/utils/number.ts @@ -259,26 +259,3 @@ export function addLocalizedTrailingDecimalZeros( } return localizedValue; } - -/** - * Returns the quantity of real decimal places for a number, which excludes trailing zeros. - * - * Adapted from: - * - * @link https://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number - * @param {string | number} decimal - decimal value - * @returns {number} the amount of decimal places in a number - */ -export function getRealDecimalPlacesCount(decimal: string | number): number { - const match = ("" + decimal).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); - if (!match || parseInt(match[1]) === 0) { - return 0; - } - return Math.max( - 0, - // Number of digits right of decimal point. - (match[1] ? match[1].length : 0) - - // Adjust for scientific notation. - (match[2] ? +match[2] : 0) - ); -} diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 6c1914ebd19..5aae7bf94ba 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -1,5 +1,6 @@ import { getDateTimeFormat, getSupportedNumberingSystem, NumberingSystem, numberStringFormatter } from "./locale"; -import { getRealDecimalPlacesCount, isValidNumber } from "./number"; +import { decimalPlaces } from "./math"; +import { isValidNumber } from "./number"; export type HourCycle = "12" | "24"; export interface LocalizedTime { @@ -25,7 +26,16 @@ export interface Time { second: string; } -export type TimePart = "hour" | "hourSuffix" | "minute" | "minuteSuffix" | "second" | "secondSuffix" | "meridiem"; +export type TimePart = + | "hour" + | "hourSuffix" + | "minute" + | "minuteSuffix" + | "second" + | "decimalSeparator" + | "fractionalSecond" + | "secondSuffix" + | "meridiem"; export const maxTenthForMinuteAndSecond = 5; @@ -48,6 +58,7 @@ function createLocaleDateTimeFormatter( } export function formatTimePart(number: number): string { + // TODO: support decimals const numberAsString = number.toString(); return number >= 0 && number <= 9 ? numberAsString.padStart(2, "0") : numberAsString; } @@ -259,7 +270,7 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { const [hour, minute, secondDecimal] = value.split(":"); - const secondDecimalPlaces = getRealDecimalPlacesCount(secondDecimal); + const secondDecimalPlaces = decimalPlaces(secondDecimal); let second, fractionalSecond = null; if (secondDecimalPlaces > 0) { From 9edd1f67881cc66ae58fb6ec77a9fea2020583f3 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 31 Jul 2023 17:07:23 -0700 Subject: [PATCH 16/67] getting the basic logic in place to increment fractional second --- .../components/time-picker/time-picker.tsx | 33 +++++++++++++++++-- packages/calcite-components/src/utils/time.ts | 11 +++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 1678530f7c0..83839fd9125 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -52,6 +52,7 @@ import { setComponentLoaded, setUpLoadableComponent, } from "../../utils/loadable"; +import { decimalPlaces } from "../../utils/math"; function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); @@ -157,6 +158,9 @@ export class TimePicker this.updateLocale(); } + // TODO: update fractional second + @State() fractionalSecond: string; + @State() hour: string; @State() hourCycle: HourCycle; @@ -462,6 +466,29 @@ export class TimePicker private incrementFractionalSecond = (): void => { // TODO: increment fractionalSecond + if (isValidNumber(this.fractionalSecond)) { + // TODO: extract this perhaps into a utility function + const fractionalSecondAsDecimalString = `0.${this.fractionalSecond}`; + const fractionalSecondAsFloat = parseFloat(fractionalSecondAsDecimalString); + const secondPlusStep = fractionalSecondAsFloat + this.step; + const precision = decimalPlaces(this.step); + const trimmed = secondPlusStep.toFixed(precision); + const trimmedAsFloat = parseFloat(trimmed); + + this.fractionalSecond = formatTimePart(trimmedAsFloat); + this.localizedFractionalSecond = this.fractionalSecond; + + // TODO: localize the result + // this.localizedFractionalSecond = localizeTimePart({ + // value: this.fractionalSecond, + // part: "fractionalSecond", + // locale: this.effectiveLocale, + // numberingSystem: this.numberingSystem, + // }); + console.log(this.step, this.fractionalSecond); + } else { + this.fractionalSecond = this.step.toString(); + } }; private incrementSecond = (): void => { @@ -618,7 +645,7 @@ export class TimePicker private setValue = (value: string, emit = true): void => { if (isValidTime(value)) { - const { hour, minute, second } = parseTimeString(value); + const { hour, minute, second, fractionalSecond } = parseTimeString(value); const { effectiveLocale: locale, numberingSystem } = this; const { localizedHour, @@ -642,6 +669,7 @@ export class TimePicker this.hour = hour; this.minute = minute; this.second = second; + this.fractionalSecond = fractionalSecond; if (localizedMeridiem) { this.localizedMeridiem = localizedMeridiem; this.meridiem = getMeridiem(this.hour); @@ -650,6 +678,7 @@ export class TimePicker } } else { this.hour = null; + this.fractionalSecond = null; this.localizedHour = null; this.localizedHourSuffix = null; this.localizedMeridiem = null; @@ -670,7 +699,7 @@ export class TimePicker }; private setValuePart = ( - key: "hour" | "minute" | "second" | "meridiem", + key: "hour" | "minute" | "second" | "fractionalSecond" | "meridiem", value: number | string | Meridiem, emit = true ): void => { diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 5aae7bf94ba..b4e4dbe68d9 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -58,9 +58,16 @@ function createLocaleDateTimeFormatter( } export function formatTimePart(number: number): string { - // TODO: support decimals const numberAsString = number.toString(); - return number >= 0 && number <= 9 ? numberAsString.padStart(2, "0") : numberAsString; + if (number < 1 && decimalPlaces(number) > 0) { + return numberAsString.replace("0.", ""); + } + if (number >= 0 && number < 10) { + return numberAsString.padStart(2, "0"); + } + if (number > 10) { + return numberAsString; + } } export function formatTimeString(value: string): string { From 853794ba1e29c664c99995bd401d38cc07a8799e Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 1 Aug 2023 13:38:48 -0700 Subject: [PATCH 17/67] simplifying increment logic, renaming to toggleSecond for clearer meaning --- .../components/time-picker/time-picker.tsx | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 83839fd9125..1224d2e6dec 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -91,7 +91,7 @@ export class TimePicker @Watch("step") stepChange(): void { - this.updateShowSecond(); + this.toggleSecond(); } /** @@ -319,11 +319,6 @@ export class TimePicker // // -------------------------------------------------------------------------- - private updateShowSecond(): void { - this.showSecond = this.step >= 0 && this.step < 60; - this.showFractionalSecond = this.step >= 0.001 && this.step < 1; - } - private async focusPart(target: TimePart): Promise { await componentFocusable(this); @@ -465,17 +460,10 @@ export class TimePicker }; private incrementFractionalSecond = (): void => { - // TODO: increment fractionalSecond if (isValidNumber(this.fractionalSecond)) { - // TODO: extract this perhaps into a utility function - const fractionalSecondAsDecimalString = `0.${this.fractionalSecond}`; - const fractionalSecondAsFloat = parseFloat(fractionalSecondAsDecimalString); - const secondPlusStep = fractionalSecondAsFloat + this.step; - const precision = decimalPlaces(this.step); - const trimmed = secondPlusStep.toFixed(precision); - const trimmedAsFloat = parseFloat(trimmed); - - this.fractionalSecond = formatTimePart(trimmedAsFloat); + const sum = parseFloat(`0.${this.fractionalSecond}`) + this.step; + const roundedSum = parseFloat(sum.toFixed(decimalPlaces(this.step))); + this.fractionalSecond = formatTimePart(roundedSum); this.localizedFractionalSecond = this.fractionalSecond; // TODO: localize the result @@ -754,6 +742,11 @@ export class TimePicker } }; + private toggleSecond(): void { + this.showSecond = this.step >= 0 && this.step < 60; + this.showFractionalSecond = this.step >= 0.001 && this.step < 1; + } + private getMeridiemOrder(formatParts: Intl.DateTimeFormatPart[]): number { const locale = this.effectiveLocale; const isRTLKind = locale === "ar" || locale === "he"; @@ -782,7 +775,7 @@ export class TimePicker connectLocalized(this); this.updateLocale(); connectMessages(this); - this.updateShowSecond(); + this.toggleSecond(); this.meridiemOrder = this.getMeridiemOrder( getTimeParts({ value: "0:00:00", From a1182e69419167920e694a314cf0ebfcfcce7880 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 1 Aug 2023 14:59:40 -0700 Subject: [PATCH 18/67] initializeValue method rounds initial value to the nearest fractional second precision based on the provided step value --- .../src/components/time-picker/time-picker.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 1224d2e6dec..e87e82e3b77 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -483,6 +483,20 @@ export class TimePicker this.incrementMinuteOrSecond("second"); }; + private initializeValue = (): void => { + if ( + this.showFractionalSecond && + this.fractionalSecond && + decimalPlaces(this.step) !== this.fractionalSecond.length + ) { + this.fractionalSecond = parseFloat(`0.${this.fractionalSecond}`) + .toFixed(decimalPlaces(this.step)) + .replace("0.", ""); + // TODO: properly localize fractional second here + this.localizedFractionalSecond = this.fractionalSecond; + } + }; + private meridiemDownButtonKeyDownHandler = (event: KeyboardEvent): void => { if (this.buttonActivated(event)) { this.decrementMeridiem(); @@ -776,6 +790,7 @@ export class TimePicker this.updateLocale(); connectMessages(this); this.toggleSecond(); + this.initializeValue(); this.meridiemOrder = this.getMeridiemOrder( getTimeParts({ value: "0:00:00", From 2c5f5a72aa41c6239abfed65a89deea685b918ab Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 2 Aug 2023 12:17:43 -0700 Subject: [PATCH 19/67] updating formatTimePart to support fractional seconds and updating increment logic to reset fractional seconds to zero when the sum is greater than 1 --- .../src/components/time-picker/time-picker.tsx | 11 +++++++---- packages/calcite-components/src/utils/time.ts | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index e87e82e3b77..ff298735c87 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -461,11 +461,15 @@ export class TimePicker private incrementFractionalSecond = (): void => { if (isValidNumber(this.fractionalSecond)) { + const stepPrecision = decimalPlaces(this.step); const sum = parseFloat(`0.${this.fractionalSecond}`) + this.step; - const roundedSum = parseFloat(sum.toFixed(decimalPlaces(this.step))); - this.fractionalSecond = formatTimePart(roundedSum); - this.localizedFractionalSecond = this.fractionalSecond; + const roundedSum = parseFloat(sum.toFixed(stepPrecision)); + this.fractionalSecond = + roundedSum < 1 && decimalPlaces(roundedSum) > 0 + ? formatTimePart(roundedSum, stepPrecision) + : "".padStart(stepPrecision, "0"); + this.localizedFractionalSecond = this.fractionalSecond; // TODO: localize the result // this.localizedFractionalSecond = localizeTimePart({ // value: this.fractionalSecond, @@ -473,7 +477,6 @@ export class TimePicker // locale: this.effectiveLocale, // numberingSystem: this.numberingSystem, // }); - console.log(this.step, this.fractionalSecond); } else { this.fractionalSecond = this.step.toString(); } diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index b4e4dbe68d9..74b037eac42 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -57,10 +57,18 @@ function createLocaleDateTimeFormatter( return getDateTimeFormat(locale, options); } -export function formatTimePart(number: number): string { +export function formatTimePart(number: number, minLength?: number): string { const numberAsString = number.toString(); - if (number < 1 && decimalPlaces(number) > 0) { - return numberAsString.replace("0.", ""); + const numberDecimalPlaces = decimalPlaces(number); + if (number < 1 && numberDecimalPlaces > 0 && numberDecimalPlaces < 4) { + const fractionalDigits = numberAsString.replace("0.", ""); + if (!minLength || fractionalDigits.length === minLength) { + return fractionalDigits; + } + if (fractionalDigits.length < minLength) { + return fractionalDigits.padEnd(minLength, "0"); + } + return fractionalDigits; } if (number >= 0 && number < 10) { return numberAsString.padStart(2, "0"); @@ -148,6 +156,7 @@ export function isValidTime(value: string): boolean { } function isValidTimePart(value: string, part: TimePart): boolean { + // TODO: add fractional seconds support here if (part === "meridiem") { return value === "AM" || value === "PM"; } @@ -166,6 +175,7 @@ interface LocalizeTimePartParameters { } export function localizeTimePart({ value, part, locale, numberingSystem }: LocalizeTimePartParameters): string { + // TODO: add fractional seconds support here if (!isValidTimePart(value, part)) { return; } From 0bf9ee145d6c8b82ac67a702122e5f3c93b71ef6 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 7 Aug 2023 17:13:29 -0700 Subject: [PATCH 20/67] adding basic decrement support, refactoring to use a single method for nudging, added a bunch of todos --- .../components/time-picker/time-picker.tsx | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index ff298735c87..58006dcebd9 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -345,10 +345,6 @@ export class TimePicker this.setValuePart("meridiem", newMeridiem); }; - private decrementFractionalSecond = (): void => { - // TODO: decrementFractionalSecond - }; - private decrementMinuteOrSecond = (key: MinuteOrSecond): void => { let newValue; if (isValidNumber(this[key])) { @@ -459,34 +455,12 @@ export class TimePicker this.incrementMinuteOrSecond("minute"); }; - private incrementFractionalSecond = (): void => { - if (isValidNumber(this.fractionalSecond)) { - const stepPrecision = decimalPlaces(this.step); - const sum = parseFloat(`0.${this.fractionalSecond}`) + this.step; - const roundedSum = parseFloat(sum.toFixed(stepPrecision)); - this.fractionalSecond = - roundedSum < 1 && decimalPlaces(roundedSum) > 0 - ? formatTimePart(roundedSum, stepPrecision) - : "".padStart(stepPrecision, "0"); - - this.localizedFractionalSecond = this.fractionalSecond; - // TODO: localize the result - // this.localizedFractionalSecond = localizeTimePart({ - // value: this.fractionalSecond, - // part: "fractionalSecond", - // locale: this.effectiveLocale, - // numberingSystem: this.numberingSystem, - // }); - } else { - this.fractionalSecond = this.step.toString(); - } - }; - private incrementSecond = (): void => { this.incrementMinuteOrSecond("second"); }; private initializeValue = (): void => { + // TODO: move this logic into this.setValue() if ( this.showFractionalSecond && this.fractionalSecond && @@ -540,7 +514,7 @@ export class TimePicker private fractionalSecondUpButtonKeyDownHandler = (event: KeyboardEvent): void => { if (this.buttonActivated(event)) { - this.incrementFractionalSecond(); + this.nudgeFractionalSecond("up"); } }; @@ -592,6 +566,34 @@ export class TimePicker } }; + private nudgeFractionalSecond = (direction: "up" | "down"): void => { + if (isValidNumber(this.fractionalSecond)) { + const stepPrecision = decimalPlaces(this.step); + const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); + const nudgedValue = + direction === "up" + ? fractionalSecondAsFloat + this.step + : fractionalSecondAsFloat - this.step; + const nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); + // TODO: set value to opposite end of range when min or max is nudged + this.fractionalSecond = + nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 + ? formatTimePart(nudgedValueRounded, stepPrecision) + : "".padStart(stepPrecision, "0"); + + this.localizedFractionalSecond = this.fractionalSecond; + // TODO: localize the result + // this.localizedFractionalSecond = localizeTimePart({ + // value: this.fractionalSecond, + // part: "fractionalSecond", + // locale: this.effectiveLocale, + // numberingSystem: this.numberingSystem, + // }); + } else { + this.fractionalSecond = this.step.toString(); + } + }; + private secondDownButtonKeyDownHandler = (event: KeyboardEvent): void => { if (this.buttonActivated(event)) { this.decrementSecond(); @@ -793,6 +795,7 @@ export class TimePicker this.updateLocale(); connectMessages(this); this.toggleSecond(); + // TODO: remove this call in favor of this.setValue() which is called above by this.updateLocale(); this.initializeValue(); this.meridiemOrder = this.getMeridiemOrder( getTimeParts({ @@ -992,7 +995,7 @@ export class TimePicker [CSS.button]: true, [CSS.buttonFractionalSecondUp]: true, }} - onClick={this.incrementFractionalSecond} + onClick={this.nudgeFractionalSecond.bind(this, "up")} onKeyDown={this.fractionalSecondUpButtonKeyDownHandler} role="button" > @@ -1030,7 +1033,7 @@ export class TimePicker [CSS.buttonFractionalSecondDown]: true, }} // TODO: onclick - onClick={this.decrementFractionalSecond} + onClick={this.nudgeFractionalSecond.bind(this, "down")} // TODO: onKeyDown // onKeyDown={this.fractionalSecondDownButtonKeyDownHandler} role="button" From 0081e1acc57bc59e6cba3120e63bda504af31979 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 9 Aug 2023 16:21:43 -0700 Subject: [PATCH 21/67] removing initializeValue in favor of setValue --- .../components/time-picker/time-picker.tsx | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 58006dcebd9..eb6fe71180d 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -459,21 +459,6 @@ export class TimePicker this.incrementMinuteOrSecond("second"); }; - private initializeValue = (): void => { - // TODO: move this logic into this.setValue() - if ( - this.showFractionalSecond && - this.fractionalSecond && - decimalPlaces(this.step) !== this.fractionalSecond.length - ) { - this.fractionalSecond = parseFloat(`0.${this.fractionalSecond}`) - .toFixed(decimalPlaces(this.step)) - .replace("0.", ""); - // TODO: properly localize fractional second here - this.localizedFractionalSecond = this.fractionalSecond; - } - }; - private meridiemDownButtonKeyDownHandler = (event: KeyboardEvent): void => { if (this.buttonActivated(event)) { this.decrementMeridiem(); @@ -665,6 +650,13 @@ export class TimePicker localizedSecondSuffix, localizedMeridiem, } = localizeTimeStringToParts({ value, locale, numberingSystem }); + this.hour = hour; + this.minute = minute; + this.second = second; + this.fractionalSecond = + fractionalSecond && decimalPlaces(this.step) !== fractionalSecond.length + ? parseFloat(`0.${fractionalSecond}`).toFixed(decimalPlaces(this.step)).replace("0.", "") + : fractionalSecond; this.localizedHour = localizedHour; this.localizedHourSuffix = localizedHourSuffix; this.localizedMinute = localizedMinute; @@ -673,10 +665,6 @@ export class TimePicker this.localizedDecimalSeparator = localizedDecimalSeparator; this.localizedFractionalSecond = localizedFractionalSecond; this.localizedSecondSuffix = localizedSecondSuffix; - this.hour = hour; - this.minute = minute; - this.second = second; - this.fractionalSecond = fractionalSecond; if (localizedMeridiem) { this.localizedMeridiem = localizedMeridiem; this.meridiem = getMeridiem(this.hour); @@ -795,8 +783,6 @@ export class TimePicker this.updateLocale(); connectMessages(this); this.toggleSecond(); - // TODO: remove this call in favor of this.setValue() which is called above by this.updateLocale(); - this.initializeValue(); this.meridiemOrder = this.getMeridiemOrder( getTimeParts({ value: "0:00:00", From ba344c08cc35c14f039490b5c518fecd205dd44d Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 9 Aug 2023 17:03:50 -0700 Subject: [PATCH 22/67] localizeTimeStringToParts: always return localizedDecimalSeparator --- packages/calcite-components/src/utils/time.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 74b037eac42..a89c51061fb 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -240,14 +240,12 @@ export function localizeTimeStringToParts({ if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - let localizedFractionalSecond = null, - localizedDecimalSeparator = null; + let localizedFractionalSecond = null; if (fractionalSecond) { numberStringFormatter.numberFormatOptions = { locale, numberingSystem, }; - localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; localizedFractionalSecond = numberStringFormatter.localize(fractionalSecond); } return { @@ -256,7 +254,7 @@ export function localizeTimeStringToParts({ localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), - localizedDecimalSeparator, + localizedDecimalSeparator: numberStringFormatter.localize("1.1").split("")[1], localizedFractionalSecond, localizedSecondSuffix: getLocalizedTimePart("secondSuffix", parts), localizedMeridiem: getLocalizedTimePart("meridiem", parts), From ee649ab456ec5128fd64469727c7b6d2bce228d8 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 9 Aug 2023 17:11:07 -0700 Subject: [PATCH 23/67] fixing issue where localizeTimeStringToParts wasn't returning decimal separator when the fractional second value was empty --- packages/calcite-components/src/utils/time.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index a89c51061fb..9d6055d8b5e 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -234,20 +234,15 @@ export function localizeTimeStringToParts({ if (!isValidTime(value)) { return null; } - const { hour, minute, second = "0", fractionalSecond } = parseTimeString(value); const dateFromTimeString = new Date(Date.UTC(0, 0, 0, parseInt(hour), parseInt(minute), parseInt(second))); if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - let localizedFractionalSecond = null; - if (fractionalSecond) { - numberStringFormatter.numberFormatOptions = { - locale, - numberingSystem, - }; - localizedFractionalSecond = numberStringFormatter.localize(fractionalSecond); - } + numberStringFormatter.numberFormatOptions = { + locale, + numberingSystem, + }; return { localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), @@ -255,7 +250,7 @@ export function localizeTimeStringToParts({ localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), localizedDecimalSeparator: numberStringFormatter.localize("1.1").split("")[1], - localizedFractionalSecond, + localizedFractionalSecond: fractionalSecond && numberStringFormatter.localize(fractionalSecond), localizedSecondSuffix: getLocalizedTimePart("secondSuffix", parts), localizedMeridiem: getLocalizedTimePart("meridiem", parts), }; From e0aac9388d89b9ab78d9e5b4cee8c91ddec76294 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Thu, 10 Aug 2023 16:27:16 -0700 Subject: [PATCH 24/67] localizeTimeStringToParts returns fractional second with padded zeros in front if necessary --- .../calcite-components/src/utils/time.spec.ts | 48 +++++++++++++++++++ packages/calcite-components/src/utils/time.ts | 9 +++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.spec.ts b/packages/calcite-components/src/utils/time.spec.ts index c2bb9020afe..f8d1d13aeab 100644 --- a/packages/calcite-components/src/utils/time.spec.ts +++ b/packages/calcite-components/src/utils/time.spec.ts @@ -31,6 +31,18 @@ describe("localizeTimeStringToParts", () => { localizedMeridiem: null, }); + expect(localizeTimeStringToParts({ value: "06:45:30", locale: "fr" })).toEqual({ + localizedHour: "06", + localizedHourSuffix: ":", + localizedMinute: "45", + localizedMinuteSuffix: ":", + localizedSecond: "30", + localizedDecimalSeparator: ",", + localizedFractionalSecond: null, + localizedSecondSuffix: null, + localizedMeridiem: null, + }); + expect(localizeTimeStringToParts({ value: "06:45:30.12123", locale: "da" })).toEqual({ localizedHour: "06", localizedHourSuffix: ".", @@ -43,6 +55,42 @@ describe("localizeTimeStringToParts", () => { localizedMeridiem: null, }); }); + + it("returns fractional second value with padded zeros when necessary", () => { + expect(localizeTimeStringToParts({ value: "06:45:30.04", locale: "en" })).toEqual({ + localizedHour: "06", + localizedHourSuffix: ":", + localizedMinute: "45", + localizedMinuteSuffix: ":", + localizedSecond: "30", + localizedDecimalSeparator: ".", + localizedFractionalSecond: "04", + localizedSecondSuffix: null, + localizedMeridiem: "AM", + }); + expect(localizeTimeStringToParts({ value: "06:45:30.003", locale: "en" })).toEqual({ + localizedHour: "06", + localizedHourSuffix: ":", + localizedMinute: "45", + localizedMinuteSuffix: ":", + localizedSecond: "30", + localizedDecimalSeparator: ".", + localizedFractionalSecond: "003", + localizedSecondSuffix: null, + localizedMeridiem: "AM", + }); + expect(localizeTimeStringToParts({ value: "06:45:30.007", locale: "ar", numberingSystem: "arab" })).toEqual({ + localizedHour: "٠٦", + localizedHourSuffix: ":", + localizedMinute: "٤٥", + localizedMinuteSuffix: ":", + localizedSecond: "٣٠", + localizedDecimalSeparator: "٫", + localizedFractionalSecond: "٠٠٧", + localizedSecondSuffix: null, + localizedMeridiem: "ص", + }); + }); }); describe("parseTimeString", () => { diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 9d6055d8b5e..332873b357d 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -243,6 +243,13 @@ export function localizeTimeStringToParts({ locale, numberingSystem, }; + const localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; + let localizedFractionalSecond = null; + if (fractionalSecond) { + localizedFractionalSecond = numberStringFormatter + .localize(`0.${fractionalSecond}`) + .replace(`${numberStringFormatter.localize("0")}${localizedDecimalSeparator}`, ""); + } return { localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), @@ -250,7 +257,7 @@ export function localizeTimeStringToParts({ localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), localizedDecimalSeparator: numberStringFormatter.localize("1.1").split("")[1], - localizedFractionalSecond: fractionalSecond && numberStringFormatter.localize(fractionalSecond), + localizedFractionalSecond, localizedSecondSuffix: getLocalizedTimePart("secondSuffix", parts), localizedMeridiem: getLocalizedTimePart("meridiem", parts), }; From 104e29a4f7a94e55fdb2448e74ea02bef4f8587c Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Fri, 11 Aug 2023 14:52:54 -0700 Subject: [PATCH 25/67] formatTimePart tests --- .../calcite-components/src/utils/time.spec.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.spec.ts b/packages/calcite-components/src/utils/time.spec.ts index f8d1d13aeab..392394e10c7 100644 --- a/packages/calcite-components/src/utils/time.spec.ts +++ b/packages/calcite-components/src/utils/time.spec.ts @@ -1,4 +1,30 @@ -import { isValidTime, localizeTimeStringToParts, parseTimeString, toISOTimeString } from "./time"; +import { formatTimePart, isValidTime, localizeTimeStringToParts, parseTimeString, toISOTimeString } from "./time"; + +describe("formatTimePart", () => { + it("returns decimals less than 1 with leading and trailing zeros to match the provided length", () => { + expect(formatTimePart(0.3)).toEqual("3"); + expect(formatTimePart(0.3, 1)).toEqual("3"); + expect(formatTimePart(0.3, 2)).toEqual("30"); + expect(formatTimePart(0.3, 3)).toEqual("300"); + expect(formatTimePart(0.03)).toEqual("03"); + expect(formatTimePart(0.03, 2)).toEqual("03"); + expect(formatTimePart(0.03, 3)).toEqual("030"); + expect(formatTimePart(0.003)).toEqual("003"); + expect(formatTimePart(0.003, 3)).toEqual("003"); + }); + it("returns hour, minute and second values between 0 and 10 with leading zeros", () => { + expect(formatTimePart(0)).toEqual("00"); + expect(formatTimePart(1)).toEqual("01"); + expect(formatTimePart(2)).toEqual("02"); + expect(formatTimePart(3)).toEqual("03"); + expect(formatTimePart(4)).toEqual("04"); + expect(formatTimePart(5)).toEqual("05"); + expect(formatTimePart(6)).toEqual("06"); + expect(formatTimePart(7)).toEqual("07"); + expect(formatTimePart(8)).toEqual("08"); + expect(formatTimePart(9)).toEqual("09"); + }); +}); describe("isValidTime", () => { it("returns true when time string contains fractional seconds", () => { From 3cfd06f67791c85026d40137364bbecffd5f51ed Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Fri, 11 Aug 2023 16:12:05 -0700 Subject: [PATCH 26/67] feat(time-picker): upward nudge of empty fractional second sets to 0 --- .../components/time-picker/time-picker.e2e.ts | 45 +++++++++++++++++++ .../components/time-picker/time-picker.tsx | 21 +++++---- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.e2e.ts b/packages/calcite-components/src/components/time-picker/time-picker.e2e.ts index 09335927671..ce83640cd7c 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.e2e.ts +++ b/packages/calcite-components/src/components/time-picker/time-picker.e2e.ts @@ -2,6 +2,7 @@ import { newE2EPage } from "@stencil/core/testing"; import { accessible, defaults, focusable, hidden, renders, t9n } from "../../tests/commonTests"; import { formatTimePart } from "../../utils/time"; import { CSS } from "./resources"; +import { getElementXY } from "../../tests/utils"; const letterKeys = [ "a", @@ -1091,4 +1092,48 @@ describe("calcite-time-picker", () => { expect(await page.find(`calcite-time-picker >>> .${CSS.second}`)).toBeNull(); }); + + describe("fractional second support", () => { + it("upward nudge of empty fractional second sets to 0 for step=0.1", async () => { + const page = await newE2EPage(); + await page.setContent(``); + const [buttonUpLocationX, buttonUpLocationY] = await getElementXY( + page, + "calcite-time-picker", + ".button--fractionalSecond-up" + ); + await page.mouse.click(buttonUpLocationX, buttonUpLocationY); + await page.waitForChanges(); + const fractionalSecondEl = await page.find(`calcite-time-picker >>> .input.fractionalSecond`); + expect(fractionalSecondEl.innerHTML).toEqual("0"); + }); + + it("upward nudge of empty fractional second sets to 00 for step=0.01", async () => { + const page = await newE2EPage(); + await page.setContent(``); + const [buttonUpLocationX, buttonUpLocationY] = await getElementXY( + page, + "calcite-time-picker", + ".button--fractionalSecond-up" + ); + await page.mouse.click(buttonUpLocationX, buttonUpLocationY); + await page.waitForChanges(); + const fractionalSecondEl = await page.find(`calcite-time-picker >>> .input.fractionalSecond`); + expect(fractionalSecondEl.innerHTML).toEqual("00"); + }); + + it("upward nudge of empty fractional second sets to 000 for step=0.001", async () => { + const page = await newE2EPage(); + await page.setContent(``); + const [buttonUpLocationX, buttonUpLocationY] = await getElementXY( + page, + "calcite-time-picker", + ".button--fractionalSecond-up" + ); + await page.mouse.click(buttonUpLocationX, buttonUpLocationY); + await page.waitForChanges(); + const fractionalSecondEl = await page.find(`calcite-time-picker >>> .input.fractionalSecond`); + expect(fractionalSecondEl.innerHTML).toEqual("000"); + }); + }); }); diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index eb6fe71180d..7958d399fac 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -552,8 +552,8 @@ export class TimePicker }; private nudgeFractionalSecond = (direction: "up" | "down"): void => { + const stepPrecision = decimalPlaces(this.step); if (isValidNumber(this.fractionalSecond)) { - const stepPrecision = decimalPlaces(this.step); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); const nudgedValue = direction === "up" @@ -567,16 +567,19 @@ export class TimePicker : "".padStart(stepPrecision, "0"); this.localizedFractionalSecond = this.fractionalSecond; - // TODO: localize the result - // this.localizedFractionalSecond = localizeTimePart({ - // value: this.fractionalSecond, - // part: "fractionalSecond", - // locale: this.effectiveLocale, - // numberingSystem: this.numberingSystem, - // }); } else { - this.fractionalSecond = this.step.toString(); + if (direction === "up") { + this.fractionalSecond = "".padEnd(stepPrecision, "0"); + } + if (direction === "down") { + this.fractionalSecond = formatTimePart( + parseInt("".padEnd(stepPrecision, "9")), + stepPrecision + ); + } } + // TODO: localize the result + this.localizedFractionalSecond = this.fractionalSecond; }; private secondDownButtonKeyDownHandler = (event: KeyboardEvent): void => { From e1dcb13c011a2d031cfb41969bbaa5b49a570d53 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 13:44:41 -0700 Subject: [PATCH 27/67] feat(time-picker): when min or max value is nudged, set to the opposite end of the range --- .../components/time-picker/time-picker.tsx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 7958d399fac..5e1d3207cbe 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -553,29 +553,29 @@ export class TimePicker private nudgeFractionalSecond = (direction: "up" | "down"): void => { const stepPrecision = decimalPlaces(this.step); - if (isValidNumber(this.fractionalSecond)) { - const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); - const nudgedValue = - direction === "up" - ? fractionalSecondAsFloat + this.step - : fractionalSecondAsFloat - this.step; - const nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); - // TODO: set value to opposite end of range when min or max is nudged + const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); + let nudgedValue, nudgedValueRounded; + if (direction === "up") { + nudgedValue = fractionalSecondAsFloat + this.step; + nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); this.fractionalSecond = nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 ? formatTimePart(nudgedValueRounded, stepPrecision) : "".padStart(stepPrecision, "0"); - - this.localizedFractionalSecond = this.fractionalSecond; - } else { - if (direction === "up") { - this.fractionalSecond = "".padEnd(stepPrecision, "0"); - } - if (direction === "down") { - this.fractionalSecond = formatTimePart( - parseInt("".padEnd(stepPrecision, "9")), - stepPrecision - ); + } + if (direction === "down") { + nudgedValue = fractionalSecondAsFloat - this.step; + nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); + if (fractionalSecondAsFloat === 0) { + this.fractionalSecond = "".padStart(stepPrecision, "9"); + } else if ( + nudgedValueRounded < 1 && + decimalPlaces(nudgedValueRounded) > 0 && + Math.sign(nudgedValueRounded) === 1 + ) { + this.fractionalSecond = formatTimePart(nudgedValueRounded, stepPrecision); + } else { + this.fractionalSecond = "".padStart(stepPrecision, "0"); } } // TODO: localize the result From 0d7d3df63f162a89d2bdd1773846965a0dc24dfb Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 14:14:52 -0700 Subject: [PATCH 28/67] feat(time-picker): always display localized h, m, s suffixes and decimal separator even with empty value --- .../components/time-picker/time-picker.tsx | 25 ++++++++++++++--- packages/calcite-components/src/utils/time.ts | 27 ++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 5e1d3207cbe..e4aadf16dfb 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -31,6 +31,8 @@ import { import { formatTimePart, getLocaleHourCycle, + getLocalizedDecimalSeparator, + getLocalizedTimePartSuffix, getMeridiem, getTimeParts, HourCycle, @@ -678,14 +680,29 @@ export class TimePicker this.hour = null; this.fractionalSecond = null; this.localizedHour = null; - this.localizedHourSuffix = null; + this.localizedHourSuffix = getLocalizedTimePartSuffix( + "hour", + this.effectiveLocale, + this.numberingSystem + ); this.localizedMeridiem = null; this.localizedMinute = null; - this.localizedMinuteSuffix = null; + this.localizedMinuteSuffix = getLocalizedTimePartSuffix( + "minute", + this.effectiveLocale, + this.numberingSystem + ); this.localizedSecond = null; - this.localizedDecimalSeparator = null; + this.localizedDecimalSeparator = getLocalizedDecimalSeparator( + this.effectiveLocale, + this.numberingSystem + ); this.localizedFractionalSecond = null; - this.localizedSecondSuffix = null; + this.localizedSecondSuffix = getLocalizedTimePartSuffix( + "second", + this.effectiveLocale, + this.numberingSystem + ); this.meridiem = null; this.minute = null; this.second = null; diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 332873b357d..c28ef938e39 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -98,6 +98,25 @@ export function getLocaleHourCycle(locale: string, numberingSystem: NumberingSys return getLocalizedTimePart("meridiem", parts) ? "12" : "24"; } +export function getLocalizedDecimalSeparator(locale: string, numberingSystem: NumberingSystem): string { + numberStringFormatter.numberFormatOptions = { + locale, + numberingSystem, + }; + return numberStringFormatter.localize("1.1").split("")[1]; +} + +export function getLocalizedTimePartSuffix( + part: "hour" | "minute" | "second", + locale: string, + numberingSystem: NumberingSystem = "latn" +): string { + const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); + const parts = formatter.formatToParts(new Date(Date.UTC(0, 0, 0, 0, 0, 0))); + console.log(parts); + return getLocalizedTimePart(`${part}Suffix` as TimePart, parts); +} + function getLocalizedTimePart(part: TimePart, parts: Intl.DateTimeFormatPart[]): string { if (!part || !parts) { return null; @@ -239,11 +258,7 @@ export function localizeTimeStringToParts({ if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - numberStringFormatter.numberFormatOptions = { - locale, - numberingSystem, - }; - const localizedDecimalSeparator = numberStringFormatter.localize("1.1").split("")[1]; + const localizedDecimalSeparator = getLocalizedDecimalSeparator(locale, numberingSystem); let localizedFractionalSecond = null; if (fractionalSecond) { localizedFractionalSecond = numberStringFormatter @@ -256,7 +271,7 @@ export function localizeTimeStringToParts({ localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), - localizedDecimalSeparator: numberStringFormatter.localize("1.1").split("")[1], + localizedDecimalSeparator, localizedFractionalSecond, localizedSecondSuffix: getLocalizedTimePart("secondSuffix", parts), localizedMeridiem: getLocalizedTimePart("meridiem", parts), From 72d0da87b3775bdaf9a9ad275c6ec7b0009e1d1e Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 14:16:52 -0700 Subject: [PATCH 29/67] removing console.log I missed --- packages/calcite-components/src/utils/time.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index c28ef938e39..a09a1294449 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -113,7 +113,6 @@ export function getLocalizedTimePartSuffix( ): string { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(new Date(Date.UTC(0, 0, 0, 0, 0, 0))); - console.log(parts); return getLocalizedTimePart(`${part}Suffix` as TimePart, parts); } From 2b603c05eab7c97b430bc6fb47346c57ca095315 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 14:25:25 -0700 Subject: [PATCH 30/67] fractional second keyboard navigation support --- .../components/time-picker/time-picker.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index e4aadf16dfb..d940cfe8c62 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -145,6 +145,8 @@ export class TimePicker private secondEl: HTMLSpanElement; + private fractionalSecondEl: HTMLSpanElement; + private meridiemOrder: number; // -------------------------------------------------------------------------- @@ -275,6 +277,22 @@ export class TimePicker this.focusPart("minute"); event.preventDefault(); break; + case "ArrowRight": + if (this.showFractionalSecond) { + this.focusPart("fractionalSecond"); + } else if (this.hourCycle === "12") { + this.focusPart("meridiem"); + event.preventDefault(); + } + break; + } + break; + case this.fractionalSecondEl: + switch (key) { + case "ArrowLeft": + this.focusPart("second"); + event.preventDefault(); + break; case "ArrowRight": if (this.hourCycle === "12") { this.focusPart("meridiem"); @@ -286,7 +304,9 @@ export class TimePicker case this.meridiemEl: switch (key) { case "ArrowLeft": - if (this.step !== 60) { + if (this.showFractionalSecond) { + this.focusPart("fractionalSecond"); + } else if (this.step !== 60) { this.focusPart("second"); event.preventDefault(); } else { @@ -640,6 +660,8 @@ export class TimePicker private setSecondEl = (el: HTMLSpanElement) => (this.secondEl = el); + private setFractionalSecondEl = (el: HTMLSpanElement) => (this.fractionalSecondEl = el); + private setValue = (value: string, emit = true): void => { if (isValidTime(value)) { const { hour, minute, second, fractionalSecond } = parseTimeString(value); @@ -1026,9 +1048,7 @@ export class TimePicker role="spinbutton" tabIndex={0} // eslint-disable-next-line react/jsx-sort-props - - // TODO: setFractionalSecondEl - // ref={this.setFractionalSecondEl} + ref={this.setFractionalSecondEl} > {this.localizedFractionalSecond || "--"} From 9ce37318b167c62af4c9b3d6dd0592760d9be85b Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 14:34:07 -0700 Subject: [PATCH 31/67] removing up/down button keydown handlers since they're not supposed to be focusable any longer, removing a zindex=0 on the second up button --- .../components/time-picker/time-picker.tsx | 76 +------------------ 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index d940cfe8c62..f953a05890b 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -11,7 +11,7 @@ import { VNode, Watch, } from "@stencil/core"; -import { isActivationKey, numberKeys } from "../../utils/key"; +import { numberKeys } from "../../utils/key"; import { isValidNumber } from "../../utils/number"; import { Scale } from "../interfaces"; @@ -347,16 +347,6 @@ export class TimePicker this[`${target || "hour"}El`]?.focus(); } - private buttonActivated(event: KeyboardEvent): boolean { - const { key } = event; - - if (key === " ") { - event.preventDefault(); - } - - return isActivationKey(key); - } - private decrementHour = (): void => { const newHour = !this.hour ? 0 : this.hour === "00" ? 23 : parseInt(this.hour) - 1; this.setValuePart("hour", newHour); @@ -390,12 +380,6 @@ export class TimePicker this.activeEl = event.currentTarget as HTMLSpanElement; }; - private hourDownButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.decrementHour(); - } - }; - private hourKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { @@ -444,12 +428,6 @@ export class TimePicker } }; - private hourUpButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.incrementHour(); - } - }; - private incrementMeridiem = (): void => { const newMeridiem = this.meridiem === "AM" ? "PM" : "AM"; this.setValuePart("meridiem", newMeridiem); @@ -481,12 +459,6 @@ export class TimePicker this.incrementMinuteOrSecond("second"); }; - private meridiemDownButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.decrementMeridiem(); - } - }; - private meridiemKeyDownHandler = (event: KeyboardEvent): void => { switch (event.key) { case "a": @@ -513,24 +485,6 @@ export class TimePicker } }; - private meridiemUpButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.incrementMeridiem(); - } - }; - - private fractionalSecondUpButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.nudgeFractionalSecond("up"); - } - }; - - private minuteDownButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.decrementMinute(); - } - }; - private minuteKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { @@ -567,12 +521,6 @@ export class TimePicker } }; - private minuteUpButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.incrementMinute(); - } - }; - private nudgeFractionalSecond = (direction: "up" | "down"): void => { const stepPrecision = decimalPlaces(this.step); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); @@ -604,12 +552,6 @@ export class TimePicker this.localizedFractionalSecond = this.fractionalSecond; }; - private secondDownButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.decrementSecond(); - } - }; - private secondKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { @@ -646,12 +588,6 @@ export class TimePicker } }; - private secondUpButtonKeyDownHandler = (event: KeyboardEvent): void => { - if (this.buttonActivated(event)) { - this.incrementSecond(); - } - }; - private setHourEl = (el: HTMLSpanElement) => (this.hourEl = el); private setMeridiemEl = (el: HTMLSpanElement) => (this.meridiemEl = el); @@ -879,7 +815,6 @@ export class TimePicker [CSS.buttonTopLeft]: true, }} onClick={this.incrementHour} - onKeyDown={this.hourUpButtonKeyDownHandler} role="button" > @@ -911,7 +846,6 @@ export class TimePicker [CSS.buttonBottomLeft]: true, }} onClick={this.decrementHour} - onKeyDown={this.hourDownButtonKeyDownHandler} role="button" > @@ -926,9 +860,7 @@ export class TimePicker [CSS.buttonMinuteUp]: true, }} onClick={this.incrementMinute} - onKeyDown={this.minuteUpButtonKeyDownHandler} role="button" - tabIndex={-1} > @@ -958,7 +890,6 @@ export class TimePicker [CSS.buttonMinuteDown]: true, }} onClick={this.decrementMinute} - onKeyDown={this.minuteDownButtonKeyDownHandler} role="button" > @@ -974,7 +905,6 @@ export class TimePicker [CSS.buttonSecondUp]: true, }} onClick={this.incrementSecond} - onKeyDown={this.secondUpButtonKeyDownHandler} role="button" > @@ -1005,7 +935,6 @@ export class TimePicker [CSS.buttonSecondDown]: true, }} onClick={this.decrementSecond} - onKeyDown={this.secondDownButtonKeyDownHandler} role="button" > @@ -1024,7 +953,6 @@ export class TimePicker [CSS.buttonFractionalSecondUp]: true, }} onClick={this.nudgeFractionalSecond.bind(this, "up")} - onKeyDown={this.fractionalSecondUpButtonKeyDownHandler} role="button" > @@ -1087,7 +1015,6 @@ export class TimePicker [CSS.buttonTopRight]: true, }} onClick={this.incrementMeridiem} - onKeyDown={this.meridiemUpButtonKeyDownHandler} role="button" > @@ -1119,7 +1046,6 @@ export class TimePicker [CSS.buttonBottomRight]: true, }} onClick={this.decrementMeridiem} - onKeyDown={this.meridiemDownButtonKeyDownHandler} role="button" > From e8d85b77803e599eaa8b4d230b4b15c249f74440 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 14:45:56 -0700 Subject: [PATCH 32/67] nudging fractional second works with up down arrow keys --- .../components/time-picker/time-picker.tsx | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index f953a05890b..2e0a051ff61 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -380,6 +380,42 @@ export class TimePicker this.activeEl = event.currentTarget as HTMLSpanElement; }; + private fractionalSecondKeyDownHandler = (event: KeyboardEvent): void => { + const { key } = event; + if (numberKeys.includes(key)) { + const keyAsNumber = parseInt(key); + let newFractionalSecond; + if (isValidNumber(this.fractionalSecond) && this.fractionalSecond.startsWith("0")) { + const secondAsNumber = parseInt(this.fractionalSecond); + newFractionalSecond = + secondAsNumber > maxTenthForMinuteAndSecond + ? keyAsNumber + : `${secondAsNumber}${keyAsNumber}`; + } else { + newFractionalSecond = keyAsNumber; + } + this.setValuePart("fractionalSecond", newFractionalSecond); + } else { + switch (key) { + case "Backspace": + case "Delete": + this.setValuePart("fractionalSecond", null); + break; + case "ArrowDown": + event.preventDefault(); + this.nudgeFractionalSecond("down"); + break; + case "ArrowUp": + event.preventDefault(); + this.nudgeFractionalSecond("up"); + break; + case " ": + event.preventDefault(); + break; + } + } + }; + private hourKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { @@ -971,8 +1007,7 @@ export class TimePicker }} onFocus={this.focusHandler} // TODO: fractionalSecondKeyDownHandler - // onKeyDown={this.fractionalSecondKeyDownHandler} - + onKeyDown={this.fractionalSecondKeyDownHandler} role="spinbutton" tabIndex={0} // eslint-disable-next-line react/jsx-sort-props @@ -986,10 +1021,7 @@ export class TimePicker [CSS.button]: true, [CSS.buttonFractionalSecondDown]: true, }} - // TODO: onclick onClick={this.nudgeFractionalSecond.bind(this, "down")} - // TODO: onKeyDown - // onKeyDown={this.fractionalSecondDownButtonKeyDownHandler} role="button" > From 2d007fbe8a133c8cf8d97a801e7664ce8dc86341 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 17:05:57 -0700 Subject: [PATCH 33/67] parseTimeString simplified to return values WYSIWYG style instead of rounding fractional seconds --- .../calcite-components/src/utils/time.spec.ts | 22 ++++++++++++------- packages/calcite-components/src/utils/time.ts | 18 ++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/calcite-components/src/utils/time.spec.ts b/packages/calcite-components/src/utils/time.spec.ts index 392394e10c7..03acc940165 100644 --- a/packages/calcite-components/src/utils/time.spec.ts +++ b/packages/calcite-components/src/utils/time.spec.ts @@ -52,7 +52,7 @@ describe("localizeTimeStringToParts", () => { localizedMinuteSuffix: ":", localizedSecond: "30", localizedDecimalSeparator: ",", - localizedFractionalSecond: "121", + localizedFractionalSecond: "12123", localizedSecondSuffix: null, localizedMeridiem: null, }); @@ -76,7 +76,7 @@ describe("localizeTimeStringToParts", () => { localizedMinuteSuffix: ".", localizedSecond: "30", localizedDecimalSeparator: ",", - localizedFractionalSecond: "121", + localizedFractionalSecond: "12123", localizedSecondSuffix: null, localizedMeridiem: null, }); @@ -120,12 +120,12 @@ describe("localizeTimeStringToParts", () => { }); describe("parseTimeString", () => { - it("returns real fractional second, rounded to the nearest millisecond", () => { + it("returns literal hour, minute, second and fractional second values from given string", () => { expect(parseTimeString("12:30:45.0")).toEqual({ hour: "12", minute: "30", second: "45", - fractionalSecond: null, + fractionalSecond: "0", }); expect(parseTimeString("12:30:45.01")).toEqual({ hour: "12", @@ -143,13 +143,13 @@ describe("parseTimeString", () => { hour: "12", minute: "30", second: "45", - fractionalSecond: null, + fractionalSecond: "0001", }); expect(parseTimeString("12:30:45.0049")).toEqual({ hour: "12", minute: "30", second: "45", - fractionalSecond: "005", + fractionalSecond: "0049", }); expect(parseTimeString("12:30:45.1")).toEqual({ hour: "12", @@ -173,13 +173,19 @@ describe("parseTimeString", () => { hour: "12", minute: "30", second: "45", - fractionalSecond: "123", + fractionalSecond: "1234", }); expect(parseTimeString("12:30:45.12345")).toEqual({ hour: "12", minute: "30", second: "45", - fractionalSecond: "123", + fractionalSecond: "12345", + }); + expect(parseTimeString("12:30:45.12345.34")).toEqual({ + hour: null, + minute: null, + second: null, + fractionalSecond: null, }); }); diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index a09a1294449..64bba2ad926 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -260,6 +260,10 @@ export function localizeTimeStringToParts({ const localizedDecimalSeparator = getLocalizedDecimalSeparator(locale, numberingSystem); let localizedFractionalSecond = null; if (fractionalSecond) { + numberStringFormatter.numberFormatOptions = { + locale, + numberingSystem, + }; localizedFractionalSecond = numberStringFormatter .localize(`0.${fractionalSecond}`) .replace(`${numberStringFormatter.localize("0")}${localizedDecimalSeparator}`, ""); @@ -300,18 +304,10 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { - const [hour, minute, secondDecimal] = value.split(":"); - const secondDecimalPlaces = decimalPlaces(secondDecimal); - let second, + let [hour, minute, second] = value.split(":"), fractionalSecond = null; - if (secondDecimalPlaces > 0) { - [second, fractionalSecond] = parseFloat(secondDecimal) - .toFixed(secondDecimalPlaces > 3 ? 3 : secondDecimalPlaces) - .toString() - .split("."); - fractionalSecond = fractionalSecond && parseInt(fractionalSecond) !== 0 ? fractionalSecond : null; - } else { - second = secondDecimal ? formatTimePart(parseInt(secondDecimal)) : "00"; + if (second?.includes(".")) { + [second, fractionalSecond] = second.split("."); } return { fractionalSecond, From 19c9f09d30ee378be80b9bd0a6de1eb378660372 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 14 Aug 2023 17:19:58 -0700 Subject: [PATCH 34/67] basic support for typing non-zero values for fractional second --- .../components/time-picker/time-picker.tsx | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 2e0a051ff61..e6630b32484 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -383,18 +383,22 @@ export class TimePicker private fractionalSecondKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { - const keyAsNumber = parseInt(key); - let newFractionalSecond; - if (isValidNumber(this.fractionalSecond) && this.fractionalSecond.startsWith("0")) { - const secondAsNumber = parseInt(this.fractionalSecond); - newFractionalSecond = - secondAsNumber > maxTenthForMinuteAndSecond - ? keyAsNumber - : `${secondAsNumber}${keyAsNumber}`; - } else { - newFractionalSecond = keyAsNumber; + const stepPrecision = decimalPlaces(this.step); + const fractionalSecondAsInteger = parseInt(this.fractionalSecond); + const fractionalSecondAsIntegerLength = fractionalSecondAsInteger.toString().length; + + let newFractionalSecondAsIntegerString; + + if (fractionalSecondAsIntegerLength >= stepPrecision) { + newFractionalSecondAsIntegerString = key.padStart(stepPrecision, "0"); + } else if (fractionalSecondAsIntegerLength < stepPrecision) { + newFractionalSecondAsIntegerString = `${fractionalSecondAsInteger}${key}`.padStart( + stepPrecision, + "0" + ); } - this.setValuePart("fractionalSecond", newFractionalSecond); + + this.setValuePart("fractionalSecond", parseFloat(`0.${newFractionalSecondAsIntegerString}`)); } else { switch (key) { case "Backspace": @@ -736,6 +740,11 @@ export class TimePicker numberingSystem, }); } + } else if (key === "fractionalSecond") { + this.fractionalSecond = + typeof value === "number" ? formatTimePart(value, decimalPlaces(this.step)) : value; + // TODO: localize fractional second + this.localizedFractionalSecond = this.fractionalSecond; } else { this[key] = typeof value === "number" ? formatTimePart(value) : value; this[`localized${capitalize(key)}`] = localizeTimePart({ @@ -750,6 +759,9 @@ export class TimePicker if (this.showSecond) { newValue = `${newValue}:${this.second ?? "00"}`; } + if (this.showFractionalSecond && this.fractionalSecond) { + newValue = `${newValue}.${this.fractionalSecond}`; + } this.value = newValue; } else { this.value = null; From 6e7fb467c2f8a945871696c5ac5a98e4e176d25a Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 15 Aug 2023 11:26:53 -0700 Subject: [PATCH 35/67] handle typing zero properly --- .../components/time-picker/time-picker.tsx | 19 +++++++++++++------ packages/calcite-components/src/utils/time.ts | 11 ++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index e6630b32484..ec912050aa4 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -592,6 +592,11 @@ export class TimePicker this.localizedFractionalSecond = this.fractionalSecond; }; + private sanitizeFractionalSecond = (fractionalSecond: string): string => + fractionalSecond && decimalPlaces(this.step) !== fractionalSecond.length + ? parseFloat(`0.${fractionalSecond}`).toFixed(decimalPlaces(this.step)).replace("0.", "") + : fractionalSecond; + private secondKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { @@ -656,10 +661,7 @@ export class TimePicker this.hour = hour; this.minute = minute; this.second = second; - this.fractionalSecond = - fractionalSecond && decimalPlaces(this.step) !== fractionalSecond.length - ? parseFloat(`0.${fractionalSecond}`).toFixed(decimalPlaces(this.step)).replace("0.", "") - : fractionalSecond; + this.fractionalSecond = this.sanitizeFractionalSecond(fractionalSecond); this.localizedHour = localizedHour; this.localizedHourSuffix = localizedHourSuffix; this.localizedMinute = localizedMinute; @@ -741,8 +743,13 @@ export class TimePicker }); } } else if (key === "fractionalSecond") { - this.fractionalSecond = - typeof value === "number" ? formatTimePart(value, decimalPlaces(this.step)) : value; + const stepPrecision = decimalPlaces(this.step); + if (typeof value === "number") { + this.fractionalSecond = + value === 0 ? "".padStart(stepPrecision, "0") : formatTimePart(value, stepPrecision); + } else { + this.fractionalSecond = value; + } // TODO: localize fractional second this.localizedFractionalSecond = this.fractionalSecond; } else { diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 64bba2ad926..a2877a61db4 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -264,9 +264,14 @@ export function localizeTimeStringToParts({ locale, numberingSystem, }; - localizedFractionalSecond = numberStringFormatter - .localize(`0.${fractionalSecond}`) - .replace(`${numberStringFormatter.localize("0")}${localizedDecimalSeparator}`, ""); + const localizedZero = numberStringFormatter.localize("0"); + if (parseInt(fractionalSecond) === 0) { + localizedFractionalSecond = "".padStart(fractionalSecond.length, localizedZero); + } else { + localizedFractionalSecond = numberStringFormatter + .localize(`0.${fractionalSecond}`) + .replace(`${localizedZero}${localizedDecimalSeparator}`, ""); + } } return { localizedHour: getLocalizedTimePart("hour", parts), From f9a88e6fd425415dabc7f3a396c62c7644afc12e Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 15 Aug 2023 14:25:44 -0700 Subject: [PATCH 36/67] aria-valuenow support --- .../src/components/time-picker/time-picker.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index ec912050aa4..f23ae7f3aee 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -850,6 +850,7 @@ export class TimePicker const iconScale = this.scale === "s" || this.scale === "m" ? "s" : "m"; const minuteIsNumber = isValidNumber(this.minute); const secondIsNumber = isValidNumber(this.second); + const fractionalSecondIsNumber = isValidNumber(this.fractionalSecond); const showMeridiem = this.hourCycle === "12"; return (
Date: Tue, 15 Aug 2023 15:24:00 -0700 Subject: [PATCH 37/67] adding fractional second support to localizeTimePart, updating nudge function to call setValuePart which now uses localizeTimePart to correctly update localized fractional second value --- .../components/time-picker/time-picker.tsx | 29 ++++++------ packages/calcite-components/src/utils/time.ts | 47 +++++++++++-------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index f23ae7f3aee..71a56a1d3dd 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -162,7 +162,6 @@ export class TimePicker this.updateLocale(); } - // TODO: update fractional second @State() fractionalSecond: string; @State() hour: string; @@ -178,7 +177,6 @@ export class TimePicker @State() localizedMeridiem: string; - // TODO: set localizedFractionalSecond on mount and whenever fractionalSecond value changes @State() localizedFractionalSecond: string; @State() localizedMinute: string; @@ -564,11 +562,11 @@ export class TimePicker private nudgeFractionalSecond = (direction: "up" | "down"): void => { const stepPrecision = decimalPlaces(this.step); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); - let nudgedValue, nudgedValueRounded; + let nudgedValue, nudgedValueRounded, newFractionalSecond; if (direction === "up") { nudgedValue = fractionalSecondAsFloat + this.step; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); - this.fractionalSecond = + newFractionalSecond = nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 ? formatTimePart(nudgedValueRounded, stepPrecision) : "".padStart(stepPrecision, "0"); @@ -577,19 +575,18 @@ export class TimePicker nudgedValue = fractionalSecondAsFloat - this.step; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); if (fractionalSecondAsFloat === 0) { - this.fractionalSecond = "".padStart(stepPrecision, "9"); + newFractionalSecond = "".padStart(stepPrecision, "9"); } else if ( nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 && Math.sign(nudgedValueRounded) === 1 ) { - this.fractionalSecond = formatTimePart(nudgedValueRounded, stepPrecision); + newFractionalSecond = formatTimePart(nudgedValueRounded, stepPrecision); } else { - this.fractionalSecond = "".padStart(stepPrecision, "0"); + newFractionalSecond = "".padStart(stepPrecision, "0"); } } - // TODO: localize the result - this.localizedFractionalSecond = this.fractionalSecond; + this.setValuePart("fractionalSecond", newFractionalSecond); }; private sanitizeFractionalSecond = (fractionalSecond: string): string => @@ -750,8 +747,12 @@ export class TimePicker } else { this.fractionalSecond = value; } - // TODO: localize fractional second - this.localizedFractionalSecond = this.fractionalSecond; + this.localizedFractionalSecond = localizeTimePart({ + value: this.fractionalSecond, + part: "fractionalSecond", + locale, + numberingSystem, + }); } else { this[key] = typeof value === "number" ? formatTimePart(value) : value; this[`localized${capitalize(key)}`] = localizeTimePart({ @@ -765,9 +766,9 @@ export class TimePicker let newValue = `${this.hour}:${this.minute}`; if (this.showSecond) { newValue = `${newValue}:${this.second ?? "00"}`; - } - if (this.showFractionalSecond && this.fractionalSecond) { - newValue = `${newValue}.${this.fractionalSecond}`; + if (this.showFractionalSecond && this.fractionalSecond) { + newValue = `${newValue}.${this.fractionalSecond}`; + } } this.value = newValue; } else { diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index a2877a61db4..ba7470a5f37 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -174,7 +174,6 @@ export function isValidTime(value: string): boolean { } function isValidTimePart(value: string, part: TimePart): boolean { - // TODO: add fractional seconds support here if (part === "meridiem") { return value === "AM" || value === "PM"; } @@ -193,7 +192,26 @@ interface LocalizeTimePartParameters { } export function localizeTimePart({ value, part, locale, numberingSystem }: LocalizeTimePartParameters): string { - // TODO: add fractional seconds support here + if (part === "fractionalSecond") { + const localizedDecimalSeparator = getLocalizedDecimalSeparator(locale, numberingSystem); + let localizedFractionalSecond = null; + if (value) { + numberStringFormatter.numberFormatOptions = { + locale, + numberingSystem, + }; + const localizedZero = numberStringFormatter.localize("0"); + if (parseInt(value) === 0) { + localizedFractionalSecond = "".padStart(value.length, localizedZero); + } else { + localizedFractionalSecond = numberStringFormatter + .localize(`0.${value}`) + .replace(`${localizedZero}${localizedDecimalSeparator}`, ""); + } + } + return localizedFractionalSecond; + } + if (!isValidTimePart(value, part)) { return; } @@ -257,30 +275,19 @@ export function localizeTimeStringToParts({ if (dateFromTimeString) { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(dateFromTimeString); - const localizedDecimalSeparator = getLocalizedDecimalSeparator(locale, numberingSystem); - let localizedFractionalSecond = null; - if (fractionalSecond) { - numberStringFormatter.numberFormatOptions = { - locale, - numberingSystem, - }; - const localizedZero = numberStringFormatter.localize("0"); - if (parseInt(fractionalSecond) === 0) { - localizedFractionalSecond = "".padStart(fractionalSecond.length, localizedZero); - } else { - localizedFractionalSecond = numberStringFormatter - .localize(`0.${fractionalSecond}`) - .replace(`${localizedZero}${localizedDecimalSeparator}`, ""); - } - } return { localizedHour: getLocalizedTimePart("hour", parts), localizedHourSuffix: getLocalizedTimePart("hourSuffix", parts), localizedMinute: getLocalizedTimePart("minute", parts), localizedMinuteSuffix: getLocalizedTimePart("minuteSuffix", parts), localizedSecond: getLocalizedTimePart("second", parts), - localizedDecimalSeparator, - localizedFractionalSecond, + localizedDecimalSeparator: getLocalizedDecimalSeparator(locale, numberingSystem), + localizedFractionalSecond: localizeTimePart({ + value: fractionalSecond, + part: "fractionalSecond", + locale, + numberingSystem, + }), localizedSecondSuffix: getLocalizedTimePart("secondSuffix", parts), localizedMeridiem: getLocalizedTimePart("meridiem", parts), }; From 333b7a9cd0e3f967378c92d4853fd7c0fb28d775 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 15 Aug 2023 15:56:30 -0700 Subject: [PATCH 38/67] sanitizing initial value to round off fractional seconds that are greater than a millisecond, updating localizedDecimalSeparator when lang changes --- .../src/components/time-picker/time-picker.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 71a56a1d3dd..febed613f89 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -168,7 +168,6 @@ export class TimePicker @State() hourCycle: HourCycle; - // TODO: set this per locale @State() localizedDecimalSeparator = "."; @State() localizedHour: string; @@ -193,7 +192,6 @@ export class TimePicker @State() second: string; - // TODO: calculate this on mount and whenever step changes @State() showFractionalSecond: boolean; @State() showSecond: boolean; @@ -589,6 +587,15 @@ export class TimePicker this.setValuePart("fractionalSecond", newFractionalSecond); }; + private sanitizeValue = (value: string): string => { + const { hour, minute, second, fractionalSecond } = parseTimeString(value); + if (fractionalSecond) { + const sanitizedFractionalSecond = this.sanitizeFractionalSecond(fractionalSecond); + return `${hour}:${minute}:${second}.${sanitizedFractionalSecond}`; + } + return isValidTime(value) && value; + }; + private sanitizeFractionalSecond = (fractionalSecond: string): string => fractionalSecond && decimalPlaces(this.step) !== fractionalSecond.length ? parseFloat(`0.${fractionalSecond}`).toFixed(decimalPlaces(this.step)).replace("0.", "") @@ -803,7 +810,11 @@ export class TimePicker private updateLocale() { updateMessages(this, this.effectiveLocale); this.hourCycle = getLocaleHourCycle(this.effectiveLocale, this.numberingSystem); - this.setValue(this.value, false); + this.localizedDecimalSeparator = getLocalizedDecimalSeparator( + this.effectiveLocale, + this.numberingSystem + ); + this.setValue(this.sanitizeValue(this.value), false); } // -------------------------------------------------------------------------- From 7a6760c37dcfddf6d9ee41c0a98c55f2d349759d Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 21 Aug 2023 14:11:18 -0700 Subject: [PATCH 39/67] fixing bug where you couldn't set hour to 10 --- packages/calcite-components/src/utils/time.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index ba7470a5f37..c1bd1cb6dc1 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -73,7 +73,7 @@ export function formatTimePart(number: number, minLength?: number): string { if (number >= 0 && number < 10) { return numberAsString.padStart(2, "0"); } - if (number > 10) { + if (number >= 10) { return numberAsString; } } From bcf5875dcc1c2ae2a16f1e49a84a593000fb222b Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 21 Aug 2023 14:48:52 -0700 Subject: [PATCH 40/67] render localized trailing zeros based on step precision --- packages/calcite-components/src/utils/time.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index c1bd1cb6dc1..9e33b11b3b7 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -207,6 +207,9 @@ export function localizeTimePart({ value, part, locale, numberingSystem }: Local localizedFractionalSecond = numberStringFormatter .localize(`0.${value}`) .replace(`${localizedZero}${localizedDecimalSeparator}`, ""); + if (localizedFractionalSecond.length < value.length) { + localizedFractionalSecond = localizedFractionalSecond.padEnd(value.length, localizedZero); + } } } return localizedFractionalSecond; From d54325d303150543c9e88876b58c8db8c89f3b0b Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 21 Aug 2023 16:32:46 -0700 Subject: [PATCH 41/67] only emit change event when full value changes --- .../components/time-picker/time-picker.tsx | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 956d2de803d..053285dbd7b 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -99,7 +99,7 @@ export class TimePicker @Watch("value") valueWatcher(newValue: string): void { - this.setValue(newValue, false); + this.setValue(newValue); } /** @@ -641,7 +641,7 @@ export class TimePicker private setFractionalSecondEl = (el: HTMLSpanElement) => (this.fractionalSecondEl = el); - private setValue = (value: string, emit = true): void => { + private setValue = (value: string): void => { if (isValidTime(value)) { const { hour, minute, second, fractionalSecond } = parseTimeString(value); const { effectiveLocale: locale, numberingSystem } = this; @@ -706,15 +706,11 @@ export class TimePicker this.second = null; this.value = null; } - if (emit) { - this.calciteInternalTimePickerChange.emit(); - } }; private setValuePart = ( key: "hour" | "minute" | "second" | "fractionalSecond" | "meridiem", - value: number | string | Meridiem, - emit = true + value: number | string | Meridiem ): void => { const { effectiveLocale: locale, numberingSystem } = this; if (key === "meridiem") { @@ -763,18 +759,23 @@ export class TimePicker numberingSystem, }); } + let emit = false, + newValue; if (this.hour && this.minute) { - let newValue = `${this.hour}:${this.minute}`; + newValue = `${this.hour}:${this.minute}`; if (this.showSecond) { newValue = `${newValue}:${this.second ?? "00"}`; if (this.showFractionalSecond && this.fractionalSecond) { newValue = `${newValue}.${this.fractionalSecond}`; } } - this.value = newValue; } else { - this.value = null; + newValue = null; + } + if (this.value !== newValue) { + emit = true; } + this.value = newValue; this.localizedMeridiem = this.value ? localizeTimeStringToParts({ value: this.value, locale, numberingSystem }) ?.localizedMeridiem || null @@ -808,7 +809,7 @@ export class TimePicker this.effectiveLocale, this.numberingSystem ); - this.setValue(this.sanitizeValue(this.value), false); + this.setValue(this.sanitizeValue(this.value)); } // -------------------------------------------------------------------------- From 92af7994f2a8289895ce1e447408c7ebacfce5e2 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Mon, 21 Aug 2023 17:17:10 -0700 Subject: [PATCH 42/67] adding fractional second support to formatTimeString, localizeTimeString and toISOTimeString. Input now showing fractional seconds when time picker popup sends it up --- packages/calcite-components/src/utils/time.ts | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 9e33b11b3b7..5856d8f82d1 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -82,14 +82,15 @@ export function formatTimeString(value: string): string { if (!isValidTime(value)) { return null; } - const [hourString, minuteString, secondString] = value.split(":"); - const hour = formatTimePart(parseInt(hourString)); - const minute = formatTimePart(parseInt(minuteString)); - if (secondString) { - const second = formatTimePart(parseInt(secondString)); - return `${hour}:${minute}:${second}`; + let { hour, minute, second, fractionalSecond } = parseTimeString(value); + let formattedValue = `${formatTimePart(parseInt(hour))}:${formatTimePart(parseInt(minute))}`; + if (second) { + formattedValue += `:${formatTimePart(parseInt(second))}`; + if (fractionalSecond) { + formattedValue += `.${fractionalSecond}`; + } } - return `${hour}:${minute}`; + return formattedValue; } export function getLocaleHourCycle(locale: string, numberingSystem: NumberingSystem): HourCycle { @@ -253,10 +254,19 @@ export function localizeTimeString({ if (!isValidTime(value)) { return null; } - const { hour, minute, second = "0" } = parseTimeString(value); + const { hour, minute, second = "0", fractionalSecond } = parseTimeString(value); + const dateFromTimeString = new Date(Date.UTC(0, 0, 0, parseInt(hour), parseInt(minute), parseInt(second))); const formatter = createLocaleDateTimeFormatter(locale, numberingSystem, includeSeconds); - return formatter?.format(dateFromTimeString) || null; + const parts = formatter.formatToParts(dateFromTimeString); + const localizedTimeString = parts?.reduce((result, part) => { + if (part.type === "second" && fractionalSecond) { + // TODO: localize fractional second here with number formatter api + return result + `${part.value}.${fractionalSecond}`; + } + return result + part.value; + }, ""); + return localizedTimeString || null; } interface LocalizeTimeStringToPartsParameters { @@ -343,12 +353,15 @@ export function toISOTimeString(value: string, includeSeconds = true): string { if (!isValidTime(value)) { return ""; } - const { hour, minute, second } = parseTimeString(value); + const { hour, minute, second, fractionalSecond } = parseTimeString(value); let isoTimeString = `${formatTimePart(parseInt(hour))}:${formatTimePart(parseInt(minute))}`; if (includeSeconds) { isoTimeString += `:${formatTimePart(parseInt((includeSeconds && second) || "0"))}`; + if (fractionalSecond) { + isoTimeString += `.${fractionalSecond}`; + } } return isoTimeString; From 33593b616be9928c0f7335ae46fc4a8341528561 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 22 Aug 2023 16:26:31 -0700 Subject: [PATCH 43/67] fixing toggleSecond logic --- .../src/components/time-picker/time-picker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 053285dbd7b..0f3fce8248c 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -786,8 +786,8 @@ export class TimePicker }; private toggleSecond(): void { - this.showSecond = this.step >= 0 && this.step < 60; - this.showFractionalSecond = this.step >= 0.001 && this.step < 1; + this.showSecond = this.step < 60; + this.showFractionalSecond = decimalPlaces(this.step) > 0; } private getMeridiemOrder(formatParts: Intl.DateTimeFormatPart[]): number { From 80bbb688828c747daceef9d511cf27f66320b0b0 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 22 Aug 2023 16:27:52 -0700 Subject: [PATCH 44/67] updating locale config to replace each LTS seconds identifier to include milliseconds --- .../src/components/input-time-picker/input-time-picker.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index 2c7bffc9085..abe7e3b9c57 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -565,10 +565,15 @@ export class InputTimePicker supportedLocale = "pt"; } - const { default: localeConfig } = await supportedDayJsLocaleToLocaleConfigImport.get( + let { default: localeConfig } = await supportedDayJsLocaleToLocaleConfigImport.get( supportedLocale )(); + const ltsFormatString = localeConfig?.formats?.LTS; + localeConfig.formats.LTS = !ltsFormatString.includes("ss.SSS") + ? ltsFormatString.replace("ss", "ss.SSS") + : ltsFormatString; + dayjs.locale(localeConfig, null, true); dayjs.updateLocale(supportedLocale, this.getExtendedLocaleConfig(supportedLocale)); } From e6a425defe1f95448a598716c86437f91f319dfc Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 22 Aug 2023 16:32:49 -0700 Subject: [PATCH 45/67] nudging fractional second up from null value sets to 0 like native browser behavior --- .../src/components/time-picker/time-picker.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 0f3fce8248c..d57747b9050 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -553,10 +553,11 @@ export class TimePicker private nudgeFractionalSecond = (direction: "up" | "down"): void => { const stepPrecision = decimalPlaces(this.step); + const fractionalSecondAsInteger = parseInt(this.fractionalSecond); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); let nudgedValue, nudgedValueRounded, newFractionalSecond; if (direction === "up") { - nudgedValue = fractionalSecondAsFloat + this.step; + nudgedValue = isNaN(fractionalSecondAsInteger) ? 0 : fractionalSecondAsFloat + this.step; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); newFractionalSecond = nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 From 5b5e522e28d01074ce2345847bf6efdb21a60f05 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 22 Aug 2023 16:55:07 -0700 Subject: [PATCH 46/67] correcting down nudging logic to calculate the difference between 1 and the step when starting value is 0 or NaN --- .../src/components/time-picker/time-picker.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index d57747b9050..0c59309170f 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -565,11 +565,12 @@ export class TimePicker : "".padStart(stepPrecision, "0"); } if (direction === "down") { - nudgedValue = fractionalSecondAsFloat - this.step; + nudgedValue = + isNaN(fractionalSecondAsInteger) || fractionalSecondAsInteger === 0 + ? 1 - this.step + : fractionalSecondAsFloat - this.step; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); - if (fractionalSecondAsFloat === 0) { - newFractionalSecond = "".padStart(stepPrecision, "9"); - } else if ( + if ( nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 && Math.sign(nudgedValueRounded) === 1 From 138c01bf702d97964315f34b4b6851f27a3650cc Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 23 Aug 2023 16:33:46 -0700 Subject: [PATCH 47/67] feat(input-time-picker): allow typing and committing a localized time value with fractional seconds --- .../input-time-picker/input-time-picker.tsx | 144 +++++++++++++++--- 1 file changed, 122 insertions(+), 22 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index abe7e3b9c57..bcf34f4dcf8 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -50,6 +50,7 @@ import { } from "../../utils/focusTrapComponent"; import { FocusTrap } from "focus-trap"; import { + formatTimePart, formatTimeString, isValidTime, localizeTimeString, @@ -68,6 +69,7 @@ import localizedFormat from "dayjs/esm/plugin/localizedFormat"; import preParsePostFormat from "dayjs/esm/plugin/preParsePostFormat"; import updateLocale from "dayjs/esm/plugin/updateLocale"; import { getSupportedLocale } from "../../utils/locale"; +import { decimalPlaces } from "../../utils/math"; // some bundlers (e.g., Webpack) need dynamic import paths to be static const supportedDayJsLocaleToLocaleConfigImport = new Map([ @@ -129,6 +131,13 @@ dayjs.extend(localizedFormat); dayjs.extend(preParsePostFormat); dayjs.extend(updateLocale); +interface DayJSTimeParts { + hour: number; + minute: number; + second: number; + millisecond: number; +} + @Component({ tag: "calcite-input-time-picker", styleUrl: "input-time-picker.scss", @@ -323,6 +332,8 @@ export class InputTimePicker private dialogId = `time-picker-dialog--${guid()}`; + private localeConfig; + /** whether the value of the input was changed as a result of user typing or not */ private userChangedValue = false; @@ -476,20 +487,103 @@ export class InputTimePicker // we need to set the corresponding locale before parsing, otherwise it defaults to English (possible dayjs bug) dayjs.locale(this.effectiveLocale.toLowerCase()); - const dayjsParseResult = dayjs(value, ["LTS", "LT"]); + let delocalizedTimeString; + + if (this.shouldIncludeFractionalSeconds()) { + const stepPrecision = decimalPlaces(this.step); + + if (stepPrecision === 1) { + delocalizedTimeString = this.getTimeStringFromParts( + this.delocalizeTimeStringToParts(value, "S") + ); + } else { + const sParts = this.delocalizeTimeStringToParts(value, "S"); + const ssParts = this.delocalizeTimeStringToParts(value, "SS"); + + if (stepPrecision === 2) { + if (ssParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(ssParts); + } else if (sParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(sParts); + } else { + delocalizedTimeString = ""; + } + } else if (stepPrecision === 3) { + const sssParts = this.delocalizeTimeStringToParts(value, "SSS"); + + if (sssParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(sssParts); + } else if (ssParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(ssParts); + } else if (sParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(sParts); + } else { + delocalizedTimeString = ""; + } + } + } + } else { + delocalizedTimeString = this.getTimeStringFromParts(this.delocalizeTimeStringToParts(value)); + } + + return delocalizedTimeString; + } + + private delocalizeTimeStringToParts( + localizedTimeString: string, + fractionalSecondFormatToken?: "S" | "SS" | "SSS" + ): DayJSTimeParts { + const ltsFormatString = this.localeConfig?.formats?.LTS; + const match = ltsFormatString.match(/ss\.*(S+)/g); + + if (fractionalSecondFormatToken && this.shouldIncludeFractionalSeconds()) { + const secondFormatToken = `ss.${fractionalSecondFormatToken}`; + this.localeConfig.formats.LTS = match + ? ltsFormatString.replace(match[0], secondFormatToken) + : ltsFormatString.replace("ss", secondFormatToken); + } else if (match) { + this.localeConfig.formats.LTS = ltsFormatString.replace(match[0], "ss"); + } + + dayjs.updateLocale( + this.getSupportedDayJSLocale(getSupportedLocale(this.effectiveLocale)), + this.localeConfig + ); + + const dayjsParseResult = dayjs(localizedTimeString, ["LTS", "LT"]); if (dayjsParseResult.isValid()) { - let unformattedTimeString = `${dayjsParseResult.get("hour")}:${dayjsParseResult.get( - "minute" - )}`; + return { + hour: dayjsParseResult.get("hour"), + minute: dayjsParseResult.get("minute"), + second: dayjsParseResult.get("second"), + millisecond: dayjsParseResult.get("milliseconds"), + }; + } + return { + hour: null, + minute: null, + second: null, + millisecond: null, + }; + } - if (this.shouldIncludeSeconds()) { - unformattedTimeString += `:${dayjsParseResult.get("seconds") || 0}`; + private getTimeStringFromParts(parts: DayJSTimeParts): string { + let timeString = ""; + if (!parts) { + return timeString; + } + if (parts.hour && parts.minute) { + timeString = `${formatTimePart(parts.hour)}:${formatTimePart(parts.minute)}`; + } + if (parts.second) { + timeString += `:${formatTimePart(parts.second)}`; + if (parts.millisecond) { + const second = (parts.millisecond * 0.001).toFixed(decimalPlaces(this.step)); + timeString += `.${second.toString().replace("0.", "")}`; } - - return formatTimeString(unformattedTimeString) || ""; } - return ""; + return timeString; } private popoverCloseHandler = () => { @@ -554,27 +648,29 @@ export class InputTimePicker } }; + private getSupportedDayJSLocale(locale: string) { + const dayjsLocale = locale.toLowerCase(); + if (dayjsLocale === "no") { + return "nb"; + } + if (dayjsLocale === "pt-pt") { + return "pt"; + } + return dayjsLocale; + } + private async loadDateTimeLocaleData(): Promise { let supportedLocale = getSupportedLocale(this.effectiveLocale).toLowerCase(); - if (supportedLocale === "no") { - supportedLocale = "nb"; - } + supportedLocale = this.getSupportedDayJSLocale(supportedLocale); - if (supportedLocale === "pt-pt") { - supportedLocale = "pt"; - } - - let { default: localeConfig } = await supportedDayJsLocaleToLocaleConfigImport.get( + const { default: localeConfig } = await supportedDayJsLocaleToLocaleConfigImport.get( supportedLocale )(); - const ltsFormatString = localeConfig?.formats?.LTS; - localeConfig.formats.LTS = !ltsFormatString.includes("ss.SSS") - ? ltsFormatString.replace("ss", "ss.SSS") - : ltsFormatString; + this.localeConfig = localeConfig; - dayjs.locale(localeConfig, null, true); + dayjs.locale(this.localeConfig, null, true); dayjs.updateLocale(supportedLocale, this.getExtendedLocaleConfig(supportedLocale)); } @@ -661,6 +757,10 @@ export class InputTimePicker return this.step < 60; } + private shouldIncludeFractionalSeconds(): boolean { + return decimalPlaces(this.step) > 0; + } + private setCalcitePopoverEl = (el: HTMLCalcitePopoverElement): void => { this.popoverEl = el; }; From cb6c5c35816766b958eeffea125c0ed5cf58512b Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 23 Aug 2023 16:50:07 -0700 Subject: [PATCH 48/67] localize fractional second in localizeTimeString --- packages/calcite-components/src/utils/time.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 5856d8f82d1..efa6f368d63 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -261,8 +261,15 @@ export function localizeTimeString({ const parts = formatter.formatToParts(dateFromTimeString); const localizedTimeString = parts?.reduce((result, part) => { if (part.type === "second" && fractionalSecond) { - // TODO: localize fractional second here with number formatter api - return result + `${part.value}.${fractionalSecond}`; + return ( + result + + `${part.value}.${localizeTimePart({ + value: fractionalSecond, + part: "fractionalSecond", + locale, + numberingSystem, + })}` + ); } return result + part.value; }, ""); From fc6e4f1274db2c26414d00e3d876fe6c8d152d7f Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Thu, 24 Aug 2023 12:23:48 -0700 Subject: [PATCH 49/67] fix: allow typing non-fractional second values when step includes fractional seconds --- .../input-time-picker/input-time-picker.tsx | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index bcf34f4dcf8..039ea43073c 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -487,43 +487,47 @@ export class InputTimePicker // we need to set the corresponding locale before parsing, otherwise it defaults to English (possible dayjs bug) dayjs.locale(this.effectiveLocale.toLowerCase()); + const nonFractionalSecondParts = this.delocalizeTimeStringToParts(value); + let delocalizedTimeString; if (this.shouldIncludeFractionalSeconds()) { const stepPrecision = decimalPlaces(this.step); + const centisecondParts = this.delocalizeTimeStringToParts(value, "S"); if (stepPrecision === 1) { - delocalizedTimeString = this.getTimeStringFromParts( - this.delocalizeTimeStringToParts(value, "S") - ); + if (centisecondParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(centisecondParts); + } else { + delocalizedTimeString = this.getTimeStringFromParts(nonFractionalSecondParts); + } } else { - const sParts = this.delocalizeTimeStringToParts(value, "S"); - const ssParts = this.delocalizeTimeStringToParts(value, "SS"); + const decisecondParts = this.delocalizeTimeStringToParts(value, "SS"); if (stepPrecision === 2) { - if (ssParts.millisecond !== 0) { - delocalizedTimeString = this.getTimeStringFromParts(ssParts); - } else if (sParts.millisecond !== 0) { - delocalizedTimeString = this.getTimeStringFromParts(sParts); + if (decisecondParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(decisecondParts); + } else if (centisecondParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(centisecondParts); } else { - delocalizedTimeString = ""; + delocalizedTimeString = this.getTimeStringFromParts(nonFractionalSecondParts); } - } else if (stepPrecision === 3) { - const sssParts = this.delocalizeTimeStringToParts(value, "SSS"); - - if (sssParts.millisecond !== 0) { - delocalizedTimeString = this.getTimeStringFromParts(sssParts); - } else if (ssParts.millisecond !== 0) { - delocalizedTimeString = this.getTimeStringFromParts(ssParts); - } else if (sParts.millisecond !== 0) { - delocalizedTimeString = this.getTimeStringFromParts(sParts); + } else if (stepPrecision >= 3) { + const millisecondParts = this.delocalizeTimeStringToParts(value, "SSS"); + + if (millisecondParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(millisecondParts); + } else if (decisecondParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(decisecondParts); + } else if (centisecondParts.millisecond !== 0) { + delocalizedTimeString = this.getTimeStringFromParts(centisecondParts); } else { - delocalizedTimeString = ""; + delocalizedTimeString = this.getTimeStringFromParts(nonFractionalSecondParts); } } } } else { - delocalizedTimeString = this.getTimeStringFromParts(this.delocalizeTimeStringToParts(value)); + delocalizedTimeString = this.getTimeStringFromParts(nonFractionalSecondParts); } return delocalizedTimeString; @@ -534,15 +538,15 @@ export class InputTimePicker fractionalSecondFormatToken?: "S" | "SS" | "SSS" ): DayJSTimeParts { const ltsFormatString = this.localeConfig?.formats?.LTS; - const match = ltsFormatString.match(/ss\.*(S+)/g); + const fractionalSecondTokenMatch = ltsFormatString.match(/ss\.*(S+)/g); if (fractionalSecondFormatToken && this.shouldIncludeFractionalSeconds()) { const secondFormatToken = `ss.${fractionalSecondFormatToken}`; - this.localeConfig.formats.LTS = match - ? ltsFormatString.replace(match[0], secondFormatToken) + this.localeConfig.formats.LTS = fractionalSecondTokenMatch + ? ltsFormatString.replace(fractionalSecondTokenMatch[0], secondFormatToken) : ltsFormatString.replace("ss", secondFormatToken); - } else if (match) { - this.localeConfig.formats.LTS = ltsFormatString.replace(match[0], "ss"); + } else if (fractionalSecondTokenMatch) { + this.localeConfig.formats.LTS = ltsFormatString.replace(fractionalSecondTokenMatch[0], "ss"); } dayjs.updateLocale( @@ -557,7 +561,7 @@ export class InputTimePicker hour: dayjsParseResult.get("hour"), minute: dayjsParseResult.get("minute"), second: dayjsParseResult.get("second"), - millisecond: dayjsParseResult.get("milliseconds"), + millisecond: dayjsParseResult.get("millisecond"), }; } return { From 9e31d8912b4715e7fd4dce2e73e81c2fec3a68b4 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Thu, 24 Aug 2023 14:47:37 -0700 Subject: [PATCH 50/67] fixing existing tests --- .../input-time-picker/input-time-picker.tsx | 34 ++++++++++--------- packages/calcite-components/src/utils/time.ts | 3 ++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index 7340b3dae92..9382bd7faf5 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -577,14 +577,14 @@ export class InputTimePicker if (!parts) { return timeString; } - if (parts.hour && parts.minute) { + if (parts.hour !== null && parts.minute !== null) { timeString = `${formatTimePart(parts.hour)}:${formatTimePart(parts.minute)}`; - } - if (parts.second) { - timeString += `:${formatTimePart(parts.second)}`; - if (parts.millisecond) { - const second = (parts.millisecond * 0.001).toFixed(decimalPlaces(this.step)); - timeString += `.${second.toString().replace("0.", "")}`; + if (this.shouldIncludeSeconds() && parts.second !== null) { + timeString += `:${formatTimePart(parts.second)}`; + if (this.shouldIncludeFractionalSeconds() && parts.millisecond !== null) { + const second = (parts.millisecond * 0.001).toFixed(decimalPlaces(this.step)); + timeString += `.${second.toString().replace("0.", "")}`; + } } } return timeString; @@ -629,17 +629,19 @@ export class InputTimePicker const newValue = this.delocalizeTimeString(this.calciteInputEl.value); - this.setValue(newValue); + if (isValidTime(newValue)) { + this.setValue(newValue); - const localizedTimeString = localizeTimeString({ - value: this.value, - locale: this.effectiveLocale, - numberingSystem: this.numberingSystem, - includeSeconds: this.shouldIncludeSeconds(), - }); + const localizedTimeString = localizeTimeString({ + value: this.value, + locale: this.effectiveLocale, + numberingSystem: this.numberingSystem, + includeSeconds: this.shouldIncludeSeconds(), + }); - if (newValue && this.calciteInputEl.value !== localizedTimeString) { - this.setInputValue(localizedTimeString); + if (newValue && this.calciteInputEl.value !== localizedTimeString) { + this.setInputValue(localizedTimeString); + } } } else if (key === "ArrowDown") { this.open = true; diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index efa6f368d63..95972c49832 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -58,6 +58,9 @@ function createLocaleDateTimeFormatter( } export function formatTimePart(number: number, minLength?: number): string { + if (number === null || number === undefined) { + return; + } const numberAsString = number.toString(); const numberDecimalPlaces = decimalPlaces(number); if (number < 1 && numberDecimalPlaces > 0 && numberDecimalPlaces < 4) { From c245cd1dd44f4f9a8085bdba2330f386f8cda378 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 10:50:21 -0700 Subject: [PATCH 51/67] refactoring localizeTimeString to use fractionalSecondDigits config option in Intl.DateTimeFormat api --- .../input-time-picker/input-time-picker.tsx | 18 +++++-- packages/calcite-components/src/utils/time.ts | 53 +++++++++++-------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index 9382bd7faf5..2ce2faf888c 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -52,6 +52,7 @@ import { FocusTrap } from "focus-trap"; import { formatTimePart, formatTimeString, + FractionalSecondDigits, isValidTime, localizeTimeString, toISOTimeString, @@ -253,6 +254,7 @@ export class InputTimePicker locale: this.effectiveLocale, numberingSystem, includeSeconds: this.shouldIncludeSeconds(), + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }) ); } @@ -358,6 +360,7 @@ export class InputTimePicker locale, numberingSystem: this.numberingSystem, includeSeconds: this.shouldIncludeSeconds(), + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }) ); } @@ -397,6 +400,7 @@ export class InputTimePicker locale: this.effectiveLocale, numberingSystem: this.numberingSystem, includeSeconds: this.shouldIncludeSeconds(), + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }); if (localizedTimeString !== inputValue) { @@ -450,6 +454,7 @@ export class InputTimePicker locale: this.effectiveLocale, numberingSystem: this.numberingSystem, includeSeconds, + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }) ); }; @@ -496,11 +501,10 @@ export class InputTimePicker const centisecondParts = this.delocalizeTimeStringToParts(value, "S"); if (stepPrecision === 1) { - if (centisecondParts.millisecond !== 0) { - delocalizedTimeString = this.getTimeStringFromParts(centisecondParts); - } else { - delocalizedTimeString = this.getTimeStringFromParts(nonFractionalSecondParts); - } + delocalizedTimeString = + centisecondParts.millisecond !== 0 + ? this.getTimeStringFromParts(centisecondParts) + : this.getTimeStringFromParts(nonFractionalSecondParts); } else { const decisecondParts = this.delocalizeTimeStringToParts(value, "SS"); @@ -637,6 +641,7 @@ export class InputTimePicker locale: this.effectiveLocale, numberingSystem: this.numberingSystem, includeSeconds: this.shouldIncludeSeconds(), + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }); if (newValue && this.calciteInputEl.value !== localizedTimeString) { @@ -821,6 +826,7 @@ export class InputTimePicker locale: this.effectiveLocale, numberingSystem: this.numberingSystem, includeSeconds: this.shouldIncludeSeconds(), + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }) ); } @@ -842,6 +848,7 @@ export class InputTimePicker includeSeconds, locale: this.effectiveLocale, numberingSystem: this.numberingSystem, + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }) : "" ); @@ -890,6 +897,7 @@ export class InputTimePicker locale: this.effectiveLocale, numberingSystem: this.numberingSystem, includeSeconds: this.shouldIncludeSeconds(), + fractionalSecondDigits: decimalPlaces(this.step) as FractionalSecondDigits, }) ); } diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 95972c49832..693afd8db0b 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -1,6 +1,9 @@ import { getDateTimeFormat, getSupportedNumberingSystem, NumberingSystem, numberStringFormatter } from "./locale"; import { decimalPlaces } from "./math"; import { isValidNumber } from "./number"; + +export type FractionalSecondDigits = 1 | 2 | 3; + export type HourCycle = "12" | "24"; export interface LocalizedTime { @@ -42,7 +45,8 @@ export const maxTenthForMinuteAndSecond = 5; function createLocaleDateTimeFormatter( locale: string, numberingSystem: NumberingSystem, - includeSeconds = true + includeSeconds = true, + fractionalSecondDigits?: FractionalSecondDigits ): Intl.DateTimeFormat { const options: Intl.DateTimeFormatOptions = { hour: "2-digit", @@ -52,6 +56,9 @@ function createLocaleDateTimeFormatter( }; if (includeSeconds) { options.second = "2-digit"; + if (fractionalSecondDigits) { + options.fractionalSecondDigits = fractionalSecondDigits; + } } return getDateTimeFormat(locale, options); @@ -85,7 +92,7 @@ export function formatTimeString(value: string): string { if (!isValidTime(value)) { return null; } - let { hour, minute, second, fractionalSecond } = parseTimeString(value); + const { hour, minute, second, fractionalSecond } = parseTimeString(value); let formattedValue = `${formatTimePart(parseInt(hour))}:${formatTimePart(parseInt(minute))}`; if (second) { formattedValue += `:${formatTimePart(parseInt(second))}`; @@ -96,6 +103,10 @@ export function formatTimeString(value: string): string { return formattedValue; } +function fractionalSecondPartToMilliseconds(fractionalSecondPart: string): number { + return parseInt((parseFloat(`0.${fractionalSecondPart}`) / 0.001).toFixed(3)); +} + export function getLocaleHourCycle(locale: string, numberingSystem: NumberingSystem): HourCycle { const formatter = createLocaleDateTimeFormatter(locale, numberingSystem); const parts = formatter.formatToParts(new Date(Date.UTC(0, 0, 0, 0, 0, 0))); @@ -244,6 +255,7 @@ export function localizeTimePart({ value, part, locale, numberingSystem }: Local interface LocalizeTimeStringParameters { value: string; includeSeconds?: boolean; + fractionalSecondDigits?: FractionalSecondDigits; locale: string; numberingSystem: NumberingSystem; } @@ -253,30 +265,26 @@ export function localizeTimeString({ locale, numberingSystem, includeSeconds = true, + fractionalSecondDigits, }: LocalizeTimeStringParameters): string { if (!isValidTime(value)) { return null; } const { hour, minute, second = "0", fractionalSecond } = parseTimeString(value); - const dateFromTimeString = new Date(Date.UTC(0, 0, 0, parseInt(hour), parseInt(minute), parseInt(second))); - const formatter = createLocaleDateTimeFormatter(locale, numberingSystem, includeSeconds); - const parts = formatter.formatToParts(dateFromTimeString); - const localizedTimeString = parts?.reduce((result, part) => { - if (part.type === "second" && fractionalSecond) { - return ( - result + - `${part.value}.${localizeTimePart({ - value: fractionalSecond, - part: "fractionalSecond", - locale, - numberingSystem, - })}` - ); - } - return result + part.value; - }, ""); - return localizedTimeString || null; + const dateFromTimeString = new Date( + Date.UTC( + 0, + 0, + 0, + parseInt(hour), + parseInt(minute), + parseInt(second), + fractionalSecond && fractionalSecondPartToMilliseconds(fractionalSecond) + ) + ); + const formatter = createLocaleDateTimeFormatter(locale, numberingSystem, includeSeconds, fractionalSecondDigits); + return formatter.format(dateFromTimeString) || null; } interface LocalizeTimeStringToPartsParameters { @@ -339,8 +347,9 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { - let [hour, minute, second] = value.split(":"), - fractionalSecond = null; + // eslint-disable-next-line prefer-const + let [hour, minute, second] = value.split(":"); + let fractionalSecond = null; if (second?.includes(".")) { [second, fractionalSecond] = second.split("."); } From c02a4e2e19355420dadad0c3c0703ed2992cdc8c Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 16:12:32 -0700 Subject: [PATCH 52/67] refactoring to prefer const eslint rule --- packages/calcite-components/src/utils/time.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 693afd8db0b..18a4e0ac7be 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -347,11 +347,11 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { - // eslint-disable-next-line prefer-const - let [hour, minute, second] = value.split(":"); + const [hour, minute, secondDecimal] = value.split(":"); + let second = null; let fractionalSecond = null; - if (second?.includes(".")) { - [second, fractionalSecond] = second.split("."); + if (secondDecimal?.includes(".")) { + [second, fractionalSecond] = secondDecimal.split("."); } return { fractionalSecond, From fb371e24d6927fad92b15306a8dd59fc86646415 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 16:41:44 -0700 Subject: [PATCH 53/67] adding ref explainer --- .../components/time-picker/time-picker.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 47ab0dc6531..9f6a6202466 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -555,7 +555,9 @@ export class TimePicker const stepPrecision = decimalPlaces(this.step); const fractionalSecondAsInteger = parseInt(this.fractionalSecond); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); - let nudgedValue, nudgedValueRounded, newFractionalSecond; + let nudgedValue; + let nudgedValueRounded; + let newFractionalSecond; if (direction === "up") { nudgedValue = isNaN(fractionalSecondAsInteger) ? 0 : fractionalSecondAsFloat + this.step; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); @@ -570,15 +572,12 @@ export class TimePicker ? 1 - this.step : fractionalSecondAsFloat - this.step; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); - if ( + newFractionalSecond = nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 && Math.sign(nudgedValueRounded) === 1 - ) { - newFractionalSecond = formatTimePart(nudgedValueRounded, stepPrecision); - } else { - newFractionalSecond = "".padStart(stepPrecision, "0"); - } + ? formatTimePart(nudgedValueRounded, stepPrecision) + : "".padStart(stepPrecision, "0"); } this.setValuePart("fractionalSecond", newFractionalSecond); }; @@ -761,8 +760,8 @@ export class TimePicker numberingSystem, }); } - let emit = false, - newValue; + let emit = false; + let newValue; if (this.hour && this.minute) { newValue = `${this.hour}:${this.minute}`; if (this.showSecond) { @@ -1036,7 +1035,7 @@ export class TimePicker onKeyDown={this.fractionalSecondKeyDownHandler} role="spinbutton" tabIndex={0} - // eslint-disable-next-line react/jsx-sort-props + // eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530) ref={this.setFractionalSecondEl} > {this.localizedFractionalSecond || "--"} From 57fb7213ef9f6b6416ffbf9cfd102b90b7dbe5be Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 17:06:34 -0700 Subject: [PATCH 54/67] fixing critical issue where seconds weren't returning correctly when creating a date object for localizeTimeString --- packages/calcite-components/src/utils/time.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/utils/time.ts b/packages/calcite-components/src/utils/time.ts index 18a4e0ac7be..adf4c0416c3 100644 --- a/packages/calcite-components/src/utils/time.ts +++ b/packages/calcite-components/src/utils/time.ts @@ -348,7 +348,7 @@ export function getTimeParts({ value, locale, numberingSystem }: GetTimePartsPar export function parseTimeString(value: string): Time { if (isValidTime(value)) { const [hour, minute, secondDecimal] = value.split(":"); - let second = null; + let second = secondDecimal; let fractionalSecond = null; if (secondDecimal?.includes(".")) { [second, fractionalSecond] = secondDecimal.split("."); From f643d333fcc13a1290415d9004813cbc566822b7 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 17:07:09 -0700 Subject: [PATCH 55/67] refactoring away from using bound functions in render --- .../src/components/time-picker/time-picker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 9f6a6202466..8d1c0ff7de5 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -1016,7 +1016,7 @@ export class TimePicker [CSS.button]: true, [CSS.buttonFractionalSecondUp]: true, }} - onClick={this.nudgeFractionalSecond.bind(this, "up")} + onClick={() => this.nudgeFractionalSecond("up")} role="button" > @@ -1046,7 +1046,7 @@ export class TimePicker [CSS.button]: true, [CSS.buttonFractionalSecondDown]: true, }} - onClick={this.nudgeFractionalSecond.bind(this, "down")} + onClick={() => this.nudgeFractionalSecond("down")} role="button" > From 02a29f66df4ac24b54cbe7e85c46f03e6833987c Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 17:07:33 -0700 Subject: [PATCH 56/67] adding millisecond examples to demo page --- .../src/demos/input-time-picker.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/calcite-components/src/demos/input-time-picker.html b/packages/calcite-components/src/demos/input-time-picker.html index 5ab370d0b29..3d222be86c6 100644 --- a/packages/calcite-components/src/demos/input-time-picker.html +++ b/packages/calcite-components/src/demos/input-time-picker.html @@ -100,6 +100,25 @@

24-Hour Locales

} else { h23.append(labelEl); } + + labelEl = document.createElement("calcite-label"); + inputTimePickerEl = document.createElement("calcite-input-time-picker"); + + inputTimePickerEl.setAttribute("lang", locale); + if (numberingSystem) { + inputTimePickerEl.setAttribute("numbering-system", numberingSystem); + } + inputTimePickerEl.setAttribute("step", 0.001); + inputTimePickerEl.setAttribute("value", "10:00:00.001"); + labelEl.append(document.createTextNode(`${name} (${locale}) (milliseconds)`)); + labelEl.append(inputTimePickerEl); + mainEl.append(labelEl); + + if (localeObject.hourCycles[0] === "h12") { + h12.append(labelEl); + } else { + h23.append(labelEl); + } }); })(); From 22a8ce654275cb1b3cf0b35b55a01f802d7f2c4a Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 17:13:15 -0700 Subject: [PATCH 57/67] adding new messages to messages_en.json too --- .../time-picker/assets/time-picker/t9n/messages.json | 6 +++--- .../time-picker/assets/time-picker/t9n/messages_en.json | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json index 0738fa48ad4..4b214d9b59e 100644 --- a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json +++ b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages.json @@ -1,13 +1,13 @@ { + "fractionalSecond": "Fractional second", + "fractionalSecondDown": "Decrease fractional second", + "fractionalSecondUp": "Increase fractional second", "hour": "Hour", "hourDown": "Decrease hour", "hourUp": "Increase hour", "meridiem": "AM/PM", "meridiemDown": "Decrease AM/PM", "meridiemUp": "Increase AM/PM", - "fractionalSecond": "Fractional second", - "fractionalSecondDown": "Decrease fractional second", - "fractionalSecondUp": "Increase fractional second", "minute": "Minute", "minuteDown": "Decrease minute", "minuteUp": "Increase minute", diff --git a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages_en.json b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages_en.json index 00f28802e29..4b214d9b59e 100644 --- a/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages_en.json +++ b/packages/calcite-components/src/components/time-picker/assets/time-picker/t9n/messages_en.json @@ -1,4 +1,7 @@ { + "fractionalSecond": "Fractional second", + "fractionalSecondDown": "Decrease fractional second", + "fractionalSecondUp": "Increase fractional second", "hour": "Hour", "hourDown": "Decrease hour", "hourUp": "Increase hour", From 7b9108f54ea69ca4cc4d409d39fc68d6babce365 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Tue, 29 Aug 2023 17:17:58 -0700 Subject: [PATCH 58/67] adding increment and decrement functions to avoid binding and creating a new function on every render --- .../src/components/time-picker/time-picker.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 8d1c0ff7de5..76a1cd7f763 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -337,6 +337,10 @@ export class TimePicker this[`${target || "hour"}El`]?.focus(); } + private decrementFractionalSecond = (): void => { + this.nudgeFractionalSecond("down"); + }; + private decrementHour = (): void => { const newHour = !this.hour ? 0 : this.hour === "00" ? 23 : parseInt(this.hour) - 1; this.setValuePart("hour", newHour); @@ -458,6 +462,10 @@ export class TimePicker } }; + private incrementFractionalSecond = (): void => { + this.nudgeFractionalSecond("up"); + }; + private incrementMeridiem = (): void => { const newMeridiem = this.meridiem === "AM" ? "PM" : "AM"; this.setValuePart("meridiem", newMeridiem); @@ -1016,7 +1024,7 @@ export class TimePicker [CSS.button]: true, [CSS.buttonFractionalSecondUp]: true, }} - onClick={() => this.nudgeFractionalSecond("up")} + onClick={this.incrementFractionalSecond} role="button" > @@ -1046,7 +1054,7 @@ export class TimePicker [CSS.button]: true, [CSS.buttonFractionalSecondDown]: true, }} - onClick={() => this.nudgeFractionalSecond("down")} + onClick={this.decrementFractionalSecond} role="button" > From b5b8d6464e73e18bfff02e8c28165a7e5b405e4c Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 11:24:35 -0700 Subject: [PATCH 59/67] adding stories --- .../input-time-picker.stories.ts | 42 +++++++++++++++++++ .../time-picker/time-picker.stories.ts | 29 +++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 packages/calcite-components/src/components/time-picker/time-picker.stories.ts diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.stories.ts b/packages/calcite-components/src/components/input-time-picker/input-time-picker.stories.ts index c58275bb4c2..2d462c6f2f5 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.stories.ts +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.stories.ts @@ -26,6 +26,48 @@ export const simple = (): string => html` `; +export const deciSeconds_TestOnly = (): string => html` + + +`; + +export const centiseconds_TestOnly = (): string => html` + + +`; + +export const milliseconds_TestOnly = (): string => html` + + +`; + export const disabled_TestOnly = (): string => html``; diff --git a/packages/calcite-components/src/components/time-picker/time-picker.stories.ts b/packages/calcite-components/src/components/time-picker/time-picker.stories.ts new file mode 100644 index 00000000000..e688e6a9b30 --- /dev/null +++ b/packages/calcite-components/src/components/time-picker/time-picker.stories.ts @@ -0,0 +1,29 @@ +import { number, select, text } from "@storybook/addon-knobs"; +import { boolean, storyFilters } from "../../../.storybook/helpers"; +import readme from "./readme.md"; +import { html } from "../../../support/formatting"; +import { defaultMenuPlacement, menuPlacements } from "../../utils/floating-ui"; +import { locales, numberingSystems } from "../../utils/locale"; + +export default { + title: "Components/Controls/Time/Time Picker", + parameters: { + notes: readme, + }, + ...storyFilters(), +}; + +export const simple = (): string => html` + + +`; From 3bc9c30f19ee36c1ee2d2882eff3ed814508a299 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 12:35:23 -0700 Subject: [PATCH 60/67] nudge fractional seconds by just the fractional portion of the step. Adding additional test to ensure decimalPlaces also works for decimals greater than 0. --- .../components/time-picker/time-picker.tsx | 20 ++++++++++++------- .../calcite-components/src/utils/math.spec.ts | 6 ++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 76a1cd7f763..8b522c60863 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -560,6 +560,7 @@ export class TimePicker }; private nudgeFractionalSecond = (direction: "up" | "down"): void => { + const stepDecimal = this.sanitizeFractionalStep(this.step); const stepPrecision = decimalPlaces(this.step); const fractionalSecondAsInteger = parseInt(this.fractionalSecond); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); @@ -567,23 +568,21 @@ export class TimePicker let nudgedValueRounded; let newFractionalSecond; if (direction === "up") { - nudgedValue = isNaN(fractionalSecondAsInteger) ? 0 : fractionalSecondAsFloat + this.step; + nudgedValue = isNaN(fractionalSecondAsInteger) ? 0 : fractionalSecondAsFloat + stepDecimal; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); newFractionalSecond = - nudgedValueRounded < 1 && decimalPlaces(nudgedValueRounded) > 0 + decimalPlaces(nudgedValueRounded) > 0 ? formatTimePart(nudgedValueRounded, stepPrecision) : "".padStart(stepPrecision, "0"); } if (direction === "down") { nudgedValue = isNaN(fractionalSecondAsInteger) || fractionalSecondAsInteger === 0 - ? 1 - this.step - : fractionalSecondAsFloat - this.step; + ? 1 - stepDecimal + : fractionalSecondAsFloat - stepDecimal; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); newFractionalSecond = - nudgedValueRounded < 1 && - decimalPlaces(nudgedValueRounded) > 0 && - Math.sign(nudgedValueRounded) === 1 + decimalPlaces(nudgedValueRounded) > 0 && Math.sign(nudgedValueRounded) === 1 ? formatTimePart(nudgedValueRounded, stepPrecision) : "".padStart(stepPrecision, "0"); } @@ -604,6 +603,13 @@ export class TimePicker ? parseFloat(`0.${fractionalSecond}`).toFixed(decimalPlaces(this.step)).replace("0.", "") : fractionalSecond; + private sanitizeFractionalStep = (step: number): number => { + if (decimalPlaces(step) > 0 && step > 0) { + return parseFloat(`0.${step.toString().split(".")[1]}`); + } + return step; + }; + private secondKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { diff --git a/packages/calcite-components/src/utils/math.spec.ts b/packages/calcite-components/src/utils/math.spec.ts index 679d9ead153..c6c2ed96af4 100644 --- a/packages/calcite-components/src/utils/math.spec.ts +++ b/packages/calcite-components/src/utils/math.spec.ts @@ -32,6 +32,12 @@ describe("decimalPlaces", () => { expect(decimalPlaces(0.01)).toBe(2); expect(decimalPlaces(0.001)).toBe(3); expect(decimalPlaces(0.0001)).toBe(4); + expect(decimalPlaces(1)).toBe(0); + expect(decimalPlaces(1.0)).toBe(0); + expect(decimalPlaces(1.1)).toBe(1); + expect(decimalPlaces(1.01)).toBe(2); + expect(decimalPlaces(1.001)).toBe(3); + expect(decimalPlaces(1.0001)).toBe(4); }); }); From e6c977c58334ed1e1365ea854c9707bf28b7f40c Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 13:46:38 -0700 Subject: [PATCH 61/67] fix/refactor of previous issue to ensure that the newly nudged value never gets set to a value greater than 1 --- .../components/time-picker/time-picker.tsx | 24 +++++++++---------- packages/calcite-components/src/utils/math.ts | 7 ++++++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 8b522c60863..705b251f38e 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -54,7 +54,7 @@ import { setComponentLoaded, setUpLoadableComponent, } from "../../utils/loadable"; -import { decimalPlaces } from "../../utils/math"; +import { decimalPlaces, getDecimals } from "../../utils/math"; function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); @@ -560,19 +560,21 @@ export class TimePicker }; private nudgeFractionalSecond = (direction: "up" | "down"): void => { - const stepDecimal = this.sanitizeFractionalStep(this.step); + const stepDecimal = getDecimals(this.step); const stepPrecision = decimalPlaces(this.step); const fractionalSecondAsInteger = parseInt(this.fractionalSecond); const fractionalSecondAsFloat = parseFloat(`0.${this.fractionalSecond}`); let nudgedValue; let nudgedValueRounded; + let nudgedValueRoundedDecimals; let newFractionalSecond; if (direction === "up") { nudgedValue = isNaN(fractionalSecondAsInteger) ? 0 : fractionalSecondAsFloat + stepDecimal; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); + nudgedValueRoundedDecimals = getDecimals(nudgedValueRounded); newFractionalSecond = - decimalPlaces(nudgedValueRounded) > 0 - ? formatTimePart(nudgedValueRounded, stepPrecision) + nudgedValueRounded < 1 && decimalPlaces(nudgedValueRoundedDecimals) > 0 + ? formatTimePart(nudgedValueRoundedDecimals, stepPrecision) : "".padStart(stepPrecision, "0"); } if (direction === "down") { @@ -581,9 +583,12 @@ export class TimePicker ? 1 - stepDecimal : fractionalSecondAsFloat - stepDecimal; nudgedValueRounded = parseFloat(nudgedValue.toFixed(stepPrecision)); + nudgedValueRoundedDecimals = getDecimals(nudgedValueRounded); newFractionalSecond = - decimalPlaces(nudgedValueRounded) > 0 && Math.sign(nudgedValueRounded) === 1 - ? formatTimePart(nudgedValueRounded, stepPrecision) + nudgedValueRounded < 1 && + decimalPlaces(nudgedValueRoundedDecimals) > 0 && + Math.sign(nudgedValueRoundedDecimals) === 1 + ? formatTimePart(nudgedValueRoundedDecimals, stepPrecision) : "".padStart(stepPrecision, "0"); } this.setValuePart("fractionalSecond", newFractionalSecond); @@ -603,13 +608,6 @@ export class TimePicker ? parseFloat(`0.${fractionalSecond}`).toFixed(decimalPlaces(this.step)).replace("0.", "") : fractionalSecond; - private sanitizeFractionalStep = (step: number): number => { - if (decimalPlaces(step) > 0 && step > 0) { - return parseFloat(`0.${step.toString().split(".")[1]}`); - } - return step; - }; - private secondKeyDownHandler = (event: KeyboardEvent): void => { const { key } = event; if (numberKeys.includes(key)) { diff --git a/packages/calcite-components/src/utils/math.ts b/packages/calcite-components/src/utils/math.ts index c727fd7a912..bc4ee18264e 100644 --- a/packages/calcite-components/src/utils/math.ts +++ b/packages/calcite-components/src/utils/math.ts @@ -26,6 +26,13 @@ export const decimalPlaces = (value: number | string): number => { ); }; +export function getDecimals(value: number): number { + if (decimalPlaces(value) > 0 && value > 0) { + return parseFloat(`0.${value.toString().split(".")[1]}`); + } + return value; +} + export function remap(value: number, fromMin: number, fromMax: number, toMin: number, toMax: number): number { return ((value - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin; } From 3a7e8fffcb7f4e14ecb110cf49dda809905f19e4 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 13:47:13 -0700 Subject: [PATCH 62/67] adding new usage file and updating existing one --- .../src/components/input-time-picker/usage/Basic.md | 8 +------- .../input-time-picker/usage/Fractional-seconds.md | 3 +++ 2 files changed, 4 insertions(+), 7 deletions(-) create mode 100644 packages/calcite-components/src/components/input-time-picker/usage/Fractional-seconds.md diff --git a/packages/calcite-components/src/components/input-time-picker/usage/Basic.md b/packages/calcite-components/src/components/input-time-picker/usage/Basic.md index b65954ce2e3..117000f7e33 100644 --- a/packages/calcite-components/src/components/input-time-picker/usage/Basic.md +++ b/packages/calcite-components/src/components/input-time-picker/usage/Basic.md @@ -1,9 +1,3 @@ ```html - + ``` diff --git a/packages/calcite-components/src/components/input-time-picker/usage/Fractional-seconds.md b/packages/calcite-components/src/components/input-time-picker/usage/Fractional-seconds.md new file mode 100644 index 00000000000..5bf80c2ea63 --- /dev/null +++ b/packages/calcite-components/src/components/input-time-picker/usage/Fractional-seconds.md @@ -0,0 +1,3 @@ +```html + +``` From 22a3117fdec5ebd66abbf3ffa0f2a4d79721dd31 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 15:55:02 -0700 Subject: [PATCH 63/67] math util comment cleanup --- packages/calcite-components/src/utils/math.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/utils/math.ts b/packages/calcite-components/src/utils/math.ts index bc4ee18264e..3555997cf96 100644 --- a/packages/calcite-components/src/utils/math.ts +++ b/packages/calcite-components/src/utils/math.ts @@ -5,11 +5,10 @@ const decimalNumberRegex = new RegExp(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); /** * Returns the quantity of real decimal places for a number, which excludes trailing zeros. * - * Adapted from: + * Adapted from {@link https://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number}. * - * @link https://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number + * @param decimal - decimal value * @param value - * @param {string | number} decimal - decimal value * @returns {number} the amount of decimal places in a number */ export const decimalPlaces = (value: number | string): number => { From 7899bb67754a7ad7d9eb8dd399e340abe321a75d Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 16:08:26 -0700 Subject: [PATCH 64/67] typing localeConfig with ILocale. Dayjs's updateLocale doesn't type the localeConfig argument with ILocale or Partial so there has to be a generic fallback type there. --- .../src/components/input-time-picker/input-time-picker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index 2ce2faf888c..f0d99165f2d 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -334,7 +334,7 @@ export class InputTimePicker private dialogId = `time-picker-dialog--${guid()}`; - private localeConfig; + private localeConfig: ILocale; /** whether the value of the input was changed as a result of user typing or not */ private userChangedValue = false; @@ -555,7 +555,7 @@ export class InputTimePicker dayjs.updateLocale( this.getSupportedDayJSLocale(getSupportedLocale(this.effectiveLocale)), - this.localeConfig + this.localeConfig as Record ); const dayjsParseResult = dayjs(localizedTimeString, ["LTS", "LT"]); From 32d7a7285f0ec8684ccf7c9f167c1e220b398acd Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 16:13:03 -0700 Subject: [PATCH 65/67] alphabetizing private prop --- .../src/components/time-picker/time-picker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 705b251f38e..1e866f84015 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -131,6 +131,8 @@ export class TimePicker private activeEl: HTMLSpanElement; + private fractionalSecondEl: HTMLSpanElement; + private hourEl: HTMLSpanElement; private meridiemEl: HTMLSpanElement; @@ -139,8 +141,6 @@ export class TimePicker private secondEl: HTMLSpanElement; - private fractionalSecondEl: HTMLSpanElement; - private meridiemOrder: number; // -------------------------------------------------------------------------- From b0f0a286c24f23fd687c28d1b1e7d507c298bb32 Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 16:13:38 -0700 Subject: [PATCH 66/67] alphabetize #2 --- .../src/components/time-picker/time-picker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/time-picker/time-picker.tsx b/packages/calcite-components/src/components/time-picker/time-picker.tsx index 1e866f84015..e245d5197af 100644 --- a/packages/calcite-components/src/components/time-picker/time-picker.tsx +++ b/packages/calcite-components/src/components/time-picker/time-picker.tsx @@ -137,12 +137,12 @@ export class TimePicker private meridiemEl: HTMLSpanElement; + private meridiemOrder: number; + private minuteEl: HTMLSpanElement; private secondEl: HTMLSpanElement; - private meridiemOrder: number; - // -------------------------------------------------------------------------- // // State From 6c57565e3e99f86ab9fddd2c51fc1769474c526b Mon Sep 17 00:00:00 2001 From: Erik Harper Date: Wed, 30 Aug 2023 16:16:31 -0700 Subject: [PATCH 67/67] casing dayjs names consistently --- .../input-time-picker/input-time-picker.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index f0d99165f2d..85308abd210 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -73,7 +73,7 @@ import { getSupportedLocale } from "../../utils/locale"; import { decimalPlaces } from "../../utils/math"; // some bundlers (e.g., Webpack) need dynamic import paths to be static -const supportedDayJsLocaleToLocaleConfigImport = new Map([ +const supportedDayjsLocaleToLocaleConfigImport = new Map([ ["ar", () => import("dayjs/esm/locale/ar.js")], ["bg", () => import("dayjs/esm/locale/bg.js")], ["bs", () => import("dayjs/esm/locale/bs.js")], @@ -132,7 +132,7 @@ dayjs.extend(localizedFormat); dayjs.extend(preParsePostFormat); dayjs.extend(updateLocale); -interface DayJSTimeParts { +interface DayjsTimeParts { hour: number; minute: number; second: number; @@ -540,7 +540,7 @@ export class InputTimePicker private delocalizeTimeStringToParts( localizedTimeString: string, fractionalSecondFormatToken?: "S" | "SS" | "SSS" - ): DayJSTimeParts { + ): DayjsTimeParts { const ltsFormatString = this.localeConfig?.formats?.LTS; const fractionalSecondTokenMatch = ltsFormatString.match(/ss\.*(S+)/g); @@ -554,7 +554,7 @@ export class InputTimePicker } dayjs.updateLocale( - this.getSupportedDayJSLocale(getSupportedLocale(this.effectiveLocale)), + this.getSupportedDayjsLocale(getSupportedLocale(this.effectiveLocale)), this.localeConfig as Record ); @@ -576,7 +576,7 @@ export class InputTimePicker }; } - private getTimeStringFromParts(parts: DayJSTimeParts): string { + private getTimeStringFromParts(parts: DayjsTimeParts): string { let timeString = ""; if (!parts) { return timeString; @@ -659,7 +659,7 @@ export class InputTimePicker } }; - private getSupportedDayJSLocale(locale: string) { + private getSupportedDayjsLocale(locale: string) { const dayjsLocale = locale.toLowerCase(); if (dayjsLocale === "no") { return "nb"; @@ -673,9 +673,9 @@ export class InputTimePicker private async loadDateTimeLocaleData(): Promise { let supportedLocale = getSupportedLocale(this.effectiveLocale).toLowerCase(); - supportedLocale = this.getSupportedDayJSLocale(supportedLocale); + supportedLocale = this.getSupportedDayjsLocale(supportedLocale); - const { default: localeConfig } = await supportedDayJsLocaleToLocaleConfigImport.get( + const { default: localeConfig } = await supportedDayjsLocaleToLocaleConfigImport.get( supportedLocale )();