@vkontakte/vkui 7.2.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Calendar/Calendar.d.ts +2 -2
- package/dist/components/Calendar/Calendar.d.ts.map +1 -1
- package/dist/components/Calendar/Calendar.js +1 -1
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/CalendarDays/CalendarDays.d.ts +1 -1
- package/dist/components/CalendarDays/CalendarDays.d.ts.map +1 -1
- package/dist/components/CalendarDays/CalendarDays.js.map +1 -1
- package/dist/components/CalendarRange/CalendarRange.d.ts +2 -2
- package/dist/components/CalendarRange/CalendarRange.d.ts.map +1 -1
- package/dist/components/CalendarRange/CalendarRange.js +4 -1
- package/dist/components/CalendarRange/CalendarRange.js.map +1 -1
- package/dist/components/CalendarTime/CalendarTime.d.ts.map +1 -1
- package/dist/components/CalendarTime/CalendarTime.js +16 -13
- package/dist/components/CalendarTime/CalendarTime.js.map +1 -1
- package/dist/components/ChipsInputBase/ChipsInputBase.d.ts.map +1 -1
- package/dist/components/ChipsInputBase/ChipsInputBase.js +1 -0
- package/dist/components/ChipsInputBase/ChipsInputBase.js.map +1 -1
- package/dist/components/ChipsInputBase/helpers.d.ts +1 -1
- package/dist/components/ChipsInputBase/helpers.d.ts.map +1 -1
- package/dist/components/ChipsInputBase/helpers.js +4 -0
- package/dist/components/ChipsInputBase/helpers.js.map +1 -1
- package/dist/components/ChipsInputBase/types.d.ts +1 -1
- package/dist/components/ChipsInputBase/types.d.ts.map +1 -1
- package/dist/components/ChipsInputBase/types.js.map +1 -1
- package/dist/components/ChipsSelect/ChipsSelect.d.ts.map +1 -1
- package/dist/components/ChipsSelect/ChipsSelect.js +6 -1
- package/dist/components/ChipsSelect/ChipsSelect.js.map +1 -1
- package/dist/components/CustomSelect/CustomSelect.d.ts.map +1 -1
- package/dist/components/CustomSelect/CustomSelect.js +3 -2
- package/dist/components/CustomSelect/CustomSelect.js.map +1 -1
- package/dist/components/CustomSelectDropdown/CustomSelectDropdown.d.ts.map +1 -1
- package/dist/components/CustomSelectDropdown/CustomSelectDropdown.js +1 -0
- package/dist/components/CustomSelectDropdown/CustomSelectDropdown.js.map +1 -1
- package/dist/components/DateInput/DateInput.d.ts +9 -1
- package/dist/components/DateInput/DateInput.d.ts.map +1 -1
- package/dist/components/DateInput/DateInput.js +14 -4
- package/dist/components/DateInput/DateInput.js.map +1 -1
- package/dist/components/DateInput/hooks.d.ts +7 -6
- package/dist/components/DateInput/hooks.d.ts.map +1 -1
- package/dist/components/DateInput/hooks.js +14 -7
- package/dist/components/DateInput/hooks.js.map +1 -1
- package/dist/components/DateRangeInput/DateRangeInput.d.ts +9 -1
- package/dist/components/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/components/DateRangeInput/DateRangeInput.js +13 -3
- package/dist/components/DateRangeInput/DateRangeInput.js.map +1 -1
- package/dist/components/ModalCard/ModalCard.d.ts.map +1 -1
- package/dist/components/ModalCard/ModalCard.js +4 -12
- package/dist/components/ModalCard/ModalCard.js.map +1 -1
- package/dist/components/ModalPage/ModalPage.d.ts.map +1 -1
- package/dist/components/ModalPage/ModalPage.js +5 -12
- package/dist/components/ModalPage/ModalPage.js.map +1 -1
- package/dist/components/ModalRoot/types.d.ts +1 -0
- package/dist/components/ModalRoot/types.d.ts.map +1 -1
- package/dist/components/ModalRoot/types.js.map +1 -1
- package/dist/components/ModalRoot/useModalManager.d.ts +4 -2
- package/dist/components/ModalRoot/useModalManager.d.ts.map +1 -1
- package/dist/components/ModalRoot/useModalManager.js +12 -3
- package/dist/components/ModalRoot/useModalManager.js.map +1 -1
- package/dist/components/ModalRoot/useModalRootContext.js +1 -0
- package/dist/components/ModalRoot/useModalRootContext.js.map +1 -1
- package/dist/components/Touch/Touch.d.ts.map +1 -1
- package/dist/components/Touch/Touch.js +2 -2
- package/dist/components/Touch/Touch.js.map +1 -1
- package/dist/components.css +1 -1
- package/dist/components.css.map +1 -1
- package/dist/cssm/components/Calendar/Calendar.js +1 -1
- package/dist/cssm/components/Calendar/Calendar.js.map +1 -1
- package/dist/cssm/components/CalendarDays/CalendarDays.js.map +1 -1
- package/dist/cssm/components/CalendarRange/CalendarRange.js +4 -1
- package/dist/cssm/components/CalendarRange/CalendarRange.js.map +1 -1
- package/dist/cssm/components/CalendarTime/CalendarTime.js +16 -13
- package/dist/cssm/components/CalendarTime/CalendarTime.js.map +1 -1
- package/dist/cssm/components/ChipsInputBase/ChipsInputBase.js +1 -0
- package/dist/cssm/components/ChipsInputBase/ChipsInputBase.js.map +1 -1
- package/dist/cssm/components/ChipsInputBase/helpers.js +4 -0
- package/dist/cssm/components/ChipsInputBase/helpers.js.map +1 -1
- package/dist/cssm/components/ChipsInputBase/types.js.map +1 -1
- package/dist/cssm/components/ChipsSelect/ChipsSelect.js +6 -1
- package/dist/cssm/components/ChipsSelect/ChipsSelect.js.map +1 -1
- package/dist/cssm/components/CustomSelect/CustomSelect.js +3 -1
- package/dist/cssm/components/CustomSelect/CustomSelect.js.map +1 -1
- package/dist/cssm/components/CustomSelectDropdown/CustomSelectDropdown.js +1 -0
- package/dist/cssm/components/CustomSelectDropdown/CustomSelectDropdown.js.map +1 -1
- package/dist/cssm/components/DateInput/DateInput.js +12 -4
- package/dist/cssm/components/DateInput/DateInput.js.map +1 -1
- package/dist/cssm/components/DateInput/hooks.js +14 -7
- package/dist/cssm/components/DateInput/hooks.js.map +1 -1
- package/dist/cssm/components/DateRangeInput/DateRangeInput.js +11 -3
- package/dist/cssm/components/DateRangeInput/DateRangeInput.js.map +1 -1
- package/dist/cssm/components/FormItem/FormItem.module.css +1 -0
- package/dist/cssm/components/ModalCard/ModalCard.js +2 -11
- package/dist/cssm/components/ModalCard/ModalCard.js.map +1 -1
- package/dist/cssm/components/ModalPage/ModalPage.js +3 -11
- package/dist/cssm/components/ModalPage/ModalPage.js.map +1 -1
- package/dist/cssm/components/ModalRoot/types.js.map +1 -1
- package/dist/cssm/components/ModalRoot/useModalManager.js +12 -3
- package/dist/cssm/components/ModalRoot/useModalManager.js.map +1 -1
- package/dist/cssm/components/ModalRoot/useModalRootContext.js +1 -0
- package/dist/cssm/components/ModalRoot/useModalRootContext.js.map +1 -1
- package/dist/cssm/components/Tappable/Tappable.module.css +1 -1
- package/dist/cssm/components/Touch/Touch.js +2 -2
- package/dist/cssm/components/Touch/Touch.js.map +1 -1
- package/dist/cssm/hooks/useCalendar.js.map +1 -1
- package/dist/cssm/hooks/useDateInput.js +3 -3
- package/dist/cssm/hooks/useDateInput.js.map +1 -1
- package/dist/cssm/lib/date.js +6 -0
- package/dist/cssm/lib/date.js.map +1 -1
- package/dist/cssm/lib/dom.js +6 -0
- package/dist/cssm/lib/dom.js.map +1 -1
- package/dist/cssm/styles/constants.css +2 -2
- package/dist/hooks/useCalendar.d.ts +1 -1
- package/dist/hooks/useCalendar.d.ts.map +1 -1
- package/dist/hooks/useCalendar.js.map +1 -1
- package/dist/hooks/useDateInput.d.ts +4 -4
- package/dist/hooks/useDateInput.d.ts.map +1 -1
- package/dist/hooks/useDateInput.js +3 -3
- package/dist/hooks/useDateInput.js.map +1 -1
- package/dist/lib/date.d.ts.map +1 -1
- package/dist/lib/date.js +6 -0
- package/dist/lib/date.js.map +1 -1
- package/dist/lib/dom.d.ts +1 -0
- package/dist/lib/dom.d.ts.map +1 -1
- package/dist/lib/dom.js +6 -0
- package/dist/lib/dom.js.map +1 -1
- package/dist/vkui.css +1 -1
- package/dist/vkui.css.map +1 -1
- package/package.json +1 -1
- package/src/components/Calendar/Calendar.tsx +7 -7
- package/src/components/CalendarDays/CalendarDays.tsx +1 -1
- package/src/components/CalendarRange/CalendarRange.tsx +11 -6
- package/src/components/CalendarTime/CalendarTime.tsx +17 -9
- package/src/components/ChipsInputBase/ChipsInputBase.tsx +1 -0
- package/src/components/ChipsInputBase/helpers.ts +5 -1
- package/src/components/ChipsInputBase/types.ts +1 -1
- package/src/components/ChipsSelect/ChipsSelect.tsx +6 -1
- package/src/components/CustomSelect/CustomSelect.tsx +2 -0
- package/src/components/CustomSelectDropdown/CustomSelectDropdown.tsx +1 -0
- package/src/components/DateInput/DateInput.tsx +37 -10
- package/src/components/DateInput/hooks.ts +32 -23
- package/src/components/DateRangeInput/DateRangeInput.tsx +26 -5
- package/src/components/FormItem/FormItem.module.css +1 -0
- package/src/components/ModalCard/ModalCard.tsx +2 -9
- package/src/components/ModalPage/ModalPage.tsx +3 -10
- package/src/components/ModalRoot/types.ts +1 -0
- package/src/components/ModalRoot/useModalManager.tsx +12 -5
- package/src/components/ModalRoot/useModalRootContext.ts +1 -1
- package/src/components/Tappable/Tappable.module.css +1 -1
- package/src/components/Touch/Touch.tsx +35 -3
- package/src/hooks/useCalendar.ts +1 -1
- package/src/hooks/useDateInput.ts +6 -6
- package/src/lib/date.ts +6 -0
- package/src/lib/dom.tsx +8 -0
- package/src/styles/constants.css +2 -2
package/package.json
CHANGED
|
@@ -52,8 +52,8 @@ export interface CalendarProps
|
|
|
52
52
|
Pick<CalendarDaysProps, 'dayProps' | 'listenDayChangesForUpdate' | 'renderDayContent'>,
|
|
53
53
|
CalendarDoneButtonProps,
|
|
54
54
|
CalendarTestsProps {
|
|
55
|
-
value?: Date;
|
|
56
|
-
defaultValue?: Date;
|
|
55
|
+
value?: Date | null;
|
|
56
|
+
defaultValue?: Date | null;
|
|
57
57
|
/**
|
|
58
58
|
* Запрещает выбор даты в прошлом.
|
|
59
59
|
* Применяется, если не заданы `shouldDisableDate` и `disableFuture`.
|
|
@@ -70,7 +70,7 @@ export interface CalendarProps
|
|
|
70
70
|
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
71
71
|
showNeighboringMonth?: boolean;
|
|
72
72
|
size?: 's' | 'm';
|
|
73
|
-
onChange?: (value?: Date) => void;
|
|
73
|
+
onChange?: (value?: Date) => void; // TODO [>=8]: поменять тип на `(value?: Date | null) => void`
|
|
74
74
|
/**
|
|
75
75
|
* Позволяет запретить выбор даты.
|
|
76
76
|
*/
|
|
@@ -155,20 +155,20 @@ export const Calendar = ({
|
|
|
155
155
|
...props
|
|
156
156
|
}: CalendarProps): React.ReactNode => {
|
|
157
157
|
const _onChange = React.useCallback(
|
|
158
|
-
(date: Date | undefined) => {
|
|
158
|
+
(date: Date | null | undefined) => {
|
|
159
159
|
onChange?.(convertDateFromTimeZone(date, timezone) || undefined);
|
|
160
160
|
},
|
|
161
161
|
[onChange, timezone],
|
|
162
162
|
);
|
|
163
163
|
|
|
164
|
-
const [value, updateValue] = useCustomEnsuredControl<Date | undefined>({
|
|
164
|
+
const [value, updateValue] = useCustomEnsuredControl<Date | null | undefined>({
|
|
165
165
|
value: valueProp,
|
|
166
166
|
defaultValue,
|
|
167
167
|
onChange: _onChange,
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
const timeZonedValue: Date | undefined = React.useMemo(
|
|
171
|
-
() => convertDateToTimeZone(value, timezone)
|
|
170
|
+
const timeZonedValue: Date | null | undefined = React.useMemo(
|
|
171
|
+
() => convertDateToTimeZone(value, timezone),
|
|
172
172
|
[timezone, value],
|
|
173
173
|
);
|
|
174
174
|
|
|
@@ -29,7 +29,7 @@ export interface CalendarDaysProps
|
|
|
29
29
|
extends Omit<HTMLAttributesWithRootRef<HTMLDivElement>, 'onChange'>,
|
|
30
30
|
Pick<CalendarDayProps, 'renderDayContent'>,
|
|
31
31
|
CalendarDaysTestsProps {
|
|
32
|
-
value?: Date | Array<Date | null
|
|
32
|
+
value?: Date | Array<Date | null> | null;
|
|
33
33
|
viewDate: Date;
|
|
34
34
|
weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
35
35
|
showNeighboringMonth?: boolean;
|
|
@@ -55,19 +55,19 @@ export interface CalendarRangeProps
|
|
|
55
55
|
>,
|
|
56
56
|
Pick<CalendarDaysProps, 'listenDayChangesForUpdate' | 'renderDayContent'>,
|
|
57
57
|
CalendarRangeTestsProps {
|
|
58
|
-
value?: DateRangeType;
|
|
59
|
-
defaultValue?: DateRangeType;
|
|
58
|
+
value?: DateRangeType | null;
|
|
59
|
+
defaultValue?: DateRangeType | null;
|
|
60
60
|
disablePast?: boolean;
|
|
61
61
|
disableFuture?: boolean;
|
|
62
62
|
disablePickers?: boolean;
|
|
63
63
|
changeDayLabel?: string;
|
|
64
64
|
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
65
|
-
onChange?: (value: DateRangeType | undefined) => void;
|
|
65
|
+
onChange?: (value: DateRangeType | undefined) => void; // TODO [>=8]: поменять тип на `(value?: DateRangeType | null) => void`
|
|
66
66
|
shouldDisableDate?: (value: Date) => boolean;
|
|
67
67
|
onClose?: () => void;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const getIsDaySelected = (day: Date, value?: DateRangeType) => {
|
|
70
|
+
const getIsDaySelected = (day: Date, value?: DateRangeType | null) => {
|
|
71
71
|
if (!value?.[0] || !value[1]) {
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
@@ -103,10 +103,15 @@ export const CalendarRange = ({
|
|
|
103
103
|
getRootRef,
|
|
104
104
|
...props
|
|
105
105
|
}: CalendarRangeProps): React.ReactNode => {
|
|
106
|
-
const
|
|
106
|
+
const _onChange = React.useCallback(
|
|
107
|
+
(newValue: DateRangeType | null | undefined) => onChange?.(newValue || undefined),
|
|
108
|
+
[onChange],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const [value, updateValue] = useCustomEnsuredControl<DateRangeType | null | undefined>({
|
|
107
112
|
value: valueProp,
|
|
108
113
|
defaultValue,
|
|
109
|
-
onChange,
|
|
114
|
+
onChange: _onChange,
|
|
110
115
|
});
|
|
111
116
|
|
|
112
117
|
const {
|
|
@@ -137,16 +137,24 @@ export const CalendarTime = ({
|
|
|
137
137
|
|
|
138
138
|
const onPickerKeyDown = (e: React.KeyboardEvent) => {
|
|
139
139
|
const key = pressedKey(e);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
140
|
+
/* Мы хотим иметь возможность быстро, по Enter перемещаться между
|
|
141
|
+
* селектами с часами и минутами, также как мы это делаем по нажатию на Tab */
|
|
142
|
+
if (key !== Keys.ENTER) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const steps = [hoursInputRef, minutesInputRef, doneButtonRef].filter((ref) =>
|
|
147
|
+
Boolean(ref.current),
|
|
148
|
+
);
|
|
149
|
+
const currentStepIndex = steps.findIndex((step) => step.current === e.target);
|
|
150
|
+
const nextStepIndex = currentStepIndex + 1;
|
|
151
|
+
if (nextStepIndex >= steps.length) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const nextStep = steps[nextStepIndex];
|
|
155
|
+
|
|
156
|
+
if (nextStep.current) {
|
|
148
157
|
e.preventDefault();
|
|
149
|
-
const nextStep = steps[nextStepIndex];
|
|
150
158
|
nextStep.current?.focus();
|
|
151
159
|
}
|
|
152
160
|
};
|
|
@@ -260,6 +260,7 @@ export const ChipsInputBase = <O extends ChipOption>({
|
|
|
260
260
|
// чтобы можно было легче найти этот чип в DOM
|
|
261
261
|
'data-index': index,
|
|
262
262
|
'data-value': option.value,
|
|
263
|
+
'data-value-type': typeof option.value,
|
|
263
264
|
// для a11y
|
|
264
265
|
'tabIndex': lastFocusedChipOptionIndex === index ? 0 : -1,
|
|
265
266
|
'role': 'option',
|
|
@@ -35,8 +35,12 @@ export const getChipOptionIndexByHTMLElement = (el: HTMLElement | null): number
|
|
|
35
35
|
/**
|
|
36
36
|
* @private
|
|
37
37
|
*/
|
|
38
|
-
export const getChipOptionValueByHTMLElement = (el: HTMLElement | null):
|
|
38
|
+
export const getChipOptionValueByHTMLElement = (el: HTMLElement | null): ChipOptionValue | -1 => {
|
|
39
39
|
const value = el && el.dataset.value;
|
|
40
|
+
const valueType = el && el.dataset.valueType;
|
|
41
|
+
if (valueType === 'number') {
|
|
42
|
+
return Number(value);
|
|
43
|
+
}
|
|
40
44
|
return typeof value === 'string' ? value : -1;
|
|
41
45
|
};
|
|
42
46
|
|
|
@@ -244,6 +244,7 @@ export const ChipsSelect = <Option extends ChipOption>({
|
|
|
244
244
|
// Связано с ChipsInputProps
|
|
245
245
|
const rootRef = useExternRef(getRootRef);
|
|
246
246
|
const inputRef = useExternRef(getRef, inputRefHook);
|
|
247
|
+
const forbidCloseByOutsideClick = React.useRef(false);
|
|
247
248
|
|
|
248
249
|
// Связано с CustomSelectDropdownProps
|
|
249
250
|
const [dropdownVerticalPlacement, setDropdownVerticalPlacement] = React.useState<
|
|
@@ -419,7 +420,10 @@ export const ChipsSelect = <Option extends ChipOption>({
|
|
|
419
420
|
}, [setFocusedOptionIndex]);
|
|
420
421
|
|
|
421
422
|
const handleClickOutside = React.useCallback(() => {
|
|
422
|
-
|
|
423
|
+
if (!forbidCloseByOutsideClick.current) {
|
|
424
|
+
setOpened(false);
|
|
425
|
+
}
|
|
426
|
+
forbidCloseByOutsideClick.current = false;
|
|
423
427
|
}, [setOpened]);
|
|
424
428
|
|
|
425
429
|
useGlobalOnClickOutside(
|
|
@@ -492,6 +496,7 @@ export const ChipsSelect = <Option extends ChipOption>({
|
|
|
492
496
|
if (!event.defaultPrevented) {
|
|
493
497
|
closeAfterSelect && setOpened(false);
|
|
494
498
|
addOption(option);
|
|
499
|
+
forbidCloseByOutsideClick.current = true;
|
|
495
500
|
clearInput();
|
|
496
501
|
}
|
|
497
502
|
},
|
|
@@ -761,6 +761,7 @@ export function CustomSelect<OptionInterfaceT extends CustomSelectOptionInterfac
|
|
|
761
761
|
// C mousemove такой проблемы нет, что позволяет реализовать поведение при наведении с клавиатуры и при наведении мышью идентично `<select>`.
|
|
762
762
|
onMouseMove: (e) => focusOptionOnMouseMove(e, index),
|
|
763
763
|
id: `${popupAriaId}-${option.value}`,
|
|
764
|
+
...option,
|
|
764
765
|
})}
|
|
765
766
|
</React.Fragment>
|
|
766
767
|
);
|
|
@@ -961,6 +962,7 @@ export function CustomSelect<OptionInterfaceT extends CustomSelectOptionInterfac
|
|
|
961
962
|
{selected?.label}
|
|
962
963
|
</CustomSelectInput>
|
|
963
964
|
<select
|
|
965
|
+
tabIndex={-1}
|
|
964
966
|
ref={selectElRef}
|
|
965
967
|
name={name}
|
|
966
968
|
onChange={onNativeSelectChange}
|
|
@@ -50,6 +50,14 @@ export type DateInputPropsTestsProps = {
|
|
|
50
50
|
* Передает атрибут `data-testid` для поля ввода минут
|
|
51
51
|
*/
|
|
52
52
|
minuteFieldTestId?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Передает атрибут `data-testid` для кнопки показа календаря.
|
|
55
|
+
*/
|
|
56
|
+
showCalendarButtonTestId?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Передает атрибут `data-testid` для кнопки очистки даты.
|
|
59
|
+
*/
|
|
60
|
+
clearButtonTestId?: string;
|
|
53
61
|
};
|
|
54
62
|
|
|
55
63
|
export interface DateInputProps
|
|
@@ -209,6 +217,8 @@ export const DateInput = ({
|
|
|
209
217
|
yearFieldTestId,
|
|
210
218
|
hourFieldTestId,
|
|
211
219
|
minuteFieldTestId,
|
|
220
|
+
showCalendarButtonTestId,
|
|
221
|
+
clearButtonTestId,
|
|
212
222
|
id,
|
|
213
223
|
onApply,
|
|
214
224
|
renderCustomValue,
|
|
@@ -221,12 +231,13 @@ export const DateInput = ({
|
|
|
221
231
|
const hoursRef = React.useRef<HTMLSpanElement>(null);
|
|
222
232
|
const minutesRef = React.useRef<HTMLSpanElement>(null);
|
|
223
233
|
|
|
224
|
-
const { value, updateValue, setInternalValue, getLastUpdatedValue } =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
const { value, updateValue, setInternalValue, getLastUpdatedValue, clearValue } =
|
|
235
|
+
useDateInputValue({
|
|
236
|
+
value: valueProp,
|
|
237
|
+
defaultValue,
|
|
238
|
+
onChange,
|
|
239
|
+
timezone,
|
|
240
|
+
});
|
|
230
241
|
|
|
231
242
|
const maxElement = enableTime ? 4 : 2;
|
|
232
243
|
|
|
@@ -277,7 +288,7 @@ export const DateInput = ({
|
|
|
277
288
|
autoFocus,
|
|
278
289
|
disabled,
|
|
279
290
|
elementsConfig,
|
|
280
|
-
|
|
291
|
+
onClear: clearValue,
|
|
281
292
|
onInternalValueChange,
|
|
282
293
|
getInternalValue,
|
|
283
294
|
value,
|
|
@@ -299,6 +310,9 @@ export const DateInput = ({
|
|
|
299
310
|
|
|
300
311
|
const onCalendarChange = React.useCallback(
|
|
301
312
|
(value?: Date | undefined) => {
|
|
313
|
+
if (!value) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
302
316
|
if (enableTime) {
|
|
303
317
|
setInternalValue(value);
|
|
304
318
|
return;
|
|
@@ -312,13 +326,16 @@ export const DateInput = ({
|
|
|
312
326
|
);
|
|
313
327
|
|
|
314
328
|
const onDoneButtonClick = React.useCallback(() => {
|
|
329
|
+
if (!value) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
315
332
|
const newValue = updateValue(value);
|
|
316
333
|
onApply?.(newValue);
|
|
317
334
|
removeFocusFromField();
|
|
318
335
|
}, [onApply, removeFocusFromField, updateValue, value]);
|
|
319
336
|
|
|
320
337
|
const customValue = React.useMemo(
|
|
321
|
-
() => !open && renderCustomValue?.(value),
|
|
338
|
+
() => !open && renderCustomValue?.(value || undefined),
|
|
322
339
|
[open, renderCustomValue, value],
|
|
323
340
|
);
|
|
324
341
|
|
|
@@ -336,11 +353,21 @@ export const DateInput = ({
|
|
|
336
353
|
getRootRef={handleRootRef}
|
|
337
354
|
after={
|
|
338
355
|
value ? (
|
|
339
|
-
<IconButton
|
|
356
|
+
<IconButton
|
|
357
|
+
hoverMode="opacity"
|
|
358
|
+
label={clearFieldLabel}
|
|
359
|
+
onClick={clear}
|
|
360
|
+
data-testid={clearButtonTestId}
|
|
361
|
+
>
|
|
340
362
|
<Icon16Clear />
|
|
341
363
|
</IconButton>
|
|
342
364
|
) : (
|
|
343
|
-
<IconButton
|
|
365
|
+
<IconButton
|
|
366
|
+
hoverMode="opacity"
|
|
367
|
+
label={showCalendarLabel}
|
|
368
|
+
onClick={openCalendar}
|
|
369
|
+
data-testid={showCalendarButtonTestId}
|
|
370
|
+
>
|
|
344
371
|
<Icon20CalendarOutline />
|
|
345
372
|
</IconButton>
|
|
346
373
|
)
|
|
@@ -2,33 +2,34 @@ import * as React from 'react';
|
|
|
2
2
|
import { convertDateFromTimeZone, convertDateToTimeZone } from '../../lib/date';
|
|
3
3
|
|
|
4
4
|
interface UseDateInputValueOptions {
|
|
5
|
-
value?: Date;
|
|
6
|
-
defaultValue?: Date;
|
|
5
|
+
value?: Date | null;
|
|
6
|
+
defaultValue?: Date | null;
|
|
7
7
|
onChange?: (value?: Date) => void;
|
|
8
8
|
timezone?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface UseDateInputValueReturn {
|
|
12
|
-
value?: Date;
|
|
13
|
-
updateValue: (v
|
|
14
|
-
setInternalValue: (v?: Date) => void;
|
|
15
|
-
getLastUpdatedValue: () => Date | undefined;
|
|
12
|
+
value?: Date | null;
|
|
13
|
+
updateValue: (v: Date) => Date;
|
|
14
|
+
setInternalValue: (v?: Date | null) => void;
|
|
15
|
+
getLastUpdatedValue: () => Date | null | undefined;
|
|
16
|
+
clearValue: () => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const _convertDateToTimeZone = (date
|
|
19
|
-
return convertDateToTimeZone(date, timezone) ||
|
|
19
|
+
const _convertDateToTimeZone = (date: Date | null, timezone?: string): Date | null => {
|
|
20
|
+
return convertDateToTimeZone(date, timezone) || null;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
const _convertDateFromTimeZone = (date
|
|
23
|
-
return convertDateFromTimeZone(date, timezone)
|
|
23
|
+
const _convertDateFromTimeZone = (date: Date, timezone?: string): Date => {
|
|
24
|
+
return convertDateFromTimeZone(date, timezone) as Date;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
const getStateValue = (
|
|
27
|
-
defaultStateValue
|
|
28
|
-
value?: Date,
|
|
29
|
-
defaultValue?: Date,
|
|
28
|
+
defaultStateValue: Date | null,
|
|
29
|
+
value?: Date | null,
|
|
30
|
+
defaultValue?: Date | null,
|
|
30
31
|
timezone?: string,
|
|
31
|
-
): Date |
|
|
32
|
+
): Date | null => {
|
|
32
33
|
if (value !== undefined) {
|
|
33
34
|
return _convertDateToTimeZone(value, timezone);
|
|
34
35
|
}
|
|
@@ -44,26 +45,27 @@ export const useDateInputValue = ({
|
|
|
44
45
|
onChange,
|
|
45
46
|
timezone,
|
|
46
47
|
}: UseDateInputValueOptions): UseDateInputValueReturn => {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const [internalValue, setInternalValue] = React.useState<Date | undefined>(
|
|
50
|
-
getStateValue(undefined, value, defaultValue, timezone),
|
|
48
|
+
const [internalValue, setInternalValue] = React.useState<Date | null | undefined>(
|
|
49
|
+
getStateValue(null, value, defaultValue, timezone),
|
|
51
50
|
);
|
|
52
|
-
const lastUpdatedValueRef = React.useRef<Date | undefined>(
|
|
53
|
-
getStateValue(
|
|
51
|
+
const lastUpdatedValueRef = React.useRef<Date | null | undefined>(
|
|
52
|
+
getStateValue(null, value, defaultValue, timezone),
|
|
54
53
|
);
|
|
55
54
|
|
|
55
|
+
const isControlled = value !== undefined;
|
|
56
|
+
|
|
56
57
|
React.useEffect(() => {
|
|
57
58
|
if (isControlled) {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const newInternalValue = _convertDateToTimeZone(value, timezone);
|
|
60
|
+
setInternalValue(newInternalValue);
|
|
61
|
+
lastUpdatedValueRef.current = newInternalValue;
|
|
60
62
|
}
|
|
61
63
|
}, [isControlled, timezone, value]);
|
|
62
64
|
|
|
63
65
|
const getLastUpdatedValue = React.useCallback(() => lastUpdatedValueRef.current, []);
|
|
64
66
|
|
|
65
67
|
const updateValue = React.useCallback(
|
|
66
|
-
(newValue
|
|
68
|
+
(newValue: Date) => {
|
|
67
69
|
if (!isControlled) {
|
|
68
70
|
setInternalValue(newValue);
|
|
69
71
|
lastUpdatedValueRef.current = newValue;
|
|
@@ -75,10 +77,17 @@ export const useDateInputValue = ({
|
|
|
75
77
|
[isControlled, onChange, timezone],
|
|
76
78
|
);
|
|
77
79
|
|
|
80
|
+
const clearValue = () => {
|
|
81
|
+
setInternalValue(null);
|
|
82
|
+
lastUpdatedValueRef.current = null;
|
|
83
|
+
onChange?.(undefined);
|
|
84
|
+
};
|
|
85
|
+
|
|
78
86
|
return {
|
|
79
87
|
value: internalValue,
|
|
80
88
|
updateValue,
|
|
81
89
|
setInternalValue,
|
|
82
90
|
getLastUpdatedValue,
|
|
91
|
+
clearValue,
|
|
83
92
|
};
|
|
84
93
|
};
|
|
@@ -57,6 +57,14 @@ export type DateRangeInputTestsProps = {
|
|
|
57
57
|
* Передает атрибуты `data-testid` для полей ввода конечной даты
|
|
58
58
|
*/
|
|
59
59
|
endDateTestsProps?: DateTestsProps;
|
|
60
|
+
/**
|
|
61
|
+
* Передает атрибут `data-testid` для кнопки показа календаря.
|
|
62
|
+
*/
|
|
63
|
+
showCalendarButtonTestId?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Передает атрибут `data-testid` для кнопки очистки даты.
|
|
66
|
+
*/
|
|
67
|
+
clearButtonTestId?: string;
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
export interface DateRangeInputProps
|
|
@@ -183,6 +191,8 @@ export const DateRangeInput = ({
|
|
|
183
191
|
calendarTestsProps,
|
|
184
192
|
startDateTestsProps,
|
|
185
193
|
endDateTestsProps,
|
|
194
|
+
clearButtonTestId,
|
|
195
|
+
showCalendarButtonTestId,
|
|
186
196
|
id,
|
|
187
197
|
...props
|
|
188
198
|
}: DateRangeInputProps): React.ReactNode => {
|
|
@@ -193,10 +203,15 @@ export const DateRangeInput = ({
|
|
|
193
203
|
const monthsEndRef = React.useRef<HTMLSpanElement>(null);
|
|
194
204
|
const yearsEndRef = React.useRef<HTMLSpanElement>(null);
|
|
195
205
|
|
|
196
|
-
const
|
|
206
|
+
const _onChange = React.useCallback(
|
|
207
|
+
(newValue: DateRangeType | null | undefined) => onChange?.(newValue || undefined),
|
|
208
|
+
[onChange],
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const [value, updateValue] = useCustomEnsuredControl<DateRangeType | null | undefined>({
|
|
197
212
|
value: valueProp,
|
|
198
213
|
defaultValue,
|
|
199
|
-
onChange,
|
|
214
|
+
onChange: _onChange,
|
|
200
215
|
});
|
|
201
216
|
|
|
202
217
|
const onInternalValueChange = React.useCallback(
|
|
@@ -248,6 +263,8 @@ export const DateRangeInput = ({
|
|
|
248
263
|
[daysStartRef, monthsStartRef, yearsStartRef, daysEndRef, monthsEndRef, yearsEndRef],
|
|
249
264
|
);
|
|
250
265
|
|
|
266
|
+
const onClear = React.useCallback(() => updateValue(undefined), [updateValue]);
|
|
267
|
+
|
|
251
268
|
const {
|
|
252
269
|
rootRef,
|
|
253
270
|
calendarRef,
|
|
@@ -266,7 +283,7 @@ export const DateRangeInput = ({
|
|
|
266
283
|
autoFocus,
|
|
267
284
|
disabled,
|
|
268
285
|
elementsConfig,
|
|
269
|
-
|
|
286
|
+
onClear,
|
|
270
287
|
onInternalValueChange,
|
|
271
288
|
getInternalValue,
|
|
272
289
|
value,
|
|
@@ -301,12 +318,16 @@ export const DateRangeInput = ({
|
|
|
301
318
|
getRootRef={handleRootRef}
|
|
302
319
|
after={
|
|
303
320
|
value ? (
|
|
304
|
-
<IconButton hoverMode="opacity" onClick={clear}>
|
|
321
|
+
<IconButton hoverMode="opacity" onClick={clear} data-testid={clearButtonTestId}>
|
|
305
322
|
<VisuallyHidden>{clearFieldLabel}</VisuallyHidden>
|
|
306
323
|
<Icon16Clear />
|
|
307
324
|
</IconButton>
|
|
308
325
|
) : (
|
|
309
|
-
<IconButton
|
|
326
|
+
<IconButton
|
|
327
|
+
hoverMode="opacity"
|
|
328
|
+
onClick={openCalendar}
|
|
329
|
+
data-testid={showCalendarButtonTestId}
|
|
330
|
+
>
|
|
310
331
|
<VisuallyHidden>{showCalendarLabel}</VisuallyHidden>
|
|
311
332
|
<Icon20CalendarOutline />
|
|
312
333
|
</IconButton>
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useId } from 'react';
|
|
4
3
|
import { ModalContext } from '../../context/ModalContext';
|
|
5
|
-
import { getNavId } from '../../lib/getNavId';
|
|
6
|
-
import { warnOnce } from '../../lib/warnOnce';
|
|
7
4
|
import { useModalManager } from '../ModalRoot/useModalManager';
|
|
8
5
|
import { ModalCardInternal } from './ModalCardInternal';
|
|
9
6
|
import type { ModalCardProps } from './types';
|
|
10
7
|
|
|
11
|
-
const warn = warnOnce('ModalCard');
|
|
12
|
-
|
|
13
8
|
/**
|
|
14
9
|
* @see https://vkcom.github.io/VKUI/#/ModalCard
|
|
15
10
|
*/
|
|
@@ -26,15 +21,13 @@ export const ModalCard = ({
|
|
|
26
21
|
keepMounted = false,
|
|
27
22
|
...restProps
|
|
28
23
|
}: ModalCardProps): React.ReactNode => {
|
|
29
|
-
const generatingId = useId();
|
|
30
|
-
const id = getNavId({ nav, id: idProp }, warn) || generatingId;
|
|
31
|
-
|
|
32
24
|
const {
|
|
33
25
|
mounted,
|
|
34
26
|
shouldPreserveSnapPoint: excludedProp,
|
|
27
|
+
id,
|
|
35
28
|
...resolvedProps
|
|
36
29
|
} = useModalManager({
|
|
37
|
-
id,
|
|
30
|
+
id: nav || idProp,
|
|
38
31
|
open,
|
|
39
32
|
keepMounted,
|
|
40
33
|
modalOverlayTestId,
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
4
|
import { ModalContext } from '../../context/ModalContext';
|
|
5
5
|
import { inRange } from '../../helpers/range';
|
|
6
|
-
import { getNavId } from '../../lib/getNavId';
|
|
7
6
|
import { SNAP_POINT_DETENTS, SNAP_POINT_SAFE_RANGE, type SnapPoint } from '../../lib/sheet';
|
|
8
|
-
import { warnOnce } from '../../lib/warnOnce';
|
|
9
7
|
import { useModalManager } from '../ModalRoot/useModalManager';
|
|
10
8
|
import { ModalPageInternal } from './ModalPageInternal';
|
|
11
9
|
import type { ModalPageProps } from './types';
|
|
12
10
|
|
|
13
|
-
const warn = warnOnce('ModalPage');
|
|
14
|
-
|
|
15
11
|
const snapPointCache = new Map<string, Exclude<SnapPoint, 'auto'>>();
|
|
16
12
|
|
|
17
13
|
/**
|
|
@@ -33,11 +29,8 @@ export const ModalPage = ({
|
|
|
33
29
|
keepMounted = false,
|
|
34
30
|
...restProps
|
|
35
31
|
}: ModalPageProps) => {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const { mounted, shouldPreserveSnapPoint, ...resolvedProps } = useModalManager({
|
|
40
|
-
id,
|
|
32
|
+
const { mounted, shouldPreserveSnapPoint, id, ...resolvedProps } = useModalManager({
|
|
33
|
+
id: nav || idProp,
|
|
41
34
|
open,
|
|
42
35
|
keepMounted,
|
|
43
36
|
modalOverlayTestId,
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { useContext, useState } from 'react';
|
|
1
|
+
import { useContext, useId, useState } from 'react';
|
|
2
|
+
import { getNavId } from '../../lib/getNavId';
|
|
2
3
|
import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect';
|
|
4
|
+
import { warnOnce } from '../../lib/warnOnce';
|
|
3
5
|
import type { AnyFunction } from '../../types';
|
|
4
6
|
import { ModalOverlay, type ModalOverlayProps } from '../ModalOverlay/ModalOverlay';
|
|
5
7
|
import { ModalRootContext } from './ModalRootContext';
|
|
6
8
|
import { VisuallyHiddenModalOverlay } from './VisuallyHiddenModalOverlay/VisuallyHiddenModalOverlay';
|
|
7
9
|
import type { ModalRootCallbackFunction } from './types';
|
|
8
10
|
|
|
11
|
+
const warn = warnOnce('useModalManager');
|
|
9
12
|
export interface UseModalManager {
|
|
10
|
-
id
|
|
13
|
+
id?: string;
|
|
11
14
|
open: boolean;
|
|
12
15
|
keepMounted: boolean;
|
|
13
16
|
modalOverlayTestId?: string;
|
|
@@ -19,6 +22,7 @@ export interface UseModalManager {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export interface UseModalManagerResolvedProps {
|
|
25
|
+
id: string;
|
|
22
26
|
open: boolean;
|
|
23
27
|
noFocusToDialog?: boolean;
|
|
24
28
|
modalOverlayTestId?: string;
|
|
@@ -30,11 +34,11 @@ export interface UseModalManagerResolvedProps {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export type UseModalManagerResult =
|
|
33
|
-
| { mounted: false; shouldPreserveSnapPoint: boolean }
|
|
37
|
+
| { mounted: false; shouldPreserveSnapPoint: boolean; id: UseModalManagerResolvedProps['id'] }
|
|
34
38
|
| ({ mounted: true; shouldPreserveSnapPoint: boolean } & UseModalManagerResolvedProps);
|
|
35
39
|
|
|
36
40
|
export const useModalManager = ({
|
|
37
|
-
id,
|
|
41
|
+
id: idProp,
|
|
38
42
|
open,
|
|
39
43
|
keepMounted,
|
|
40
44
|
modalOverlayTestId,
|
|
@@ -45,6 +49,8 @@ export const useModalManager = ({
|
|
|
45
49
|
onClosed,
|
|
46
50
|
}: UseModalManager): UseModalManagerResult => {
|
|
47
51
|
const context = useContext(ModalRootContext);
|
|
52
|
+
const generatingId = useId();
|
|
53
|
+
const id = getNavId({ nav: idProp }, context.isInsideModal ? warn : undefined) || generatingId;
|
|
48
54
|
const opened = context.isInsideModal ? context.activeModal === id : open;
|
|
49
55
|
const shouldPreserveSnapPoint = context.isInsideModal ? context.activeModal !== null : false;
|
|
50
56
|
|
|
@@ -60,10 +66,11 @@ export const useModalManager = ({
|
|
|
60
66
|
);
|
|
61
67
|
|
|
62
68
|
if (unmounted) {
|
|
63
|
-
return { mounted: false, shouldPreserveSnapPoint };
|
|
69
|
+
return { mounted: false, shouldPreserveSnapPoint, id };
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
return {
|
|
73
|
+
id,
|
|
67
74
|
mounted: true,
|
|
68
75
|
open: opened,
|
|
69
76
|
shouldPreserveSnapPoint,
|
|
@@ -17,5 +17,5 @@ export const useModalRootContext = (): UseModalRootContext => {
|
|
|
17
17
|
}
|
|
18
18
|
}, [activeModal, onCloseContext]);
|
|
19
19
|
|
|
20
|
-
return { isInsideModal, onClose, updateModalHeight, registerModal };
|
|
20
|
+
return { activeModal, isInsideModal, onClose, updateModalHeight, registerModal };
|
|
21
21
|
};
|