From 383f63bc1277141820776c8037ddc553c2f2f200 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Thu, 7 Oct 2021 16:16:42 +0200 Subject: [PATCH 01/34] refactor: remove empty instruction --- src/components/Calendar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Calendar/index.js b/src/components/Calendar/index.js index 73b9faa48..79e64c2cf 100644 --- a/src/components/Calendar/index.js +++ b/src/components/Calendar/index.js @@ -493,7 +493,7 @@ class Calendar extends PureComponent { isVertical ? this.styles.monthsVertical : this.styles.monthsHorizontal )}> {new Array(this.props.months).fill(null).map((_, i) => { - let monthStep = addMonths(this.state.focusedDate, i);; + let monthStep = addMonths(this.state.focusedDate, i); if (this.props.calendarFocus === 'backwards') { monthStep = subMonths(this.state.focusedDate, this.props.months - 1 - i); } From 5fa02ec151e51e0d62e9a70b57763f9be3874955 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Thu, 7 Oct 2021 16:17:55 +0200 Subject: [PATCH 02/34] feat: allow custom weekStartsOn for DefinedRange --- src/components/DefinedRange/index.js | 20 +++-- src/defaultRanges.js | 108 ++++++++++++++------------- 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/components/DefinedRange/index.js b/src/components/DefinedRange/index.js index 10ca9f960..dfc76f9d7 100644 --- a/src/components/DefinedRange/index.js +++ b/src/components/DefinedRange/index.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import styles from '../../styles'; -import { defaultInputRanges, defaultStaticRanges } from '../../defaultRanges'; +import { defaultInputRangesGen, defaultStaticRangesGen } from '../../defaultRanges'; import { rangeShape } from '../DayCell'; import InputRangeField from '../InputRangeField'; import cx from 'classnames'; @@ -9,6 +9,17 @@ import cx from 'classnames'; class DefinedRange extends Component { constructor(props) { super(props); + this.dateOptions = {}; + const weekStartsOn = props.weekStartsOn !== undefined ? props.weekStartsOn : 0; + this.dateOptions = { + weekStartsOn, + inputRanges: + props.inputRanges !== undefined ? props.inputRanges : defaultInputRangesGen(weekStartsOn), + staticRanges: + props.staticRanges !== undefined + ? props.staticRanges + : defaultStaticRangesGen(weekStartsOn), + }; this.state = { rangeOffset: 0, focusedInput: -1, @@ -49,14 +60,14 @@ class DefinedRange extends Component { headerContent, footerContent, onPreviewChange, - inputRanges, - staticRanges, ranges, renderStaticRangeLabel, rangeColors, className, } = this.props; + const { inputRanges, staticRanges } = this.dateOptions; + return (
{headerContent} @@ -118,6 +129,7 @@ class DefinedRange extends Component { } DefinedRange.propTypes = { + weekStartsOn: PropTypes.number, inputRanges: PropTypes.array, staticRanges: PropTypes.array, ranges: PropTypes.arrayOf(rangeShape), @@ -132,8 +144,6 @@ DefinedRange.propTypes = { }; DefinedRange.defaultProps = { - inputRanges: defaultInputRanges, - staticRanges: defaultStaticRanges, ranges: [], rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], focusedRange: [0, 0], diff --git a/src/defaultRanges.js b/src/defaultRanges.js index fb91ebb25..437da4149 100644 --- a/src/defaultRanges.js +++ b/src/defaultRanges.js @@ -11,11 +11,11 @@ import { differenceInCalendarDays, } from 'date-fns'; -const defineds = { - startOfWeek: startOfWeek(new Date()), - endOfWeek: endOfWeek(new Date()), - startOfLastWeek: startOfWeek(addDays(new Date(), -7)), - endOfLastWeek: endOfWeek(addDays(new Date(), -7)), +const definedsGen = ({ weekStartsOn }) => ({ + startOfWeek: startOfWeek(new Date(), { weekStartsOn }), + endOfWeek: endOfWeek(new Date(), { weekStartsOn }), + startOfLastWeek: startOfWeek(addDays(new Date(), -7), { weekStartsOn }), + endOfLastWeek: endOfWeek(addDays(new Date(), -7), { weekStartsOn }), startOfToday: startOfDay(new Date()), endOfToday: endOfDay(new Date()), startOfYesterday: startOfDay(addDays(new Date(), -1)), @@ -24,7 +24,9 @@ const defineds = { endOfMonth: endOfMonth(new Date()), startOfLastMonth: startOfMonth(addMonths(new Date(), -1)), endOfLastMonth: endOfMonth(addMonths(new Date(), -1)), -}; +}); + +const defineds = definedsGen({ weekStartsOn: 0 }); const staticRangeHandler = { range: {}, @@ -41,53 +43,55 @@ export function createStaticRanges(ranges) { return ranges.map(range => ({ ...staticRangeHandler, ...range })); } -export const defaultStaticRanges = createStaticRanges([ - { - label: 'Today', - range: () => ({ - startDate: defineds.startOfToday, - endDate: defineds.endOfToday, - }), - }, - { - label: 'Yesterday', - range: () => ({ - startDate: defineds.startOfYesterday, - endDate: defineds.endOfYesterday, - }), - }, +export const defaultStaticRangesGen = defineds => + createStaticRanges([ + { + label: 'Last Month', + range: () => ({ + startDate: defineds.startOfLastMonth, + endDate: defineds.endOfLastMonth, + }), + }, + { + label: 'Last Week', + range: () => ({ + startDate: defineds.startOfLastWeek, + endDate: defineds.endOfLastWeek, + }), + }, + { + label: 'Yesterday', + range: () => ({ + startDate: defineds.startOfYesterday, + endDate: defineds.endOfYesterday, + }), + }, + { + label: 'Today', + range: () => ({ + startDate: defineds.startOfToday, + endDate: defineds.endOfToday, + }), + }, + { + label: 'This Week', + range: () => ({ + startDate: defineds.startOfWeek, + endDate: defineds.endOfWeek, + }), + }, + { + label: 'This Month', + range: () => ({ + startDate: defineds.startOfMonth, + endDate: defineds.endOfMonth, + }), + }, + ]); - { - label: 'This Week', - range: () => ({ - startDate: defineds.startOfWeek, - endDate: defineds.endOfWeek, - }), - }, - { - label: 'Last Week', - range: () => ({ - startDate: defineds.startOfLastWeek, - endDate: defineds.endOfLastWeek, - }), - }, - { - label: 'This Month', - range: () => ({ - startDate: defineds.startOfMonth, - endDate: defineds.endOfMonth, - }), - }, - { - label: 'Last Month', - range: () => ({ - startDate: defineds.startOfLastMonth, - endDate: defineds.endOfLastMonth, - }), - }, -]); +export const defaultStaticRanges = defaultStaticRangesGen(defineds); -export const defaultInputRanges = [ +export const defaultInputRangesGen = defineds => [ { label: 'days up to today', range(value) { @@ -118,3 +122,5 @@ export const defaultInputRanges = [ }, }, ]; + +export const defaultInputRanges = defaultInputRangesGen(defineds); From 8636bbbbd286a3ee2cd2f148d37e659ba75e1bc6 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Thu, 7 Oct 2021 16:54:09 +0200 Subject: [PATCH 03/34] build: new version --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f723d2650..05c582c2c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-date-range", - "version": "1.4.0", + "version": "1.4.1", "description": "A React component for choosing dates and date ranges.", "main": "dist/index.js", "scripts": { @@ -24,7 +24,8 @@ "Mehmet Kamil Morcay (https://github.com/mkg0)", "Kamyar Ghasemlou (https://github.com/kamyar)", "Engin Semih Basmacı (https://github.com/mortargrind)", - "Onur Kerimov (https://github.com/onurkerimov)" + "Onur Kerimov (https://github.com/onurkerimov)", + "Guillermo Pages (https://github.com/gbili)" ], "license": "MIT", "repository": { From 9671f9d979eb5d6aa6e29327cacc055245517af2 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Mon, 11 Oct 2021 17:28:12 +0200 Subject: [PATCH 04/34] feat: migrate to tsc, no errors --- .babelrc | 12 - .eslintrc.js | 30 -- jest.config.js | 7 - jest.config.ts | 13 + package.json | 11 +- setupTests.js => setupTests.ts | 0 .../Calendar/{index.test.js => index.test.ts} | 2 +- .../Calendar/{index.js => index.tsx} | 456 +++++++++++------- .../DateInput/{index.js => index.tsx} | 73 +-- src/components/DateRange/index.js | 167 ------- src/components/DateRange/index.tsx | 258 ++++++++++ .../DayCell/{index.js => index.tsx} | 132 ++--- src/components/InputRangeField/index.js | 71 --- src/components/InputRangeField/index.tsx | 71 +++ src/components/Month/{index.js => index.tsx} | 86 ++-- src/{defaultRanges.js => defaultRanges.ts} | 45 +- src/{index.js => index.ts} | 0 src/locale/{index.js => index.ts} | 0 src/{styles.js => styles.ts} | 79 +-- src/types.d.ts | 3 + src/types.ts | 263 ++++++++++ src/utils.js | 79 --- src/utils.ts | 172 +++++++ tsconfig.json | 30 ++ yarn.lock | 324 ++++++++++++- 25 files changed, 1646 insertions(+), 738 deletions(-) delete mode 100755 .babelrc delete mode 100644 .eslintrc.js delete mode 100644 jest.config.js create mode 100644 jest.config.ts rename setupTests.js => setupTests.ts (100%) rename src/components/Calendar/{index.test.js => index.test.ts} (77%) rename src/components/Calendar/{index.js => index.tsx} (61%) rename src/components/DateInput/{index.js => index.tsx} (59%) delete mode 100644 src/components/DateRange/index.js create mode 100644 src/components/DateRange/index.tsx rename src/components/DayCell/{index.js => index.tsx} (65%) delete mode 100644 src/components/InputRangeField/index.js create mode 100644 src/components/InputRangeField/index.tsx rename src/components/Month/{index.js => index.tsx} (71%) rename src/{defaultRanges.js => defaultRanges.ts} (72%) rename src/{index.js => index.ts} (100%) rename src/locale/{index.js => index.ts} (100%) rename src/{styles.js => styles.ts} (89%) create mode 100644 src/types.d.ts create mode 100644 src/types.ts delete mode 100644 src/utils.js create mode 100644 src/utils.ts create mode 100644 tsconfig.json diff --git a/.babelrc b/.babelrc deleted file mode 100755 index b5ad76866..000000000 --- a/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env"], - ["@babel/preset-react"] - ], - "plugins": [ - ["@babel/plugin-proposal-class-properties"], - ["@babel/plugin-proposal-export-default-from"], - "date-fns" - ] -} - diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 147ba164b..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - extends: [ - "eslint:recommended", - "plugin:react/recommended", - "prettier", - ], - plugins: [ - "react", - "prettier", - ], - rules: { - "prettier/prettier": ["error", { - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": true, - "printWidth": 100, - "parser": "babylon", - }], - "no-debugger": 0, - "no-console": 0, - }, - parser: "babel-eslint", - env: { - "es6": true, - "node": true, - "browser": true, - "jest": true, - }, -}; \ No newline at end of file diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 0cdb8c97a..000000000 --- a/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - verbose: true, - testURL: 'http://localhost/', - setupFiles: ['/setupTests.js'], - testPathIgnorePatterns: ['/node_modules/', '/dist/', '/demo/dist/'], - snapshotSerializers: ['enzyme-to-json/serializer'], -}; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 000000000..a63354010 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types'; +import { defaults } from 'jest-config'; + +// Sync object +const config: Config.InitialOptions = { + verbose: true, + testURL: 'http://localhost/', + setupFiles: ['/setupTests.ts'], + moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], + testPathIgnorePatterns: ['/node_modules/', '/src/', '/demo/dist/'], + snapshotSerializers: ['enzyme-to-json/serializer'], +}; +export default config; \ No newline at end of file diff --git a/package.json b/package.json index 05c582c2c..f123342d2 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-date-range", - "version": "1.4.1", + "name": "react-date-range-won", + "version": "1.5.0", "description": "A React component for choosing dates and date ranges.", "main": "dist/index.js", "scripts": { @@ -41,6 +41,7 @@ "dependencies": { "classnames": "^2.2.6", "prop-types": "^15.7.2", + "ramda": "^0.27.1", "react-list": "^0.8.13", "shallow-equal": "^1.2.1" }, @@ -55,6 +56,11 @@ "@babel/plugin-proposal-export-default-from": "^7.7.4", "@babel/preset-env": "^7.4.4", "@babel/preset-react": "^7.7.4", + "@types/jest": "^27.0.2", + "@types/ramda": "^0.27.45", + "@types/react": "^17.0.27", + "@types/react-list": "^0.8.6", + "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^9.7.3", "babel-eslint": "^10.0.3", "babel-loader": "^8.0.6", @@ -82,6 +88,7 @@ "react-dom": "^16.12.0", "react-styleguidist": "^10.4.0", "style-loader": "^1.0.0", + "typescript": "^4.4.3", "url-loader": "^3.0.0", "webpack": "^4.41.5" } diff --git a/setupTests.js b/setupTests.ts similarity index 100% rename from setupTests.js rename to setupTests.ts diff --git a/src/components/Calendar/index.test.js b/src/components/Calendar/index.test.ts similarity index 77% rename from src/components/Calendar/index.test.js rename to src/components/Calendar/index.test.ts index 8d7f3cbc7..642eba975 100644 --- a/src/components/Calendar/index.test.js +++ b/src/components/Calendar/index.test.ts @@ -1,4 +1,4 @@ -import Calendar from '../Calendar'; +import Calendar from '.'; describe('Calendar', () => { test('Should resolve', () => { diff --git a/src/components/Calendar/index.js b/src/components/Calendar/index.tsx similarity index 61% rename from src/components/Calendar/index.js rename to src/components/Calendar/index.tsx index 79e64c2cf..aa1f6b2a3 100644 --- a/src/components/Calendar/index.js +++ b/src/components/Calendar/index.tsx @@ -1,9 +1,9 @@ +/// import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { rangeShape } from '../DayCell'; -import Month from '../Month'; +import Month, { Drag } from '../Month'; import DateInput from '../DateInput'; -import { calcFocusDate, generateStyles, getMonthDisplayRange } from '../../utils'; +import { calcFocusDate, generateStyles, getMonthDisplayRange, PartialStyles } from '../../utils'; +import { enUS } from '../../locale' import classnames from 'classnames'; import ReactList from 'react-list'; import { shallowEqualObjects } from 'shallow-equal'; @@ -26,58 +26,215 @@ import { differenceInDays, min, max, + Locale, } from 'date-fns'; import defaultLocale from 'date-fns/locale/en-US'; -import coreStyles from '../../styles'; -import { ariaLabelsShape } from '../../accessibility'; - -class Calendar extends PureComponent { - constructor(props, context) { - super(props, context); - this.dateOptions = { locale: props.locale }; - if (props.weekStartsOn !== undefined) this.dateOptions.weekStartsOn = props.weekStartsOn; - this.styles = generateStyles([coreStyles, props.classNames]); +import coreStyles, { ClassNames, CoreStyles } from '../../styles'; +import { CalendarDirection, CalendarFocus, CommonCalendarProps, DateOptions, DisplayMode, isSureRange, Preview, Range, RangeFocus, ScrollOptions, StartEndDate, SureStartEndDate } from '../../types'; +import { DateReceivingFunc, OptionalDateReceivingFunc } from '../DayCell'; + +type ScrollArea = { + enabled: boolean; + monthHeight?: number; + longMonthHeight?: number; + calendarWidth?: number | 'auto'; + calendarHeight?: number; + monthWidth?: number; +} + +type ComponentState = { + monthNames: string[]; + focusedDate: Date; + drag: Drag; + scrollArea: ScrollArea; + preview?: Preview | null; + +} + +type AriaLabelShape = { + dateInput?: { + [x: string]: StartEndDate; + }; + monthPicker?: string; + yearPicker?: string; + prevButton?: string; + nextButton?: string; +} + +type DefaultCalendarProps = { + ariaLabels: AriaLabelShape; + calendarFocus: CalendarFocus; + classNames: Partial; + color: string; + dateDisplayFormat: string; + dayDisplayFormat: string; + direction: CalendarDirection; + disabledDates: Date[]; + disabledDay: (day: Date) => boolean; + displayMode: DisplayMode; + dragSelectionEnabled: boolean; + editableDateInputs: boolean; + endDatePlaceholder: string; + fixedHeight: boolean; + focusedRange: RangeFocus; + locale: Locale; + maxDate: Date; + minDate: Date; + monthDisplayFormat: string; + months: number; + preventSnapRefocus: boolean; + preview?: Preview | null; + rangeColors: string[]; + ranges: Range[]; + scroll: { + enabled: boolean; + }, + showDateDisplay: boolean; + showMonthAndYearPickers: boolean; + showMonthArrow: boolean; + showPreview: boolean; + startDatePlaceholder: string; + weekdayDisplayFormat: string; +}; + +export type CalendarProps = CommonCalendarProps & { + /** default: today */ + date: Date | number; + /** default: none */ + onChange?: DateReceivingFunc; + scroll?: { enabled: boolean; }; + preventSnapRefocus?: boolean; + onPreviewChange?: OptionalDateReceivingFunc; + updateRange?: (range: SureStartEndDate) => void; + className?: string; + displayMode?: DisplayMode; +} + + +type ModeMapper = { + monthOffset: () => Date, + setMonth: () => Date, + setYear: () => Date, + set: () => number, +} + +; + +const inferAriaLabel = (ariaLabels: AriaLabelShape, range: Range): string => { + const sd = ariaLabels.dateInput && range.key + && ariaLabels.dateInput[range.key] + && ariaLabels.dateInput[range.key].startDate; + if (sd instanceof Date) { + return sd.toLocaleString(); + } else if (typeof sd === 'string') { + return sd; + } else { + console.log(ariaLabels); + throw new Error('Unsupported ariaLabel type'); + } +} + +function isModeMapperKey(s: string, o: ModeMapper): s is keyof ModeMapper { + return Object.keys(o).indexOf(s) !== -1; +} + +type ComponentProps = CalendarProps & DefaultCalendarProps; + +class Calendar extends PureComponent { + + dateOptions: DateOptions; + styles: any; + listSizeCache: any; + isFirstRender: boolean; + list: ReactList | null; + + public static defaultProps: DefaultCalendarProps = { + ariaLabels: {}, + calendarFocus: 'forwards', + classNames: {}, + color: '#3d91ff', + dateDisplayFormat: 'MMM d, yyyy', + dayDisplayFormat: 'd', + direction: 'vertical', + disabledDates: [], + disabledDay: () => false, + displayMode: 'date', + dragSelectionEnabled: true, + editableDateInputs: false, + endDatePlaceholder: 'Continuous', + fixedHeight: false, + focusedRange: [0, 0], + locale: defaultLocale, + maxDate: addYears(new Date(), 20), + minDate: addYears(new Date(), -100), + monthDisplayFormat: 'MMM yyyy', + months: 1, + preventSnapRefocus: false, + rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], + ranges: [], + scroll: { + enabled: false, + }, + showDateDisplay: true, + showMonthAndYearPickers: true, + showMonthArrow: true, + showPreview: true, + startDatePlaceholder: 'Early', + weekdayDisplayFormat: 'E', + } + + constructor(props: ComponentProps) { + super(props); + this.dateOptions = { locale: this.props.locale, ...( this.props.weekStartsOn !== undefined ? { weekStartsOn: this.props.weekStartsOn } : {})}; + this.styles = generateStyles([coreStyles, this.props.classNames]); this.listSizeCache = {}; this.isFirstRender = true; + this.list = null; this.state = { monthNames: this.getMonthNames(), - focusedDate: calcFocusDate(null, props), + focusedDate: calcFocusDate(null, this.props), drag: { status: false, range: { startDate: null, endDate: null }, disablePreview: false, }, - scrollArea: this.calcScrollArea(props), + scrollArea: this.calcScrollArea(this.props), }; } + getMonthNames() { - return [...Array(12).keys()].map(i => this.props.locale.localize.month(i)); + const locale = this.props.locale || enUS; + return [...Array(12).keys()].map(i => locale.localize?.month(i)); } - calcScrollArea(props) { + calcScrollArea(props: { + direction: 'vertical' | 'horizontal'; + months: number; + scroll: ScrollOptions; + }): ScrollArea { const { direction, months, scroll } = props; if (!scroll.enabled) return { enabled: false }; const longMonthHeight = scroll.longMonthHeight || scroll.monthHeight; - if (direction === 'vertical') { - return { + return (direction === 'vertical') + ? { enabled: true, monthHeight: scroll.monthHeight || 220, longMonthHeight: longMonthHeight || 260, calendarWidth: 'auto', calendarHeight: (scroll.calendarHeight || longMonthHeight || 240) * months, - }; - } - return { - enabled: true, - monthWidth: scroll.monthWidth || 332, - calendarWidth: (scroll.calendarWidth || scroll.monthWidth || 332) * months, - monthHeight: longMonthHeight || 300, - calendarHeight: longMonthHeight || 300, + } + : { + enabled: true, + monthWidth: scroll.monthWidth || 332, + calendarWidth: (scroll.calendarWidth || scroll.monthWidth || 332) * months, + monthHeight: longMonthHeight || 300, + calendarHeight: longMonthHeight || 300, }; } - focusToDate = (date, props = this.props, preventUnnecessary = true) => { - if (!props.scroll.enabled) { + + focusToDate = (date: Date, props = this.props, preventUnnecessary = true) => { + if (!props.scroll?.enabled) { if (preventUnnecessary && props.preventSnapRefocus) { const focusedDateDiff = differenceInCalendarMonths(date, this.state.focusedDate); const isAllowedForward = props.calendarFocus === 'forwards' && focusedDateDiff >= 0; @@ -89,24 +246,26 @@ class Calendar extends PureComponent { this.setState({ focusedDate: date }); return; } - const targetMonthIndex = differenceInCalendarMonths(date, props.minDate, this.dateOptions); - const visibleMonths = this.list.getVisibleRange(); - if (preventUnnecessary && visibleMonths.includes(targetMonthIndex)) return; + const targetMonthIndex = differenceInCalendarMonths(date, props.minDate || new Date()); + const visibleMonths = this.list?.getVisibleRange(); + if (preventUnnecessary && visibleMonths?.includes(targetMonthIndex)) return; this.isFirstRender = true; - this.list.scrollTo(targetMonthIndex); + this.list?.scrollTo(targetMonthIndex); this.setState({ focusedDate: date }); - }; + } + updateShownDate = (props = this.props) => { - const newProps = props.scroll.enabled + const newProps = props.scroll?.enabled ? { ...props, - months: this.list.getVisibleRange().length, + months: this.list?.getVisibleRange().length || 0, } : props; const newFocus = calcFocusDate(this.state.focusedDate, newProps); this.focusToDate(newFocus, newProps); - }; - updatePreview = val => { + } + + updatePreview = (val?: Date) => { if (!val) { this.setState({ preview: null }); return; @@ -117,7 +276,8 @@ class Calendar extends PureComponent { color: this.props.color, }; this.setState({ preview }); - }; + } + componentDidMount() { if (this.props.scroll.enabled) { // prevent react-list's initial render focus problem @@ -125,12 +285,12 @@ class Calendar extends PureComponent { } } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: ComponentProps) { const propMapper = { dateRange: 'ranges', date: 'date', }; - const targetProp = propMapper[this.props.displayMode]; + const targetProp = propMapper[this.props.displayMode] as 'ranges' | 'date'; if (this.props[targetProp] !== prevProps[targetProp]) { this.updateShownDate(this.props); } @@ -152,9 +312,13 @@ class Calendar extends PureComponent { } } - changeShownDate = (value, mode = 'set') => { + changeShownDate = (value: number | string | Date, mode: string = 'set') => { const { focusedDate } = this.state; const { onShownDateChange, minDate, maxDate } = this.props; + if (value instanceof Date || typeof value !== 'number') { + console.log(value); + throw new Error('value should always be a number, string given'); + } const modeMapper = { monthOffset: () => addMonths(focusedDate, value), setMonth: () => setMonth(focusedDate, value), @@ -162,21 +326,28 @@ class Calendar extends PureComponent { set: () => value, }; + if (!isModeMapperKey(mode, modeMapper)) { + console.log('mode: ', mode); + throw new Error('Unsupported mode'); + } + const newDate = min([max([modeMapper[mode](), minDate]), maxDate]); this.focusToDate(newDate, this.props, false); onShownDateChange && onShownDateChange(newDate); - }; - handleRangeFocusChange = (rangesIndex, rangeItemIndex) => { + } + + handleRangeFocusChange = (rangesIndex: number, rangeItemIndex: number) => { this.props.onRangeFocusChange && this.props.onRangeFocusChange([rangesIndex, rangeItemIndex]); - }; + } + handleScroll = () => { const { onShownDateChange, minDate } = this.props; const { focusedDate } = this.state; const { isFirstRender } = this; - const visibleMonths = this.list.getVisibleRange(); + const visibleMonths = this.list?.getVisibleRange(); // prevent scroll jump with wrong visible value - if (visibleMonths[0] === undefined) return; + if (visibleMonths === undefined || visibleMonths[0] === undefined) return; const visibleMonth = addMonths(minDate, visibleMonths[0] || 0); const isFocusedToDifferent = !isSameMonth(visibleMonth, focusedDate); if (isFocusedToDifferent && !isFirstRender) { @@ -185,7 +356,8 @@ class Calendar extends PureComponent { } this.isFirstRender = false; }; - renderMonthAndYear = (focusedDate, changeShownDate, props) => { + + renderMonthAndYear = (focusedDate: Date, changeShownDate: (n: number | string, method: string) => void, props: ComponentProps) => { const { showMonthArrow, minDate, maxDate, showMonthAndYearPickers, ariaLabels } = props; const upperYearLimit = (maxDate || Calendar.defaultProps.maxDate).getFullYear(); const lowerYearLimit = (minDate || Calendar.defaultProps.minDate).getFullYear(); @@ -250,7 +422,8 @@ class Calendar extends PureComponent { ) : null}
); - }; + } + renderWeekdays() { const now = new Date(); return ( @@ -266,6 +439,7 @@ class Calendar extends PureComponent { ); } + renderDateDisplay = () => { const { focusedRange, @@ -282,6 +456,7 @@ class Calendar extends PureComponent { const defaultColor = rangeColors[focusedRange[0]] || color; const styles = this.styles; + return (
{ranges.map((range, i) => { @@ -298,15 +473,11 @@ class Calendar extends PureComponent { })} readOnly={!editableDateInputs} disabled={range.disabled} - value={range.startDate} + value={range.startDate === null ? undefined : range.startDate} placeholder={startDatePlaceholder} dateOptions={this.dateOptions} dateDisplayFormat={dateDisplayFormat} - ariaLabel={ - ariaLabels.dateInput && - ariaLabels.dateInput[range.key] && - ariaLabels.dateInput[range.key].startDate - } + ariaLabel={inferAriaLabel(ariaLabels, range)} onChange={this.onDragSelectionEnd} onFocus={() => this.handleRangeFocusChange(i, 0)} /> @@ -316,17 +487,13 @@ class Calendar extends PureComponent { })} readOnly={!editableDateInputs} disabled={range.disabled} - value={range.endDate} + value={range.endDate === null ? undefined : range.endDate} placeholder={endDatePlaceholder} dateOptions={this.dateOptions} dateDisplayFormat={dateDisplayFormat} - ariaLabel={ - ariaLabels.dateInput && - ariaLabels.dateInput[range.key] && - ariaLabels.dateInput[range.key].endDate - } - onChange={this.onDragSelectionEnd} - onFocus={() => this.handleRangeFocusChange(i, 1)} + ariaLabel={inferAriaLabel(ariaLabels, range)} + onChange={_ => this.onDragSelectionEnd} + onFocus={_ => this.handleRangeFocusChange(i, 1)} />
); @@ -334,23 +501,27 @@ class Calendar extends PureComponent { ); }; - onDragSelectionStart = date => { + + onDragSelectionStart = (date?: Date) => { const { onChange, dragSelectionEnabled } = this.props; if (dragSelectionEnabled) { this.setState({ drag: { status: true, - range: { startDate: date, endDate: date }, + range: { startDate: date || null, endDate: date || null}, disablePreview: true, }, }); } else { + if (!date) { + throw new Error('Bug, expecting date to not be undefined'); + } onChange && onChange(date); } }; - onDragSelectionEnd = date => { + onDragSelectionEnd = (date: Date) => { const { updateRange, displayMode, onChange, dragSelectionEnabled } = this.props; if (!dragSelectionEnabled) return; @@ -359,19 +530,24 @@ class Calendar extends PureComponent { onChange && onChange(date); return; } - const newRange = { - startDate: this.state.drag.range.startDate, + const newRange: Range = { + startDate: this.state.drag.range.startDate || date, endDate: date, }; - if (displayMode !== 'dateRange' || isSameDay(newRange.startDate, date)) { - this.setState({ drag: { status: false, range: {} } }, () => onChange && onChange(date)); + if (displayMode !== 'dateRange' || (newRange.startDate && date && isSameDay(newRange.startDate, date))) { + this.setState({ drag: { status: false, range: {}, disablePreview: this.state.drag.disablePreview } }, () => onChange && onChange(date)); } else { - this.setState({ drag: { status: false, range: {} } }, () => { - updateRange && updateRange(newRange); + this.setState({ drag: { status: false, range: {}, disablePreview: this.state.drag.disablePreview } }, () => { + if (!isSureRange(newRange)) { + console.log(newRange); + throw new Error('Bug, expecting sure range, and found something else, likely a null range'); + } + return updateRange && updateRange(newRange); }); } - }; - onDragSelectionMove = date => { + } + + onDragSelectionMove = (date?: Date) => { const { drag } = this.state; if (!drag.status || !this.props.dragSelectionEnabled) return; this.setState({ @@ -383,18 +559,34 @@ class Calendar extends PureComponent { }); }; - estimateMonthSize = (index, cache) => { + estimateMonthSize = (index: number, cache?: { [k: number]: number; }): number => { const { direction, minDate } = this.props; const { scrollArea } = this.state; if (cache) { this.listSizeCache = cache; if (cache[index]) return cache[index]; } - if (direction === 'horizontal') return scrollArea.monthWidth; + if (direction === 'horizontal') { + if (typeof scrollArea.monthWidth !== 'number') { + console.log(scrollArea); + throw new Error('scrollArea.monthWidth should be a number'); + } + return scrollArea.monthWidth + }; const monthStep = addMonths(minDate, index); const { start, end } = getMonthDisplayRange(monthStep, this.dateOptions); - const isLongMonth = differenceInDays(end, start, this.dateOptions) + 1 > 7 * 5; - return isLongMonth ? scrollArea.longMonthHeight : scrollArea.monthHeight; + const isLongMonth = differenceInDays(end, start) + 1 > 7 * 5; + if (isLongMonth) { + if (undefined === scrollArea.longMonthHeight) { + throw new Error('scrollArea.longMonthWidth should be a number, but is undefined'); + } + return scrollArea.longMonthHeight; + } else { + if (undefined === scrollArea.monthHeight) { + throw new Error('scrollArea.monthHeight should be a number, but is undefined'); + } + return scrollArea.monthHeight; + } }; render() { const { @@ -423,15 +615,15 @@ class Calendar extends PureComponent { return (
this.setState({ drag: { status: false, range: {} } })} + onMouseUp={() => this.setState({ drag: { status: false, range: {}, disablePreview: this.state.drag.disablePreview } })} onMouseLeave={() => { - this.setState({ drag: { status: false, range: {} } }); + this.setState({ drag: { status: false, range: {}, disablePreview: this.state.drag.disablePreview } }); }}> {showDateDisplay && this.renderDateDisplay()} {monthAndYearRenderer(focusedDate, this.changeShownDate, this.props)} {scroll.enabled ? (
- {isVertical && this.renderWeekdays(this.dateOptions)} + {isVertical && this.renderWeekdays()}
onPreviewChange && onPreviewChange()} style={{ - width: scrollArea.calendarWidth + 11, - height: scrollArea.calendarHeight + 11, + width: (typeof scrollArea.calendarWidth === 'string' && scrollArea.calendarWidth) || 11 + typeof scrollArea.calendarWidth !== 'undefined' ? scrollArea.calendarWidth : 0, + height: 11 + (typeof scrollArea.calendarHeight !== 'undefined' ? scrollArea.calendarHeight : 0), }} onScroll={this.handleScroll}> (this.list = target)} itemSizeEstimator={this.estimateMonthSize} @@ -459,7 +650,6 @@ class Calendar extends PureComponent { return ( onPreviewChange && onPreviewChange()} styles={this.styles} style={ @@ -526,95 +717,4 @@ class Calendar extends PureComponent { } } -Calendar.defaultProps = { - showMonthArrow: true, - showMonthAndYearPickers: true, - disabledDates: [], - disabledDay: () => {}, - classNames: {}, - locale: defaultLocale, - ranges: [], - focusedRange: [0, 0], - dateDisplayFormat: 'MMM d, yyyy', - monthDisplayFormat: 'MMM yyyy', - weekdayDisplayFormat: 'E', - dayDisplayFormat: 'd', - showDateDisplay: true, - showPreview: true, - displayMode: 'date', - months: 1, - color: '#3d91ff', - scroll: { - enabled: false, - }, - direction: 'vertical', - maxDate: addYears(new Date(), 20), - minDate: addYears(new Date(), -100), - rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], - startDatePlaceholder: 'Early', - endDatePlaceholder: 'Continuous', - editableDateInputs: false, - dragSelectionEnabled: true, - fixedHeight: false, - calendarFocus: 'forwards', - preventSnapRefocus: false, - ariaLabels: {}, -}; - -Calendar.propTypes = { - showMonthArrow: PropTypes.bool, - showMonthAndYearPickers: PropTypes.bool, - disabledDates: PropTypes.array, - disabledDay: PropTypes.func, - minDate: PropTypes.object, - maxDate: PropTypes.object, - date: PropTypes.object, - onChange: PropTypes.func, - onPreviewChange: PropTypes.func, - onRangeFocusChange: PropTypes.func, - classNames: PropTypes.object, - locale: PropTypes.object, - shownDate: PropTypes.object, - onShownDateChange: PropTypes.func, - ranges: PropTypes.arrayOf(rangeShape), - preview: PropTypes.shape({ - startDate: PropTypes.object, - endDate: PropTypes.object, - color: PropTypes.string, - }), - dateDisplayFormat: PropTypes.string, - monthDisplayFormat: PropTypes.string, - weekdayDisplayFormat: PropTypes.string, - weekStartsOn: PropTypes.number, - dayDisplayFormat: PropTypes.string, - focusedRange: PropTypes.arrayOf(PropTypes.number), - initialFocusedRange: PropTypes.arrayOf(PropTypes.number), - months: PropTypes.number, - className: PropTypes.string, - showDateDisplay: PropTypes.bool, - showPreview: PropTypes.bool, - displayMode: PropTypes.oneOf(['dateRange', 'date']), - color: PropTypes.string, - updateRange: PropTypes.func, - scroll: PropTypes.shape({ - enabled: PropTypes.bool, - monthHeight: PropTypes.number, - longMonthHeight: PropTypes.number, - monthWidth: PropTypes.number, - calendarWidth: PropTypes.number, - calendarHeight: PropTypes.number, - }), - direction: PropTypes.oneOf(['vertical', 'horizontal']), - startDatePlaceholder: PropTypes.string, - endDatePlaceholder: PropTypes.string, - navigatorRenderer: PropTypes.func, - rangeColors: PropTypes.arrayOf(PropTypes.string), - editableDateInputs: PropTypes.bool, - dragSelectionEnabled: PropTypes.bool, - fixedHeight: PropTypes.bool, - calendarFocus: PropTypes.string, - preventSnapRefocus: PropTypes.bool, - ariaLabels: ariaLabelsShape, -}; - export default Calendar; diff --git a/src/components/DateInput/index.js b/src/components/DateInput/index.tsx similarity index 59% rename from src/components/DateInput/index.js rename to src/components/DateInput/index.tsx index d1864c541..6147bf285 100644 --- a/src/components/DateInput/index.js +++ b/src/components/DateInput/index.tsx @@ -1,35 +1,63 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEvent, KeyboardEvent, PureComponent } from 'react'; import classnames from 'classnames'; import { format, parse, isValid, isEqual } from 'date-fns'; +import { DateOptions } from '../../types'; -class DateInput extends PureComponent { - constructor(props, context) { - super(props, context); +const defaultProps = { + readOnly: true, + disabled: false, + dateDisplayFormat: 'MMM D, YYYY', +}; + +type FormatDateProps = { + value?: Date; + dateDisplayFormat: string; + dateOptions?: DateOptions; +} + +type CompontentProps = FormatDateProps & { + placeholder?: string; + disabled?: boolean; + readOnly?: boolean; + ariaLabel?: string; + className?: string; + onFocus: React.FocusEventHandler; + onChange: (d: Date) => void; +}; + +type ComponentState = { + invalid: boolean; + changed: boolean; + value: string; +} + +class DateInput extends PureComponent { + constructor(props: CompontentProps) { + super({...defaultProps, ...props}); this.state = { invalid: false, changed: false, - value: this.formatDate(props), + value: this.formatDate(this.props), }; } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: CompontentProps) { const { value } = prevProps; - if (!isEqual(value, this.props.value)) { + if (typeof value !== 'undefined' && typeof this.props.value !== 'undefined' && !isEqual(value, this.props.value)) { this.setState({ value: this.formatDate(this.props) }); } } - formatDate({ value, dateDisplayFormat, dateOptions }) { - if (value && isValid(value)) { + formatDate({ value, dateDisplayFormat, dateOptions }: FormatDateProps) { + if (value && isValid(value) && dateDisplayFormat) { return format(value, dateDisplayFormat, dateOptions); } return ''; } - update(value) { + update(value: string) { const { invalid, changed } = this.state; if (invalid || !changed || !value) { @@ -46,7 +74,7 @@ class DateInput extends PureComponent { } } - onKeyDown = e => { + onKeyDown = (e: KeyboardEvent) => { const { value } = this.state; if (e.key === 'Enter') { @@ -54,7 +82,7 @@ class DateInput extends PureComponent { } }; - onChange = e => { + onChange = (e: ChangeEvent) => { this.setState({ value: e.target.value, changed: true, invalid: false }); }; @@ -86,23 +114,4 @@ class DateInput extends PureComponent { } } -DateInput.propTypes = { - value: PropTypes.object, - placeholder: PropTypes.string, - disabled: PropTypes.bool, - readOnly: PropTypes.bool, - dateOptions: PropTypes.object, - dateDisplayFormat: PropTypes.string, - ariaLabel: PropTypes.string, - className: PropTypes.string, - onFocus: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, -}; - -DateInput.defaultProps = { - readOnly: true, - disabled: false, - dateDisplayFormat: 'MMM D, YYYY', -}; - export default DateInput; diff --git a/src/components/DateRange/index.js b/src/components/DateRange/index.js deleted file mode 100644 index 3c963ecd2..000000000 --- a/src/components/DateRange/index.js +++ /dev/null @@ -1,167 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import Calendar from '../Calendar'; -import { rangeShape } from '../DayCell'; -import { findNextRangeIndex, generateStyles } from '../../utils'; -import { isBefore, differenceInCalendarDays, addDays, min, isWithinInterval, max } from 'date-fns'; -import classnames from 'classnames'; -import coreStyles from '../../styles'; - -class DateRange extends Component { - constructor(props, context) { - super(props, context); - this.state = { - focusedRange: props.initialFocusedRange || [findNextRangeIndex(props.ranges), 0], - preview: null, - }; - this.styles = generateStyles([coreStyles, props.classNames]); - } - calcNewSelection = (value, isSingleValue = true) => { - const focusedRange = this.props.focusedRange || this.state.focusedRange; - const { - ranges, - onChange, - maxDate, - moveRangeOnFirstSelection, - retainEndDateOnFirstSelection, - disabledDates, - } = this.props; - const focusedRangeIndex = focusedRange[0]; - const selectedRange = ranges[focusedRangeIndex]; - if (!selectedRange || !onChange) return {}; - let { startDate, endDate } = selectedRange; - const now = new Date(); - let nextFocusRange; - if (!isSingleValue) { - startDate = value.startDate; - endDate = value.endDate; - } else if (focusedRange[1] === 0) { - // startDate selection - const dayOffset = differenceInCalendarDays(endDate || now, startDate); - const calculateEndDate = () => { - if (moveRangeOnFirstSelection) { - return addDays(value, dayOffset); - } - if (retainEndDateOnFirstSelection) { - if (!endDate || isBefore(value, endDate)) { - return endDate; - } - return value; - } - return value || now; - }; - startDate = value; - endDate = calculateEndDate(); - if (maxDate) endDate = min([endDate, maxDate]); - nextFocusRange = [focusedRange[0], 1]; - } else { - endDate = value; - } - - // reverse dates if startDate before endDate - let isStartDateSelected = focusedRange[1] === 0; - if (isBefore(endDate, startDate)) { - isStartDateSelected = !isStartDateSelected; - [startDate, endDate] = [endDate, startDate]; - } - - const inValidDatesWithinRange = disabledDates.filter(disabledDate => - isWithinInterval(disabledDate, { - start: startDate, - end: endDate, - }) - ); - - if (inValidDatesWithinRange.length > 0) { - if (isStartDateSelected) { - startDate = addDays(max(inValidDatesWithinRange), 1); - } else { - endDate = addDays(min(inValidDatesWithinRange), -1); - } - } - - if (!nextFocusRange) { - const nextFocusRangeIndex = findNextRangeIndex(this.props.ranges, focusedRange[0]); - nextFocusRange = [nextFocusRangeIndex, 0]; - } - return { - wasValid: !(inValidDatesWithinRange.length > 0), - range: { startDate, endDate }, - nextFocusRange: nextFocusRange, - }; - }; - setSelection = (value, isSingleValue) => { - const { onChange, ranges, onRangeFocusChange } = this.props; - const focusedRange = this.props.focusedRange || this.state.focusedRange; - const focusedRangeIndex = focusedRange[0]; - const selectedRange = ranges[focusedRangeIndex]; - if (!selectedRange) return; - const newSelection = this.calcNewSelection(value, isSingleValue); - onChange({ - [selectedRange.key || `range${focusedRangeIndex + 1}`]: { - ...selectedRange, - ...newSelection.range, - }, - }); - this.setState({ - focusedRange: newSelection.nextFocusRange, - preview: null, - }); - onRangeFocusChange && onRangeFocusChange(newSelection.nextFocusRange); - }; - handleRangeFocusChange = focusedRange => { - this.setState({ focusedRange }); - this.props.onRangeFocusChange && this.props.onRangeFocusChange(focusedRange); - }; - updatePreview = val => { - if (!val) { - this.setState({ preview: null }); - return; - } - const { rangeColors, ranges } = this.props; - const focusedRange = this.props.focusedRange || this.state.focusedRange; - const color = ranges[focusedRange[0]]?.color || rangeColors[focusedRange[0]] || color; - this.setState({ preview: { ...val.range, color } }); - }; - render() { - return ( - { - this.updatePreview(value ? this.calcNewSelection(value) : null); - }} - {...this.props} - displayMode="dateRange" - className={classnames(this.styles.dateRangeWrapper, this.props.className)} - onChange={this.setSelection} - updateRange={val => this.setSelection(val, false)} - ref={target => { - this.calendar = target; - }} - /> - ); - } -} - -DateRange.defaultProps = { - classNames: {}, - ranges: [], - moveRangeOnFirstSelection: false, - retainEndDateOnFirstSelection: false, - rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], - disabledDates: [], -}; - -DateRange.propTypes = { - ...Calendar.propTypes, - onChange: PropTypes.func, - onRangeFocusChange: PropTypes.func, - className: PropTypes.string, - ranges: PropTypes.arrayOf(rangeShape), - moveRangeOnFirstSelection: PropTypes.bool, - retainEndDateOnFirstSelection: PropTypes.bool, -}; - -export default DateRange; diff --git a/src/components/DateRange/index.tsx b/src/components/DateRange/index.tsx new file mode 100644 index 000000000..108ab85ef --- /dev/null +++ b/src/components/DateRange/index.tsx @@ -0,0 +1,258 @@ +import React, { Component } from 'react'; +import Calendar from '../Calendar'; +import { findNextRangeIndex, generateStyles } from '../../utils'; +import { isBefore, differenceInCalendarDays, addDays, min, isWithinInterval, max } from 'date-fns'; +import coreStyles, { ClassNames } from '../../styles'; +import { DateRangeProps, isRangeValue, isSureRange, Preview, Range, RangeFocus, SureRange, SureStartEndDate } from '../../types'; +import classnames from 'classnames'; +import { compose } from 'ramda'; + +type DefaultComponentProps = { + classNames: Partial; + ranges: Range[]; + moveRangeOnFirstSelection: boolean; + retainEndDateOnFirstSelection: boolean; + rangeColors: string[]; + disabledDates: Date[], +} + +type ComponentState = { + focusedRange: RangeFocus; + preview: null | Preview; +} + +const defaultProps: DefaultComponentProps = { + classNames: {}, + ranges: [], + moveRangeOnFirstSelection: false, + retainEndDateOnFirstSelection: false, + rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], + disabledDates: [], +}; + +const calculateEndDate = (value: Date, now: Date, endDate: Date | null, dayOffset: number, moveRangeOnFirstSelection: boolean, retainEndDateOnFirstSelection: boolean, maxDate?: Date) => { + const userEndDate = (() => { + if (moveRangeOnFirstSelection) { + return addDays(value, dayOffset); + } + if (retainEndDateOnFirstSelection) { + if (!endDate || isBefore(value, endDate)) { + return endDate; + } + return value; + } + return value || now; + })(); + const nextEndDate = maxDate ? (userEndDate ? min([userEndDate, maxDate]) : maxDate) : userEndDate ; + if (nextEndDate === null) { + throw new Error('Bug! endDate is null, and it should not. RetainEndDateOnFirstSelection tiggers buggy behavior, what should the endDate be now that retainEDFS is true?'); + } + return nextEndDate; +}; + +type ShapeChangingParams = { value: Date | Range; }; +type BaseParams = { focusedRange: RangeFocus; disabledDates: Date[]; ranges: Range[]; } +type ComputeProps = BaseParams & ShapeChangingParams & { selectedRange: SureStartEndDate; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; maxDate?: Date; }; +function computeStartDateEndDate({ value, selectedRange, focusedRange, disabledDates, moveRangeOnFirstSelection, retainEndDateOnFirstSelection, ranges, maxDate }: ComputeProps) { + const base = { + isStartDateSelected: focusedRange[1] === 0, + focusedRange, + disabledDates, + ranges, + } + if (isRangeValue(value)) { + if (!isSureRange(value)) { + console.log(value); + throw new Error('Bug, expecting value to be a SureRange (aka startDate and endDate instanceof Date), but not the case!'); + } + return { + ...base, + startDate: value.startDate, + endDate: value.endDate, + }; + } + if (focusedRange[1] === 0) { + // startDate selection + const now = new Date(); + const dayOffset = differenceInCalendarDays(selectedRange.endDate || now, selectedRange.startDate); + return { + ...base, + startDate: value, + endDate: calculateEndDate(value, now, selectedRange.endDate, dayOffset, moveRangeOnFirstSelection, retainEndDateOnFirstSelection, maxDate), + nextFocusRange: [focusedRange[0], 1] as RangeFocus, + }; + } else { + return { + ...base, + startDate: selectedRange.startDate, + endDate: value, + } + } +} + +type FlipProps = BaseParams & SureStartEndDate & { nextFocusRange?: RangeFocus; isStartDateSelected: boolean; }; +const flipIfReversed = (params: FlipProps) => { + const { startDate, endDate, isStartDateSelected } = params; + return isBefore(endDate, startDate) + ? { + ...params, + isStartDateSelected: !isStartDateSelected, + startDate: endDate, + endDate: startDate, + } + : params; +} + +const getNextFocusRange = (ranges: Range[], focusedRange: RangeFocus, nextFocusRange?: RangeFocus): RangeFocus => { + if (nextFocusRange) return nextFocusRange; + const nextFocusRangeIndex = findNextRangeIndex(ranges, focusedRange[0]); + return [nextFocusRangeIndex, 0]; +} + +const computeRange = ({ disabledDates, startDate, endDate, isStartDateSelected, focusedRange, nextFocusRange, ranges }: FlipProps) => { + const inValidDatesWithinRange = disabledDates.filter(disabledDate => + isWithinInterval(disabledDate, { + start: startDate, + end: endDate, + }) + ); + + const wasValid = !(inValidDatesWithinRange.length > 0); + + const range = { + startDate: !wasValid && isStartDateSelected + ? addDays(max(inValidDatesWithinRange), 1) + : startDate, + endDate: !wasValid && !isStartDateSelected + ? addDays(min(inValidDatesWithinRange), -1) + : endDate, + }; + + return { + wasValid, + range, + nextFocusRange: getNextFocusRange(ranges, focusedRange, nextFocusRange), + }; +} + +type CalcNewSelectionRet = { + wasValid: boolean; + range: { + startDate: Date; + endDate: Date; + }; + nextFocusRange: RangeFocus; +} | undefined; + +const getRes = compose(computeRange, flipIfReversed, computeStartDateEndDate); + +type ComponentProps = DateRangeProps & DefaultComponentProps; + +class DateRange extends Component { + styles: Partial; + calendar: Calendar | null; + + constructor(props: ComponentProps) { + super({ ...defaultProps, ...props }); + this.state = { + focusedRange: props.initialFocusedRange || [findNextRangeIndex(props.ranges), 0], + preview: null, + }; + this.styles = generateStyles([coreStyles, props.classNames]); + this.calendar = null; + } + + calcNewSelection = (value: Date | Range): CalcNewSelectionRet => { + const focusedRange = this.props.focusedRange || this.state.focusedRange; + const { + ranges, + onChange, + maxDate, + moveRangeOnFirstSelection, + retainEndDateOnFirstSelection, + disabledDates, + } = this.props; + const focusedRangeIndex = focusedRange[0]; + const selectedRange = ranges[focusedRangeIndex]; + + if (!selectedRange || !onChange) return; + + if (!isSureRange(selectedRange)) { + throw new Error('Bug, expecting selected range to be a sure range, but it is not'); + } + + return getRes({ + value, + selectedRange, + focusedRange, + disabledDates, + moveRangeOnFirstSelection, + ranges, + retainEndDateOnFirstSelection, + maxDate, + }); + + }; + + setSelection = (value: Date | SureStartEndDate) => { + const { onChange, ranges, onRangeFocusChange } = this.props; + const focusedRange = this.props.focusedRange || this.state.focusedRange; + const focusedRangeIndex = focusedRange[0]; + const selectedRange = ranges[focusedRangeIndex]; + if (!selectedRange) return; + const newSelection = this.calcNewSelection(value); + if (!newSelection) { + throw new Error('Bug, expecting new selection to not be undefined'); + } + onChange && onChange({ + [selectedRange.key || `range${focusedRangeIndex + 1}`]: { + ...selectedRange, + ...newSelection.range, + }, + }); + this.setState({ + focusedRange: newSelection.nextFocusRange, + preview: null, + }); + onRangeFocusChange && onRangeFocusChange(newSelection.nextFocusRange); + }; + handleRangeFocusChange = (focusedRange: RangeFocus) => { + this.setState({ focusedRange }); + this.props.onRangeFocusChange && this.props.onRangeFocusChange(focusedRange); + }; + + updatePreview = (val?: { range: SureRange; }) => { + if (!val) { + this.setState({ preview: null }); + return; + } + const { rangeColors, ranges } = this.props; + const focusedRange = this.props.focusedRange || this.state.focusedRange; + const color = ranges[focusedRange[0]]?.color || rangeColors[focusedRange[0]]; + this.setState({ preview: { ...val.range, color } }); + }; + render() { + return ( + { + const newSelection = date ? this.calcNewSelection(date) : undefined; + this.updatePreview(newSelection); + } } + {...this.props} + displayMode="dateRange" + className={classnames(this.styles.dateRangeWrapper, this.props.className)} + onChange={this.setSelection} + updateRange={this.setSelection} + ref={target => { + this.calendar = target; + } } + /> + ); + } +} + +export default DateRange; diff --git a/src/components/DayCell/index.js b/src/components/DayCell/index.tsx similarity index 65% rename from src/components/DayCell/index.js rename to src/components/DayCell/index.tsx index 26f12539e..737d20c46 100644 --- a/src/components/DayCell/index.js +++ b/src/components/DayCell/index.tsx @@ -1,12 +1,54 @@ /* eslint-disable no-fallthrough */ -import React, { Component } from 'react'; +import React, { Component, FocusEvent, KeyboardEvent, MouseEvent } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { startOfDay, format, isSameDay, isAfter, isBefore, endOfDay } from 'date-fns'; +import { Preview, Range } from '../../types'; +import { CoreStyles } from '../../styles'; -class DayCell extends Component { - constructor(props, context) { - super(props, context); +export type InRange = ({ + isStartEdge: boolean; + isEndEdge: boolean; + isInRange: boolean; +} & Range); + +export type DateReceivingFunc = (date: Date) => void; +export type OptionalDateReceivingFunc = (date?: Date) => void; + +type ComponentProps = { + color?: string; + date?: Date; + day: Date; + dayContentRenderer?: DateReceivingFunc; + dayDisplayFormat: string; + disabled: boolean; + displayMode: 'dateRange' | 'date'; + isEndOfMonth: boolean; + isEndOfWeek: boolean; + isPassive: boolean; + isStartOfMonth: boolean; + isStartOfWeek: boolean; + isToday: boolean; + isWeekend: boolean; + onMouseDown: DateReceivingFunc; + onMouseEnter: DateReceivingFunc; + onMouseUp: DateReceivingFunc; + onPreviewChange?: OptionalDateReceivingFunc; + preview?: Preview | null; + previewColor?: string; + ranges: Range[], + styles: CoreStyles; +}; + +type ComponentState = { + hover: boolean; + active: boolean; +} + + +class DayCell extends Component { + constructor(props: ComponentProps) { + super(props); this.state = { hover: false, @@ -14,48 +56,46 @@ class DayCell extends Component { }; } - handleKeyEvent = event => { + handleKeyEvent = (event: KeyboardEvent) => { const { day, onMouseDown, onMouseUp } = this.props; if ([13 /* space */, 32 /* enter */].includes(event.keyCode)) { if (event.type === 'keydown') onMouseDown(day); else onMouseUp(day); } - }; - handleMouseEvent = event => { + } + + handleMouseEvent = (event: MouseEvent | FocusEvent) => { const { day, disabled, onPreviewChange, onMouseEnter, onMouseDown, onMouseUp } = this.props; - const stateChanges = {}; if (disabled) { - onPreviewChange(); + onPreviewChange && onPreviewChange(); return; } switch (event.type) { case 'mouseenter': onMouseEnter(day); - onPreviewChange(day); - stateChanges.hover = true; + onPreviewChange && onPreviewChange(day); + this.setState({ hover: true }); break; case 'blur': case 'mouseleave': - stateChanges.hover = false; + this.setState({ hover: false }); break; case 'mousedown': - stateChanges.active = true; + this.setState({ active: true }); onMouseDown(day); break; case 'mouseup': event.stopPropagation(); - stateChanges.active = false; + this.setState({ active: false }); onMouseUp(day); break; case 'focus': - onPreviewChange(day); + onPreviewChange && onPreviewChange(day); break; } - if (Object.keys(stateChanges).length) { - this.setState(stateChanges); - } }; + getClassNames = () => { const { isPassive, @@ -89,8 +129,8 @@ class DayCell extends Component { const endDate = preview.endDate ? startOfDay(preview.endDate) : null; const isInRange = (!startDate || isAfter(day, startDate)) && (!endDate || isBefore(day, endDate)); - const isStartEdge = !isInRange && isSameDay(day, startDate); - const isEndEdge = !isInRange && isSameDay(day, endDate); + const isStartEdge = !isInRange && !!startDate && isSameDay(day, startDate); + const isEndEdge = !isInRange && !!endDate && isSameDay(day, endDate); return ( { const { styles, ranges, day } = this.props; if (this.props.displayMode === 'date') { - let isSelected = isSameDay(this.props.day, this.props.date); + const isSelected = this.props.date && isSameDay(this.props.day, this.props.date); return isSelected ? ( ) : null; } - const inRanges = ranges.reduce((result, range) => { - let startDate = range.startDate; - let endDate = range.endDate; - if (startDate && endDate && isBefore(endDate, startDate)) { - [startDate, endDate] = [endDate, startDate]; - } - startDate = startDate ? endOfDay(startDate) : null; - endDate = endDate ? startOfDay(endDate) : null; + const inRanges = ranges.reduce((result: InRange[], range: Range) => { + const st = range.startDate; + const en = range.endDate; + const [start, end] = (st && en && isBefore(en, st)) ? [en, st] : [st, en]; + const startDate = start ? endOfDay(start) : null; + const endDate = end ? startOfDay(end) : null; const isInRange = (!startDate || isAfter(day, startDate)) && (!endDate || isBefore(day, endDate)); - const isStartEdge = !isInRange && isSameDay(day, startDate); - const isEndEdge = !isInRange && isSameDay(day, endDate); + const isStartEdge = !isInRange && !!startDate && isSameDay(day, startDate); + const isEndEdge = !isInRange && !!endDate && isSameDay(day, endDate); if (isInRange || isStartEdge || isEndEdge) { return [ ...result, @@ -164,7 +202,7 @@ class DayCell extends Component { onPauseCapture={this.handleMouseEvent} onKeyDown={this.handleKeyEvent} onKeyUp={this.handleKeyEvent} - className={this.getClassNames(this.props.styles)} + className={this.getClassNames()} {...(this.props.disabled || this.props.isPassive ? { tabIndex: -1 } : {})} style={{ color: this.props.color }}> {this.renderSelectionPlaceholders()} @@ -180,8 +218,6 @@ class DayCell extends Component { } } -DayCell.defaultProps = {}; - export const rangeShape = PropTypes.shape({ startDate: PropTypes.object, endDate: PropTypes.object, @@ -192,33 +228,5 @@ export const rangeShape = PropTypes.shape({ showDateDisplay: PropTypes.bool, }); -DayCell.propTypes = { - day: PropTypes.object.isRequired, - dayDisplayFormat: PropTypes.string, - date: PropTypes.object, - ranges: PropTypes.arrayOf(rangeShape), - preview: PropTypes.shape({ - startDate: PropTypes.object, - endDate: PropTypes.object, - color: PropTypes.string, - }), - onPreviewChange: PropTypes.func, - previewColor: PropTypes.string, - disabled: PropTypes.bool, - isPassive: PropTypes.bool, - isToday: PropTypes.bool, - isWeekend: PropTypes.bool, - isStartOfWeek: PropTypes.bool, - isEndOfWeek: PropTypes.bool, - isStartOfMonth: PropTypes.bool, - isEndOfMonth: PropTypes.bool, - color: PropTypes.string, - displayMode: PropTypes.oneOf(['dateRange', 'date']), - styles: PropTypes.object, - onMouseDown: PropTypes.func, - onMouseUp: PropTypes.func, - onMouseEnter: PropTypes.func, - dayContentRenderer: PropTypes.func, -}; export default DayCell; diff --git a/src/components/InputRangeField/index.js b/src/components/InputRangeField/index.js deleted file mode 100644 index 71b1280b4..000000000 --- a/src/components/InputRangeField/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -const MIN = 0; -const MAX = 99999; - -class InputRangeField extends Component { - constructor(props, context) { - super(props, context); - } - - shouldComponentUpdate(nextProps) { - const { value, label, placeholder } = this.props; - - return ( - value !== nextProps.value || - label !== nextProps.label || - placeholder !== nextProps.placeholder - ); - } - - onChange = e => { - const { onChange } = this.props; - - let value = parseInt(e.target.value, 10); - value = isNaN(value) ? 0 : Math.max(Math.min(MAX, value), MIN); - - onChange(value); - }; - - render() { - const { label, placeholder, value, styles, onBlur, onFocus } = this.props; - - return ( -
- - {label} -
- ); - } -} - -InputRangeField.propTypes = { - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - label: PropTypes.oneOfType([PropTypes.element, PropTypes.node]).isRequired, - placeholder: PropTypes.string, - styles: PropTypes.shape({ - inputRange: PropTypes.string, - inputRangeInput: PropTypes.string, - inputRangeLabel: PropTypes.string, - }).isRequired, - onBlur: PropTypes.func.isRequired, - onFocus: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, -}; - -InputRangeField.defaultProps = { - value: '', - placeholder: '-', -}; - -export default InputRangeField; diff --git a/src/components/InputRangeField/index.tsx b/src/components/InputRangeField/index.tsx new file mode 100644 index 000000000..cf5f71a10 --- /dev/null +++ b/src/components/InputRangeField/index.tsx @@ -0,0 +1,71 @@ +import React, { ChangeEvent, ChangeEventHandler, Component, FocusEvent, FocusEventHandler } from 'react'; +import PropTypes from 'prop-types'; + +const MIN = 0; +const MAX = 99999; + +type Styles = { + inputRange: string; + inputRangeInput: string; + inputRangeLabel: string; +} + +type ComponentProps = { + value: string | number; + label: string | JSX.Element, + placeholder: string, + styles: Styles; + onBlur: FocusEventHandler; + onFocus: FocusEventHandler; + onChange: (n: number) => void; +}; + +const defaultProps = { + value: '', + placeholder: '-', +}; + +class InputRangeField extends Component { + constructor(props: ComponentProps) { + super({...defaultProps, ...props }); + } + + shouldComponentUpdate(nextProps: ComponentProps) { + const { value, label, placeholder } = this.props; + + return ( + value !== nextProps.value || + label !== nextProps.label || + placeholder !== nextProps.placeholder + ); + } + + onChange = (e: ChangeEvent) => { + const value = parseInt(e.target.value, 10); + const changeArg = isNaN(value) ? 0 : Math.max(Math.min(MAX, value), MIN); + this.props.onChange(changeArg); + }; + + render() { + const { label, placeholder, value, styles, onBlur, onFocus } = this.props; + + return ( +
+ + {label} +
+ ); + } +} + + +export default InputRangeField; diff --git a/src/components/Month/index.js b/src/components/Month/index.tsx similarity index 71% rename from src/components/Month/index.js rename to src/components/Month/index.tsx index 6f7802636..4c4c3bfb5 100644 --- a/src/components/Month/index.js +++ b/src/components/Month/index.tsx @@ -1,7 +1,7 @@ -/* eslint-disable no-fallthrough */ -import React, { PureComponent } from 'react'; +// /* -eslint-disable no-fallthrough */ +import React, { MouseEvent, PureComponent } from 'react'; import PropTypes from 'prop-types'; -import DayCell, { rangeShape } from '../DayCell'; +import DayCell, { DateReceivingFunc, OptionalDateReceivingFunc, rangeShape } from '../DayCell'; import { format, startOfDay, @@ -16,8 +16,10 @@ import { eachDayOfInterval, } from 'date-fns'; import { getMonthDisplayRange } from '../../utils'; +import { CoreStyles } from '../../styles'; +import { DateOptions, Preview, Range } from '../../types'; -function renderWeekdays(styles, dateOptions, weekdayDisplayFormat) { +function renderWeekdays(styles: CoreStyles, dateOptions: DateOptions, weekdayDisplayFormat: string) { const now = new Date(); return (
@@ -33,7 +35,41 @@ function renderWeekdays(styles, dateOptions, weekdayDisplayFormat) { ); } -class Month extends PureComponent { +export type Drag = { + range: Range; + status: boolean; + disablePreview: boolean; +} + +type ComponentProps = { + style?: React.CSSProperties; + styles: CoreStyles; + month: Date; + drag: Drag, + dateOptions: DateOptions, + disabledDates: Date[]; + disabledDay: (date: Date) => boolean; + preview?: Preview | null; + showPreview: boolean; + displayMode: 'dateRange' | 'date'; + minDate: Date; + maxDate: Date; + ranges: Range[]; + focusedRange: number[]; + onDragSelectionStart: DateReceivingFunc; + onDragSelectionEnd: DateReceivingFunc; + onDragSelectionMove: DateReceivingFunc; + onPreviewChange?: OptionalDateReceivingFunc; + onMouseLeave: (e: MouseEvent) => void; + monthDisplayFormat: string; + weekdayDisplayFormat: string; + dayDisplayFormat: string; + showWeekDays: boolean, + showMonthName: boolean; + fixedHeight: boolean; +}; + +class Month extends PureComponent { render() { const now = new Date(); const { displayMode, focusedRange, drag, styles, disabledDates, disabledDay } = this.props; @@ -46,7 +82,7 @@ class Month extends PureComponent { ); let ranges = this.props.ranges; if (displayMode === 'dateRange' && drag.status) { - let { startDate, endDate } = drag.range; + const { startDate, endDate } = drag.range; ranges = ranges.map((range, i) => { if (i !== focusedRange[0]) return range; return { @@ -82,8 +118,8 @@ class Month extends PureComponent { {...this.props} ranges={ranges} day={day} - preview={showPreview ? this.props.preview : null} - isWeekend={isWeekend(day, this.props.dateOptions)} + preview={this.props.preview && showPreview ? this.props.preview : null} + isWeekend={isWeekend(day)} isToday={isSameDay(day, now)} isStartOfWeek={isSameDay(day, startOfWeek(day, this.props.dateOptions))} isEndOfWeek={isSameDay(day, endOfWeek(day, this.props.dateOptions))} @@ -101,8 +137,6 @@ class Month extends PureComponent { onMouseDown={this.props.onDragSelectionStart} onMouseUp={this.props.onDragSelectionEnd} onMouseEnter={this.props.onDragSelectionMove} - dragRange={drag.range} - drag={drag.status} /> ); } @@ -113,36 +147,4 @@ class Month extends PureComponent { } } -Month.defaultProps = {}; - -Month.propTypes = { - style: PropTypes.object, - styles: PropTypes.object, - month: PropTypes.object, - drag: PropTypes.object, - dateOptions: PropTypes.object, - disabledDates: PropTypes.array, - disabledDay: PropTypes.func, - preview: PropTypes.shape({ - startDate: PropTypes.object, - endDate: PropTypes.object, - }), - showPreview: PropTypes.bool, - displayMode: PropTypes.oneOf(['dateRange', 'date']), - minDate: PropTypes.object, - maxDate: PropTypes.object, - ranges: PropTypes.arrayOf(rangeShape), - focusedRange: PropTypes.arrayOf(PropTypes.number), - onDragSelectionStart: PropTypes.func, - onDragSelectionEnd: PropTypes.func, - onDragSelectionMove: PropTypes.func, - onMouseLeave: PropTypes.func, - monthDisplayFormat: PropTypes.string, - weekdayDisplayFormat: PropTypes.string, - dayDisplayFormat: PropTypes.string, - showWeekDays: PropTypes.bool, - showMonthName: PropTypes.bool, - fixedHeight: PropTypes.bool, -}; - export default Month; diff --git a/src/defaultRanges.js b/src/defaultRanges.ts similarity index 72% rename from src/defaultRanges.js rename to src/defaultRanges.ts index 437da4149..961fab3bb 100644 --- a/src/defaultRanges.js +++ b/src/defaultRanges.ts @@ -10,8 +10,10 @@ import { isSameDay, differenceInCalendarDays, } from 'date-fns'; +import { LabeledStartEndDateGen, StartEndDateGen, SureStartEndDate, WeekStartsOn } from './types'; -const definedsGen = ({ weekStartsOn }) => ({ +type GenProps = { weekStartsOn: WeekStartsOn; } +const definedsGen = ({ weekStartsOn }: GenProps): DefinedDates => ({ startOfWeek: startOfWeek(new Date(), { weekStartsOn }), endOfWeek: endOfWeek(new Date(), { weekStartsOn }), startOfLastWeek: startOfWeek(addDays(new Date(), -7), { weekStartsOn }), @@ -26,24 +28,39 @@ const definedsGen = ({ weekStartsOn }) => ({ endOfLastMonth: endOfMonth(addMonths(new Date(), -1)), }); -const defineds = definedsGen({ weekStartsOn: 0 }); +type DefinedDates = { + startOfWeek: Date; + endOfWeek: Date; + startOfLastWeek: Date; + endOfLastWeek: Date; + startOfToday: Date; + endOfToday: Date; + startOfYesterday: Date; + endOfYesterday: Date; + startOfMonth: Date; + endOfMonth: Date; + startOfLastMonth: Date; + endOfLastMonth: Date; +}; + +const defineds: DefinedDates = definedsGen({ weekStartsOn: 0 }); -const staticRangeHandler = { - range: {}, - isSelected(range) { +const staticRangeHandler = (withRangeGen: { range: StartEndDateGen; }) => ({ + ...withRangeGen, + isSelected(range: SureStartEndDate) { const definedRange = this.range(); return ( isSameDay(range.startDate, definedRange.startDate) && isSameDay(range.endDate, definedRange.endDate) ); }, -}; +}); -export function createStaticRanges(ranges) { - return ranges.map(range => ({ ...staticRangeHandler, ...range })); +export function createStaticRanges(ranges: LabeledStartEndDateGen[]) { + return ranges.map(staticRangeHandler); } -export const defaultStaticRangesGen = defineds => +export const defaultStaticRangesGen = (defineds: DefinedDates) => createStaticRanges([ { label: 'Last Month', @@ -91,16 +108,16 @@ export const defaultStaticRangesGen = defineds => export const defaultStaticRanges = defaultStaticRangesGen(defineds); -export const defaultInputRangesGen = defineds => [ +export const defaultInputRangesGen = (defineds: DefinedDates) => [ { label: 'days up to today', - range(value) { + range(value: any) { return { startDate: addDays(defineds.startOfToday, (Math.max(Number(value), 1) - 1) * -1), endDate: defineds.endOfToday, }; }, - getCurrentValue(range) { + getCurrentValue(range: SureStartEndDate) { if (!isSameDay(range.endDate, defineds.endOfToday)) return '-'; if (!range.startDate) return '∞'; return differenceInCalendarDays(defineds.endOfToday, range.startDate) + 1; @@ -108,14 +125,14 @@ export const defaultInputRangesGen = defineds => [ }, { label: 'days starting today', - range(value) { + range(value: any) { const today = new Date(); return { startDate: today, endDate: addDays(today, Math.max(Number(value), 1) - 1), }; }, - getCurrentValue(range) { + getCurrentValue(range: SureStartEndDate) { if (!isSameDay(range.startDate, defineds.startOfToday)) return '-'; if (!range.endDate) return '∞'; return differenceInCalendarDays(range.endDate, defineds.startOfToday) + 1; diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/locale/index.js b/src/locale/index.ts similarity index 100% rename from src/locale/index.js rename to src/locale/index.ts diff --git a/src/styles.js b/src/styles.ts similarity index 89% rename from src/styles.js rename to src/styles.ts index cf7a5eb2a..1a9e85242 100644 --- a/src/styles.js +++ b/src/styles.ts @@ -1,53 +1,60 @@ -export default { - dateRangeWrapper: 'rdrDateRangeWrapper', +const coreStyles = { calendarWrapper: 'rdrCalendarWrapper', dateDisplay: 'rdrDateDisplay', dateDisplayItem: 'rdrDateDisplayItem', dateDisplayItemActive: 'rdrDateDisplayItemActive', - monthAndYearWrapper: 'rdrMonthAndYearWrapper', - monthAndYearPickers: 'rdrMonthAndYearPickers', - nextPrevButton: 'rdrNextPrevButton', - month: 'rdrMonth', - weekDays: 'rdrWeekDays', - weekDay: 'rdrWeekDay', - days: 'rdrDays', + dateDisplayWrapper: 'rdrDateDisplayWrapper', + dateRangePickerWrapper: 'rdrDateRangePickerWrapper', + dateRangeWrapper: 'rdrDateRangeWrapper', day: 'rdrDay', + dayActive: 'rdrDayActive', + dayDisabled: 'rdrDayDisabled', + dayEndOfMonth: 'rdrDayEndOfMonth', + dayEndOfWeek: 'rdrDayEndOfWeek', + dayEndPreview: 'rdrDayEndPreview', + dayHovered: 'rdrDayHovered', + dayInPreview: 'rdrDayInPreview', dayNumber: 'rdrDayNumber', dayPassive: 'rdrDayPassive', - dayToday: 'rdrDayToday', - dayStartOfWeek: 'rdrDayStartOfWeek', - dayEndOfWeek: 'rdrDayEndOfWeek', daySelected: 'rdrDaySelected', - dayDisabled: 'rdrDayDisabled', dayStartOfMonth: 'rdrDayStartOfMonth', - dayEndOfMonth: 'rdrDayEndOfMonth', - dayWeekend: 'rdrDayWeekend', + dayStartOfWeek: 'rdrDayStartOfWeek', dayStartPreview: 'rdrDayStartPreview', - dayInPreview: 'rdrDayInPreview', - dayEndPreview: 'rdrDayEndPreview', - dayHovered: 'rdrDayHovered', - dayActive: 'rdrDayActive', - inRange: 'rdrInRange', + dayToday: 'rdrDayToday', + dayWeekend: 'rdrDayWeekend', + days: 'rdrDays', + definedRangesWrapper: 'rdrDefinedRangesWrapper', endEdge: 'rdrEndEdge', - startEdge: 'rdrStartEdge', - prevButton: 'rdrPprevButton', + inRange: 'rdrInRange', + infiniteMonths: 'rdrInfiniteMonths', + inputRange: 'rdrInputRange', + inputRangeInput: 'rdrInputRangeInput', + inputRanges: 'rdrInputRanges', + month: 'rdrMonth', + monthAndYearDivider: 'rdrMonthAndYearDivider', + monthAndYearPickers: 'rdrMonthAndYearPickers', + monthAndYearWrapper: 'rdrMonthAndYearWrapper', + monthName: 'rdrMonthName', + monthPicker: 'rdrMonthPicker', + months: 'rdrMonths', + monthsHorizontal: 'rdrMonthsHorizontal', + monthsVertical: 'rdrMonthsVertical', nextButton: 'rdrNextButton', + nextPrevButton: 'rdrNextPrevButton', + prevButton: 'rdrPprevButton', selected: 'rdrSelected', - months: 'rdrMonths', - monthPicker: 'rdrMonthPicker', - yearPicker: 'rdrYearPicker', - dateDisplayWrapper: 'rdrDateDisplayWrapper', - definedRangesWrapper: 'rdrDefinedRangesWrapper', - staticRanges: 'rdrStaticRanges', + startEdge: 'rdrStartEdge', staticRange: 'rdrStaticRange', - inputRanges: 'rdrInputRanges', - inputRange: 'rdrInputRange', - inputRangeInput: 'rdrInputRangeInput', - dateRangePickerWrapper: 'rdrDateRangePickerWrapper', staticRangeLabel: 'rdrStaticRangeLabel', staticRangeSelected: 'rdrStaticRangeSelected', - monthName: 'rdrMonthName', - infiniteMonths: 'rdrInfiniteMonths', - monthsVertical: 'rdrMonthsVertical', - monthsHorizontal: 'rdrMonthsHorizontal', + staticRanges: 'rdrStaticRanges', + weekDay: 'rdrWeekDay', + weekDays: 'rdrWeekDays', + yearPicker: 'rdrYearPicker', }; + +export type CoreStyles = typeof coreStyles; +export type ClassNames = { + [k in keyof CoreStyles]: string; +} +export default coreStyles; \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 000000000..55bcb36ee --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,3 @@ +declare module "shallow-equal" { + export function shallowEqualObjects(objA: any, objB: any): boolean; +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..f2eab2705 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,263 @@ +// Type definitions for react-date-range-won 1.5 +// Project: https://github.com/gbili/react-date-range-won (Does not have to be to GitHub, but prefer linking to a source code repository rather than to a project website.) +// Definitions by: Guillermo Pages +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +import React from 'react'; +import { Locale } from 'date-fns'; +import { ClassNames } from './styles'; + +export type AnyDate = string | Date; +export type DateOrTimestamp = Date | number; +export type DateFunc = (now: Date) => AnyDate; +export type DateGen = (now: Date) => Date; +export type DateInputType = AnyDate | DateGen; +export type LanguageType = 'cn' | 'jp' | 'fr' | 'it' | 'de' | 'ko' | 'es' | 'ru' | 'tr'; +export type SizeType = number; + +export interface DateContainerType { + date: Date; +} + +export interface CalendarTheme { + DateRange?: React.CSSProperties; + Calendar?: React.CSSProperties; + Day?: React.CSSProperties; + DayPassive?: React.CSSProperties; + DayHover?: React.CSSProperties; + DayToday?: React.CSSProperties; + DaySunday?: React.CSSProperties; + DaySpecialDay?: React.CSSProperties; + DayActive?: React.CSSProperties; + DaySelected?: React.CSSProperties; + DayStartEdge?: React.CSSProperties; + DayEndEdge?: React.CSSProperties; + DayInRange?: React.CSSProperties; + Weekday?: React.CSSProperties; + MonthAndYear?: React.CSSProperties; + MonthButton?: React.CSSProperties; + MonthArrow?: React.CSSProperties; + MonthArrowPrev?: React.CSSProperties; + MonthArrowNext?: React.CSSProperties; + PredefinedRanges?: React.CSSProperties; + PredefinedRangesItem?: React.CSSProperties; + PredefinedRangesItemActive?: React.CSSProperties; +} + +export interface RangeWithKey extends Range { + key: 'selection'; +} + +export interface OnDateRangeChangeProps { + [key: string]: Range; +} + +export type WeekStartsOn = 0|1|2|3|4|5|6; + +export type DateOptions = { locale: Locale; weekStartsOn?: WeekStartsOn; }; + +export function isRangeValue(value: Range | Date): value is Range { + return value.hasOwnProperty('startDate') && value.hasOwnProperty('endDate'); +} +export function isSureRange(range: Range): range is SureStartEndDate { + return range.startDate instanceof Date && range.endDate instanceof Date; +} + +export interface CommonCalendarProps { + /** default: DD/MM/YYY */ + format?: string; + firstDayOfWeek?: number; + theme?: CalendarTheme; + /** default: none */ + onInit?: ((range: Range) => void); + /** default: none */ + minDate?: Date; + /** default: none */ + maxDate?: Date; + /** default: */ + weekStartsOn?: WeekStartsOn; + /** default: enUs from locale. Complete list here https://github.com/Adphorus/react-date-range/blob/next/src/locale/index.js */ + locale?: Locale; + /** Custom class names for elements */ + classNames?: Partial; + /** default: none */ + navigatorRenderer?: ( + currentFocusedDate: Date, + changeShownDate: (shownDate: Date) => void, + props: CommonCalendarProps + ) => JSX.Element; + /** default: none */ + onShownDateChange?: ((visibleMonth: Date) => void); + /** default: none */ + onRangeFocusChange?: ((focusedRange: RangeFocus) => void); + /** default: false */ + editableDateInputs?: boolean; + /** default: true */ + dragSelectionEnabled?: boolean; + /** default: false */ + fixedHeight?: boolean; +} + +export type DisplayMode = 'date' | 'dateRange'; +export type CalendarFocus = 'forwards' | 'backwards'; +export type CalendarDirection = 'vertical' | 'horizontal'; + +export interface CalendarProps extends CommonCalendarProps { + /** default: today */ + date: DateInputType; + /** default: none */ + onChange?: (date?: Date) => void; + scroll?: { enabled: boolean; }; + preventSnapRefocus?: boolean; + calendarFocus: CalendarFocus; + months: number; +} + +export class Calendar extends React.Component {} + +export interface DateRangeProps extends Range, CommonCalendarProps { + /** default: enUs from locale. Complete list here https://github.com/Adphorus/react-date-range/blob/next/src/locale/index.js */ + locale?: Locale; + /** default: false */ + linkedCalendars?: boolean; + /** default: 2 */ + calendars?: number; + /** default: none */ + ranges?: Range[]; + /** default: { enabled: false } */ + scroll?: ScrollOptions; + /** default: false */ + showSelectionPreview?: boolean; + /** default: false */ + twoStepChange?: boolean; + /** default: true */ + showMonthArrow?: boolean; + /** default: false */ + rangedCalendars?: boolean; + /** default: none */ + specialDays?: DateContainerType[]; + /** default: 1 */ + months?: number; + /** default: true */ + showMonthAndYearPickers?: boolean; + /** default: [] */ + rangeColors?: string[]; + /** default: */ + shownDate?: Date; + /** default: */ + disabledDates?: Date[]; + /** default: */ + disabledDay?: ((date: Date) => boolean); + /** default: Early */ + startDatePlaceholder?: string; + /** default: */ + className?: string; + /** default: Continuous */ + endDatePlaceholder?: string; + /** default: MMM d, yyyy */ + dateDisplayFormat?: string; + /** default: d */ + dayDisplayFormat?: string; + /** default: E */ + weekdayDisplayFormat?: string; + /** default: MMM yyyy */ + monthDisplayFormat?: string; + /** default: vertical */ + direction?: CalendarDirection; + /** default: false */ + moveRangeOnFirstSelection?: boolean; + /** default: false */ + retainEndDateOnFirstSelection?: boolean; + /** default: false */ + editableDateInputs?: boolean; + /** default: */ + focusedRange?: RangeFocus; + /** default: [0, 0] */ + initialFocusedRange?: RangeFocus; + /** default: */ + onRangeFocusChange?: ((focusedRange: RangeFocus) => void); + /** default: */ + preview?: Preview; + /** default: true */ + showPreview?: boolean; + /** default: */ + // onPreviewChange?: (preview?: Preview) => void; + /** default: none */ + onChange?: (range: OnDateRangeChangeProps) => void; +} + +export interface DateRangePickerProps extends DateRangeProps { + renderStaticRangeLabel?: ((range: DefinedRange) => JSX.Element); + staticRanges?: StaticRange[]; + inputRanges?: InputRange[]; +} + +export class DateRange extends React.Component {} + +export class DateRangePicker extends React.Component {} + +export type DateRangeIndex = 'Today' | 'Yesterday' | 'Last 7 Days' | 'Last 30 Days'; + +export type SureStartEndDate = { + startDate: D; + endDate: D; +} + +export type StartEndDate = Partial>; + +export type StartEndDateGen = () => SureStartEndDate; +export type LabeledStartEndDateGen = { + label: string; + range: StartEndDateGen; +} + +interface OtherRangeProps { + color?: string; + key?: string; + autoFocus?: boolean; + disabled?: boolean; + showDateDisplay?: boolean; +} + +export interface SureRange extends SureStartEndDate, OtherRangeProps {} + +export interface Range extends StartEndDate, OtherRangeProps {} + +export interface ScrollOptions { + enabled: boolean; + monthHeight?: number; + longMonthHeight?: number; + monthWidth?: number; + calendarWidth?: number; + calendarHeight?: number; +} + +export interface DefinedRangeCommon { + label: string; + isSelected: (range: Range) => boolean; + hasCustomRendering?: boolean; +} + +export interface StaticRange extends DefinedRangeCommon { + range: (props: CommonCalendarProps) => Range; +} + +export interface InputRange extends DefinedRangeCommon { + range: (value: string, props: CommonCalendarProps) => Range; + getCurrentValue: (range: Range) => string; +} + +export type DefinedRange = StaticRange | InputRange; + +/** + * Represents range focus `[range, rangeElement]`. `range` represents the index of the range + * that's focused and the `rangeElement` the element of the range that's + * focused, `0` for start date and `1` for end date + */ +export type RangeFocus = [number, number]; + +export interface Preview { + startDate: Date; + endDate: Date; + color?: string; +} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 335c18eb8..000000000 --- a/src/utils.js +++ /dev/null @@ -1,79 +0,0 @@ -import classnames from 'classnames'; -import { - startOfMonth, - endOfMonth, - startOfWeek, - endOfWeek, - differenceInCalendarDays, - differenceInCalendarMonths, - addDays, -} from 'date-fns'; - -export function calcFocusDate(currentFocusedDate, props) { - const { shownDate, date, months, ranges, focusedRange, displayMode } = props; - // find primary date according the props - let targetInterval; - if (displayMode === 'dateRange') { - const range = ranges[focusedRange[0]] || {}; - targetInterval = { - start: range.startDate, - end: range.endDate, - }; - } else { - targetInterval = { - start: date, - end: date, - }; - } - targetInterval.start = startOfMonth(targetInterval.start || new Date()); - targetInterval.end = endOfMonth(targetInterval.end || targetInterval.start); - const targetDate = targetInterval.start || targetInterval.end || shownDate || new Date(); - - // initial focus - if (!currentFocusedDate) return shownDate || targetDate; - - // // just return targetDate for native scrolled calendars - // if (props.scroll.enabled) return targetDate; - if (differenceInCalendarMonths(targetInterval.start, targetInterval.end) > months) { - // don't change focused if new selection in view area - return currentFocusedDate; - } - return targetDate; -} - -export function findNextRangeIndex(ranges, currentRangeIndex = -1) { - const nextIndex = ranges.findIndex( - (range, i) => i > currentRangeIndex && range.autoFocus !== false && !range.disabled - ); - if (nextIndex !== -1) return nextIndex; - return ranges.findIndex(range => range.autoFocus !== false && !range.disabled); -} - -export function getMonthDisplayRange(date, dateOptions, fixedHeight) { - const startDateOfMonth = startOfMonth(date, dateOptions); - const endDateOfMonth = endOfMonth(date, dateOptions); - const startDateOfCalendar = startOfWeek(startDateOfMonth, dateOptions); - let endDateOfCalendar = endOfWeek(endDateOfMonth, dateOptions); - if (fixedHeight && differenceInCalendarDays(endDateOfCalendar, startDateOfCalendar) <= 34) { - endDateOfCalendar = addDays(endDateOfCalendar, 7); - } - return { - start: startDateOfCalendar, - end: endDateOfCalendar, - startDateOfMonth, - endDateOfMonth, - }; -} - -export function generateStyles(sources) { - if (!sources.length) return {}; - const generatedStyles = sources - .filter(source => Boolean(source)) - .reduce((styles, styleSource) => { - Object.keys(styleSource).forEach(key => { - styles[key] = classnames(styles[key], styleSource[key]); - }); - return styles; - }, {}); - return generatedStyles; -} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..4cf89a9fd --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,172 @@ +import classnames from 'classnames'; +import { DateInputType, Range } from '../src/types' +import { + startOfMonth, + endOfMonth, + startOfWeek, + endOfWeek, + differenceInCalendarDays, + differenceInCalendarMonths, + addDays, +} from 'date-fns'; +import coreStyles, { ClassNames, CoreStyles } from './styles'; + +export type DateRange = { + dateStart: Date; + dateEnd: Date; +} + +export type CalcFocusDateBaseProps = { + date: Date | number; + months: number; + shownDate?: Date; + focusedRange?: number[]; +} + +export type CalcFocusDateRangeModeProps = CalcFocusDateBaseProps & { + ranges?: DateRange[]; + displayMode: 'dateRange'; +}; + +export type CalcFocusRangeModeProps = CalcFocusDateBaseProps & { + ranges?: Range[]; + displayMode: 'date'; +}; + +export type CalcFocusDateProps = CalcFocusDateBaseProps & { + ranges?: DateRange[] | Range[]; + displayMode: 'dateRange' | 'date'; +}; + +function isDateRangeMode(p: CalcFocusDateProps): p is CalcFocusDateRangeModeProps { + return p.displayMode === 'dateRange'; +} + +function isRangeMode(p: CalcFocusDateProps): p is CalcFocusRangeModeProps { + return p.displayMode === 'date'; +} + +export function calcFocusDateMode(currentFocusedDate: Date | null, props: CalcFocusRangeModeProps) { + const { shownDate, date, months } = props; + // find primary date according the props + const targetInterval = { + start: date, + end: date, + }; + targetInterval.start = startOfMonth(date || new Date()); + targetInterval.end = endOfMonth(date); + const targetDate = targetInterval.start || targetInterval.end || shownDate || new Date(); + + // initial focus + if (!currentFocusedDate) return shownDate || targetDate; + + // // just return targetDate for native scrolled calendars + // if (props.scroll.enabled) return targetDate; + if (differenceInCalendarMonths(targetInterval.start, targetInterval.end) > months) { + // don't change focused if new selection in view area + return currentFocusedDate; + } + return targetDate; +} + +export function calcFocusDateRangeMode(currentFocusedDate: Date | null, props: CalcFocusDateRangeModeProps): Date { + const { shownDate, months, ranges, focusedRange } = props; + // find primary date according the props + const now = new Date(); + const range = (ranges && focusedRange && ranges.length > 0 && ranges[focusedRange[0]]) || { + dateStart: now, + dateEnd: now, + }; + const targetIntervalInput = { + start: range.dateStart, + end: range.dateEnd, + }; + + const targetInterval = { + start: startOfMonth(targetIntervalInput.start), + end: endOfMonth(targetIntervalInput.end || targetIntervalInput.start), + }; + + const targetDate = targetInterval.start || targetInterval.end || shownDate || new Date(); + + // initial focus + if (!currentFocusedDate) return shownDate || targetDate; + + // // just return targetDate for native scrolled calendars + // if (props.scroll.enabled) return targetDate; + if (differenceInCalendarMonths(targetInterval.start, targetInterval.end) > months) { + // don't change focused if new selection in view area + return currentFocusedDate; + } + return targetDate; +} + +export function calcFocusDate(currentFocusedDate: Date | null, props: CalcFocusDateProps) { + if (isDateRangeMode(props)) { + return calcFocusDateRangeMode(currentFocusedDate, props); + } else if (isRangeMode(props)) { + return calcFocusDateMode(currentFocusedDate, props); + } else { + throw new Error('Never happened'); + } +} + +export function findNextRangeIndex(ranges: Range[], currentRangeIndex: number = -1) { + const nextIndex = ranges.findIndex( + (range, i) => i > currentRangeIndex && range.autoFocus !== false && !range.disabled + ); + if (nextIndex !== -1) return nextIndex; + return ranges.findIndex(range => range.autoFocus !== false && !range.disabled); +} + +type GetMonthDisplayRangeProp = { + locale?: Locale; + weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; +} +export function getMonthDisplayRange(date: Date, dateOptions: GetMonthDisplayRangeProp, fixedHeight: boolean = false) { + const startDateOfMonth = startOfMonth(date); + const endDateOfMonth = endOfMonth(date); + const startDateOfCalendar = startOfWeek(startDateOfMonth, dateOptions); + let endDateOfCalendar = endOfWeek(endDateOfMonth, dateOptions); + if (fixedHeight && differenceInCalendarDays(endDateOfCalendar, startDateOfCalendar) <= 34) { + endDateOfCalendar = addDays(endDateOfCalendar, 7); + } + return { + start: startDateOfCalendar, + end: endDateOfCalendar, + startDateOfMonth, + endDateOfMonth, + }; +} + +type JoinedStyles = Partial; + +function isStyleSourceKey(a: JoinedStyles, k: string): k is keyof JoinedStyles { + return a.hasOwnProperty(k); +} + +function isStylesMap(s: CoreStyles | Partial | {} | undefined): s is JoinedStyles { + return Boolean(s); +} + +export type PartialStyles = Partial; + +export function generateStyles(sources: [CoreStyles, PartialStyles | {} | undefined]) { + const emptyStyles: JoinedStyles = {}; + if (!sources.length) return emptyStyles; + const generatedStyles = sources + .filter(isStylesMap) + .reduce((styles, styleSource) => { + Object.keys(styleSource).forEach(key => { + const alreadyAddedClassNames = styles.hasOwnProperty(key) + ? styles[key as keyof typeof styles] + : []; + return { + ...styles, + [key]: classnames(alreadyAddedClassNames, isStyleSourceKey(styleSource, key) ? styleSource[key] : []), + }; + }); + return styles; + }, emptyStyles); + return generatedStyles; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..a691a7672 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "checkJs": false, + "downlevelIteration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "module": "esnext", + "moduleResolution": "node", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "outDir": "./dist/", + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "es5", + }, + "include": [ + "src", + "server.js" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0b061bf99..970a64735 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1132,6 +1132,17 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@jest/types@^27.2.5": + version "27.2.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.2.5.tgz#420765c052605e75686982d24b061b4cbba22132" + integrity sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1157,11 +1168,32 @@ readdirp "^2.2.1" upath "^1.1.1" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@types/babel__core@^7.1.0": version "7.1.14" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.14.tgz#faaeefc4185ec71c389f4501ee5ec84b170cc402" @@ -1230,6 +1262,21 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^27.0.2": + version "27.0.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" + integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA== + dependencies: + jest-diff "^27.0.0" + pretty-format "^27.0.0" + "@types/json-schema@^7.0.5": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -1250,6 +1297,39 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-15.6.1.tgz#32d43390d5c62c5b6ec486a9bc9c59544de39a08" integrity sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA== +"@types/prop-types@*": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + +"@types/ramda@^0.27.45": + version "0.27.45" + resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.45.tgz#1d692736da8f8c199e10c751d4799cab03cd9acd" + integrity sha512-WDH7bIuy+JQHzYx6jgo+ytSHco/J+DWaUfxXQ2eBjilxIj4rG0aqQNU56AtO5Tem9hmx8na2ouSAtn5Tz8RePQ== + dependencies: + ts-toolbelt "^6.15.1" + +"@types/react-list@^0.8.6": + version "0.8.6" + resolved "https://registry.yarnpkg.com/@types/react-list/-/react-list-0.8.6.tgz#e502811a7e73666ba90a6aef196e0883c0671229" + integrity sha512-AdJ4SQ++FZx3bT1Jyb+0giK+EJr7q/Y0LY5iCkC2K05ZW1aClp6ugeRDsKBQCGByzj7+CPv5MzkL1pUlRhW2Eg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^17.0.27": + version "17.0.27" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.27.tgz#6498ed9b3ad117e818deb5525fa1946c09f2e0e6" + integrity sha512-zgiJwtsggVGtr53MndV7jfiUESTqrbxOcBvwfe6KS/9bzaVPCTDieTWnFNecVNx6EAaapg5xsLLWFfHHR437AA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -1310,6 +1390,57 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/parser@^4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" + integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== + dependencies: + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" + integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + +"@typescript-eslint/types@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== + +"@typescript-eslint/typescript-estree@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== + dependencies: + "@typescript-eslint/types" "4.33.0" + eslint-visitor-keys "^2.0.0" + "@vxna/mini-html-webpack-template@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@vxna/mini-html-webpack-template/-/mini-html-webpack-template-1.0.0.tgz#66ce883b6e6678e1242ab51da04be21d3f1001bc" @@ -1613,6 +1744,11 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -1632,6 +1768,11 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1716,6 +1857,11 @@ array-union@^1.0.1, array-union@^1.0.2: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -2140,7 +2286,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -2457,6 +2603,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" @@ -3128,6 +3282,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -3268,6 +3429,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff-sequences@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" + integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3292,6 +3458,13 @@ dir-glob@^2.0.0, dir-glob@^2.2.2: dependencies: path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" @@ -3754,6 +3927,11 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + eslint@^6.8.0: version "6.8.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" @@ -4039,6 +4217,17 @@ fast-glob@^2.0.2, fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -4049,6 +4238,13 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.11.3, faye-websocket@~0.11.1: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" @@ -4421,7 +4617,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -4486,6 +4682,18 @@ globby@8.0.2: pify "^3.0.0" slash "^1.0.0" +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -4838,6 +5046,11 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + immer@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" @@ -5498,6 +5711,16 @@ jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-diff@^27.0.0: + version "27.2.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.2.5.tgz#908f7a6aca5653824516ad30e0a9fd9767e53623" + integrity sha512-7gfwwyYkeslOOVQY4tVq5TaQa92mWfC9COsVYMNVYyJTOYAqbIkoD3twi5A+h+tAPtAelRxkqY6/xu+jwTr0dA== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.0.6" + jest-get-type "^27.0.6" + pretty-format "^27.2.5" + jest-docblock@^24.3.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" @@ -5544,6 +5767,11 @@ jest-get-type@^24.9.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.0.6.tgz#0eb5c7f755854279ce9b68a9f1a4122f69047cfe" + integrity sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -6215,6 +6443,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + magic-string@^0.25.3: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -6337,7 +6572,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3: +merge2@^1.2.3, merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -6371,6 +6606,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -7173,6 +7416,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" @@ -7189,7 +7437,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== @@ -7781,6 +8029,16 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^27.0.0, pretty-format@^27.2.5: + version "27.2.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.2.5.tgz#7cfe2a8e8f01a5b5b29296a0b70f4140df0830c5" + integrity sha512-+nYn2z9GgicO9JiqmY25Xtq8SYfZ/5VCpEU3pppHHNAhd1y+ZXxmNPd1evmNcAd6Hz4iBV2kf0UpGth5A/VJ7g== + dependencies: + "@jest/types" "^27.2.5" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -7953,6 +8211,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" @@ -7965,6 +8228,11 @@ railroad-diagrams@^1.0.0: resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= +ramda@^0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + randexp@0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" @@ -8093,6 +8361,11 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.4, react- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-list@^0.8.13: version "0.8.16" resolved "https://registry.yarnpkg.com/react-list/-/react-list-0.8.16.tgz#3f19b249998de0086787da3789d35b59553ede3a" @@ -8565,6 +8838,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -8605,6 +8883,13 @@ run-async@^2.2.0, run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -8719,6 +9004,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -8862,6 +9154,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -9567,6 +9864,11 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +ts-toolbelt@^6.15.1: + version "6.15.5" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz#cb3b43ed725cb63644782c64fbcad7d8f28c0a83" + integrity sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A== + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -9577,7 +9879,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -9587,6 +9889,13 @@ tslib@^2.0.1, tslib@^2.2.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -9639,6 +9948,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" + integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" From e0175a19bc8ee3a7989b9efbff59807837ccb2bc Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Mon, 11 Oct 2021 17:28:45 +0200 Subject: [PATCH 05/34] fix: remove scss warning --- src/theme/default.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/theme/default.scss b/src/theme/default.scss index 0e17aecac..1972dba80 100644 --- a/src/theme/default.scss +++ b/src/theme/default.scss @@ -207,8 +207,6 @@ right: 2px; } -.rdrInRange{} - .rdrStartEdge{ border-top-left-radius: 1.042em; border-bottom-left-radius: 1.042em; From 683d20cecf44274e43ebcf51813796a782cc337c Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Wed, 13 Oct 2021 15:30:40 +0200 Subject: [PATCH 06/34] feat: tests passing 2 snapshots obsolete --- jest.config.ts | 14 +- package.json | 25 +- src/accessibility/index.js | 11 - src/components/Calendar/index.tsx | 112 +- src/components/DateInput/index.tsx | 14 +- .../{index.test.js => index.test.tsx} | 50 +- src/components/DateRange/index.tsx | 57 +- src/components/DateRangePicker/index.js | 52 - src/components/DateRangePicker/index.tsx | 55 + src/components/DayCell/index.tsx | 13 +- .../__snapshots__/index.test.tsx.snap | 109 + src/components/DefinedRange/index.test.js | 135 - src/components/DefinedRange/index.test.tsx | 88 + .../DefinedRange/{index.js => index.tsx} | 126 +- .../__snapshots__/index.test.tsx.snap | 88 + .../{index.test.js => index.test.tsx} | 4 +- src/components/InputRangeField/index.tsx | 14 +- src/components/Month/index.tsx | 72 +- src/defaultRanges.ts | 52 +- src/styles.ts | 1 + src/types.ts | 99 +- src/utils.test.ts | 66 + src/{utils.ts => utils.tsx} | 148 +- tsconfig.json | 2 +- yarn.lock | 2841 ++++++++++------- 25 files changed, 2571 insertions(+), 1677 deletions(-) delete mode 100644 src/accessibility/index.js rename src/components/DateRange/{index.test.js => index.test.tsx} (70%) delete mode 100644 src/components/DateRangePicker/index.js create mode 100644 src/components/DateRangePicker/index.tsx create mode 100644 src/components/DefinedRange/__snapshots__/index.test.tsx.snap delete mode 100644 src/components/DefinedRange/index.test.js create mode 100644 src/components/DefinedRange/index.test.tsx rename src/components/DefinedRange/{index.js => index.tsx} (50%) create mode 100644 src/components/InputRangeField/__snapshots__/index.test.tsx.snap rename src/components/InputRangeField/{index.test.js => index.test.tsx} (97%) create mode 100644 src/utils.test.ts rename src/{utils.ts => utils.tsx} (57%) diff --git a/jest.config.ts b/jest.config.ts index a63354010..19a1b4cec 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -2,12 +2,20 @@ import type { Config } from '@jest/types'; import { defaults } from 'jest-config'; // Sync object -const config: Config.InitialOptions = { +const config = { verbose: true, testURL: 'http://localhost/', setupFiles: ['/setupTests.ts'], - moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], - testPathIgnorePatterns: ['/node_modules/', '/src/', '/demo/dist/'], + maxWorkers: 100, + roots: ['/src'], + testMatch: [ + "**/__tests__/**/*.+(ts|tsx|js)", + "**/?(*.)+(spec|test).+(ts|tsx|js)" + ], + testEnvironment: "jsdom", + transform: { "^.+\\.(ts|tsx)$": "ts-jest" }, + moduleFileExtensions: ['ts', 'tsx', 'js'], //...defaults.moduleFileExtensions, + testPathIgnorePatterns: ['/node_modules/', '/dist/', '/demo/dist/'], snapshotSerializers: ['enzyme-to-json/serializer'], }; export default config; \ No newline at end of file diff --git a/package.json b/package.json index f123342d2..cca1da407 100755 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "yarn build:css & styleguidist server", "build": "yarn build:css & yarn build:js & styleguidist build", "build:css": "postcss 'src/styles.scss' -d dist --ext css & postcss 'src/theme/*.scss' -d 'dist/theme' --ext css", - "build:js": "babel ./src --out-dir ./dist --ignore test.js", + "build:js": "tsc", "lint": "eslint 'src/**/*.js'", "test": "jest", "preversion": "yarn clear & yarn build" @@ -42,12 +42,13 @@ "classnames": "^2.2.6", "prop-types": "^15.7.2", "ramda": "^0.27.1", - "react-list": "^0.8.13", + "react-list": "^0.8.16", "shallow-equal": "^1.2.1" }, "peerDependencies": { - "date-fns": "2.0.0-alpha.7 || >=2.0.0", - "react": "^0.14 || ^15.0.0-rc || >=15.0" + "date-fns": "2.25.0", + "react": "^0.14 || ^15.0.0-rc || >=15.0", + "react-list": "^0.8.13" }, "devDependencies": { "@babel/cli": "^7.7.7", @@ -56,17 +57,21 @@ "@babel/plugin-proposal-export-default-from": "^7.7.4", "@babel/preset-env": "^7.4.4", "@babel/preset-react": "^7.7.4", + "@types/enzyme": "^3.10.9", + "@types/enzyme-adapter-react-16": "^1.0.6", "@types/jest": "^27.0.2", + "@types/jsdom-global": "^3.0.2", "@types/ramda": "^0.27.45", "@types/react": "^17.0.27", "@types/react-list": "^0.8.6", + "@types/react-test-renderer": "^17.0.1", "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^9.7.3", "babel-eslint": "^10.0.3", "babel-loader": "^8.0.6", "babel-plugin-date-fns": "^2.0.0", "css-loader": "^3.2.0", - "date-fns": "^2.8.1", + "date-fns": "^2.25.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", "enzyme-to-json": "^3.4.3", @@ -76,7 +81,9 @@ "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.17.0", - "jest": "^24.9.0", + "jest": "^27.2.5", + "jsdom": "^18.0.0", + "jsdom-global": "^3.0.2", "normalize.css": "^8.0.1", "postcss": "^7.0.25", "postcss-cli": "^6.1.3", @@ -84,10 +91,12 @@ "postcss-loader": "^3.0.0", "precss": "^4.0.0", "prettier": "^1.19.1", - "react": "^16.12.0", - "react-dom": "^16.12.0", + "react": "16", + "react-dom": "16", "react-styleguidist": "^10.4.0", "style-loader": "^1.0.0", + "ts-jest": "^27.0.5", + "ts-node": "^10.3.0", "typescript": "^4.4.3", "url-loader": "^3.0.0", "webpack": "^4.41.5" diff --git a/src/accessibility/index.js b/src/accessibility/index.js deleted file mode 100644 index 57d762b6f..000000000 --- a/src/accessibility/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import PropTypes from 'prop-types'; - -export const ariaLabelsShape = PropTypes.shape({ - dateInput: PropTypes.objectOf( - PropTypes.shape({ startDate: PropTypes.string, endDate: PropTypes.string }) - ), - monthPicker: PropTypes.string, - yearPicker: PropTypes.string, - prevButton: PropTypes.string, - nextButton: PropTypes.string, -}); diff --git a/src/components/Calendar/index.tsx b/src/components/Calendar/index.tsx index aa1f6b2a3..fe6956b1e 100644 --- a/src/components/Calendar/index.tsx +++ b/src/components/Calendar/index.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import Month, { Drag } from '../Month'; import DateInput from '../DateInput'; -import { calcFocusDate, generateStyles, getMonthDisplayRange, PartialStyles } from '../../utils'; +import { calcFocusDate, CalcFocusDateProps, generateStyles, getMonthDisplayRange, inferAriaLabel, renderWeekdays } from '../../utils'; import { enUS } from '../../locale' import classnames from 'classnames'; import ReactList from 'react-list'; @@ -10,10 +10,6 @@ import { shallowEqualObjects } from 'shallow-equal'; import { addMonths, subMonths, - format, - eachDayOfInterval, - startOfWeek, - endOfWeek, isSameDay, addYears, setYear, @@ -27,10 +23,11 @@ import { min, max, Locale, + isValid, } from 'date-fns'; import defaultLocale from 'date-fns/locale/en-US'; -import coreStyles, { ClassNames, CoreStyles } from '../../styles'; -import { CalendarDirection, CalendarFocus, CommonCalendarProps, DateOptions, DisplayMode, isSureRange, Preview, Range, RangeFocus, ScrollOptions, StartEndDate, SureStartEndDate } from '../../types'; +import coreStyles, { ClassNames } from '../../styles'; +import { AriaLabelShape, CalendarDirection, CalendarFocus, CommonCalendarProps, DateOptions, DisplayMode, isModeMapperKey, isSureRange, Preview, Range, RangeFocus, ScrollOptions, SureStartEndDate } from '../../types'; import { DateReceivingFunc, OptionalDateReceivingFunc } from '../DayCell'; type ScrollArea = { @@ -51,18 +48,8 @@ type ComponentState = { } -type AriaLabelShape = { - dateInput?: { - [x: string]: StartEndDate; - }; - monthPicker?: string; - yearPicker?: string; - prevButton?: string; - nextButton?: string; -} - -type DefaultCalendarProps = { - ariaLabels: AriaLabelShape; +type DefaultCalendarProps = CalcFocusDateProps & { + ariaLabels?: AriaLabelShape; calendarFocus: CalendarFocus; classNames: Partial; color: string; @@ -71,7 +58,6 @@ type DefaultCalendarProps = { direction: CalendarDirection; disabledDates: Date[]; disabledDay: (day: Date) => boolean; - displayMode: DisplayMode; dragSelectionEnabled: boolean; editableDateInputs: boolean; endDatePlaceholder: string; @@ -95,6 +81,8 @@ type DefaultCalendarProps = { showPreview: boolean; startDatePlaceholder: string; weekdayDisplayFormat: string; + //added by g + date: Date | number; }; export type CalendarProps = CommonCalendarProps & { @@ -110,34 +98,6 @@ export type CalendarProps = CommonCalendarProps & { displayMode?: DisplayMode; } - -type ModeMapper = { - monthOffset: () => Date, - setMonth: () => Date, - setYear: () => Date, - set: () => number, -} - -; - -const inferAriaLabel = (ariaLabels: AriaLabelShape, range: Range): string => { - const sd = ariaLabels.dateInput && range.key - && ariaLabels.dateInput[range.key] - && ariaLabels.dateInput[range.key].startDate; - if (sd instanceof Date) { - return sd.toLocaleString(); - } else if (typeof sd === 'string') { - return sd; - } else { - console.log(ariaLabels); - throw new Error('Unsupported ariaLabel type'); - } -} - -function isModeMapperKey(s: string, o: ModeMapper): s is keyof ModeMapper { - return Object.keys(o).indexOf(s) !== -1; -} - type ComponentProps = CalendarProps & DefaultCalendarProps; class Calendar extends PureComponent { @@ -149,10 +109,16 @@ class Calendar extends PureComponent { list: ReactList | null; public static defaultProps: DefaultCalendarProps = { - ariaLabels: {}, + ariaLabels: { + monthPicker: "month picker", + yearPicker: "year picker", + prevButton: "previous month button", + nextButton: "next month button", + }, calendarFocus: 'forwards', classNames: {}, color: '#3d91ff', + date: new Date(), dateDisplayFormat: 'MMM d, yyyy', dayDisplayFormat: 'd', direction: 'vertical', @@ -190,9 +156,10 @@ class Calendar extends PureComponent { this.listSizeCache = {}; this.isFirstRender = true; this.list = null; + const focusedDate = calcFocusDate(null, this.props); this.state = { monthNames: this.getMonthNames(), - focusedDate: calcFocusDate(null, this.props), + focusedDate, drag: { status: false, range: { startDate: null, endDate: null }, @@ -246,7 +213,7 @@ class Calendar extends PureComponent { this.setState({ focusedDate: date }); return; } - const targetMonthIndex = differenceInCalendarMonths(date, props.minDate || new Date()); + const targetMonthIndex = differenceInCalendarMonths(date, props.minDate); const visibleMonths = this.list?.getVisibleRange(); if (preventUnnecessary && visibleMonths?.includes(targetMonthIndex)) return; this.isFirstRender = true; @@ -286,11 +253,12 @@ class Calendar extends PureComponent { } componentDidUpdate(prevProps: ComponentProps) { + const displayMode: DisplayMode = this.props.displayMode || 'date'; const propMapper = { dateRange: 'ranges', date: 'date', }; - const targetProp = propMapper[this.props.displayMode] as 'ranges' | 'date'; + const targetProp = propMapper[displayMode] as 'ranges' | 'date'; if (this.props[targetProp] !== prevProps[targetProp]) { this.updateShownDate(this.props); } @@ -369,7 +337,7 @@ class Calendar extends PureComponent { type="button" className={classnames(styles.nextPrevButton, styles.prevButton)} onClick={() => changeShownDate(-1, 'monthOffset')} - aria-label={ariaLabels.prevButton}> + aria-label={ariaLabels?.prevButton}> ) : null} @@ -379,7 +347,7 @@ class Calendar extends PureComponent { changeShownDate(e.target.value, 'setYear')} - aria-label={ariaLabels.yearPicker}> + aria-label={ariaLabels?.yearPicker}> {new Array(upperYearLimit - lowerYearLimit + 1) .fill(upperYearLimit) .map((val, i) => { @@ -416,7 +384,7 @@ class Calendar extends PureComponent { type="button" className={classnames(styles.nextPrevButton, styles.nextButton)} onClick={() => changeShownDate(+1, 'monthOffset')} - aria-label={ariaLabels.nextButton}> + aria-label={ariaLabels?.nextButton}> ) : null} @@ -424,22 +392,6 @@ class Calendar extends PureComponent { ); } - renderWeekdays() { - const now = new Date(); - return ( -
- {eachDayOfInterval({ - start: startOfWeek(now, this.dateOptions), - end: endOfWeek(now, this.dateOptions), - }).map((day, i) => ( - - {format(day, this.props.weekdayDisplayFormat, this.dateOptions)} - - ))} -
- ); - } - renderDateDisplay = () => { const { focusedRange, @@ -456,7 +408,6 @@ class Calendar extends PureComponent { const defaultColor = rangeColors[focusedRange[0]] || color; const styles = this.styles; - return (
{ranges.map((range, i) => { @@ -477,7 +428,7 @@ class Calendar extends PureComponent { placeholder={startDatePlaceholder} dateOptions={this.dateOptions} dateDisplayFormat={dateDisplayFormat} - ariaLabel={inferAriaLabel(ariaLabels, range)} + ariaLabel={inferAriaLabel(range, ariaLabels, 'startDate')} onChange={this.onDragSelectionEnd} onFocus={() => this.handleRangeFocusChange(i, 0)} /> @@ -491,7 +442,7 @@ class Calendar extends PureComponent { placeholder={endDatePlaceholder} dateOptions={this.dateOptions} dateDisplayFormat={dateDisplayFormat} - ariaLabel={inferAriaLabel(ariaLabels, range)} + ariaLabel={inferAriaLabel(range, ariaLabels, 'endDate')} onChange={_ => this.onDragSelectionEnd} onFocus={_ => this.handleRangeFocusChange(i, 1)} /> @@ -588,6 +539,7 @@ class Calendar extends PureComponent { return scrollArea.monthHeight; } }; + render() { const { showDateDisplay, @@ -612,6 +564,7 @@ class Calendar extends PureComponent { ...range, color: range.color || rangeColors[i] || color, })); + return (
{ {monthAndYearRenderer(focusedDate, this.changeShownDate, this.props)} {scroll.enabled ? (
- {isVertical && this.renderWeekdays()} + {isVertical && renderWeekdays(this.styles, this.dateOptions, this.props.weekdayDisplayFormat)}
{ isVertical ? this.styles.monthsVertical : this.styles.monthsHorizontal )}> {new Array(this.props.months).fill(null).map((_, i) => { - let monthStep = addMonths(this.state.focusedDate, i); - if (this.props.calendarFocus === 'backwards') { - monthStep = subMonths(this.state.focusedDate, this.props.months - 1 - i); - } + const monthStep = this.props.calendarFocus === 'backwards' + ? subMonths(this.state.focusedDate, this.props.months - 1 - i) + : addMonths(this.state.focusedDate, i); return ( { + public static defaultProps = { + readOnly: true, + disabled: false, + dateDisplayFormat: 'MMM D, YYYY', + }; + constructor(props: CompontentProps) { - super({...defaultProps, ...props}); + super(props); this.state = { invalid: false, diff --git a/src/components/DateRange/index.test.js b/src/components/DateRange/index.test.tsx similarity index 70% rename from src/components/DateRange/index.test.js rename to src/components/DateRange/index.test.tsx index 9c3fcdf1d..236b0141e 100644 --- a/src/components/DateRange/index.test.js +++ b/src/components/DateRange/index.test.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { subDays, addDays, isSameDay } from 'date-fns'; -import DateRange from '../DateRange'; -import renderer from 'react-test-renderer'; +import DateRange from '.'; +import renderer, { ReactTestRenderer } from 'react-test-renderer'; +import { isSureRange, MaybeMaybeRange, SureStartEndDate } from '../../types'; -let testRenderer = null; -let instance = null; +let testRenderer: ReactTestRenderer; +let instance: DateRange; const endDate = new Date(); const startDate = subDays(endDate, 7); @@ -14,9 +15,12 @@ const commonProps = { moveRangeOnFirstSelection: false, }; -const compareRanges = (newRange, assertionRange) => { - ['startDate', 'endDate'].forEach(key => { - if (!newRange[key] || !assertionRange[key]) { +const compareRanges = (assertionRange: MaybeMaybeRange, newRange?: MaybeMaybeRange) => { + if (!newRange) { + return expect(typeof newRange).toEqual(typeof assertionRange); + } + (['startDate', 'endDate'] as (keyof SureStartEndDate)[]).forEach(key => { + if (!isSureRange(assertionRange) || !isSureRange(newRange)) { return expect(newRange[key]).toEqual(assertionRange[key]); } return expect(isSameDay(newRange[key], assertionRange[key])).toEqual(true); @@ -25,7 +29,7 @@ const compareRanges = (newRange, assertionRange) => { beforeEach(() => { testRenderer = renderer.create(); - instance = testRenderer.getInstance(); + instance = testRenderer.getInstance() as unknown as DateRange; }); describe('DateRange', () => { @@ -34,37 +38,37 @@ describe('DateRange', () => { }); test('calculate new selection by resetting end date', () => { - const methodResult = instance.calcNewSelection(subDays(endDate, 10), true); - compareRanges(methodResult.range, { + const methodResult = instance.calcNewSelection(subDays(endDate, 10)); + compareRanges({ startDate: subDays(endDate, 10), endDate: subDays(endDate, 10), - }); + }, methodResult?.range); }); test('calculate new selection by resetting end date if start date is not before', () => { - const methodResult = instance.calcNewSelection(addDays(endDate, 2), true); - compareRanges(methodResult.range, { + const methodResult = instance.calcNewSelection(addDays(endDate, 2)); + compareRanges({ startDate: addDays(endDate, 2), endDate: addDays(endDate, 2), - }); + }, methodResult?.range); }); test('calculate new selection based on moveRangeOnFirstSelection prop', () => { testRenderer.update(); - const methodResult = instance.calcNewSelection(subDays(endDate, 10), true); - compareRanges(methodResult.range, { + const methodResult = instance.calcNewSelection(subDays(endDate, 10)); + compareRanges({ startDate: subDays(endDate, 10), endDate: subDays(endDate, 3), - }); + }, methodResult?.range); }); test('calculate new selection by retaining end date, based on retainEndDateOnFirstSelection prop', () => { testRenderer.update(); - const methodResult = instance.calcNewSelection(subDays(endDate, 10), true); - compareRanges(methodResult.range, { + const methodResult = instance.calcNewSelection(subDays(endDate, 10)); + compareRanges({ startDate: subDays(endDate, 10), endDate, - }); + }, methodResult?.range); }); test('calculate new selection by retaining the unset end date, based on retainEndDateOnFirstSelection prop', () => { @@ -75,10 +79,10 @@ describe('DateRange', () => { retainEndDateOnFirstSelection /> ); - const methodResult = instance.calcNewSelection(subDays(endDate, 10), true); - compareRanges(methodResult.range, { + const methodResult = instance.calcNewSelection(subDays(endDate, 10)); + compareRanges({ startDate: subDays(endDate, 10), endDate: null, - }); + }, methodResult?.range); }); }); diff --git a/src/components/DateRange/index.tsx b/src/components/DateRange/index.tsx index 108ab85ef..5de8f1bd2 100644 --- a/src/components/DateRange/index.tsx +++ b/src/components/DateRange/index.tsx @@ -3,13 +3,13 @@ import Calendar from '../Calendar'; import { findNextRangeIndex, generateStyles } from '../../utils'; import { isBefore, differenceInCalendarDays, addDays, min, isWithinInterval, max } from 'date-fns'; import coreStyles, { ClassNames } from '../../styles'; -import { DateRangeProps, isRangeValue, isSureRange, Preview, Range, RangeFocus, SureRange, SureStartEndDate } from '../../types'; +import { DateRangeProps, isNoEndDateRange, isRangeValue, isSureRange, MaybeMaybeRange, Preview, Range, RangeFocus, SureRange, SureStartEndDate } from '../../types'; import classnames from 'classnames'; import { compose } from 'ramda'; type DefaultComponentProps = { classNames: Partial; - ranges: Range[]; + ranges: MaybeMaybeRange[]; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; rangeColors: string[]; @@ -21,15 +21,6 @@ type ComponentState = { preview: null | Preview; } -const defaultProps: DefaultComponentProps = { - classNames: {}, - ranges: [], - moveRangeOnFirstSelection: false, - retainEndDateOnFirstSelection: false, - rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], - disabledDates: [], -}; - const calculateEndDate = (value: Date, now: Date, endDate: Date | null, dayOffset: number, moveRangeOnFirstSelection: boolean, retainEndDateOnFirstSelection: boolean, maxDate?: Date) => { const userEndDate = (() => { if (moveRangeOnFirstSelection) { @@ -44,15 +35,15 @@ const calculateEndDate = (value: Date, now: Date, endDate: Date | null, dayOffse return value || now; })(); const nextEndDate = maxDate ? (userEndDate ? min([userEndDate, maxDate]) : maxDate) : userEndDate ; - if (nextEndDate === null) { - throw new Error('Bug! endDate is null, and it should not. RetainEndDateOnFirstSelection tiggers buggy behavior, what should the endDate be now that retainEDFS is true?'); - } + // if (nextEndDate === null) { + // throw new Error('Bug! endDate is null, and it should not. RetainEndDateOnFirstSelection tiggers buggy behavior, what should the endDate be now that retainEDFS is true?'); + // } return nextEndDate; }; type ShapeChangingParams = { value: Date | Range; }; type BaseParams = { focusedRange: RangeFocus; disabledDates: Date[]; ranges: Range[]; } -type ComputeProps = BaseParams & ShapeChangingParams & { selectedRange: SureStartEndDate; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; maxDate?: Date; }; +type ComputeProps = BaseParams & ShapeChangingParams & { selectedRange: MaybeMaybeRange; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; maxDate?: Date; }; function computeStartDateEndDate({ value, selectedRange, focusedRange, disabledDates, moveRangeOnFirstSelection, retainEndDateOnFirstSelection, ranges, maxDate }: ComputeProps) { const base = { isStartDateSelected: focusedRange[1] === 0, @@ -90,10 +81,10 @@ function computeStartDateEndDate({ value, selectedRange, focusedRange, disabledD } } -type FlipProps = BaseParams & SureStartEndDate & { nextFocusRange?: RangeFocus; isStartDateSelected: boolean; }; +type FlipProps = BaseParams & MaybeMaybeRange & { nextFocusRange?: RangeFocus; isStartDateSelected: boolean; }; const flipIfReversed = (params: FlipProps) => { const { startDate, endDate, isStartDateSelected } = params; - return isBefore(endDate, startDate) + return endDate && isBefore(endDate, startDate) ? { ...params, isStartDateSelected: !isStartDateSelected, @@ -111,11 +102,10 @@ const getNextFocusRange = (ranges: Range[], focusedRange: RangeFocus, nextFocusR const computeRange = ({ disabledDates, startDate, endDate, isStartDateSelected, focusedRange, nextFocusRange, ranges }: FlipProps) => { const inValidDatesWithinRange = disabledDates.filter(disabledDate => - isWithinInterval(disabledDate, { + endDate && isWithinInterval(disabledDate, { start: startDate, end: endDate, - }) - ); + })); const wasValid = !(inValidDatesWithinRange.length > 0); @@ -139,7 +129,7 @@ type CalcNewSelectionRet = { wasValid: boolean; range: { startDate: Date; - endDate: Date; + endDate: Date | null; }; nextFocusRange: RangeFocus; } | undefined; @@ -149,16 +139,24 @@ const getRes = compose(computeRange, flipIfReversed, computeStartDateEndDate); type ComponentProps = DateRangeProps & DefaultComponentProps; class DateRange extends Component { + public static defaultProps: DefaultComponentProps = { + classNames: {}, + ranges: [], + moveRangeOnFirstSelection: false, + retainEndDateOnFirstSelection: false, + rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], + disabledDates: [], + }; styles: Partial; calendar: Calendar | null; constructor(props: ComponentProps) { - super({ ...defaultProps, ...props }); + super(props); this.state = { - focusedRange: props.initialFocusedRange || [findNextRangeIndex(props.ranges), 0], + focusedRange: props.initialFocusedRange || [findNextRangeIndex(this.props.ranges), 0], preview: null, }; - this.styles = generateStyles([coreStyles, props.classNames]); + this.styles = generateStyles([coreStyles, this.props.classNames]); this.calendar = null; } @@ -177,8 +175,10 @@ class DateRange extends Component { if (!selectedRange || !onChange) return; - if (!isSureRange(selectedRange)) { - throw new Error('Bug, expecting selected range to be a sure range, but it is not'); + if (!isSureRange(selectedRange) && !isNoEndDateRange(selectedRange)) { + console.log('selectedRange', selectedRange); + console.log('value', value); + throw new Error('Bug, expecting selected range to be a sure range or no end date range, but it is neither'); } return getRes({ @@ -221,7 +221,7 @@ class DateRange extends Component { this.props.onRangeFocusChange && this.props.onRangeFocusChange(focusedRange); }; - updatePreview = (val?: { range: SureRange; }) => { + updatePreview = (val?: { range: MaybeMaybeRange; }) => { if (!val) { this.setState({ preview: null }); return; @@ -234,7 +234,6 @@ class DateRange extends Component { render() { return ( { this.updatePreview(newSelection); } } {...this.props} - displayMode="dateRange" + displayMode={"dateRange"} className={classnames(this.styles.dateRangeWrapper, this.props.className)} onChange={this.setSelection} updateRange={this.setSelection} diff --git a/src/components/DateRangePicker/index.js b/src/components/DateRangePicker/index.js deleted file mode 100644 index 949a17233..000000000 --- a/src/components/DateRangePicker/index.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import DateRange from '../DateRange'; -import DefinedRange from '../DefinedRange'; -import { findNextRangeIndex, generateStyles } from '../../utils'; -import classnames from 'classnames'; -import coreStyles from '../../styles'; - -class DateRangePicker extends Component { - constructor(props) { - super(props); - this.state = { - focusedRange: [findNextRangeIndex(props.ranges), 0], - }; - this.styles = generateStyles([coreStyles, props.classNames]); - } - render() { - const { focusedRange } = this.state; - return ( -
- - this.dateRange.updatePreview( - value ? this.dateRange.calcNewSelection(value, typeof value === 'string') : null - ) - } - {...this.props} - range={this.props.ranges[focusedRange[0]]} - className={undefined} - /> - this.setState({ focusedRange })} - focusedRange={focusedRange} - {...this.props} - ref={t => (this.dateRange = t)} - className={undefined} - /> -
- ); - } -} - -DateRangePicker.defaultProps = {}; - -DateRangePicker.propTypes = { - ...DateRange.propTypes, - ...DefinedRange.propTypes, - className: PropTypes.string, -}; - -export default DateRangePicker; diff --git a/src/components/DateRangePicker/index.tsx b/src/components/DateRangePicker/index.tsx new file mode 100644 index 000000000..234e51c9f --- /dev/null +++ b/src/components/DateRangePicker/index.tsx @@ -0,0 +1,55 @@ +import React, { Component } from 'react'; +import DateRange from '../DateRange'; +import DefinedRange, { DefinedRangeProps } from '../DefinedRange'; +import { findNextRangeIndex, generateStyles } from '../../utils'; +import classnames from 'classnames'; +import coreStyles, { ClassNames, CoreStyles } from '../../styles'; +import { DateRangeProps, Range, RangeFocus } from '../../types'; + +type ComponentProps = DateRangeProps & DefinedRangeProps & { + className: string, +}; + +type ComponentState = { focusedRange: RangeFocus; }; + +class DateRangePicker extends Component { + dateRange: any; + styles: Partial; + + constructor(props: ComponentProps) { + super(props); + this.state = { + focusedRange: [findNextRangeIndex(props.ranges), 0], + }; + this.styles = generateStyles([coreStyles, props.classNames]); + } + render() { + const focusedRange = this.props.focusedRange || this.state.focusedRange; + const onPreviewChange = this.props.onPreviewChange || (value => + this.dateRange.updatePreview( + value ? this.dateRange.calcNewSelection(value, typeof value === 'string') : null + ) + ) + return ( +
+ + this.setState({ focusedRange })} + {...this.props} + focusedRange={focusedRange} + ref={t => (this.dateRange = t)} + className={undefined} + /> +
+ ); + } +} + +export default DateRangePicker; diff --git a/src/components/DayCell/index.tsx b/src/components/DayCell/index.tsx index 737d20c46..6457217e9 100644 --- a/src/components/DayCell/index.tsx +++ b/src/components/DayCell/index.tsx @@ -22,7 +22,7 @@ type ComponentProps = { dayContentRenderer?: DateReceivingFunc; dayDisplayFormat: string; disabled: boolean; - displayMode: 'dateRange' | 'date'; + displayMode?: 'dateRange' | 'date'; isEndOfMonth: boolean; isEndOfWeek: boolean; isPassive: boolean; @@ -218,15 +218,4 @@ class DayCell extends Component { } } -export const rangeShape = PropTypes.shape({ - startDate: PropTypes.object, - endDate: PropTypes.object, - color: PropTypes.string, - key: PropTypes.string, - autoFocus: PropTypes.bool, - disabled: PropTypes.bool, - showDateDisplay: PropTypes.bool, -}); - - export default DayCell; diff --git a/src/components/DefinedRange/__snapshots__/index.test.tsx.snap b/src/components/DefinedRange/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..d0deb7bce --- /dev/null +++ b/src/components/DefinedRange/__snapshots__/index.test.tsx.snap @@ -0,0 +1,109 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DefinedRange tests Should render dynamic static label contents correctly 1`] = ` +
+
+ + + + +
+
+
+`; diff --git a/src/components/DefinedRange/index.test.js b/src/components/DefinedRange/index.test.js deleted file mode 100644 index 7a56f34ab..000000000 --- a/src/components/DefinedRange/index.test.js +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react'; -import { mount, shallow } from 'enzyme'; - -import DefinedRange from '../DefinedRange'; -import { isSameDay } from 'date-fns'; - -describe('DefinedRange tests', () => { - test('Should call "renderStaticRangeLabel" callback correct amount of times according to the "hasCustomRendering" option', () => { - const renderStaticRangeLabel = jest.fn(); - - mount( - - ); - - expect(renderStaticRangeLabel).toHaveBeenCalledTimes(2); - }); - - test('Should render dynamic static label contents correctly', () => { - const renderItalicLabelContent = () => ( - {'Italic Content'} - ); - const renderBoldLabelContent = () => {'Bold Content'}; - const renderSomethingElse = () => ; - - const renderStaticRangeLabel = function(staticRange) { - let result; - - if (staticRange.id === 'italic') { - result = renderItalicLabelContent(); - } else if (staticRange.id === 'bold') { - result = renderBoldLabelContent(); - } else { - result = renderSomethingElse(); - } - - return result; - }; - - const wrapper = shallow( - - ); - - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/components/DefinedRange/index.test.tsx b/src/components/DefinedRange/index.test.tsx new file mode 100644 index 000000000..76a037bd1 --- /dev/null +++ b/src/components/DefinedRange/index.test.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import DefinedRange from '.'; +import { StaticRange } from '../../types'; +import { createStaticRanges } from '../..'; + +describe('DefinedRange tests', () => { + test('Should call "renderStaticRangeLabel" callback correct amount of times according to the "hasCustomRendering" option', () => { + const renderStaticRangeLabel = jest.fn(); + const staticRanges = createStaticRanges([ + { + label: 'Dynamic Label', + range: {}, + hasCustomRendering: true, + }, + { + label: 'Static Label', + range: {}, + }, + { + label: 'Hede', + range: {}, + hasCustomRendering: true, + }, + ]); + console.log('staticRanges', staticRanges); + mount( + + ); + + expect(renderStaticRangeLabel).toHaveBeenCalledTimes(2); + }); + + test('Should render dynamic static label contents correctly', () => { + const renderItalicLabelContent = () => ( + {'Italic Content'} + ); + const renderBoldLabelContent = () => {'Bold Content'}; + const renderSomethingElse = () => ; + + const renderStaticRangeLabel = function(staticRange: StaticRange): JSX.Element { + let result; + + if (staticRange.id === 'italic') { + result = renderItalicLabelContent(); + } else if (staticRange.id === 'bold') { + result = renderBoldLabelContent(); + } else { + result = renderSomethingElse(); + } + + return result; + }; + + const wrapper = shallow( + + ); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/components/DefinedRange/index.js b/src/components/DefinedRange/index.tsx similarity index 50% rename from src/components/DefinedRange/index.js rename to src/components/DefinedRange/index.tsx index dfc76f9d7..7e3280594 100644 --- a/src/components/DefinedRange/index.js +++ b/src/components/DefinedRange/index.tsx @@ -1,54 +1,83 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import styles from '../../styles'; import { defaultInputRangesGen, defaultStaticRangesGen } from '../../defaultRanges'; -import { rangeShape } from '../DayCell'; import InputRangeField from '../InputRangeField'; import cx from 'classnames'; +import { InputRange, InputRangeWihLabel, isSureRange, isWithRangeGen, MaybeMaybeRange, OtherRangeProps, Range, RangeFocus, StaticRange, WeekStartsOn } from '../../types'; -class DefinedRange extends Component { - constructor(props) { +type ComponentProps = { + className?: string; + focusedRange: RangeFocus; + footerContent?: JSX.Element; + headerContent?: JSX.Element; + inputRanges: InputRangeWihLabel[]; + onChange?: (keyedRange: { [k: string]: Range; }) => void; + onPreviewChange?: (r?: Range) => void; + rangeColors: string[]; + ranges: MaybeMaybeRange[]; + renderStaticRangeLabel?: (r: StaticRange) => JSX.Element; + staticRanges: StaticRange[]; + weekStartsOn: WeekStartsOn; +} + +export type DefinedRangeProps = ComponentProps; + +class DefinedRange extends Component { + + public static defaultProps = { + ranges: [], + inputRanges: [], + staticRanges: [], + rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], + focusedRange: [0, 0], + weekStartsOn: 1, + }; + // dateOptions: { weekStartsOn: any; inputRanges: InputRangeWihLabel[]; staticRanges: StaticRange[]; }; + + constructor(props: ComponentProps) { super(props); - this.dateOptions = {}; - const weekStartsOn = props.weekStartsOn !== undefined ? props.weekStartsOn : 0; - this.dateOptions = { - weekStartsOn, - inputRanges: - props.inputRanges !== undefined ? props.inputRanges : defaultInputRangesGen(weekStartsOn), - staticRanges: - props.staticRanges !== undefined - ? props.staticRanges - : defaultStaticRangesGen(weekStartsOn), - }; + // const weekStartsOn = this.props.weekStartsOn !== undefined ? this.props.weekStartsOn : 0; + // this.dateOptions = { + // weekStartsOn, + // inputRanges: + // this.props.inputRanges !== undefined ? this.props.inputRanges : defaultInputRangesGen({ weekStartsOn }), + // staticRanges: + // this.props.staticRanges !== undefined + // ? this.props.staticRanges + // : defaultStaticRangesGen({ weekStartsOn }), + // }; this.state = { rangeOffset: 0, focusedInput: -1, }; } - handleRangeChange = range => { + handleRangeChange = (range: Range) => { const { onChange, ranges, focusedRange } = this.props; const selectedRange = ranges[focusedRange[0]]; if (!onChange || !selectedRange) return; onChange({ [selectedRange.key || `range${focusedRange[0] + 1}`]: { ...selectedRange, ...range }, }); - }; + } - getRangeOptionValue(option) { + getRangeOptionValue(option: InputRange) { const { ranges = [], focusedRange = [] } = this.props; if (typeof option.getCurrentValue !== 'function') { return ''; } - const selectedRange = ranges[focusedRange[0]] || {}; + const selectedRange = (focusedRange[0] && ranges[focusedRange[0]]) || {}; + if (!isSureRange(selectedRange)) { + throw new Error('Bug, expecting sure range at this stage'); + } return option.getCurrentValue(selectedRange) || ''; } - getSelectedRange(ranges, staticRange) { + getSelectedRange(ranges: Range[], staticRange: StaticRange) { const focusedRangeIndex = ranges.findIndex(range => { - if (!range.startDate || !range.endDate || range.disabled) return false; + if (!isSureRange(range) || range.disabled) return false; return staticRange.isSelected(range); }); const selectedRange = ranges[focusedRangeIndex]; @@ -57,16 +86,19 @@ class DefinedRange extends Component { render() { const { - headerContent, + className, footerContent, + headerContent, + inputRanges, onPreviewChange, + rangeColors, ranges, renderStaticRangeLabel, - rangeColors, - className, + staticRanges, } = this.props; - const { inputRanges, staticRanges } = this.dateOptions; + console.log('ir', inputRanges); + console.log('sr', staticRanges); return (
@@ -74,13 +106,16 @@ class DefinedRange extends Component {
{staticRanges.map((staticRange, i) => { const { selectedRange, focusedRangeIndex } = this.getSelectedRange(ranges, staticRange); - let labelContent; - - if (staticRange.hasCustomRendering) { - labelContent = renderStaticRangeLabel(staticRange); - } else { - labelContent = staticRange.label; + if (staticRange.hasCustomRendering && !renderStaticRangeLabel) { + throw new Error('You should provie a renderStaticRangeLabel function when setting staticRange.hasCustomRendering = true'); + } + if (!staticRange.label && !renderStaticRangeLabel) { + throw new Error('Either provie a range with a label property or a renderStaticRangeLabel function to this component'); } + const labelContent = renderStaticRangeLabel && staticRange.hasCustomRendering + ? renderStaticRangeLabel(staticRange) + : staticRange.label; + const actualStaticRange = isWithRangeGen(staticRange) ? staticRange.range(this.props) : staticRange.range as Range; return ( - - - -
-
- - -
-
-`; diff --git a/src/components/DefinedRange/__snapshots__/index.test.tsx.snap b/src/components/DefinedRange/__snapshots__/index.test.tsx.snap index d0deb7bce..d71a14bf1 100644 --- a/src/components/DefinedRange/__snapshots__/index.test.tsx.snap +++ b/src/components/DefinedRange/__snapshots__/index.test.tsx.snap @@ -104,6 +104,139 @@ exports[`DefinedRange tests Should render dynamic static label contents correctl
+ > + + +
`; diff --git a/src/components/DefinedRange/index.test.tsx b/src/components/DefinedRange/index.test.tsx index 76a037bd1..1c4b630f7 100644 --- a/src/components/DefinedRange/index.test.tsx +++ b/src/components/DefinedRange/index.test.tsx @@ -3,7 +3,7 @@ import { mount, shallow } from 'enzyme'; import DefinedRange from '.'; import { StaticRange } from '../../types'; -import { createStaticRanges } from '../..'; +import { createStaticRanges, defaultInputRanges } from '../..'; describe('DefinedRange tests', () => { test('Should call "renderStaticRangeLabel" callback correct amount of times according to the "hasCustomRendering" option', () => { @@ -24,7 +24,6 @@ describe('DefinedRange tests', () => { hasCustomRendering: true, }, ]); - console.log('staticRanges', staticRanges); mount( { public static defaultProps = { ranges: [], - inputRanges: [], - staticRanges: [], + staticRanges: defaultStaticRanges, + inputRanges: defaultInputRanges, rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'], focusedRange: [0, 0], weekStartsOn: 1, @@ -69,9 +70,6 @@ class DefinedRange extends Component { } const selectedRange = (focusedRange[0] && ranges[focusedRange[0]]) || {}; - if (!isSureRange(selectedRange)) { - throw new Error('Bug, expecting sure range at this stage'); - } return option.getCurrentValue(selectedRange) || ''; } @@ -97,9 +95,6 @@ class DefinedRange extends Component { staticRanges, } = this.props; - console.log('ir', inputRanges); - console.log('sr', staticRanges); - return (
{headerContent} diff --git a/src/components/InputRangeField/__snapshots__/index.test.js.snap b/src/components/InputRangeField/__snapshots__/index.test.js.snap deleted file mode 100644 index 0a03f7ac9..000000000 --- a/src/components/InputRangeField/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,88 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InputRangeField tests Should parse input value to number 1`] = ` - -
- - - Input label - -
-
-`; diff --git a/src/defaultRanges.ts b/src/defaultRanges.ts index e29221258..aececa7bb 100644 --- a/src/defaultRanges.ts +++ b/src/defaultRanges.ts @@ -11,7 +11,7 @@ import { differenceInCalendarDays, } from 'date-fns'; import { compose } from 'ramda'; -import { InputRangeWihLabel, isWithRangeGen, Range, SureStartEndDate, WeekStartsOn, WithIsSelected, WithRangeOrRangeGen } from './types'; +import { InputRangeWihLabel, isWithRangeGen, MaybeMaybeRange, Range, SureStartEndDate, WeekStartsOn, WithIsSelected, WithRangeOrRangeGen } from './types'; type GenProps = { weekStartsOn: WeekStartsOn; } const definedsGen = ({ weekStartsOn }: GenProps): DefinedDates => ({ @@ -58,7 +58,6 @@ export function isSameRangeDay(someRange: Range, otherRange: Range): boolean { || (!someRange.startDate && !otherRange.startDate); const isSameEnd = (someRange.endDate && otherRange.endDate && isSameDay(someRange.endDate, otherRange.endDate)) || (!someRange.endDate && !otherRange.endDate); - console.log('isSame start end', isSameStart, isSameEnd); return isSameStart && isSameEnd; } @@ -138,8 +137,8 @@ export const defaultInputRangesGenerator = (defineds: DefinedDates): InputRangeW endDate: defineds.endOfToday, }; }, - getCurrentValue(range: SureStartEndDate) { - if (!isSameDay(range.endDate, defineds.endOfToday)) return '-'; + getCurrentValue(range: Range) { + if (!range.startDate || !isSameDay(range.startDate, defineds.startOfToday)) return '-'; if (!range.startDate) return '∞'; return differenceInCalendarDays(defineds.endOfToday, range.startDate) + 1; }, @@ -153,8 +152,8 @@ export const defaultInputRangesGenerator = (defineds: DefinedDates): InputRangeW endDate: addDays(today, Math.max(Number(value), 1) - 1), }; }, - getCurrentValue(range: SureStartEndDate) { - if (!isSameDay(range.startDate, defineds.startOfToday)) return '-'; + getCurrentValue(range: Range) { + if (!range.startDate || !isSameDay(range.startDate, defineds.startOfToday)) return '-'; if (!range.endDate) return '∞'; return differenceInCalendarDays(range.endDate, defineds.startOfToday) + 1; }, diff --git a/src/types.ts b/src/types.ts index 4e924b8c5..cc47e9e81 100644 --- a/src/types.ts +++ b/src/types.ts @@ -314,7 +314,7 @@ export type StaticRangeWihLabel = StaticRange & WithLabel & WithIsSelected; export interface InputRange { range: (value: Date | number, props?: CommonCalendarProps) => Range; - getCurrentValue: (range: SureStartEndDate) => number | "-" | "∞"; + getCurrentValue: (range: Range) => number | "-" | "∞"; } export type InputRangeWihLabel = InputRange & WithLabel; From b378c84b3f215bf891817dc7eef513ffe0b0ac62 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Wed, 13 Oct 2021 16:39:03 +0200 Subject: [PATCH 09/34] refactor: remove unused comment --- src/components/DefinedRange/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/components/DefinedRange/index.tsx b/src/components/DefinedRange/index.tsx index 03be68381..e8cce356f 100644 --- a/src/components/DefinedRange/index.tsx +++ b/src/components/DefinedRange/index.tsx @@ -33,20 +33,9 @@ class DefinedRange extends Component { focusedRange: [0, 0], weekStartsOn: 1, }; - // dateOptions: { weekStartsOn: any; inputRanges: InputRangeWihLabel[]; staticRanges: StaticRange[]; }; constructor(props: ComponentProps) { super(props); - // const weekStartsOn = this.props.weekStartsOn !== undefined ? this.props.weekStartsOn : 0; - // this.dateOptions = { - // weekStartsOn, - // inputRanges: - // this.props.inputRanges !== undefined ? this.props.inputRanges : defaultInputRangesGen({ weekStartsOn }), - // staticRanges: - // this.props.staticRanges !== undefined - // ? this.props.staticRanges - // : defaultStaticRangesGen({ weekStartsOn }), - // }; this.state = { rangeOffset: 0, focusedInput: -1, From 65022cd73cbe765043f79fda1600fa0af85b78c4 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Wed, 13 Oct 2021 16:43:37 +0200 Subject: [PATCH 10/34] refactor: rename MaybeMaybe to something more explicit --- src/components/DateRange/index.test.tsx | 4 ++-- src/components/DateRange/index.tsx | 10 +++++----- src/components/DefinedRange/index.tsx | 4 ++-- src/types.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/DateRange/index.test.tsx b/src/components/DateRange/index.test.tsx index 236b0141e..71c56809e 100644 --- a/src/components/DateRange/index.test.tsx +++ b/src/components/DateRange/index.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { subDays, addDays, isSameDay } from 'date-fns'; import DateRange from '.'; import renderer, { ReactTestRenderer } from 'react-test-renderer'; -import { isSureRange, MaybeMaybeRange, SureStartEndDate } from '../../types'; +import { isSureRange, NotFullyEmptyRange, SureStartEndDate } from '../../types'; let testRenderer: ReactTestRenderer; let instance: DateRange; @@ -15,7 +15,7 @@ const commonProps = { moveRangeOnFirstSelection: false, }; -const compareRanges = (assertionRange: MaybeMaybeRange, newRange?: MaybeMaybeRange) => { +const compareRanges = (assertionRange: NotFullyEmptyRange, newRange?: NotFullyEmptyRange) => { if (!newRange) { return expect(typeof newRange).toEqual(typeof assertionRange); } diff --git a/src/components/DateRange/index.tsx b/src/components/DateRange/index.tsx index 5de8f1bd2..2c00fa392 100644 --- a/src/components/DateRange/index.tsx +++ b/src/components/DateRange/index.tsx @@ -3,13 +3,13 @@ import Calendar from '../Calendar'; import { findNextRangeIndex, generateStyles } from '../../utils'; import { isBefore, differenceInCalendarDays, addDays, min, isWithinInterval, max } from 'date-fns'; import coreStyles, { ClassNames } from '../../styles'; -import { DateRangeProps, isNoEndDateRange, isRangeValue, isSureRange, MaybeMaybeRange, Preview, Range, RangeFocus, SureRange, SureStartEndDate } from '../../types'; +import { DateRangeProps, isNoEndDateRange, isRangeValue, isSureRange, NotFullyEmptyRange, Preview, Range, RangeFocus, SureRange, SureStartEndDate } from '../../types'; import classnames from 'classnames'; import { compose } from 'ramda'; type DefaultComponentProps = { classNames: Partial; - ranges: MaybeMaybeRange[]; + ranges: NotFullyEmptyRange[]; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; rangeColors: string[]; @@ -43,7 +43,7 @@ const calculateEndDate = (value: Date, now: Date, endDate: Date | null, dayOffse type ShapeChangingParams = { value: Date | Range; }; type BaseParams = { focusedRange: RangeFocus; disabledDates: Date[]; ranges: Range[]; } -type ComputeProps = BaseParams & ShapeChangingParams & { selectedRange: MaybeMaybeRange; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; maxDate?: Date; }; +type ComputeProps = BaseParams & ShapeChangingParams & { selectedRange: NotFullyEmptyRange; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; maxDate?: Date; }; function computeStartDateEndDate({ value, selectedRange, focusedRange, disabledDates, moveRangeOnFirstSelection, retainEndDateOnFirstSelection, ranges, maxDate }: ComputeProps) { const base = { isStartDateSelected: focusedRange[1] === 0, @@ -81,7 +81,7 @@ function computeStartDateEndDate({ value, selectedRange, focusedRange, disabledD } } -type FlipProps = BaseParams & MaybeMaybeRange & { nextFocusRange?: RangeFocus; isStartDateSelected: boolean; }; +type FlipProps = BaseParams & NotFullyEmptyRange & { nextFocusRange?: RangeFocus; isStartDateSelected: boolean; }; const flipIfReversed = (params: FlipProps) => { const { startDate, endDate, isStartDateSelected } = params; return endDate && isBefore(endDate, startDate) @@ -221,7 +221,7 @@ class DateRange extends Component { this.props.onRangeFocusChange && this.props.onRangeFocusChange(focusedRange); }; - updatePreview = (val?: { range: MaybeMaybeRange; }) => { + updatePreview = (val?: { range: NotFullyEmptyRange; }) => { if (!val) { this.setState({ preview: null }); return; diff --git a/src/components/DefinedRange/index.tsx b/src/components/DefinedRange/index.tsx index e8cce356f..3ef118921 100644 --- a/src/components/DefinedRange/index.tsx +++ b/src/components/DefinedRange/index.tsx @@ -3,7 +3,7 @@ import styles from '../../styles'; import { defaultInputRangesGen, defaultStaticRangesGen } from '../../defaultRanges'; import InputRangeField from '../InputRangeField'; import cx from 'classnames'; -import { InputRange, InputRangeWihLabel, isSureRange, isWithRangeGen, MaybeMaybeRange, OtherRangeProps, Range, RangeFocus, StaticRange, WeekStartsOn } from '../../types'; +import { InputRange, InputRangeWihLabel, isSureRange, isWithRangeGen, NotFullyEmptyRange, OtherRangeProps, Range, RangeFocus, StaticRange, WeekStartsOn } from '../../types'; import { defaultInputRanges, defaultStaticRanges } from '../..'; type ComponentProps = { @@ -15,7 +15,7 @@ type ComponentProps = { onChange?: (keyedRange: { [k: string]: Range; }) => void; onPreviewChange?: (r?: Range) => void; rangeColors: string[]; - ranges: MaybeMaybeRange[]; + ranges: NotFullyEmptyRange[]; renderStaticRangeLabel?: (r: StaticRange) => JSX.Element; staticRanges: StaticRange[]; weekStartsOn: WeekStartsOn; diff --git a/src/types.ts b/src/types.ts index cc47e9e81..717299489 100644 --- a/src/types.ts +++ b/src/types.ts @@ -239,9 +239,9 @@ export type SureStartNoEndDate = { endDate: null; } -export type MaybeMaybeRange = OtherRangeProps & (SureStartEndDate | SureStartMaybeEndDate | SureStartNoEndDate); +export type NotFullyEmptyRange = OtherRangeProps & (SureStartEndDate | SureStartMaybeEndDate | SureStartNoEndDate); -export function isNoEndDateRange(r: MaybeMaybeRange): r is SureStartNoEndDate { +export function isNoEndDateRange(r: NotFullyEmptyRange): r is SureStartNoEndDate { return r.hasOwnProperty('startDate') && r.endDate === null; } @@ -327,6 +327,6 @@ export type DefinedRange = StaticRange | InputRange; */ export type RangeFocus = [number, number]; -export type Preview = MaybeMaybeRange & { +export type Preview = NotFullyEmptyRange & { color?: string; } From 0f05fb36387bb551d33ece7ae77497b471b76ee9 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Wed, 13 Oct 2021 16:46:24 +0200 Subject: [PATCH 11/34] refactor: rename Range to express it may be empty --- src/components/Calendar/index.tsx | 6 ++--- src/components/DateRange/index.tsx | 10 ++++----- src/components/DateRangePicker/index.tsx | 4 ++-- src/components/DayCell/index.tsx | 9 ++++---- src/components/DefinedRange/index.tsx | 12 +++++----- src/components/Month/index.tsx | 8 +++---- src/defaultRanges.ts | 12 +++++----- src/types.ts | 28 ++++++++++++------------ src/utils.tsx | 10 ++++----- 9 files changed, 48 insertions(+), 51 deletions(-) diff --git a/src/components/Calendar/index.tsx b/src/components/Calendar/index.tsx index fe6956b1e..1306340f5 100644 --- a/src/components/Calendar/index.tsx +++ b/src/components/Calendar/index.tsx @@ -27,7 +27,7 @@ import { } from 'date-fns'; import defaultLocale from 'date-fns/locale/en-US'; import coreStyles, { ClassNames } from '../../styles'; -import { AriaLabelShape, CalendarDirection, CalendarFocus, CommonCalendarProps, DateOptions, DisplayMode, isModeMapperKey, isSureRange, Preview, Range, RangeFocus, ScrollOptions, SureStartEndDate } from '../../types'; +import { AriaLabelShape, CalendarDirection, CalendarFocus, CommonCalendarProps, DateOptions, DisplayMode, isModeMapperKey, isSureRange, Preview, MaybeEmptyRange, RangeFocus, ScrollOptions, SureStartEndDate } from '../../types'; import { DateReceivingFunc, OptionalDateReceivingFunc } from '../DayCell'; type ScrollArea = { @@ -71,7 +71,7 @@ type DefaultCalendarProps = CalcFocusDateProps & { preventSnapRefocus: boolean; preview?: Preview | null; rangeColors: string[]; - ranges: Range[]; + ranges: MaybeEmptyRange[]; scroll: { enabled: boolean; }, @@ -481,7 +481,7 @@ class Calendar extends PureComponent { onChange && onChange(date); return; } - const newRange: Range = { + const newRange: MaybeEmptyRange = { startDate: this.state.drag.range.startDate || date, endDate: date, }; diff --git a/src/components/DateRange/index.tsx b/src/components/DateRange/index.tsx index 2c00fa392..bbbdef216 100644 --- a/src/components/DateRange/index.tsx +++ b/src/components/DateRange/index.tsx @@ -3,7 +3,7 @@ import Calendar from '../Calendar'; import { findNextRangeIndex, generateStyles } from '../../utils'; import { isBefore, differenceInCalendarDays, addDays, min, isWithinInterval, max } from 'date-fns'; import coreStyles, { ClassNames } from '../../styles'; -import { DateRangeProps, isNoEndDateRange, isRangeValue, isSureRange, NotFullyEmptyRange, Preview, Range, RangeFocus, SureRange, SureStartEndDate } from '../../types'; +import { DateRangeProps, isNoEndDateRange, isRangeValue, isSureRange, NotFullyEmptyRange, Preview, MaybeEmptyRange, RangeFocus, SureRange, SureStartEndDate } from '../../types'; import classnames from 'classnames'; import { compose } from 'ramda'; @@ -41,8 +41,8 @@ const calculateEndDate = (value: Date, now: Date, endDate: Date | null, dayOffse return nextEndDate; }; -type ShapeChangingParams = { value: Date | Range; }; -type BaseParams = { focusedRange: RangeFocus; disabledDates: Date[]; ranges: Range[]; } +type ShapeChangingParams = { value: Date | MaybeEmptyRange; }; +type BaseParams = { focusedRange: RangeFocus; disabledDates: Date[]; ranges: MaybeEmptyRange[]; } type ComputeProps = BaseParams & ShapeChangingParams & { selectedRange: NotFullyEmptyRange; moveRangeOnFirstSelection: boolean; retainEndDateOnFirstSelection: boolean; maxDate?: Date; }; function computeStartDateEndDate({ value, selectedRange, focusedRange, disabledDates, moveRangeOnFirstSelection, retainEndDateOnFirstSelection, ranges, maxDate }: ComputeProps) { const base = { @@ -94,7 +94,7 @@ const flipIfReversed = (params: FlipProps) => { : params; } -const getNextFocusRange = (ranges: Range[], focusedRange: RangeFocus, nextFocusRange?: RangeFocus): RangeFocus => { +const getNextFocusRange = (ranges: MaybeEmptyRange[], focusedRange: RangeFocus, nextFocusRange?: RangeFocus): RangeFocus => { if (nextFocusRange) return nextFocusRange; const nextFocusRangeIndex = findNextRangeIndex(ranges, focusedRange[0]); return [nextFocusRangeIndex, 0]; @@ -160,7 +160,7 @@ class DateRange extends Component { this.calendar = null; } - calcNewSelection = (value: Date | Range): CalcNewSelectionRet => { + calcNewSelection = (value: Date | MaybeEmptyRange): CalcNewSelectionRet => { const focusedRange = this.props.focusedRange || this.state.focusedRange; const { ranges, diff --git a/src/components/DateRangePicker/index.tsx b/src/components/DateRangePicker/index.tsx index 234e51c9f..b89bcc6e7 100644 --- a/src/components/DateRangePicker/index.tsx +++ b/src/components/DateRangePicker/index.tsx @@ -3,8 +3,8 @@ import DateRange from '../DateRange'; import DefinedRange, { DefinedRangeProps } from '../DefinedRange'; import { findNextRangeIndex, generateStyles } from '../../utils'; import classnames from 'classnames'; -import coreStyles, { ClassNames, CoreStyles } from '../../styles'; -import { DateRangeProps, Range, RangeFocus } from '../../types'; +import coreStyles, { ClassNames } from '../../styles'; +import { DateRangeProps, RangeFocus } from '../../types'; type ComponentProps = DateRangeProps & DefinedRangeProps & { className: string, diff --git a/src/components/DayCell/index.tsx b/src/components/DayCell/index.tsx index 6457217e9..43ab06dee 100644 --- a/src/components/DayCell/index.tsx +++ b/src/components/DayCell/index.tsx @@ -1,16 +1,15 @@ /* eslint-disable no-fallthrough */ import React, { Component, FocusEvent, KeyboardEvent, MouseEvent } from 'react'; -import PropTypes from 'prop-types'; import classnames from 'classnames'; import { startOfDay, format, isSameDay, isAfter, isBefore, endOfDay } from 'date-fns'; -import { Preview, Range } from '../../types'; +import { Preview, MaybeEmptyRange } from '../../types'; import { CoreStyles } from '../../styles'; export type InRange = ({ isStartEdge: boolean; isEndEdge: boolean; isInRange: boolean; -} & Range); +} & MaybeEmptyRange); export type DateReceivingFunc = (date: Date) => void; export type OptionalDateReceivingFunc = (date?: Date) => void; @@ -36,7 +35,7 @@ type ComponentProps = { onPreviewChange?: OptionalDateReceivingFunc; preview?: Preview | null; previewColor?: string; - ranges: Range[], + ranges: MaybeEmptyRange[], styles: CoreStyles; }; @@ -151,7 +150,7 @@ class DayCell extends Component { ) : null; } - const inRanges = ranges.reduce((result: InRange[], range: Range) => { + const inRanges = ranges.reduce((result: InRange[], range: MaybeEmptyRange) => { const st = range.startDate; const en = range.endDate; const [start, end] = (st && en && isBefore(en, st)) ? [en, st] : [st, en]; diff --git a/src/components/DefinedRange/index.tsx b/src/components/DefinedRange/index.tsx index 3ef118921..b2290748e 100644 --- a/src/components/DefinedRange/index.tsx +++ b/src/components/DefinedRange/index.tsx @@ -3,7 +3,7 @@ import styles from '../../styles'; import { defaultInputRangesGen, defaultStaticRangesGen } from '../../defaultRanges'; import InputRangeField from '../InputRangeField'; import cx from 'classnames'; -import { InputRange, InputRangeWihLabel, isSureRange, isWithRangeGen, NotFullyEmptyRange, OtherRangeProps, Range, RangeFocus, StaticRange, WeekStartsOn } from '../../types'; +import { InputRange, InputRangeWihLabel, isSureRange, isWithRangeGen, NotFullyEmptyRange, OtherRangeProps, MaybeEmptyRange, RangeFocus, StaticRange, WeekStartsOn } from '../../types'; import { defaultInputRanges, defaultStaticRanges } from '../..'; type ComponentProps = { @@ -12,8 +12,8 @@ type ComponentProps = { footerContent?: JSX.Element; headerContent?: JSX.Element; inputRanges: InputRangeWihLabel[]; - onChange?: (keyedRange: { [k: string]: Range; }) => void; - onPreviewChange?: (r?: Range) => void; + onChange?: (keyedRange: { [k: string]: MaybeEmptyRange; }) => void; + onPreviewChange?: (r?: MaybeEmptyRange) => void; rangeColors: string[]; ranges: NotFullyEmptyRange[]; renderStaticRangeLabel?: (r: StaticRange) => JSX.Element; @@ -42,7 +42,7 @@ class DefinedRange extends Component { }; } - handleRangeChange = (range: Range) => { + handleRangeChange = (range: MaybeEmptyRange) => { const { onChange, ranges, focusedRange } = this.props; const selectedRange = ranges[focusedRange[0]]; if (!onChange || !selectedRange) return; @@ -62,7 +62,7 @@ class DefinedRange extends Component { return option.getCurrentValue(selectedRange) || ''; } - getSelectedRange(ranges: Range[], staticRange: StaticRange) { + getSelectedRange(ranges: MaybeEmptyRange[], staticRange: StaticRange) { const focusedRangeIndex = ranges.findIndex(range => { if (!isSureRange(range) || range.disabled) return false; return staticRange.isSelected(range); @@ -99,7 +99,7 @@ class DefinedRange extends Component { const labelContent = renderStaticRangeLabel && staticRange.hasCustomRendering ? renderStaticRangeLabel(staticRange) : staticRange.label; - const actualStaticRange = isWithRangeGen(staticRange) ? staticRange.range(this.props) : staticRange.range as Range; + const actualStaticRange = isWithRangeGen(staticRange) ? staticRange.range(this.props) : staticRange.range as MaybeEmptyRange; return ( From fd779e07dd3201d523dfa35cd1782e9be2b35202 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Fri, 15 Oct 2021 13:19:45 +0200 Subject: [PATCH 31/34] refactor: simplify --- .../__snapshots__/index.test.tsx.snap | 1062 +++++++++++++++++ src/components/Calendar/index.test.tsx | 20 + src/components/DateRange/index.tsx | 5 +- .../__snapshots__/index.test.tsx.snap | 6 +- src/components/DateRangePicker/index.test.tsx | 6 +- 5 files changed, 1089 insertions(+), 10 deletions(-) create mode 100644 src/components/Calendar/__snapshots__/index.test.tsx.snap create mode 100644 src/components/Calendar/index.test.tsx diff --git a/src/components/Calendar/__snapshots__/index.test.tsx.snap b/src/components/Calendar/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..a6fe8f7f8 --- /dev/null +++ b/src/components/Calendar/__snapshots__/index.test.tsx.snap @@ -0,0 +1,1062 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Calendar tests Should render 1`] = ` +
+
+
+ + + + + + + + + + + +
+
+ +
+
+`; diff --git a/src/components/Calendar/index.test.tsx b/src/components/Calendar/index.test.tsx new file mode 100644 index 000000000..b41dd3401 --- /dev/null +++ b/src/components/Calendar/index.test.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import Calendar from '.'; + +describe('Calendar tests', () => { + test('Should render', () => { + Date.now = jest.fn(() => 1482363367071); + const wrapper = shallow( + { + console.log('item selected:', item); + }} + /> + ); + + expect(wrapper).toMatchSnapshot(); + }); +}); \ No newline at end of file diff --git a/src/components/DateRange/index.tsx b/src/components/DateRange/index.tsx index ca61c2344..74b9757c0 100644 --- a/src/components/DateRange/index.tsx +++ b/src/components/DateRange/index.tsx @@ -237,10 +237,7 @@ class DateRange extends Component { focusedRange={this.state.focusedRange} onRangeFocusChange={this.handleRangeFocusChange} preview={this.state.preview} - onPreviewChange={(date?: Date) => { - const newSelection = date ? this.calcNewSelection(date) : undefined; - this.updatePreview(newSelection); - } } + onPreviewChange={(date?: Date) => this.updatePreview(date && this.calcNewSelection(date))} {...this.props} displayMode={"dateRange"} className={classnames(this.styles.dateRangeWrapper, this.props.className)} diff --git a/src/components/DateRangePicker/__snapshots__/index.test.tsx.snap b/src/components/DateRangePicker/__snapshots__/index.test.tsx.snap index 6f1895bd0..9fb849e42 100644 --- a/src/components/DateRangePicker/__snapshots__/index.test.tsx.snap +++ b/src/components/DateRangePicker/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DateRangePicker tests Should render dynamic static label contents correctly 1`] = ` +exports[`DateRangePicker tests Should render 1`] = `
@@ -71,7 +71,7 @@ exports[`DateRangePicker tests Should render dynamic static label contents corre }, ] } - weekStartsOn={1} + weekStartsOn={0} />
`; diff --git a/src/components/DateRangePicker/index.test.tsx b/src/components/DateRangePicker/index.test.tsx index ed619e7df..404837d04 100644 --- a/src/components/DateRangePicker/index.test.tsx +++ b/src/components/DateRangePicker/index.test.tsx @@ -23,12 +23,12 @@ describe('DateRangePicker tests', () => { expect(renderStaticRangeLabel).toHaveBeenCalledTimes(0); }); - test('Should render dynamic static label contents correctly', () => { - + test('Should render', () => { + Date.now = jest.fn(() => 1482363367071); const wrapper = shallow( { if (!item || item.selection === undefined) return; From 50166a0513f327242a4d379ef8699d06feab3b86 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Sun, 17 Oct 2021 12:53:29 +0200 Subject: [PATCH 32/34] refactor: make clearer what the function is --- src/components/DefinedRange/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/DefinedRange/index.tsx b/src/components/DefinedRange/index.tsx index b3367563a..60c80ff75 100644 --- a/src/components/DefinedRange/index.tsx +++ b/src/components/DefinedRange/index.tsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import styles from '../../styles'; import { defaultInputRangesGen, defaultStaticRangesGen } from '../../defaultRanges'; import InputRangeField from '../InputRangeField'; -import cx from 'classnames'; +import classnames from 'classnames'; import { InputRange, InputRangeWihLabel, isSureRange, isWithRangeGen, NotFullyEmptyRange, OtherRangeProps, MaybeEmptyRange, RangeFocus, StaticRange, WeekStartsOn } from '../../types'; import { defaultInputRanges, defaultStaticRanges } from '../..'; @@ -106,7 +106,7 @@ class DefinedRange extends Component { } = this.rangesRespectingWeekStartsOn; return ( -
+
{headerContent}
{staticRanges.map((staticRange, i) => { @@ -125,7 +125,7 @@ class DefinedRange extends Component { return ( - ); - })} -
-
- {inputRanges.map((rangeOption, i) => ( - = (props) => { + const allProps: ComponentProps = { ...defaultProps, ...props }; + + const weekStartsOn = allProps.weekStartsOn !== undefined && allProps.weekStartsOn || DEFAULT_WEEK_STARTS_ON; + // commented out by g because unused + // const [rangeOffset, setRangeOffset] = useState(0); + // const [focusedInput, setFocusedInput] = useState(-1); + const [rangesRespectingWeekStartsOn,] = useState((allProps.weekStartsOn !== undefined && allProps.weekStartsOn !== DEFAULT_WEEK_STARTS_ON) + ? { + staticRanges: defaultStaticRangesGen({ weekStartsOn }), + inputRanges: defaultInputRangesGen({ weekStartsOn }), + } + : { + staticRanges: allProps.staticRanges || defaultStaticRanges, + inputRanges: allProps.inputRanges || defaultInputRanges, + } + ); + + const { + className, + footerContent, + headerContent, + onPreviewChange, + rangeColors, + ranges, + renderStaticRangeLabel, + } = allProps; + + const { + inputRanges, + staticRanges, + } = rangesRespectingWeekStartsOn; + + return ( +
+ {headerContent} +
+ {staticRanges.map((staticRange, i) => { + const { selectedRange, focusedRangeIndex } = getSelectedRange(ranges || [], staticRange); + if (staticRange.hasCustomRendering && !renderStaticRangeLabel) { + throw new Error('You should provie a renderStaticRangeLabel function when setting staticRange.hasCustomRendering = true'); + } + if (!staticRange.label && !renderStaticRangeLabel) { + throw new Error('Either provie a range with a label property or a renderStaticRangeLabel function to this component'); + } + const labelContent = renderStaticRangeLabel && staticRange.hasCustomRendering + ? renderStaticRangeLabel(staticRange) + : staticRange.label; + const actualStaticRange = hasRangeGen(staticRange) ? staticRange.range(allProps) : staticRange.range as MaybeEmptyRange; + + return ( +
- {footerContent} + onClick={() => handleRangeChange(actualStaticRange, allProps)} + onFocus={() => onPreviewChange && onPreviewChange(actualStaticRange)} + onMouseOver={() => + onPreviewChange && onPreviewChange(actualStaticRange) + } + onMouseLeave={() => { + onPreviewChange && onPreviewChange(); + }}> + + {labelContent} + + + ); + })}
- ); - } +
+ {inputRanges.map((rangeOption, i) => ( + { setFocusedInput(i), setRangeOffset(0) }} + // onBlur={() => setRangeOffset(0)} + onFocus={() => {}} + onBlur={() => {}} + onChange={value => handleRangeChange(rangeOption.range(value, allProps), allProps)} + value={getRangeOptionValue(rangeOption, allProps)} + /> + ))} +
+ {footerContent} +
+ ); } export default DefinedRange; diff --git a/src/defaultRanges.ts b/src/defaultRanges.ts index 01e338330..0d5c171da 100644 --- a/src/defaultRanges.ts +++ b/src/defaultRanges.ts @@ -11,7 +11,9 @@ import { differenceInCalendarDays, } from 'date-fns'; import { compose } from 'ramda'; -import { InputRangeWihLabel, isWithRangeGen, MaybeEmptyRange, SureStartEndDate, WeekStartsOn, WithIsSelected, WithRangeOrRangeGen } from './types'; +import { InputRangeWihLabel, hasRangeGen, MaybeEmptyRange, SureStartEndDate, WeekStartsOn, WithIsSelected, WithRangeOrRangeGen } from './types'; + +export const DEFAULT_WEEK_STARTS_ON: WeekStartsOn = 0; type GenProps = { weekStartsOn: WeekStartsOn; } export const definedsGen = ({ weekStartsOn }: GenProps): DefinedDates => ({ @@ -44,7 +46,7 @@ type DefinedDates = { endOfLastMonth: Date; }; -const defineds: DefinedDates = definedsGen({ weekStartsOn: 0 }); +const defineds: DefinedDates = definedsGen({ weekStartsOn: DEFAULT_WEEK_STARTS_ON }); export function getEmptyRange(startDate: Date | number = 0, endDate: Date | number = 0): SureStartEndDate { return { @@ -62,7 +64,7 @@ export function isSameRangeDay(someRange: MaybeEmptyRange, otherRange: MaybeEmpt } export function extractRange(wr: WithRangeOrRangeGen): MaybeEmptyRange { - if (isWithRangeGen(wr)) { + if (hasRangeGen(wr)) { return wr.range(); } return wr.range as MaybeEmptyRange; diff --git a/src/index.ts b/src/index.ts index fdd50eeb5..68ca98f29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,4 @@ export { default as DateRange } from './components/DateRange'; export { default as Calendar } from './components/Calendar'; export { default as DateRangePicker } from './components/DateRangePicker'; export { default as DefinedRange } from './components/DefinedRange'; -export { defaultInputRanges, defaultStaticRanges, createStaticRanges, defaultInputRangesGen, defaultStaticRangesGen, definedsGen } from './defaultRanges'; +export { DEFAULT_WEEK_STARTS_ON, defaultInputRanges, defaultStaticRanges, createStaticRanges, defaultInputRangesGen, defaultStaticRangesGen, definedsGen } from './defaultRanges'; diff --git a/src/types.ts b/src/types.ts index f0914931d..8854e0de2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -248,10 +248,10 @@ export type WithRangeGen = { range: RangeGen; }; export type WithRange = { range: MaybeEmptyRange; }; export type WithRangeOrRangeGen = { range: MaybeEmptyRange | RangeGen; }; -export function isWithRangeGen(wr: WithRangeOrRangeGen): wr is WithRangeGen { +export function hasRangeGen(wr: WithRangeOrRangeGen): wr is WithRangeGen { return typeof wr.range === 'function'; } -export function isWithRange(wr: WithRangeOrRangeGen): wr is WithRange { +export function hasRange(wr: WithRangeOrRangeGen): wr is WithRange { return typeof wr.range !== 'function'; }