Skip to content

Commit

Permalink
fix(DateField): correctly work with 2-digits years (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
ValeraS authored Nov 20, 2023
1 parent 0872c11 commit 0d23300
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 177 deletions.
28 changes: 17 additions & 11 deletions src/components/DateField/hooks/useDateFieldProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,40 +176,46 @@ export function useDateFieldProps(
onMouseUp(e: React.MouseEvent) {
e.preventDefault();
},
onBeforeInput(e: React.FormEvent) {
onBeforeInput(e) {
e.preventDefault();
// @ts-expect-error
const key = e.data;
// eslint-disable-next-line no-eq-null, eqeqeq
if (key != null) {
if (key !== undefined && key !== null) {
state.onInput(key);
}
},
onPaste(e: React.ClipboardEvent) {
e.preventDefault();
if (state.readOnly) {
e.preventDefault();
return;
}

const pastedValue = e.clipboardData.getData('text');
if (state.setValueFromString(pastedValue)) {
e.preventDefault();
} else if (
if (
state.selectedSectionIndexes &&
state.selectedSectionIndexes.startIndex ===
state.selectedSectionIndexes.endIndex
) {
const activeSection =
state.sections[state.selectedSectionIndexes.startIndex];

const digitsOnly = /^\d+$/.test(pastedValue);
const lettersOnly = /^[a-zA-Z]+$/.test(pastedValue);

const isValidValue =
Boolean(activeSection) &&
((activeSection.contentType === 'digit' && /^\d+$/.test(pastedValue)) ||
activeSection.contentType === 'letter');
if (!isValidValue) {
e.preventDefault();
((activeSection.contentType === 'digit' && digitsOnly) ||
(activeSection.contentType === 'letter' && lettersOnly));
if (isValidValue) {
state.onInput(pastedValue);
return;
}
if (digitsOnly || lettersOnly) {
return;
}
}

state.setValueFromString(pastedValue);
},
},
},
Expand Down
189 changes: 23 additions & 166 deletions src/components/DateField/hooks/useDateFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import type {
DateFieldSectionType,
DateFieldSectionWithoutPosition,
} from '../types';
import {splitFormatIntoSections} from '../utils';
import {
addSegment,
getDurationUnitFromSectionType,
getSectionLimits,
getSectionValue,
setSegment,
splitFormatIntoSections,
} from '../utils';

export interface DateFieldStateOptions extends DateFieldBase {}

Expand All @@ -27,12 +34,6 @@ const EDITABLE_SEGMENTS: Partial<Record<DateFieldSectionType, boolean>> = {
weekday: true,
};

const TYPE_MAPPING = {
weekday: 'day',
day: 'date',
dayPeriod: 'hour',
} as const;

const PAGE_STEP: Partial<Record<DateFieldSectionType, number>> = {
year: 5,
month: 2,
Expand Down Expand Up @@ -194,8 +195,13 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
}

if (Object.keys(validSegments).length >= Object.keys(allSegments).length) {
setDate(newValue);
if (!value || !newValue.isSame(value)) {
setDate(newValue);
}
} else {
if (value) {
setDate(null);
}
setPlaceholderDate(newValue);
}
}
Expand Down Expand Up @@ -396,9 +402,6 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
currentValue = displayValue.set(type, placeholder[type]());
}

if (value) {
setDate(null);
}
setValue(currentValue);
},
clearAll() {
Expand Down Expand Up @@ -534,6 +537,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
}
},
setValueFromString(str: string) {
enteredKeys.current = '';
let date = parseDate({input: str, format, timeZone: props.timeZone});
if (isValid(date)) {
if (props.timeZone && !isDateStringWithTimeZone(str)) {
Expand All @@ -560,65 +564,6 @@ function isDateStringWithTimeZone(str: string) {
return /z$/i.test(str) || /[+-]\d\d:\d\d$/.test(str);
}

function addSegment(section: DateFieldSection, date: DateTime, amount: number) {
let val = section.value ?? 0;
if (section.type === 'dayPeriod') {
val = date.hour() + (date.hour() > 12 ? -12 : 12);
} else {
val = val + amount;
const min = section.minValue;
const max = section.maxValue;
if (typeof min === 'number' && typeof max === 'number') {
const length = max - min + 1;
val = ((val - min + length) % length) + min;
}
}
const type = getDurationUnitFromSectionType(section.type);
return date.set(type, val);
}

function setSegment(section: DateFieldSection, date: DateTime, amount: number) {
const type = section.type;
switch (type) {
case 'day':
case 'weekday':
case 'month':
case 'year': {
return date.set(getDurationUnitFromSectionType(type), amount);
}
case 'dayPeriod': {
const hours = date.hour();
const wasPM = hours >= 12;
const isPM = amount >= 12;
if (isPM === wasPM) {
return date;
}
return date.set('hour', wasPM ? hours - 12 : hours + 12);
}
case 'hour': {
// In 12 hour time, ensure that AM/PM does not change
let sectionAmount = amount;
if (section.minValue === 12 || section.maxValue === 11) {
const hours = date.hour();
const wasPM = hours >= 12;
if (!wasPM && sectionAmount === 12) {
sectionAmount = 0;
}
if (wasPM && sectionAmount < 12) {
sectionAmount += 12;
}
}
return date.set('hour', sectionAmount);
}
case 'minute':
case 'second': {
return date.set(type, amount);
}
}

return date;
}

function getCurrentEditableSectionIndex(
sections: DateFieldSection[],
selectedSections: 'all' | number,
Expand Down Expand Up @@ -690,20 +635,26 @@ function getEditableSections(
let renderedValue = section.placeholder;
if ((isEditable && validSegments[section.type]) || section.type === 'timeZoneName') {
renderedValue = value.format(section.format);
if (
section.contentType === 'digit' &&
renderedValue.length < section.placeholder.length
) {
renderedValue = renderedValue.padStart(section.placeholder.length, '0');
}
}

const sectionLength = renderedValue.length;

const newSection = {
...section,
value: getValue(value, section.type),
value: getSectionValue(section, value),
textValue: renderedValue,
start: position,
end: position + sectionLength,
modified: false,
previousEditableSection,
nextEditableSection: previousEditableSection,
...getSectionLimits(value, section.type, {hour12: isHour12(section)}),
...getSectionLimits(section, value),
};

newSections.push(newSection);
Expand All @@ -726,97 +677,3 @@ function getEditableSections(

return newSections;
}

function getValue(date: DateTime, type: DateFieldSectionType) {
switch (type) {
case 'year':
case 'month':
case 'hour':
case 'minute':
case 'second': {
return date[type]();
}
case 'day': {
return date.date();
}
case 'weekday': {
return date.day();
}
case 'dayPeriod': {
return date.hour() >= 12 ? 12 : 0;
}
}
return undefined;
}

function isHour12(section: DateFieldSectionWithoutPosition) {
if (section.type === 'hour') {
return dateTime().set('hour', 15).format(section.format) !== '15';
}
return false;
}

function getSectionLimits(date: DateTime, type: DateFieldSectionType, options: {hour12: boolean}) {
switch (type) {
case 'year': {
return {
minValue: 1,
maxValue: 9999,
};
}
case 'month': {
return {
minValue: 0,
maxValue: 11,
};
}
case 'weekday': {
return {
minValue: 0,
maxValue: 6,
};
}
case 'day': {
return {
minValue: 1,
maxValue: date ? date.daysInMonth() : 31,
};
}
case 'hour': {
if (options.hour12) {
const isPM = date.hour() >= 12;
return {
minValue: isPM ? 12 : 0,
maxValue: isPM ? 23 : 11,
};
}
return {
minValue: 0,
maxValue: 23,
};
}
case 'minute':
case 'second': {
return {
minValue: 0,
maxValue: 59,
};
}
}
return {};
}

function getDurationUnitFromSectionType(type: DateFieldSectionType) {
if (type === 'literal' || type === 'timeZoneName' || type === 'unknown') {
throw new Error(`${type} section does not have duration unit.`);
}

if (type in TYPE_MAPPING) {
return TYPE_MAPPING[type as keyof typeof TYPE_MAPPING];
}

return type as Exclude<
DateFieldSectionType,
keyof typeof TYPE_MAPPING | 'literal' | 'timeZoneName' | 'unknown'
>;
}
Loading

0 comments on commit 0d23300

Please sign in to comment.