From e5fdb745139a1199141168eceaf8260f1c14d5aa Mon Sep 17 00:00:00 2001 From: Artur-A Date: Sun, 12 Feb 2017 07:40:45 +0300 Subject: [PATCH 1/5] types for airbnb react-dates --- react-dates/index.d.ts | 364 +++++++++++++++++++++++++++++++ react-dates/react-dates-test.tsx | 357 ++++++++++++++++++++++++++++++ react-dates/tsconfig.json | 24 ++ 3 files changed, 745 insertions(+) create mode 100644 react-dates/index.d.ts create mode 100644 react-dates/react-dates-test.tsx create mode 100644 react-dates/tsconfig.json diff --git a/react-dates/index.d.ts b/react-dates/index.d.ts new file mode 100644 index 0000000000..b1e28cbe92 --- /dev/null +++ b/react-dates/index.d.ts @@ -0,0 +1,364 @@ +// Type definitions for react-dates v7.0.1 +// Project: https://github.com/airbnb/react-dates +// Definitions by: Artur Ampilogov +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 + +import moment from "moment"; +import * as React from "react"; + +export = ReactDates; + +declare namespace momentPropTypes{ + type momentObj = any; + type momentString = any; + type momentDurationObj = any; +} + + +declare namespace ReactDates{ + type AnchorDirectionShape = 'left' | 'right'; + type FocusedInputShape = 'startDate' | 'endDate'; + type OrientationShape = 'horizontal' | 'vertical'; + type ScrollableOrientationShape = 'horizontal' | 'vertical' | 'verticalScrollable'; + + + interface DateRangePickerShape{ + startDate?: momentPropTypes.momentObj, + endDate?: momentPropTypes.momentObj, + focusedInput?: FocusedInputShape, + screenReaderInputMessage?: string, + minimumNights?: number, + isDayBlocked?: (day: any) => boolean, + isOutsideRange?: (day: any) => boolean, + enableOutsideDays?: boolean, + reopenPickerOnClearDates?: boolean, + keepOpenOnDateSelect?: boolean, + numberOfMonths?: number, + showClearDates?: boolean, + disabled?: boolean, + required?: boolean, + showDefaultInputIcon?: boolean, + + orientation?: OrientationShape, + anchorDirection?: AnchorDirectionShape, + horizontalMargin?: number, + // portal options + withPortal?: boolean, + withFullScreenPortal?: boolean, + + startDateId?: string, + startDatePlaceholderText?: string, + endDateId?: string, + endDatePlaceholderText?: string, + + initialVisibleMonth?: () => moment.Moment, + onDatesChange?: (arg: { startDate: any, endDate: any }) => void, + onFocusChange?: (arg: FocusedInputShape) => void, + onPrevMonthClick?: (e: React.EventHandler>) => void, + onNextMonthClick?: (e: React.EventHandler>) => void, + + renderDay?: (day: any) => (string | JSX.Element), + + // i18n + displayFormat?: (string | (()=> string)), + monthFormat?: string, + phrases?: { + closeDatePicker: string | JSX.Element, + clearDates: string | JSX.Element, + } + } + + type DateRangePicker = React.ClassicComponentClass; + var DateRangePicker: React.ClassicComponentClass; + + interface SingleDatePickerShape{ + id: string, + placeholder?: string, + date?: momentPropTypes.momentObj, + focused?: boolean, + showClearDate?: boolean, + reopenPickerOnClearDates?: boolean, + keepOpenOnDateSelect?: boolean, + disabled?: boolean, + required?: boolean, + screenReaderInputMessage?: string, + + onDateChange?: (date: any) => void, + onFocusChange?: (arg: { focused: boolean | null }) => void, + + isDayBlocked?: (day: any) => boolean, + isOutsideRange?: (day: any) => boolean, + enableOutsideDays?: boolean, + numberOfMonths?: number, + orientation?: OrientationShape, + initialVisibleMonth?: () => moment.Moment, + anchorDirection?: AnchorDirectionShape, + horizontalMargin?: number, + + navPrev?: string | JSX.Element, + navNext?: string | JSX.Element, + + // portal options + withPortal?: boolean, + withFullScreenPortal?: boolean, + + onPrevMonthClick?: (e: React.EventHandler>) => void, + onNextMonthClick?: (e: React.EventHandler>) => void, + + renderDay?: (day: any) => (string | JSX.Element), + + // i18n + displayFormat?: (string | (()=> string)), + monthFormat?: string, + phrases?: { + closeDatePicker: string | JSX.Element, + }, + } + type SingleDatePicker = React.ClassicComponentClass; + var SingleDatePicker: React.ClassicComponentClass; + + + + interface DateRangePickerInputControllerShape { + startDate?: momentPropTypes.momentObj, + startDateId?: string, + startDatePlaceholderText?: string, + isStartDateFocused?: boolean, + + endDate?: momentPropTypes.momentObj, + endDateId?: string, + endDatePlaceholderText?: string, + isEndDateFocused?: boolean, + + screenReaderMessage?: string, + showClearDates?: boolean, + showCaret?: boolean, + showDefaultInputIcon?: boolean, + disabled?: boolean, + required?: boolean, + + keepOpenOnDateSelect?: boolean, + reopenPickerOnClearDates?: boolean, + withFullScreenPortal?: boolean, + isOutsideRange?: (day: any) => boolean, + displayFormat?: (string | (()=> string)), + + onFocusChange?: (arg: FocusedInputShape) => void, + onDatesChange?: (arg: { startDate: any, endDate: any }) => void, + + customInputIcon?: string | JSX.Element, + customArrowIcon?: string | JSX.Element, + + // i18n + phrases?: { + clearDates: string | JSX.Element, + } + } + type DateRangePickerInputController = React.ClassicComponentClass; + var DateRangePickerInputController: React.ClassicComponentClass; + + + + interface DateRangePickerInputShape{ + startDateId?: string, + startDatePlaceholderText?: string, + screenReaderMessage?: string, + + endDateId?: string, + endDatePlaceholderText?: string, + + onStartDateFocus?: (e: React.EventHandler>) => void, + onEndDateFocus?: (e: React.EventHandler>) => void, + onStartDateChange?: (e: React.EventHandler>) => void, + onEndDateChange?: (e: React.EventHandler>) => void, + onStartDateShiftTab?: (e: React.EventHandler>) => void, + onEndDateTab?: (e: React.EventHandler>) => void, + onClearDates?: (e: React.EventHandler>) => void, + + startDate?: string, + startDateValue?: string, + endDate?: string, + endDateValue?: string, + + isStartDateFocused?: boolean, + isEndDateFocused?: boolean, + showClearDates?: boolean, + disabled?: boolean, + required?: boolean, + showCaret?: boolean, + showDefaultInputIcon?: boolean, + customInputIcon?: string | JSX.Element, + customArrowIcon?: string | JSX.Element, + + // i18n + phrases?:{ + clearDates: string | JSX.Element, + }, + } + type DateRangePickerInput = React.ClassicComponentClass; + var DateRangePickerInput: React.ClassicComponentClass; + + + + interface SingleDatePickerInputShape{ + id: string, + placeholder?: string, // also used as label + displayValue?: string, + inputValue?: string, + screenReaderMessage?: string, + focused?: boolean, + disabled?: boolean, + required?: boolean, + showCaret?: boolean, + showClearDate?: boolean, + + onChange?: (e: React.EventHandler>) => void, + onClearDate?: (e: React.EventHandler>) => void, + onFocus?: (e: React.EventHandler>) => void, + onKeyDownShiftTab?: (e: React.EventHandler>) => void, + onKeyDownTab?: (e: React.EventHandler>) => void, + + // i18n + phrases?: { + clearDate: string | JSX.Element, + } + } + type SingleDatePickerInput = React.ClassicComponentClass; + var SingleDatePickerInput: React.ClassicComponentClass; + + + + + interface DayPickerShape{ + enableOutsideDays?: boolean, + numberOfMonths?: number, + modifiers?: any, + orientation?: ScrollableOrientationShape, + withPortal?: boolean, + hidden?: boolean, + initialVisibleMonth?: () => moment.Moment, + + navPrev?: string | JSX.Element, + navNext?: string | JSX.Element, + + onDayClick?: (day: any, e: React.EventHandler>) => void, + onDayMouseEnter?: (day: any, e: React.EventHandler>) => void, + onDayMouseLeave?: (day: any, e: React.EventHandler>) => void, + onPrevMonthClick?: (e: React.EventHandler>) => void, + onNextMonthClick?: (e: React.EventHandler>) => void, + onOutsideClick?: (e: MouseEvent) => void, + + renderDay?: (day: any) => (string | JSX.Element), + + // i18n + monthFormat?: string, + } + type DayPicker = React.ClassicComponentClass; + var DayPicker: React.ClassicComponentClass; + + + + interface DayPickerRangeControllerShape{ + startDate?: momentPropTypes.momentObj, + endDate?: momentPropTypes.momentObj, + onDatesChange?: (arg: { startDate: any, endDate: any }) => void, + + focusedInput?: FocusedInputShape, + onFocusChange?: (arg: FocusedInputShape) => void, + + keepOpenOnDateSelect?: boolean, + minimumNights?: number, + isOutsideRange?: (day: any) => boolean, + isDayBlocked?: (day: any) => boolean, + isDayHighlighted?: (day: any) => boolean, + + // DayPicker props + enableOutsideDays?: boolean, + numberOfMonths?: number, + orientation?: ScrollableOrientationShape, + withPortal?: boolean, + hidden?: boolean, + initialVisibleMonth?: () => moment.Moment, + + navPrev?: string | JSX.Element, + navNext?: string | JSX.Element, + + onPrevMonthClick?: (e: React.EventHandler>) => void, + onNextMonthClick?: (e: React.EventHandler>) => void, + onOutsideClick?: (e: MouseEvent) => void, + renderDay?: (day: any) => (string | JSX.Element), + + // i18n + monthFormat?: string, + } + type DayPickerRangeController = React.ClassicComponentClass; + var DayPickerRangeController: React.ClassicComponentClass; + + + interface CalendarMonthGridShape{ + enableOutsideDays?: boolean, + firstVisibleMonthIndex?: number, + initialMonth?: momentPropTypes.momentObj, + isAnimating?: boolean, + numberOfMonths?: number, + modifiers?: any, + orientation?: ScrollableOrientationShape, + onDayClick?: (day: any, e: React.EventHandler>) => void, + onDayMouseEnter?: (day: any, e: React.EventHandler>) => void, + onDayMouseLeave?: (day: any, e: React.EventHandler>) => void, + onMonthTransitionEnd?: ()=> void, + renderDay?: (day: any) => (string | JSX.Element), + transformValue?: string, + + // i18n + monthFormat?: string, + } + type CalendarMonthGrid = React.ClassicComponentClass; + var CalendarMonthGrid: React.ClassicComponentClass; + + + + interface CalendarMonthShape{ + month?: momentPropTypes.momentObj, + isVisible?: boolean, + enableOutsideDays?: boolean, + modifiers?: any, + orientation?: ScrollableOrientationShape, + onDayClick?: (day: any, e: React.EventHandler>) => void, + onDayMouseEnter?: (day: any, e: React.EventHandler>) => void, + onDayMouseLeave?: (day: any, e: React.EventHandler>) => void, + renderDay?: (day: any) => (string | JSX.Element), + + // i18n + monthFormat?: string, + } + type CalendarMonth = React.ClassicComponentClass; + var CalendarMonth: React.ClassicComponentClass; + + + interface CalendarDayShape{ + day?: momentPropTypes.momentObj, + isOutsideDay?: boolean, + modifiers?: any, + onDayClick?: (day: any, e: React.EventHandler>) => void, + onDayMouseEnter?: (day: any, e: React.EventHandler>) => void, + onDayMouseLeave?: (day: any, e: React.EventHandler>) => void, + renderDay?: (day: any) => (string | JSX.Element), + } + type CalendarDay = React.ClassicComponentClass; + var CalendarDay: React.ClassicComponentClass; + + + + + var isInclusivelyAfterDay: (a: moment.Moment, b: moment.Moment) => boolean; + var isInclusivelyBeforeDay: (a: moment.Moment, b: moment.Moment) => boolean; + var isNextDay: (a: moment.Moment, b: moment.Moment) => boolean; + var isSameDay: (a: moment.Moment, b: moment.Moment) => boolean; + + var toISODateString: (date: moment.MomentInput, currentFormat: moment.MomentFormatSpecification) => string | null; + var toLocalizedDateString: (date: moment.MomentInput, currentFormat: moment.MomentFormatSpecification) => string | null; + + var toMomentObject: (dateString: moment.MomentInput, customFormat: moment.MomentFormatSpecification) => moment.Moment | null; +} diff --git a/react-dates/react-dates-test.tsx b/react-dates/react-dates-test.tsx new file mode 100644 index 0000000000..0878bfb141 --- /dev/null +++ b/react-dates/react-dates-test.tsx @@ -0,0 +1,357 @@ +import moment from "moment"; +import * as React from "react"; + +import { + CalendarDay, + CalendarMonth, + CalendarMonthGrid, + SingleDatePickerInput, + SingleDatePicker, + DayPicker, + DayPickerRangeController, + DateRangePickerInput, + DateRangePickerInputController, + DateRangePicker, + isInclusivelyAfterDay, + isInclusivelyBeforeDay, + isNextDay, + isSameDay, + toISODateString, + toLocalizedDateString, + toMomentObject} from "react-dates"; + + +class CalendarDayRenderingMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class CalendarDayRenderingFullTest extends React.Component<{}, {}> { + render() { + return day.toString()} + onDayClick={(day,e) => {}} + onDayMouseEnter={(day,e) => {}} + onDayMouseLeave={(day,e) => {}} + /> + } +} + +class CalendarMonthMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class CalendarMonthFullTest extends React.Component<{}, {}> { + render() { + return day.toString()} + onDayClick={(day,e) => {}} + onDayMouseEnter={(day,e) => {}} + onDayMouseLeave={(day,e) => {}} + /> + } +} + + + + +class SingleDatePickerInputMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class SingleDatePickerInputFullTest extends React.Component<{}, {}> { + render() { + return {}} + onClearDate={e => {}} + onFocus={e => {}} + onKeyDownShiftTab={e => {}} + onKeyDownTab={e => {}} + phrases={{clearDate: "clear"}} + placeholder="test" + required={false} + screenReaderMessage="arial-test" + showCaret={true} + showClearDate={true} + /> + } +} + + + + +class SingleDatePickerMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class SingleDatePickerFullTest extends React.Component<{}, {}> { + render() { + return moment()} + placeholder="test" + required={false} + showClearDate={true} + isDayBlocked={(day:any)=> false} + isOutsideRange={(day:any)=> false} + keepOpenOnDateSelect={true} + navNext="next" + navPrev="prev" + withPortal={false} + onDateChange={d => {}} + focused={false} + phrases={{closeDatePicker: Close}} + reopenPickerOnClearDates={true} + screenReaderInputMessage="arial-test" + withFullScreenPortal={true} + onFocusChange={arg => {}} + onNextMonthClick={e => {}} + onPrevMonthClick={e => {}} + numberOfMonths={2} + orientation="horizontal" + monthFormat="MM" + renderDay={day => day.toString()} + /> + } +} + + + +class DayPickerRangeControllerMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class DayPickerRangeControllerFullTest extends React.Component<{}, {}> { + render() { + return moment()} + hidden={false} + isDayBlocked={(day:any)=> false} + isDayHighlighted={(day:any)=> false} + isOutsideRange={(day:any)=> false} + keepOpenOnDateSelect={true} + minimumNights={3} + navNext="next" + navPrev="prev" + withPortal={false} + onDatesChange={arg => {}} + onFocusChange={arg => {}} + onNextMonthClick={e => {}} + onPrevMonthClick={e => {}} + onOutsideClick={e => {}} + enableOutsideDays={true} + numberOfMonths={2} + orientation="horizontal" + monthFormat="MM" + renderDay={day => day.toString()} + /> + } +} + + +class DayPickerTest extends React.Component<{}, {}> { + render() { + return + } +} + +class DayPickerFullTest extends React.Component<{}, {}> { + render() { + return {}} + onDayMouseEnter={(day,e)=>{}} + onDayMouseLeave={(day,e)=>{}} + initialVisibleMonth={() => moment()} + hidden={false} + navNext="next" + navPrev="prev" + withPortal={false} + onNextMonthClick={e => {}} + onPrevMonthClick={e => {}} + onOutsideClick={e => {}} + enableOutsideDays={true} + numberOfMonths={2} + orientation="horizontal" + monthFormat="MM" + renderDay={day => day.toString()} + /> + } +} + + + + +class DateRangePickerInputMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class DateRangePickerInputFullTest extends React.Component<{}, {}> { + render() { + return {}} + onEndDateChange={e => {}} + onEndDateFocus={e => {}} + onEndDateTab={e => {}} + onStartDateChange={e => {}} + onStartDateFocus={e => {}} + onStartDateShiftTab={e => {}} + showDefaultInputIcon={true} + required={false} + screenReaderMessage="arial-test" + showCaret={true} + showClearDates={true} + phrases={{clearDates: "clear"}} + /> + } +} + + + + + +class DateRangePickerInputControllerMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class DateRangePickerInputControllerFullTest extends React.Component<{}, {}> { + render() { + return {}} + onEndDateChange={e => {}} + onEndDateFocus={e => {}} + onEndDateTab={e => {}} + onStartDateChange={e => {}} + onStartDateFocus={e => {}} + onStartDateShiftTab={e => {}} + showDefaultInputIcon={true} + required={false} + screenReaderMessage="arial-test" + showCaret={true} + showClearDates={true} + phrases={{clearDates: "clear"}} + startDate="1.1.2020" + endDate="1.1.2020" + /> + } +} + + + + + + + + + + + +class DateRangePickerMinimumTest extends React.Component<{}, {}> { + render() { + return + } +} + +class DateRangePickerFullTest extends React.Component<{}, {}> { + render() { + return {arg.startDate; arg.endDate;}} + displayFormat="dd.mm.yyyy" + enableOutsideDays={true} + horizontalMargin={20} + initialVisibleMonth={() => moment()} + isDayBlocked={(day:any)=> false} + isOutsideRange={(day:any)=> false} + keepOpenOnDateSelect={true} + withPortal={false} + reopenPickerOnClearDates={true} + screenReaderInputMessage="arial-test" + withFullScreenPortal={true} + onFocusChange={arg => {}} + onNextMonthClick={e => {}} + onPrevMonthClick={e => {}} + numberOfMonths={2} + orientation="horizontal" + monthFormat="MM" + renderDay={day => day.toString()} + /> + } +} + + +const isInclusivelyAfterDayResult: boolean = isInclusivelyAfterDay(moment(),moment()); +const isInclusivelyBeforeDayResult: boolean = isInclusivelyBeforeDay(moment(),moment()); +const isNextDayDayResult: boolean = isNextDay(moment(),moment()); +const isSameDayResult: boolean = isSameDay(moment(),moment()); +const toISODateStringResult: string | null = toISODateString(moment(), "dd.mm.yyyy"); +const toLocalizedDateStringResult: string | null = toLocalizedDateString(moment(), "dd.mm.yyyy"); +const toMomentObjectResult: moment.Moment | null = toMomentObject(moment(), "dd.mm.yyyy"); + + \ No newline at end of file diff --git a/react-dates/tsconfig.json b/react-dates/tsconfig.json new file mode 100644 index 0000000000..943c51e1e6 --- /dev/null +++ b/react-dates/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "baseUrl": "../", + "jsx": "react", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "react-day-picker-tests.tsx" + ] +} From 6e9010d7bd19edafe047a292c247d499d495af2f Mon Sep 17 00:00:00 2001 From: Artur-A Date: Sun, 12 Feb 2017 07:44:08 +0300 Subject: [PATCH 2/5] fixed tscofig.json --- react-dates/{react-dates-test.tsx => react-dates-tests.tsx} | 0 react-dates/tsconfig.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename react-dates/{react-dates-test.tsx => react-dates-tests.tsx} (100%) diff --git a/react-dates/react-dates-test.tsx b/react-dates/react-dates-tests.tsx similarity index 100% rename from react-dates/react-dates-test.tsx rename to react-dates/react-dates-tests.tsx diff --git a/react-dates/tsconfig.json b/react-dates/tsconfig.json index 943c51e1e6..7d246baede 100644 --- a/react-dates/tsconfig.json +++ b/react-dates/tsconfig.json @@ -19,6 +19,6 @@ }, "files": [ "index.d.ts", - "react-day-picker-tests.tsx" + "react-dates-tests.tsx" ] } From 3f9b056eaaa233b6585c49b88cda9bb426923de3 Mon Sep 17 00:00:00 2001 From: Artur-A Date: Sun, 12 Feb 2017 08:05:53 +0300 Subject: [PATCH 3/5] added moment dependency --- react-dates/index.d.ts | 2 +- react-dates/package.json | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 react-dates/package.json diff --git a/react-dates/index.d.ts b/react-dates/index.d.ts index b1e28cbe92..8a1ac11441 100644 --- a/react-dates/index.d.ts +++ b/react-dates/index.d.ts @@ -4,8 +4,8 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.1 -import moment from "moment"; import * as React from "react"; +import moment from "moment"; export = ReactDates; diff --git a/react-dates/package.json b/react-dates/package.json new file mode 100644 index 0000000000..d5ac741601 --- /dev/null +++ b/react-dates/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "moment": ">=2.17.1" + } +} \ No newline at end of file From 4ec7d1b63aabeee74fc2591ad11361096e7891ec Mon Sep 17 00:00:00 2001 From: Artur-A Date: Sun, 12 Feb 2017 08:19:01 +0300 Subject: [PATCH 4/5] fixing moment import --- react-dates/index.d.ts | 2 +- react-dates/package.json | 2 +- react-dates/react-dates-tests.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-dates/index.d.ts b/react-dates/index.d.ts index 8a1ac11441..e3ca801887 100644 --- a/react-dates/index.d.ts +++ b/react-dates/index.d.ts @@ -5,7 +5,7 @@ // TypeScript Version: 2.1 import * as React from "react"; -import moment from "moment"; +import * as moment from "moment"; export = ReactDates; diff --git a/react-dates/package.json b/react-dates/package.json index d5ac741601..ed92982595 100644 --- a/react-dates/package.json +++ b/react-dates/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "moment": ">=2.17.1" + "moment": "^2.17.1" } } \ No newline at end of file diff --git a/react-dates/react-dates-tests.tsx b/react-dates/react-dates-tests.tsx index 0878bfb141..b975b1124b 100644 --- a/react-dates/react-dates-tests.tsx +++ b/react-dates/react-dates-tests.tsx @@ -1,5 +1,5 @@ -import moment from "moment"; import * as React from "react"; +import * as moment from "moment"; import { CalendarDay, From 4b53f02b05e53e2675df2ab97afa9e8cf4480f7d Mon Sep 17 00:00:00 2001 From: Artur-A Date: Sun, 12 Feb 2017 08:25:02 +0300 Subject: [PATCH 5/5] set strong min moment version with ts 2.0 support --- react-dates/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-dates/package.json b/react-dates/package.json index ed92982595..d5ac741601 100644 --- a/react-dates/package.json +++ b/react-dates/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "moment": "^2.17.1" + "moment": ">=2.17.1" } } \ No newline at end of file