@cloud-ru/ds-fields 1.0.0
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/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +888 -0
- package/dist/cjs/components/FieldColor/FieldColor.d.ts +48 -0
- package/dist/cjs/components/FieldColor/FieldColor.js +147 -0
- package/dist/cjs/components/FieldColor/index.d.ts +1 -0
- package/dist/cjs/components/FieldColor/index.js +17 -0
- package/dist/cjs/components/FieldColor/styles.module.css +79 -0
- package/dist/cjs/components/FieldColor/utils.d.ts +7 -0
- package/dist/cjs/components/FieldColor/utils.js +19 -0
- package/dist/cjs/components/FieldCombo/FieldCombo.d.ts +91 -0
- package/dist/cjs/components/FieldCombo/FieldCombo.js +200 -0
- package/dist/cjs/components/FieldCombo/index.d.ts +1 -0
- package/dist/cjs/components/FieldCombo/index.js +17 -0
- package/dist/cjs/components/FieldDate/FieldDate.d.ts +2 -0
- package/dist/cjs/components/FieldDate/FieldDate.js +369 -0
- package/dist/cjs/components/FieldDate/constants.d.ts +10 -0
- package/dist/cjs/components/FieldDate/constants.js +13 -0
- package/dist/cjs/components/FieldDate/index.d.ts +3 -0
- package/dist/cjs/components/FieldDate/index.js +19 -0
- package/dist/cjs/components/FieldDate/mask.d.ts +20 -0
- package/dist/cjs/components/FieldDate/mask.js +117 -0
- package/dist/cjs/components/FieldDate/styles.module.css +43 -0
- package/dist/cjs/components/FieldDate/types.d.ts +100 -0
- package/dist/cjs/components/FieldDate/types.js +2 -0
- package/dist/cjs/components/FieldDate/utils.d.ts +3 -0
- package/dist/cjs/components/FieldDate/utils.js +8 -0
- package/dist/cjs/components/FieldSecure/FieldSecure.d.ts +64 -0
- package/dist/cjs/components/FieldSecure/FieldSecure.js +158 -0
- package/dist/cjs/components/FieldSecure/index.d.ts +1 -0
- package/dist/cjs/components/FieldSecure/index.js +17 -0
- package/dist/cjs/components/FieldSelect/FieldSelect.d.ts +2 -0
- package/dist/cjs/components/FieldSelect/FieldSelect.js +360 -0
- package/dist/cjs/components/FieldSelect/constants.d.ts +4 -0
- package/dist/cjs/components/FieldSelect/constants.js +7 -0
- package/dist/cjs/components/FieldSelect/index.d.ts +3 -0
- package/dist/cjs/components/FieldSelect/index.js +19 -0
- package/dist/cjs/components/FieldSelect/styles.module.css +92 -0
- package/dist/cjs/components/FieldSelect/types.d.ts +197 -0
- package/dist/cjs/components/FieldSelect/types.js +2 -0
- package/dist/cjs/components/FieldSelect/utils.d.ts +19 -0
- package/dist/cjs/components/FieldSelect/utils.js +104 -0
- package/dist/cjs/components/FieldSlider/FieldSlider.d.ts +92 -0
- package/dist/cjs/components/FieldSlider/FieldSlider.js +196 -0
- package/dist/cjs/components/FieldSlider/index.d.ts +2 -0
- package/dist/cjs/components/FieldSlider/index.js +18 -0
- package/dist/cjs/components/FieldSlider/styles.module.css +44 -0
- package/dist/cjs/components/FieldSlider/types.d.ts +4 -0
- package/dist/cjs/components/FieldSlider/types.js +2 -0
- package/dist/cjs/components/FieldSlider/utils/generateAllowedValues.d.ts +1 -0
- package/dist/cjs/components/FieldSlider/utils/generateAllowedValues.js +16 -0
- package/dist/cjs/components/FieldSlider/utils/getClosestMark.d.ts +4 -0
- package/dist/cjs/components/FieldSlider/utils/getClosestMark.js +16 -0
- package/dist/cjs/components/FieldSlider/utils/getMarkLabel.d.ts +2 -0
- package/dist/cjs/components/FieldSlider/utils/getMarkLabel.js +7 -0
- package/dist/cjs/components/FieldSlider/utils/getTextFieldValue.d.ts +2 -0
- package/dist/cjs/components/FieldSlider/utils/getTextFieldValue.js +9 -0
- package/dist/cjs/components/FieldSlider/utils/index.d.ts +5 -0
- package/dist/cjs/components/FieldSlider/utils/index.js +21 -0
- package/dist/cjs/components/FieldSlider/utils/isMarkObject.d.ts +6 -0
- package/dist/cjs/components/FieldSlider/utils/isMarkObject.js +6 -0
- package/dist/cjs/components/FieldStepper/FieldStepper.d.ts +81 -0
- package/dist/cjs/components/FieldStepper/FieldStepper.js +160 -0
- package/dist/cjs/components/FieldStepper/index.d.ts +1 -0
- package/dist/cjs/components/FieldStepper/index.js +17 -0
- package/dist/cjs/components/FieldStepper/styles.module.css +78 -0
- package/dist/cjs/components/FieldStepper/utils.d.ts +14 -0
- package/dist/cjs/components/FieldStepper/utils.js +45 -0
- package/dist/cjs/components/FieldText/FieldText.d.ts +11 -0
- package/dist/cjs/components/FieldText/FieldText.js +14 -0
- package/dist/cjs/components/FieldText/index.d.ts +1 -0
- package/dist/cjs/components/FieldText/index.js +17 -0
- package/dist/cjs/components/FieldTextArea/FieldTextArea.d.ts +95 -0
- package/dist/cjs/components/FieldTextArea/FieldTextArea.js +114 -0
- package/dist/cjs/components/FieldTextArea/index.d.ts +1 -0
- package/dist/cjs/components/FieldTextArea/index.js +17 -0
- package/dist/cjs/components/FieldTextArea/styles.module.css +123 -0
- package/dist/cjs/components/FieldTime/FieldTime.d.ts +74 -0
- package/dist/cjs/components/FieldTime/FieldTime.js +167 -0
- package/dist/cjs/components/FieldTime/index.d.ts +2 -0
- package/dist/cjs/components/FieldTime/index.js +17 -0
- package/dist/cjs/components/FieldTime/mask.d.ts +3 -0
- package/dist/cjs/components/FieldTime/mask.js +24 -0
- package/dist/cjs/components/FieldTime/styles.module.css +3 -0
- package/dist/cjs/components/index.d.ts +14 -0
- package/dist/cjs/components/index.js +32 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButton.d.ts +35 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButton.js +47 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.d.ts +26 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.js +24 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/constants.d.ts +9 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/constants.js +12 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/index.d.ts +4 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/index.js +20 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/styles.module.css +204 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/types.d.ts +4 -0
- package/dist/cjs/components/shared/helperComponents/FieldElementButton/types.js +2 -0
- package/dist/cjs/components/shared/helperComponents/FieldShell/FieldShell.d.ts +43 -0
- package/dist/cjs/components/shared/helperComponents/FieldShell/FieldShell.js +22 -0
- package/dist/cjs/components/shared/helperComponents/FieldShell/index.d.ts +1 -0
- package/dist/cjs/components/shared/helperComponents/FieldShell/index.js +17 -0
- package/dist/cjs/components/shared/helperComponents/FieldShell/styles.module.css +11 -0
- package/dist/cjs/components/shared/hooks/useCopyButton.d.ts +20 -0
- package/dist/cjs/components/shared/hooks/useCopyButton.js +58 -0
- package/dist/cjs/components/shared/index.d.ts +4 -0
- package/dist/cjs/components/shared/index.js +20 -0
- package/dist/cjs/components/shared/styles.module.css +673 -0
- package/dist/cjs/components/shared/utils.d.ts +69 -0
- package/dist/cjs/components/shared/utils.js +104 -0
- package/dist/cjs/constants.d.ts +61 -0
- package/dist/cjs/constants.js +76 -0
- package/dist/cjs/hooks/index.d.ts +1 -0
- package/dist/cjs/hooks/index.js +17 -0
- package/dist/cjs/hooks/useAdaptiveAutoFocus.d.ts +7 -0
- package/dist/cjs/hooks/useAdaptiveAutoFocus.js +10 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/segments/index.d.ts +2 -0
- package/dist/cjs/segments/index.js +18 -0
- package/dist/cjs/segments/segments.d.ts +22 -0
- package/dist/cjs/segments/segments.js +46 -0
- package/dist/cjs/segments/useSegmentedMask.d.ts +43 -0
- package/dist/cjs/segments/useSegmentedMask.js +282 -0
- package/dist/cjs/style.css +1350 -0
- package/dist/esm/components/FieldColor/FieldColor.d.ts +48 -0
- package/dist/esm/components/FieldColor/FieldColor.js +141 -0
- package/dist/esm/components/FieldColor/index.d.ts +1 -0
- package/dist/esm/components/FieldColor/index.js +1 -0
- package/dist/esm/components/FieldColor/styles.module.css +79 -0
- package/dist/esm/components/FieldColor/utils.d.ts +7 -0
- package/dist/esm/components/FieldColor/utils.js +16 -0
- package/dist/esm/components/FieldCombo/FieldCombo.d.ts +91 -0
- package/dist/esm/components/FieldCombo/FieldCombo.js +194 -0
- package/dist/esm/components/FieldCombo/index.d.ts +1 -0
- package/dist/esm/components/FieldCombo/index.js +1 -0
- package/dist/esm/components/FieldDate/FieldDate.d.ts +2 -0
- package/dist/esm/components/FieldDate/FieldDate.js +363 -0
- package/dist/esm/components/FieldDate/constants.d.ts +10 -0
- package/dist/esm/components/FieldDate/constants.js +10 -0
- package/dist/esm/components/FieldDate/index.d.ts +3 -0
- package/dist/esm/components/FieldDate/index.js +3 -0
- package/dist/esm/components/FieldDate/mask.d.ts +20 -0
- package/dist/esm/components/FieldDate/mask.js +111 -0
- package/dist/esm/components/FieldDate/styles.module.css +43 -0
- package/dist/esm/components/FieldDate/types.d.ts +100 -0
- package/dist/esm/components/FieldDate/types.js +1 -0
- package/dist/esm/components/FieldDate/utils.d.ts +3 -0
- package/dist/esm/components/FieldDate/utils.js +5 -0
- package/dist/esm/components/FieldSecure/FieldSecure.d.ts +64 -0
- package/dist/esm/components/FieldSecure/FieldSecure.js +152 -0
- package/dist/esm/components/FieldSecure/index.d.ts +1 -0
- package/dist/esm/components/FieldSecure/index.js +1 -0
- package/dist/esm/components/FieldSelect/FieldSelect.d.ts +2 -0
- package/dist/esm/components/FieldSelect/FieldSelect.js +354 -0
- package/dist/esm/components/FieldSelect/constants.d.ts +4 -0
- package/dist/esm/components/FieldSelect/constants.js +4 -0
- package/dist/esm/components/FieldSelect/index.d.ts +3 -0
- package/dist/esm/components/FieldSelect/index.js +3 -0
- package/dist/esm/components/FieldSelect/styles.module.css +92 -0
- package/dist/esm/components/FieldSelect/types.d.ts +197 -0
- package/dist/esm/components/FieldSelect/types.js +1 -0
- package/dist/esm/components/FieldSelect/utils.d.ts +19 -0
- package/dist/esm/components/FieldSelect/utils.js +93 -0
- package/dist/esm/components/FieldSlider/FieldSlider.d.ts +92 -0
- package/dist/esm/components/FieldSlider/FieldSlider.js +190 -0
- package/dist/esm/components/FieldSlider/index.d.ts +2 -0
- package/dist/esm/components/FieldSlider/index.js +2 -0
- package/dist/esm/components/FieldSlider/styles.module.css +44 -0
- package/dist/esm/components/FieldSlider/types.d.ts +4 -0
- package/dist/esm/components/FieldSlider/types.js +1 -0
- package/dist/esm/components/FieldSlider/utils/generateAllowedValues.d.ts +1 -0
- package/dist/esm/components/FieldSlider/utils/generateAllowedValues.js +13 -0
- package/dist/esm/components/FieldSlider/utils/getClosestMark.d.ts +4 -0
- package/dist/esm/components/FieldSlider/utils/getClosestMark.js +13 -0
- package/dist/esm/components/FieldSlider/utils/getMarkLabel.d.ts +2 -0
- package/dist/esm/components/FieldSlider/utils/getMarkLabel.js +4 -0
- package/dist/esm/components/FieldSlider/utils/getTextFieldValue.d.ts +2 -0
- package/dist/esm/components/FieldSlider/utils/getTextFieldValue.js +6 -0
- package/dist/esm/components/FieldSlider/utils/index.d.ts +5 -0
- package/dist/esm/components/FieldSlider/utils/index.js +5 -0
- package/dist/esm/components/FieldSlider/utils/isMarkObject.d.ts +6 -0
- package/dist/esm/components/FieldSlider/utils/isMarkObject.js +3 -0
- package/dist/esm/components/FieldStepper/FieldStepper.d.ts +81 -0
- package/dist/esm/components/FieldStepper/FieldStepper.js +154 -0
- package/dist/esm/components/FieldStepper/index.d.ts +1 -0
- package/dist/esm/components/FieldStepper/index.js +1 -0
- package/dist/esm/components/FieldStepper/styles.module.css +78 -0
- package/dist/esm/components/FieldStepper/utils.d.ts +14 -0
- package/dist/esm/components/FieldStepper/utils.js +38 -0
- package/dist/esm/components/FieldText/FieldText.d.ts +11 -0
- package/dist/esm/components/FieldText/FieldText.js +11 -0
- package/dist/esm/components/FieldText/index.d.ts +1 -0
- package/dist/esm/components/FieldText/index.js +1 -0
- package/dist/esm/components/FieldTextArea/FieldTextArea.d.ts +95 -0
- package/dist/esm/components/FieldTextArea/FieldTextArea.js +108 -0
- package/dist/esm/components/FieldTextArea/index.d.ts +1 -0
- package/dist/esm/components/FieldTextArea/index.js +1 -0
- package/dist/esm/components/FieldTextArea/styles.module.css +123 -0
- package/dist/esm/components/FieldTime/FieldTime.d.ts +74 -0
- package/dist/esm/components/FieldTime/FieldTime.js +161 -0
- package/dist/esm/components/FieldTime/index.d.ts +2 -0
- package/dist/esm/components/FieldTime/index.js +1 -0
- package/dist/esm/components/FieldTime/mask.d.ts +3 -0
- package/dist/esm/components/FieldTime/mask.js +20 -0
- package/dist/esm/components/FieldTime/styles.module.css +3 -0
- package/dist/esm/components/index.d.ts +14 -0
- package/dist/esm/components/index.js +12 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButton.d.ts +35 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButton.js +41 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.d.ts +26 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.js +21 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/constants.d.ts +9 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/constants.js +9 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/index.d.ts +4 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/index.js +4 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/styles.module.css +204 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/types.d.ts +4 -0
- package/dist/esm/components/shared/helperComponents/FieldElementButton/types.js +1 -0
- package/dist/esm/components/shared/helperComponents/FieldShell/FieldShell.d.ts +43 -0
- package/dist/esm/components/shared/helperComponents/FieldShell/FieldShell.js +16 -0
- package/dist/esm/components/shared/helperComponents/FieldShell/index.d.ts +1 -0
- package/dist/esm/components/shared/helperComponents/FieldShell/index.js +1 -0
- package/dist/esm/components/shared/helperComponents/FieldShell/styles.module.css +11 -0
- package/dist/esm/components/shared/hooks/useCopyButton.d.ts +20 -0
- package/dist/esm/components/shared/hooks/useCopyButton.js +55 -0
- package/dist/esm/components/shared/index.d.ts +4 -0
- package/dist/esm/components/shared/index.js +4 -0
- package/dist/esm/components/shared/styles.module.css +673 -0
- package/dist/esm/components/shared/utils.d.ts +69 -0
- package/dist/esm/components/shared/utils.js +91 -0
- package/dist/esm/constants.d.ts +61 -0
- package/dist/esm/constants.js +73 -0
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/hooks/useAdaptiveAutoFocus.d.ts +7 -0
- package/dist/esm/hooks/useAdaptiveAutoFocus.js +7 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/segments/index.d.ts +2 -0
- package/dist/esm/segments/index.js +2 -0
- package/dist/esm/segments/segments.d.ts +22 -0
- package/dist/esm/segments/segments.js +43 -0
- package/dist/esm/segments/useSegmentedMask.d.ts +43 -0
- package/dist/esm/segments/useSegmentedMask.js +279 -0
- package/dist/esm/style.css +1350 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +62 -0
- package/src/components/FieldColor/FieldColor.tsx +426 -0
- package/src/components/FieldColor/index.ts +1 -0
- package/src/components/FieldColor/styles.module.scss +120 -0
- package/src/components/FieldColor/utils.ts +17 -0
- package/src/components/FieldCombo/FieldCombo.tsx +495 -0
- package/src/components/FieldCombo/index.ts +1 -0
- package/src/components/FieldDate/FieldDate.tsx +658 -0
- package/src/components/FieldDate/constants.ts +11 -0
- package/src/components/FieldDate/index.ts +3 -0
- package/src/components/FieldDate/mask.ts +123 -0
- package/src/components/FieldDate/styles.module.scss +56 -0
- package/src/components/FieldDate/types.ts +124 -0
- package/src/components/FieldDate/utils.ts +7 -0
- package/src/components/FieldSecure/FieldSecure.tsx +398 -0
- package/src/components/FieldSecure/index.ts +1 -0
- package/src/components/FieldSelect/FieldSelect.tsx +689 -0
- package/src/components/FieldSelect/constants.ts +4 -0
- package/src/components/FieldSelect/index.ts +3 -0
- package/src/components/FieldSelect/styles.module.scss +104 -0
- package/src/components/FieldSelect/types.ts +212 -0
- package/src/components/FieldSelect/utils.ts +134 -0
- package/src/components/FieldSlider/FieldSlider.tsx +502 -0
- package/src/components/FieldSlider/index.ts +2 -0
- package/src/components/FieldSlider/styles.module.scss +47 -0
- package/src/components/FieldSlider/types.ts +5 -0
- package/src/components/FieldSlider/utils/generateAllowedValues.ts +13 -0
- package/src/components/FieldSlider/utils/getClosestMark.ts +21 -0
- package/src/components/FieldSlider/utils/getMarkLabel.ts +7 -0
- package/src/components/FieldSlider/utils/getTextFieldValue.ts +8 -0
- package/src/components/FieldSlider/utils/index.ts +5 -0
- package/src/components/FieldSlider/utils/isMarkObject.ts +7 -0
- package/src/components/FieldStepper/FieldStepper.tsx +457 -0
- package/src/components/FieldStepper/index.ts +1 -0
- package/src/components/FieldStepper/styles.module.scss +98 -0
- package/src/components/FieldStepper/utils.ts +44 -0
- package/src/components/FieldText/FieldText.tsx +27 -0
- package/src/components/FieldText/index.ts +1 -0
- package/src/components/FieldTextArea/FieldTextArea.tsx +374 -0
- package/src/components/FieldTextArea/index.ts +1 -0
- package/src/components/FieldTextArea/styles.module.scss +192 -0
- package/src/components/FieldTime/FieldTime.tsx +401 -0
- package/src/components/FieldTime/index.ts +2 -0
- package/src/components/FieldTime/mask.ts +22 -0
- package/src/components/FieldTime/styles.module.scss +3 -0
- package/src/components/index.ts +14 -0
- package/src/components/shared/helperComponents/FieldElementButton/FieldElementButton.tsx +127 -0
- package/src/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.tsx +72 -0
- package/src/components/shared/helperComponents/FieldElementButton/constants.ts +10 -0
- package/src/components/shared/helperComponents/FieldElementButton/index.ts +4 -0
- package/src/components/shared/helperComponents/FieldElementButton/styles.module.scss +137 -0
- package/src/components/shared/helperComponents/FieldElementButton/types.ts +6 -0
- package/src/components/shared/helperComponents/FieldShell/FieldShell.tsx +100 -0
- package/src/components/shared/helperComponents/FieldShell/index.ts +1 -0
- package/src/components/shared/helperComponents/FieldShell/styles.module.scss +13 -0
- package/src/components/shared/hooks/useCopyButton.tsx +103 -0
- package/src/components/shared/index.ts +4 -0
- package/src/components/shared/styles.module.scss +409 -0
- package/src/components/shared/utils.ts +128 -0
- package/src/constants.ts +73 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAdaptiveAutoFocus.ts +16 -0
- package/src/index.ts +3 -0
- package/src/segments/index.ts +2 -0
- package/src/segments/segments.ts +74 -0
- package/src/segments/useSegmentedMask.ts +361 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { CalendarDropdown, CalendarDropdownProps } from '@cloud-ru/ds-calendar';
|
|
2
|
+
import { FieldDecorator, SIZE, VALIDATION_STATE } from '@cloud-ru/ds-field-decorator';
|
|
3
|
+
import { CalendarSVG } from '@cloud-ru/ds-icons/interface/system';
|
|
4
|
+
import { InputPrivate, useButtonNavigation, useClearButton } from '@cloud-ru/ds-input-private';
|
|
5
|
+
import cn from 'classnames';
|
|
6
|
+
import mergeRefs from 'merge-refs';
|
|
7
|
+
import {
|
|
8
|
+
CSSProperties,
|
|
9
|
+
FocusEvent,
|
|
10
|
+
forwardRef,
|
|
11
|
+
KeyboardEvent,
|
|
12
|
+
useCallback,
|
|
13
|
+
useEffect,
|
|
14
|
+
useMemo,
|
|
15
|
+
useRef,
|
|
16
|
+
useState,
|
|
17
|
+
} from 'react';
|
|
18
|
+
|
|
19
|
+
import { TEST_IDS } from '../../constants';
|
|
20
|
+
import { useAdaptiveAutoFocus } from '../../hooks';
|
|
21
|
+
import { buildSegments, SegmentsMode, useSegmentedMask } from '../../segments';
|
|
22
|
+
import {
|
|
23
|
+
copyTextToClipboard,
|
|
24
|
+
getAcrylicProps,
|
|
25
|
+
preventSlotMouseDown,
|
|
26
|
+
stopSlotClickPropagation,
|
|
27
|
+
toInputSize,
|
|
28
|
+
useCopyButton,
|
|
29
|
+
} from '../shared';
|
|
30
|
+
import fieldStyles from '../shared/styles.module.scss';
|
|
31
|
+
import { DATE_MODE, DATE_RANGE_LABELS } from './constants';
|
|
32
|
+
import { dateToMaskString, formatMask, MaskMode, parseMask } from './mask';
|
|
33
|
+
import styles from './styles.module.scss';
|
|
34
|
+
import { DateRangeValue, DateValue, FieldDateProps } from './types';
|
|
35
|
+
import { isRange } from './utils';
|
|
36
|
+
|
|
37
|
+
export const FieldDate = forwardRef<HTMLInputElement, FieldDateProps>(function FieldDate(props, ref) {
|
|
38
|
+
const {
|
|
39
|
+
label = '',
|
|
40
|
+
labelTooltip,
|
|
41
|
+
caption,
|
|
42
|
+
hint,
|
|
43
|
+
error,
|
|
44
|
+
validationState = VALIDATION_STATE.Default,
|
|
45
|
+
showHintIcon,
|
|
46
|
+
length,
|
|
47
|
+
required,
|
|
48
|
+
size = SIZE.M,
|
|
49
|
+
className,
|
|
50
|
+
fieldClassName,
|
|
51
|
+
placeholder,
|
|
52
|
+
labelFrom = DATE_RANGE_LABELS.from,
|
|
53
|
+
labelTo = DATE_RANGE_LABELS.to,
|
|
54
|
+
iconBefore,
|
|
55
|
+
disabled,
|
|
56
|
+
readonly: readOnly,
|
|
57
|
+
background = true,
|
|
58
|
+
open: openProp,
|
|
59
|
+
onOpenChange,
|
|
60
|
+
placement,
|
|
61
|
+
today,
|
|
62
|
+
showHolidays,
|
|
63
|
+
buildCellProps,
|
|
64
|
+
locale,
|
|
65
|
+
presets,
|
|
66
|
+
closeOnApply = true,
|
|
67
|
+
closeOnPopstate,
|
|
68
|
+
showClearButton = true,
|
|
69
|
+
showCopyButton = true,
|
|
70
|
+
onCopyButtonClick,
|
|
71
|
+
showSeconds = true,
|
|
72
|
+
id,
|
|
73
|
+
name,
|
|
74
|
+
autoFocus,
|
|
75
|
+
layoutPresets,
|
|
76
|
+
onFocus: onFocusProp,
|
|
77
|
+
onBlur: onBlurProp,
|
|
78
|
+
'data-test-id': dataTestId = TEST_IDS.fieldDate,
|
|
79
|
+
} = props;
|
|
80
|
+
|
|
81
|
+
const range = isRange(props);
|
|
82
|
+
const maskMode: MaskMode = props.mode === DATE_MODE.DateTime ? DATE_MODE.DateTime : DATE_MODE.Date;
|
|
83
|
+
const segMode: SegmentsMode = maskMode === DATE_MODE.DateTime ? DATE_MODE.DateTime : DATE_MODE.Date;
|
|
84
|
+
const { mask: segMask, slots: segSlots } = useMemo(() => buildSegments(segMode, showSeconds), [segMode, showSeconds]);
|
|
85
|
+
const placeholderMask = placeholder ?? segMask;
|
|
86
|
+
|
|
87
|
+
const fromInputRef = useRef<HTMLInputElement>(null);
|
|
88
|
+
const toInputRef = useRef<HTMLInputElement>(null);
|
|
89
|
+
const clearButtonRef = useRef<HTMLButtonElement>(null);
|
|
90
|
+
const copyButtonRef = useRef<HTMLButtonElement>(null);
|
|
91
|
+
const navigationStartRef = useRef<{ focus(): void }>(null);
|
|
92
|
+
const [hover, setHover] = useState(false);
|
|
93
|
+
const [focusVisible, setFocusVisible] = useState(false);
|
|
94
|
+
const [openLocal, setOpenLocal] = useState(false);
|
|
95
|
+
const open = openProp ?? openLocal;
|
|
96
|
+
|
|
97
|
+
const resolvedAutoFocus = useAdaptiveAutoFocus(autoFocus, layoutPresets);
|
|
98
|
+
|
|
99
|
+
const [singleLocal, setSingleLocal] = useState<DateValue>(!range ? (props.defaultValue as DateValue) : undefined);
|
|
100
|
+
const [rangeLocal, setRangeLocal] = useState<DateRangeValue>(
|
|
101
|
+
range ? ((props.defaultValue as DateRangeValue | undefined) ?? [undefined, undefined]) : [undefined, undefined],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const singleValue = useMemo<DateValue>(
|
|
105
|
+
() => (!range ? ((props.value as DateValue) ?? singleLocal) : undefined),
|
|
106
|
+
[range, props.value, singleLocal],
|
|
107
|
+
);
|
|
108
|
+
const rangeValue = useMemo<DateRangeValue>(
|
|
109
|
+
() => (range ? ((props.value as DateRangeValue | undefined) ?? rangeLocal) : [undefined, undefined]),
|
|
110
|
+
[range, props.value, rangeLocal],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const effectiveValidationState = useMemo(
|
|
114
|
+
() => (error ? VALIDATION_STATE.Error : validationState),
|
|
115
|
+
[error, validationState],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Локальное состояние input'а(ов) — синхронизируется при внешних изменениях value, но не блокирует ввод.
|
|
119
|
+
const [singleInputValue, setSingleInputValue] = useState(() =>
|
|
120
|
+
!range && singleValue ? dateToMaskString(singleValue, maskMode, showSeconds) : '',
|
|
121
|
+
);
|
|
122
|
+
const [rangeInputValue, setRangeInputValue] = useState<[string, string]>(() => [
|
|
123
|
+
range && rangeValue[0] ? dateToMaskString(rangeValue[0], maskMode, showSeconds) : '',
|
|
124
|
+
range && rangeValue[1] ? dateToMaskString(rangeValue[1], maskMode, showSeconds) : '',
|
|
125
|
+
]);
|
|
126
|
+
// Зеркало `rangeInputValue` — чтобы handleRangeInput читал актуальное состояние без side-effect в updater'е.
|
|
127
|
+
const rangeInputValueRef = useRef(rangeInputValue);
|
|
128
|
+
rangeInputValueRef.current = rangeInputValue;
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (range) return;
|
|
132
|
+
const next = singleValue ? dateToMaskString(singleValue, maskMode, showSeconds) : '';
|
|
133
|
+
if (fromInputRef.current && document.activeElement === fromInputRef.current) return;
|
|
134
|
+
setSingleInputValue(next);
|
|
135
|
+
}, [singleValue, range, maskMode, showSeconds]);
|
|
136
|
+
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!range) return;
|
|
139
|
+
const next: [string, string] = [
|
|
140
|
+
rangeValue[0] ? dateToMaskString(rangeValue[0], maskMode, showSeconds) : '',
|
|
141
|
+
rangeValue[1] ? dateToMaskString(rangeValue[1], maskMode, showSeconds) : '',
|
|
142
|
+
];
|
|
143
|
+
// Чтение фокуса — до updater'а (updater остаётся чистым, без side-effect/DOM-доступа).
|
|
144
|
+
const fromFocused = fromInputRef.current && document.activeElement === fromInputRef.current;
|
|
145
|
+
const toFocused = toInputRef.current && document.activeElement === toInputRef.current;
|
|
146
|
+
setRangeInputValue(prev => [fromFocused ? prev[0] : next[0], toFocused ? prev[1] : next[1]]);
|
|
147
|
+
}, [rangeValue, range, maskMode, showSeconds]);
|
|
148
|
+
|
|
149
|
+
const handleOpenChange = useCallback(
|
|
150
|
+
(next: boolean) => {
|
|
151
|
+
// Календарь открывается кликом по полю (CalendarDropdown дёргает onOpenChange(true)).
|
|
152
|
+
// readonly/disabled поле открывать нельзя — блокируем открытие, закрытие разрешаем.
|
|
153
|
+
if (next && (disabled || readOnly)) return;
|
|
154
|
+
if (openProp === undefined) setOpenLocal(next);
|
|
155
|
+
onOpenChange?.(next);
|
|
156
|
+
},
|
|
157
|
+
[openProp, onOpenChange, disabled, readOnly],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const commitSingle = useCallback(
|
|
161
|
+
(next: DateValue) => {
|
|
162
|
+
if (range) return;
|
|
163
|
+
if (props.value === undefined) setSingleLocal(next);
|
|
164
|
+
props.onChange?.(next);
|
|
165
|
+
},
|
|
166
|
+
[range, props],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const commitRange = useCallback(
|
|
170
|
+
(next: DateRangeValue) => {
|
|
171
|
+
if (!range) return;
|
|
172
|
+
if (props.value === undefined) setRangeLocal(next);
|
|
173
|
+
props.onChange?.(next);
|
|
174
|
+
},
|
|
175
|
+
[range, props],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Сегментный ввод одиночной даты/даты-времени (range остаётся type-through ниже).
|
|
179
|
+
const singleSeg = useSegmentedMask({
|
|
180
|
+
inputRef: fromInputRef,
|
|
181
|
+
mask: segMask,
|
|
182
|
+
slots: segSlots,
|
|
183
|
+
readonly: readOnly,
|
|
184
|
+
disabled,
|
|
185
|
+
setValue: setSingleInputValue,
|
|
186
|
+
onMaskedChange: masked => {
|
|
187
|
+
if (masked === '') {
|
|
188
|
+
commitSingle(undefined);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const parsed = parseMask(masked, maskMode, showSeconds);
|
|
192
|
+
if (parsed) commitSingle(parsed);
|
|
193
|
+
},
|
|
194
|
+
onArrowDown: () => {
|
|
195
|
+
if (readOnly || disabled) return;
|
|
196
|
+
handleOpenChange(true);
|
|
197
|
+
// Клавиатурный handoff: фокус уходит в сетку календаря (паритет с легаси checkForLeavingFocus).
|
|
198
|
+
setTimeout(() => navigationStartRef.current?.focus(), 0);
|
|
199
|
+
},
|
|
200
|
+
// ArrowUp в поле закрывает открытый календарь (фокус остаётся в input на текущем сегменте).
|
|
201
|
+
onArrowUp: () => {
|
|
202
|
+
if (readOnly || disabled) return;
|
|
203
|
+
handleOpenChange(false);
|
|
204
|
+
},
|
|
205
|
+
onEscape: () => handleOpenChange(false),
|
|
206
|
+
onEdit: () => handleOpenChange(false),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const handleRangeInput = useCallback(
|
|
210
|
+
(idx: 0 | 1) => (input: string) => {
|
|
211
|
+
const formatted = formatMask(input, maskMode, showSeconds);
|
|
212
|
+
// Считаем следующее состояние из текущего rangeInputValue (ref), без side-effect в updater'е.
|
|
213
|
+
const prev = rangeInputValueRef.current;
|
|
214
|
+
const next: [string, string] = idx === 0 ? [formatted, prev[1]] : [prev[0], formatted];
|
|
215
|
+
setRangeInputValue(next);
|
|
216
|
+
|
|
217
|
+
const parsedFrom = next[0] ? parseMask(next[0], maskMode, showSeconds) : undefined;
|
|
218
|
+
const parsedTo = next[1] ? parseMask(next[1], maskMode, showSeconds) : undefined;
|
|
219
|
+
// Эмитим только когда обе даты валидны либо обе пусты — иначе ждём.
|
|
220
|
+
const bothValid = (parsedFrom !== undefined || next[0] === '') && (parsedTo !== undefined || next[1] === '');
|
|
221
|
+
if (bothValid) commitRange([parsedFrom, parsedTo]);
|
|
222
|
+
},
|
|
223
|
+
[maskMode, showSeconds, commitRange],
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const handleClear = useCallback(() => {
|
|
227
|
+
if (range) {
|
|
228
|
+
setRangeInputValue(['', '']);
|
|
229
|
+
commitRange([undefined, undefined]);
|
|
230
|
+
} else {
|
|
231
|
+
setSingleInputValue('');
|
|
232
|
+
commitSingle(undefined);
|
|
233
|
+
}
|
|
234
|
+
fromInputRef.current?.focus();
|
|
235
|
+
}, [range, commitRange, commitSingle]);
|
|
236
|
+
|
|
237
|
+
const handleCalendarSingleChange = useCallback(
|
|
238
|
+
(next: DateValue) => {
|
|
239
|
+
if (range || disabled || readOnly) return;
|
|
240
|
+
commitSingle(next);
|
|
241
|
+
// Sync-эффект пропускает обновление сфокусированного input'а — обновляем строку сами.
|
|
242
|
+
const nextStr = next ? dateToMaskString(next, maskMode, showSeconds) : '';
|
|
243
|
+
setSingleInputValue(nextStr);
|
|
244
|
+
// Императивно кладём строку в DOM ДО focus(): клик по дате блюрит input → handleBlur движка
|
|
245
|
+
// сбрасывает `input.value` (был === mask) в ''. Возврат фокуса синхронно вызывает
|
|
246
|
+
// `ensureMask`, который на пустом DOM пишет mask обратно и перетирает только что выбранную
|
|
247
|
+
// дату (setSingleInputValue ещё не применён React'ом). Синхронизируем DOM сами — ensureMask
|
|
248
|
+
// видит непустое значение и no-op'ает (баг: маска «залипала» в value-цвете после выбора).
|
|
249
|
+
if (fromInputRef.current) {
|
|
250
|
+
fromInputRef.current.value = nextStr;
|
|
251
|
+
}
|
|
252
|
+
// Выбор даты возвращает фокус в input и закрывает календарь (легаси handleSelectDate).
|
|
253
|
+
// В date-time onChangeValue эмитится только по Apply (клик по дате копит internal state
|
|
254
|
+
// календаря), поэтому выбор времени закрытием не обрывается — паритет с легаси.
|
|
255
|
+
fromInputRef.current?.focus();
|
|
256
|
+
handleOpenChange(false);
|
|
257
|
+
},
|
|
258
|
+
[range, disabled, readOnly, commitSingle, maskMode, showSeconds, handleOpenChange],
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const handleCalendarRangeChange = useCallback(
|
|
262
|
+
(next: DateRangeValue) => {
|
|
263
|
+
if (!range || disabled || readOnly) return;
|
|
264
|
+
commitRange(next);
|
|
265
|
+
// Закрываем только когда выбраны оба конца периода (календарь эмитит полный Range).
|
|
266
|
+
if (next[0] && next[1]) {
|
|
267
|
+
setRangeInputValue([
|
|
268
|
+
dateToMaskString(next[0], maskMode, showSeconds),
|
|
269
|
+
dateToMaskString(next[1], maskMode, showSeconds),
|
|
270
|
+
]);
|
|
271
|
+
fromInputRef.current?.focus();
|
|
272
|
+
handleOpenChange(false);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
[range, disabled, readOnly, commitRange, maskMode, showSeconds, handleOpenChange],
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Общие input-обработчики range-инпутов (`inputCommon` ниже). Открытие календаря — за
|
|
279
|
+
// click-триггером Dropdown и ArrowDown (легаси-паритет); open-on-focus конфликтовал с
|
|
280
|
+
// click-toggle (первый клик открывал и тут же закрывал календарь).
|
|
281
|
+
const handleInputFocus = useCallback(
|
|
282
|
+
(event: FocusEvent<HTMLInputElement>) => {
|
|
283
|
+
setFocusVisible(event.target.matches(':focus-visible'));
|
|
284
|
+
onFocusProp?.(event);
|
|
285
|
+
},
|
|
286
|
+
[onFocusProp],
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const handleInputBlur = useCallback(
|
|
290
|
+
(event: FocusEvent<HTMLInputElement>) => {
|
|
291
|
+
setFocusVisible(false);
|
|
292
|
+
onBlurProp?.(event);
|
|
293
|
+
},
|
|
294
|
+
[onBlurProp],
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const handleInputKeyDown = useCallback(
|
|
298
|
+
(event: KeyboardEvent<HTMLInputElement>) => {
|
|
299
|
+
if (disabled || readOnly) return;
|
|
300
|
+
if (event.key === 'Escape' && open) {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
handleOpenChange(false);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// ArrowDown открывает календарь и передаёт фокус в сетку — тот же клавиатурный handoff,
|
|
306
|
+
// что у singleSeg.onArrowDown (легаси checkForLeavingFocus).
|
|
307
|
+
if (event.key === 'ArrowDown') {
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
handleOpenChange(true);
|
|
310
|
+
setTimeout(() => navigationStartRef.current?.focus(), 0);
|
|
311
|
+
}
|
|
312
|
+
// ArrowUp закрывает открытый календарь (фокус остаётся в range-input).
|
|
313
|
+
if (event.key === 'ArrowUp' && open) {
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
handleOpenChange(false);
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
[disabled, readOnly, open, handleOpenChange],
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Single-input делегирует фокус/блюр сегментному движку, поэтому отдельные обработчики.
|
|
322
|
+
const handleSingleFocus = useCallback(
|
|
323
|
+
(event: FocusEvent<HTMLInputElement>) => {
|
|
324
|
+
setFocusVisible(event.target.matches(':focus-visible'));
|
|
325
|
+
singleSeg.handleFocus();
|
|
326
|
+
onFocusProp?.(event);
|
|
327
|
+
},
|
|
328
|
+
[singleSeg, onFocusProp],
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const handleSingleBlur = useCallback(
|
|
332
|
+
(event: FocusEvent<HTMLInputElement>) => {
|
|
333
|
+
setFocusVisible(false);
|
|
334
|
+
singleSeg.handleBlur();
|
|
335
|
+
onBlurProp?.(event);
|
|
336
|
+
},
|
|
337
|
+
[singleSeg, onBlurProp],
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const handleCalendarFocusLeave = useCallback(() => {
|
|
341
|
+
fromInputRef.current?.focus();
|
|
342
|
+
handleOpenChange(false);
|
|
343
|
+
}, [handleOpenChange]);
|
|
344
|
+
|
|
345
|
+
const handleMouseEnter = useCallback(() => {
|
|
346
|
+
if (!readOnly && !disabled) {
|
|
347
|
+
setHover(true);
|
|
348
|
+
}
|
|
349
|
+
}, [readOnly, disabled]);
|
|
350
|
+
|
|
351
|
+
const handleMouseLeave = useCallback(() => {
|
|
352
|
+
setHover(false);
|
|
353
|
+
}, []);
|
|
354
|
+
|
|
355
|
+
const valueToCopy = range ? [rangeInputValue[0], rangeInputValue[1]].filter(Boolean).join(' – ') : singleInputValue;
|
|
356
|
+
|
|
357
|
+
// Галочку «скопировано» рисует useCopyButton по `true`-результату (паритет с FieldCombo/FieldTime).
|
|
358
|
+
const handleCopy = useCallback(async () => {
|
|
359
|
+
const copied = Boolean(valueToCopy) && (await copyTextToClipboard(valueToCopy));
|
|
360
|
+
if (copied) {
|
|
361
|
+
onCopyButtonClick?.();
|
|
362
|
+
}
|
|
363
|
+
return copied;
|
|
364
|
+
}, [onCopyButtonClick, valueToCopy]);
|
|
365
|
+
|
|
366
|
+
const hasValue = range ? Boolean(rangeValue[0] || rangeValue[1]) : singleValue !== undefined;
|
|
367
|
+
const showClear = Boolean(showClearButton && hasValue && !disabled && !readOnly);
|
|
368
|
+
const showCopy = Boolean(showCopyButton && hasValue && readOnly && !disabled);
|
|
369
|
+
|
|
370
|
+
const clearButtonSettings = useClearButton({
|
|
371
|
+
clearButtonRef,
|
|
372
|
+
showClearButton: showClear,
|
|
373
|
+
size: toInputSize(size),
|
|
374
|
+
onClear: handleClear,
|
|
375
|
+
disabled: Boolean(disabled || readOnly),
|
|
376
|
+
dataTestId: TEST_IDS.fieldDateClear,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const copyButtonSettings = useCopyButton({
|
|
380
|
+
copyButtonRef,
|
|
381
|
+
showCopyButton: showCopy,
|
|
382
|
+
size: toInputSize(size),
|
|
383
|
+
onCopy: handleCopy,
|
|
384
|
+
disabled: Boolean(disabled),
|
|
385
|
+
dataTestId: TEST_IDS.fieldDateCopy,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// ArrowDown с postfix-кнопки открывает календарь и передаёт фокус в сетку
|
|
389
|
+
// (легаси checkForLeavingFocus на onButtonKeyDown).
|
|
390
|
+
const handlePostfixButtonKeyDown = useCallback(
|
|
391
|
+
(event: KeyboardEvent<HTMLButtonElement>) => {
|
|
392
|
+
if (event.key === 'ArrowDown' && !disabled && !readOnly) {
|
|
393
|
+
handleOpenChange(true);
|
|
394
|
+
setTimeout(() => navigationStartRef.current?.focus(), 0);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
[disabled, readOnly, handleOpenChange],
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// Возврат фокуса с кнопок (ArrowLeft): handleFocus движка выбирает первый сегмент, а возврат
|
|
401
|
+
// идёт «справа» — перевыбираем последний (легаси setInputFocusFromButtons: Year/Seconds).
|
|
402
|
+
const setInputFocusFromButtons = useCallback(() => {
|
|
403
|
+
if (range) {
|
|
404
|
+
toInputRef.current?.focus();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
fromInputRef.current?.focus();
|
|
408
|
+
singleSeg.selectSlot(singleSeg.lastSlot);
|
|
409
|
+
}, [range, singleSeg]);
|
|
410
|
+
|
|
411
|
+
const { postfixButtons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
|
|
412
|
+
// Roving-композит живёт на «последнем» инпуте перед кнопками: single или range «to».
|
|
413
|
+
inputRef: range ? toInputRef : fromInputRef,
|
|
414
|
+
setInputFocus: setInputFocusFromButtons,
|
|
415
|
+
postfixButtons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
|
|
416
|
+
onButtonKeyDown: handlePostfixButtonKeyDown,
|
|
417
|
+
readonly: Boolean(readOnly),
|
|
418
|
+
submitKeys: ['Enter', 'Space', 'Tab'],
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Нав-цепочка бежит безусловно ПОСЛЕ движка (легаси useHandlers): движок ставит каретку в конец
|
|
422
|
+
// на ArrowRight с последнего сегмента, nav читает её в том же keydown и забирает фокус на кнопку.
|
|
423
|
+
const handleSingleKeyDown = useCallback(
|
|
424
|
+
(event: KeyboardEvent<HTMLInputElement>) => {
|
|
425
|
+
singleSeg.handleKeyDown(event);
|
|
426
|
+
onInputKeyDown(event);
|
|
427
|
+
},
|
|
428
|
+
[singleSeg, onInputKeyDown],
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const handleToInputKeyDown = useCallback(
|
|
432
|
+
(event: KeyboardEvent<HTMLInputElement>) => {
|
|
433
|
+
handleInputKeyDown(event);
|
|
434
|
+
onInputKeyDown(event);
|
|
435
|
+
},
|
|
436
|
+
[handleInputKeyDown, onInputKeyDown],
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const inputCommon = {
|
|
440
|
+
disabled,
|
|
441
|
+
readonly: readOnly,
|
|
442
|
+
inputMode: 'numeric' as const,
|
|
443
|
+
onFocus: handleInputFocus,
|
|
444
|
+
onBlur: handleInputBlur,
|
|
445
|
+
onKeyDown: handleInputKeyDown,
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const trigger = (
|
|
449
|
+
<div
|
|
450
|
+
className={cn(fieldStyles.fieldWrapper, styles.trigger, fieldClassName)}
|
|
451
|
+
data-size={size}
|
|
452
|
+
data-validation-state={effectiveValidationState}
|
|
453
|
+
data-disabled={disabled || undefined}
|
|
454
|
+
data-readonly={readOnly || undefined}
|
|
455
|
+
data-withbackground={background || undefined}
|
|
456
|
+
data-focusvisible={focusVisible || open || undefined}
|
|
457
|
+
data-hover={!readOnly && !disabled && hover ? true : undefined}
|
|
458
|
+
data-test-id={dataTestId}
|
|
459
|
+
onMouseEnter={handleMouseEnter}
|
|
460
|
+
onMouseLeave={handleMouseLeave}
|
|
461
|
+
>
|
|
462
|
+
<div className={fieldStyles.backgroundWrapper}>
|
|
463
|
+
<div
|
|
464
|
+
className={fieldStyles.materialLayer}
|
|
465
|
+
{...getAcrylicProps({
|
|
466
|
+
validationState: effectiveValidationState,
|
|
467
|
+
disabled,
|
|
468
|
+
readonly: readOnly,
|
|
469
|
+
hover,
|
|
470
|
+
focusVisible: focusVisible || open,
|
|
471
|
+
})}
|
|
472
|
+
>
|
|
473
|
+
<div className={fieldStyles.acrylicBg} aria-hidden />
|
|
474
|
+
</div>
|
|
475
|
+
<div className={fieldStyles.borderStateLayer} data-state='borderOnBackground' />
|
|
476
|
+
<div className={fieldStyles.focusLayer} />
|
|
477
|
+
</div>
|
|
478
|
+
<div className={fieldStyles.fieldContainer}>
|
|
479
|
+
<div className={fieldStyles.contentWrapper}>
|
|
480
|
+
{iconBefore && <div className={fieldStyles.iconSlot}>{iconBefore}</div>}
|
|
481
|
+
<div className={fieldStyles.inputLine}>
|
|
482
|
+
<div className={fieldStyles.inputArea}>
|
|
483
|
+
{range ? (
|
|
484
|
+
// --field-date-mask-ch — длина маски для ширины from-инпута (.rangeInputFrom, ширина в ch):
|
|
485
|
+
// «дата – дата» слитно слева, остаток поля забирает to-инпут.
|
|
486
|
+
<span
|
|
487
|
+
className={styles.rangeInputs}
|
|
488
|
+
style={{ '--field-date-mask-ch': placeholderMask.length } as CSSProperties}
|
|
489
|
+
>
|
|
490
|
+
<InputPrivate
|
|
491
|
+
{...inputCommon}
|
|
492
|
+
ref={mergeRefs(ref, fromInputRef)}
|
|
493
|
+
className={cn(fieldStyles.fieldInput, styles.rangeInputFrom)}
|
|
494
|
+
value={rangeInputValue[0]}
|
|
495
|
+
placeholder={placeholderMask}
|
|
496
|
+
onChange={handleRangeInput(0)}
|
|
497
|
+
id={id}
|
|
498
|
+
name={name}
|
|
499
|
+
autoFocus={resolvedAutoFocus}
|
|
500
|
+
aria-label={labelFrom}
|
|
501
|
+
data-test-id={TEST_IDS.fieldDateInputFrom}
|
|
502
|
+
/>
|
|
503
|
+
<span className={styles.rangeSeparator} aria-hidden>
|
|
504
|
+
–
|
|
505
|
+
</span>
|
|
506
|
+
<InputPrivate
|
|
507
|
+
{...inputCommon}
|
|
508
|
+
ref={toInputRef}
|
|
509
|
+
className={fieldStyles.fieldInput}
|
|
510
|
+
value={rangeInputValue[1]}
|
|
511
|
+
placeholder={placeholderMask}
|
|
512
|
+
onChange={handleRangeInput(1)}
|
|
513
|
+
// «to» — последний инпут перед кнопками: на нём живёт roving-композит
|
|
514
|
+
// (ArrowRight в конце → clear/copy), keydown-цепочка с nav-handler'ом.
|
|
515
|
+
onKeyDown={handleToInputKeyDown}
|
|
516
|
+
tabIndex={inputTabIndex}
|
|
517
|
+
aria-label={labelTo}
|
|
518
|
+
data-test-id={TEST_IDS.fieldDateInputTo}
|
|
519
|
+
/>
|
|
520
|
+
</span>
|
|
521
|
+
) : (
|
|
522
|
+
<InputPrivate
|
|
523
|
+
ref={mergeRefs(ref, fromInputRef)}
|
|
524
|
+
className={fieldStyles.fieldInput}
|
|
525
|
+
value={singleInputValue}
|
|
526
|
+
placeholder={placeholderMask}
|
|
527
|
+
// Single/date-time ведёт сегментный движок (keydown, useSegmentedMask), нативный
|
|
528
|
+
// onChange игнорируется. Range — обычный ввод-по-маске (formatMask в handleRangeInput).
|
|
529
|
+
onChange={() => {}}
|
|
530
|
+
onKeyDown={handleSingleKeyDown}
|
|
531
|
+
onClick={singleSeg.handleClick}
|
|
532
|
+
onPaste={singleSeg.handlePaste}
|
|
533
|
+
onFocus={handleSingleFocus}
|
|
534
|
+
onBlur={handleSingleBlur}
|
|
535
|
+
disabled={disabled}
|
|
536
|
+
readonly={readOnly}
|
|
537
|
+
inputMode='numeric'
|
|
538
|
+
tabIndex={inputTabIndex}
|
|
539
|
+
id={id}
|
|
540
|
+
name={name}
|
|
541
|
+
autoFocus={resolvedAutoFocus}
|
|
542
|
+
aria-haspopup='dialog'
|
|
543
|
+
aria-expanded={open}
|
|
544
|
+
data-test-id={TEST_IDS.fieldDateInput}
|
|
545
|
+
/>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
{/* Clear/Copy — roving-композит useButtonNavigation (Figma `buttonClear`): Tab уходит
|
|
549
|
+
из поля целиком, по кнопкам ходят стрелки. test-id кнопок — TEST_IDS.fieldDateClear/
|
|
550
|
+
fieldDateCopy (адресуемый = интерактивный узел). preventSlotMouseDown держит фокус
|
|
551
|
+
в инпуте, stopSlotClickPropagation не даёт клику тогглить dropdown. */}
|
|
552
|
+
{postfixButtons && (
|
|
553
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
554
|
+
<span
|
|
555
|
+
className={fieldStyles.postfixButtonsSlot}
|
|
556
|
+
onMouseDown={preventSlotMouseDown}
|
|
557
|
+
onClick={stopSlotClickPropagation}
|
|
558
|
+
>
|
|
559
|
+
{postfixButtons}
|
|
560
|
+
</span>
|
|
561
|
+
)}
|
|
562
|
+
</div>
|
|
563
|
+
{/* Иконка календаря — декоративная; дроплист открывается кликом по полю. */}
|
|
564
|
+
<div className={fieldStyles.iconSlot} data-test-id={TEST_IDS.fieldDateCalendar} aria-hidden>
|
|
565
|
+
<CalendarSVG />
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
// Дискриминированный union по `mode` — каждый вариант несёт корректно типизированные `value`/`onChangeValue`.
|
|
573
|
+
type CalendarModeProps =
|
|
574
|
+
| {
|
|
575
|
+
mode: typeof DATE_MODE.DateRange;
|
|
576
|
+
// undefined — частично заполненный/пустой период: в сетку календаря не передаём (см. ниже).
|
|
577
|
+
value: DateRangeValue | undefined;
|
|
578
|
+
onChangeValue(next: DateRangeValue): void;
|
|
579
|
+
}
|
|
580
|
+
| {
|
|
581
|
+
mode: typeof DATE_MODE.DateTime;
|
|
582
|
+
value: DateValue;
|
|
583
|
+
onChangeValue(next: DateValue): void;
|
|
584
|
+
showSeconds: boolean;
|
|
585
|
+
}
|
|
586
|
+
| { mode: typeof DATE_MODE.Date; value: DateValue; onChangeValue(next: DateValue): void };
|
|
587
|
+
|
|
588
|
+
let calendarProps: CalendarModeProps;
|
|
589
|
+
if (range) {
|
|
590
|
+
// @cloud-ru/ds-calendar ожидает `Range = [Date, Date]` (оба края обязательны) и в date-range падает на
|
|
591
|
+
// `[undefined, …]` (getInRangePosition → .getFullYear() на undefined). FieldDate допускает частично
|
|
592
|
+
// заполненный период, поэтому передаём в сетку value только когда обе даты реальны — иначе undefined
|
|
593
|
+
// (нет выделения), а ввод периода продолжает работать через инпуты.
|
|
594
|
+
const bothEndsFilled = rangeValue[0] !== undefined && rangeValue[1] !== undefined;
|
|
595
|
+
calendarProps = {
|
|
596
|
+
mode: DATE_MODE.DateRange,
|
|
597
|
+
value: bothEndsFilled ? rangeValue : undefined,
|
|
598
|
+
onChangeValue: handleCalendarRangeChange,
|
|
599
|
+
};
|
|
600
|
+
} else if (maskMode === DATE_MODE.DateTime) {
|
|
601
|
+
calendarProps = {
|
|
602
|
+
mode: DATE_MODE.DateTime,
|
|
603
|
+
value: singleValue,
|
|
604
|
+
onChangeValue: handleCalendarSingleChange,
|
|
605
|
+
showSeconds,
|
|
606
|
+
};
|
|
607
|
+
} else {
|
|
608
|
+
calendarProps = {
|
|
609
|
+
mode: DATE_MODE.Date,
|
|
610
|
+
value: singleValue,
|
|
611
|
+
onChangeValue: handleCalendarSingleChange,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return (
|
|
616
|
+
<FieldDecorator
|
|
617
|
+
className={className}
|
|
618
|
+
label={label}
|
|
619
|
+
labelTooltip={labelTooltip}
|
|
620
|
+
caption={caption}
|
|
621
|
+
hint={hint}
|
|
622
|
+
error={error}
|
|
623
|
+
size={size}
|
|
624
|
+
validationState={validationState}
|
|
625
|
+
showHintIcon={showHintIcon}
|
|
626
|
+
length={length}
|
|
627
|
+
required={required}
|
|
628
|
+
labelFor={id}
|
|
629
|
+
disabled={disabled}
|
|
630
|
+
readonly={readOnly}
|
|
631
|
+
>
|
|
632
|
+
<CalendarDropdown
|
|
633
|
+
// CalendarDropdown ожидает `Range = [Date, Date]` в режиме date-range, а FieldDate хранит
|
|
634
|
+
// `[Date | undefined, Date | undefined]` (период может быть заполнен частично). Локализованный
|
|
635
|
+
// cast в `mode`-union календаря — единственная точка, где сходятся эти представления периода.
|
|
636
|
+
{...(calendarProps as Extract<CalendarDropdownProps, { mode: CalendarModeProps['mode'] }>)}
|
|
637
|
+
// CalendarDropdown тегирует контент открытого календаря как `content-<id>`. Без id портальный
|
|
638
|
+
// контент остаётся неадресуемым (e2e/visual не находят открытый календарь).
|
|
639
|
+
data-test-id={TEST_IDS.fieldDateCalendarDropdown}
|
|
640
|
+
size={size}
|
|
641
|
+
open={open}
|
|
642
|
+
onOpenChange={handleOpenChange}
|
|
643
|
+
placement={placement ?? 'bottom-start'}
|
|
644
|
+
today={today}
|
|
645
|
+
showHolidays={showHolidays}
|
|
646
|
+
buildCellProps={buildCellProps}
|
|
647
|
+
locale={locale}
|
|
648
|
+
presets={presets}
|
|
649
|
+
closeOnApply={closeOnApply}
|
|
650
|
+
closeOnPopstate={closeOnPopstate}
|
|
651
|
+
navigationStartRef={navigationStartRef}
|
|
652
|
+
onFocusLeave={handleCalendarFocusLeave}
|
|
653
|
+
>
|
|
654
|
+
{trigger}
|
|
655
|
+
</CalendarDropdown>
|
|
656
|
+
</FieldDecorator>
|
|
657
|
+
);
|
|
658
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DATE_MODE = {
|
|
2
|
+
Date: 'date',
|
|
3
|
+
DateTime: 'date-time',
|
|
4
|
+
DateRange: 'date-range',
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
/** Дефолтные aria-label'ы полей периода (RU — паритет с языком компонента по умолчанию). */
|
|
8
|
+
export const DATE_RANGE_LABELS = {
|
|
9
|
+
from: 'Начало периода',
|
|
10
|
+
to: 'Конец периода',
|
|
11
|
+
} as const;
|