zkit-ui 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/README.md +267 -0
- package/dist/ComponentLibProvider.d.ts +15 -0
- package/dist/ComponentLibProvider.d.ts.map +1 -0
- package/dist/ComponentLibProvider.js +18 -0
- package/dist/assets/animations/spinner.json +1 -0
- package/dist/assets/icons/check.svg +4 -0
- package/dist/assets/icons/toast/error.webp +0 -0
- package/dist/assets/icons/toast/success.webp +0 -0
- package/dist/assets/icons/toast/warning.webp +0 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/i18n/I18nProvider.d.ts +11 -0
- package/dist/i18n/I18nProvider.d.ts.map +1 -0
- package/dist/i18n/I18nProvider.js +84 -0
- package/dist/i18n/locale.d.ts +7 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +87 -0
- package/dist/i18n/locales/de.d.ts +51 -0
- package/dist/i18n/locales/de.d.ts.map +1 -0
- package/dist/i18n/locales/de.js +62 -0
- package/dist/i18n/locales/en-US.d.ts +51 -0
- package/dist/i18n/locales/en-US.d.ts.map +1 -0
- package/dist/i18n/locales/en-US.js +62 -0
- package/dist/i18n/locales/index.d.ts +9 -0
- package/dist/i18n/locales/index.d.ts.map +1 -0
- package/dist/i18n/locales/index.js +23 -0
- package/dist/i18n/locales/ja.d.ts +51 -0
- package/dist/i18n/locales/ja.d.ts.map +1 -0
- package/dist/i18n/locales/ja.js +62 -0
- package/dist/i18n/locales/zh-CN.d.ts +51 -0
- package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
- package/dist/i18n/locales/zh-CN.js +62 -0
- package/dist/i18n/locales/zh-TW.d.ts +51 -0
- package/dist/i18n/locales/zh-TW.d.ts.map +1 -0
- package/dist/i18n/locales/zh-TW.js +62 -0
- package/dist/i18n/types.d.ts +11 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +2 -0
- package/dist/i18n/useI18n.d.ts +2 -0
- package/dist/i18n/useI18n.d.ts.map +1 -0
- package/dist/i18n/useI18n.js +41 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/services/ActionDialogService/ActionDialog.d.ts +4 -0
- package/dist/services/ActionDialogService/ActionDialog.d.ts.map +1 -0
- package/dist/services/ActionDialogService/ActionDialog.js +531 -0
- package/dist/services/ActionDialogService/index.d.ts +4 -0
- package/dist/services/ActionDialogService/index.d.ts.map +1 -0
- package/dist/services/ActionDialogService/index.js +8 -0
- package/dist/services/ActionDialogService/service.d.ts +7 -0
- package/dist/services/ActionDialogService/service.d.ts.map +1 -0
- package/dist/services/ActionDialogService/service.js +309 -0
- package/dist/services/ActionDialogService/shared.d.ts +17 -0
- package/dist/services/ActionDialogService/shared.d.ts.map +1 -0
- package/dist/services/ActionDialogService/shared.js +167 -0
- package/dist/services/ActionDialogService/types.d.ts +160 -0
- package/dist/services/ActionDialogService/types.d.ts.map +1 -0
- package/dist/services/ActionDialogService/types.js +2 -0
- package/dist/services/CardToastService/index.d.ts +115 -0
- package/dist/services/CardToastService/index.d.ts.map +1 -0
- package/dist/services/CardToastService/index.js +899 -0
- package/dist/services/ImagePreviewService/index.d.ts +179 -0
- package/dist/services/ImagePreviewService/index.d.ts.map +1 -0
- package/dist/services/ImagePreviewService/index.js +1383 -0
- package/dist/services/LoadingService/index.d.ts +146 -0
- package/dist/services/LoadingService/index.d.ts.map +1 -0
- package/dist/services/LoadingService/index.js +827 -0
- package/dist/services/PermissionPurposeDialogService/index.d.ts +99 -0
- package/dist/services/PermissionPurposeDialogService/index.d.ts.map +1 -0
- package/dist/services/PermissionPurposeDialogService/index.js +360 -0
- package/dist/services/PickerService/index.d.ts +128 -0
- package/dist/services/PickerService/index.d.ts.map +1 -0
- package/dist/services/PickerService/index.js +250 -0
- package/dist/theme/ThemeProvider.d.ts +15 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.js +61 -0
- package/dist/theme/defaultTheme.d.ts +3 -0
- package/dist/theme/defaultTheme.d.ts.map +1 -0
- package/dist/theme/defaultTheme.js +28 -0
- package/dist/theme/mergeTheme.d.ts +3 -0
- package/dist/theme/mergeTheme.d.ts.map +1 -0
- package/dist/theme/mergeTheme.js +19 -0
- package/dist/theme/types.d.ts +17 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +2 -0
- package/dist/theme/useTheme.d.ts +2 -0
- package/dist/theme/useTheme.d.ts.map +1 -0
- package/dist/theme/useTheme.js +46 -0
- package/dist/ui/Accordion/index.d.ts +99 -0
- package/dist/ui/Accordion/index.d.ts.map +1 -0
- package/dist/ui/Accordion/index.js +777 -0
- package/dist/ui/AddressCascader/index.d.ts +58 -0
- package/dist/ui/AddressCascader/index.d.ts.map +1 -0
- package/dist/ui/AddressCascader/index.js +710 -0
- package/dist/ui/BetweenTime/index.d.ts +82 -0
- package/dist/ui/BetweenTime/index.d.ts.map +1 -0
- package/dist/ui/BetweenTime/index.js +702 -0
- package/dist/ui/Button/index.d.ts +93 -0
- package/dist/ui/Button/index.d.ts.map +1 -0
- package/dist/ui/Button/index.js +832 -0
- package/dist/ui/Checkbox/index.d.ts +103 -0
- package/dist/ui/Checkbox/index.d.ts.map +1 -0
- package/dist/ui/Checkbox/index.js +694 -0
- package/dist/ui/DatePicker/index.d.ts +5 -0
- package/dist/ui/DatePicker/index.d.ts.map +1 -0
- package/dist/ui/DatePicker/index.js +157 -0
- package/dist/ui/DatePicker/types.d.ts +99 -0
- package/dist/ui/DatePicker/types.d.ts.map +1 -0
- package/dist/ui/DatePicker/types.js +2 -0
- package/dist/ui/DatePicker/utils.d.ts +32 -0
- package/dist/ui/DatePicker/utils.d.ts.map +1 -0
- package/dist/ui/DatePicker/utils.js +316 -0
- package/dist/ui/LinkedScroll/index.d.ts +78 -0
- package/dist/ui/LinkedScroll/index.d.ts.map +1 -0
- package/dist/ui/LinkedScroll/index.js +470 -0
- package/dist/ui/LoadingSpinner/index.d.ts +8 -0
- package/dist/ui/LoadingSpinner/index.d.ts.map +1 -0
- package/dist/ui/LoadingSpinner/index.js +122 -0
- package/dist/ui/Picker/actionBar.d.ts +14 -0
- package/dist/ui/Picker/actionBar.d.ts.map +1 -0
- package/dist/ui/Picker/actionBar.js +28 -0
- package/dist/ui/Picker/index.d.ts +5 -0
- package/dist/ui/Picker/index.d.ts.map +1 -0
- package/dist/ui/Picker/index.js +412 -0
- package/dist/ui/Picker/types.d.ts +80 -0
- package/dist/ui/Picker/types.d.ts.map +1 -0
- package/dist/ui/Picker/types.js +2 -0
- package/dist/ui/Picker/utils.d.ts +27 -0
- package/dist/ui/Picker/utils.d.ts.map +1 -0
- package/dist/ui/Picker/utils.js +182 -0
- package/dist/ui/Radio/index.d.ts +95 -0
- package/dist/ui/Radio/index.d.ts.map +1 -0
- package/dist/ui/Radio/index.js +633 -0
- package/dist/ui/Sheet/index.d.ts +12 -0
- package/dist/ui/Sheet/index.d.ts.map +1 -0
- package/dist/ui/Sheet/index.js +832 -0
- package/dist/ui/Sheet/nativeBottom.d.ts +52 -0
- package/dist/ui/Sheet/nativeBottom.d.ts.map +1 -0
- package/dist/ui/Sheet/nativeBottom.js +442 -0
- package/dist/ui/Sheet/nativeBottom.web.d.ts +18 -0
- package/dist/ui/Sheet/nativeBottom.web.d.ts.map +1 -0
- package/dist/ui/Sheet/nativeBottom.web.js +46 -0
- package/dist/ui/Sheet/slots.d.ts +5 -0
- package/dist/ui/Sheet/slots.d.ts.map +1 -0
- package/dist/ui/Sheet/slots.js +40 -0
- package/dist/ui/Sheet/types.d.ts +131 -0
- package/dist/ui/Sheet/types.d.ts.map +1 -0
- package/dist/ui/Sheet/types.js +2 -0
- package/dist/ui/SliderCaptcha/index.d.ts +57 -0
- package/dist/ui/SliderCaptcha/index.d.ts.map +1 -0
- package/dist/ui/SliderCaptcha/index.js +586 -0
- package/dist/ui/Switch/index.d.ts +72 -0
- package/dist/ui/Switch/index.d.ts.map +1 -0
- package/dist/ui/Switch/index.js +671 -0
- package/dist/ui/Text/index.d.ts +41 -0
- package/dist/ui/Text/index.d.ts.map +1 -0
- package/dist/ui/Text/index.js +325 -0
- package/dist/ui/TextInput/TextInputPrimitive.d.ts +8 -0
- package/dist/ui/TextInput/TextInputPrimitive.d.ts.map +1 -0
- package/dist/ui/TextInput/TextInputPrimitive.js +54 -0
- package/dist/ui/TextInput/index.d.ts +89 -0
- package/dist/ui/TextInput/index.d.ts.map +1 -0
- package/dist/ui/TextInput/index.js +545 -0
- package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts +39 -0
- package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts.map +1 -0
- package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.js +60 -0
- package/dist/ui/WheelColumn/constants.d.ts +2 -0
- package/dist/ui/WheelColumn/constants.d.ts.map +1 -0
- package/dist/ui/WheelColumn/constants.js +4 -0
- package/dist/ui/WheelColumn/index.d.ts +59 -0
- package/dist/ui/WheelColumn/index.d.ts.map +1 -0
- package/dist/ui/WheelColumn/index.js +547 -0
- package/ios/ZKitWheelPicker.h +18 -0
- package/ios/ZKitWheelPicker.mm +230 -0
- package/ios/ZKitWheelPickerLabel.h +10 -0
- package/ios/ZKitWheelPickerLabel.mm +31 -0
- package/ios/ZKitWheelPickerManager.h +5 -0
- package/ios/ZKitWheelPickerManager.mm +53 -0
- package/package.json +75 -0
- package/react-native.config.js +10 -0
- package/src/ComponentLibProvider.tsx +56 -0
- package/src/assets/animations/spinner.json +1 -0
- package/src/assets/icons/check.svg +4 -0
- package/src/assets/icons/toast/error.webp +0 -0
- package/src/assets/icons/toast/success.webp +0 -0
- package/src/assets/icons/toast/warning.webp +0 -0
- package/src/config.ts +65 -0
- package/src/i18n/I18nProvider.tsx +69 -0
- package/src/i18n/locale.ts +108 -0
- package/src/i18n/locales/de.ts +67 -0
- package/src/i18n/locales/en-US.ts +67 -0
- package/src/i18n/locales/index.ts +19 -0
- package/src/i18n/locales/ja.ts +67 -0
- package/src/i18n/locales/zh-CN.ts +67 -0
- package/src/i18n/locales/zh-TW.ts +67 -0
- package/src/i18n/types.ts +21 -0
- package/src/i18n/useI18n.ts +7 -0
- package/src/index.ts +395 -0
- package/src/services/ActionDialogService/ActionDialog.tsx +756 -0
- package/src/services/ActionDialogService/README.md +146 -0
- package/src/services/ActionDialogService/index.tsx +39 -0
- package/src/services/ActionDialogService/service.tsx +355 -0
- package/src/services/ActionDialogService/shared.ts +204 -0
- package/src/services/ActionDialogService/types.ts +197 -0
- package/src/services/CardToastService/README.md +128 -0
- package/src/services/CardToastService/index.tsx +1284 -0
- package/src/services/ImagePreviewService/README.md +67 -0
- package/src/services/ImagePreviewService/index.tsx +2148 -0
- package/src/services/LoadingService/README.md +128 -0
- package/src/services/LoadingService/index.tsx +1113 -0
- package/src/services/PermissionPurposeDialogService/README.md +80 -0
- package/src/services/PermissionPurposeDialogService/index.tsx +449 -0
- package/src/services/PickerService/README.md +154 -0
- package/src/services/PickerService/index.tsx +410 -0
- package/src/theme/ThemeProvider.tsx +40 -0
- package/src/theme/defaultTheme.ts +30 -0
- package/src/theme/mergeTheme.ts +16 -0
- package/src/theme/types.ts +36 -0
- package/src/theme/useTheme.ts +10 -0
- package/src/types/svg.d.ts +9 -0
- package/src/types/vant-area-data.d.ts +19 -0
- package/src/ui/Accordion/README.md +67 -0
- package/src/ui/Accordion/index.tsx +1221 -0
- package/src/ui/AddressCascader/README.md +144 -0
- package/src/ui/AddressCascader/index.tsx +1084 -0
- package/src/ui/BetweenTime/README.md +490 -0
- package/src/ui/BetweenTime/index.tsx +1001 -0
- package/src/ui/Button/README.md +186 -0
- package/src/ui/Button/index.tsx +1356 -0
- package/src/ui/Checkbox/README.md +211 -0
- package/src/ui/Checkbox/index.tsx +1231 -0
- package/src/ui/DatePicker/README.md +117 -0
- package/src/ui/DatePicker/index.tsx +295 -0
- package/src/ui/DatePicker/types.ts +115 -0
- package/src/ui/DatePicker/utils.ts +405 -0
- package/src/ui/LinkedScroll/README.md +55 -0
- package/src/ui/LinkedScroll/index.tsx +809 -0
- package/src/ui/LoadingSpinner/index.tsx +104 -0
- package/src/ui/Picker/README.md +190 -0
- package/src/ui/Picker/actionBar.tsx +70 -0
- package/src/ui/Picker/index.tsx +694 -0
- package/src/ui/Picker/types.ts +94 -0
- package/src/ui/Picker/utils.ts +239 -0
- package/src/ui/Radio/README.md +207 -0
- package/src/ui/Radio/index.tsx +1117 -0
- package/src/ui/Sheet/README.md +82 -0
- package/src/ui/Sheet/index.tsx +1163 -0
- package/src/ui/Sheet/nativeBottom.tsx +683 -0
- package/src/ui/Sheet/nativeBottom.web.tsx +40 -0
- package/src/ui/Sheet/slots.tsx +75 -0
- package/src/ui/Sheet/types.ts +195 -0
- package/src/ui/SliderCaptcha/README.md +61 -0
- package/src/ui/SliderCaptcha/index.tsx +816 -0
- package/src/ui/Switch/README.md +143 -0
- package/src/ui/Switch/index.tsx +1092 -0
- package/src/ui/Text/README.md +157 -0
- package/src/ui/Text/index.tsx +450 -0
- package/src/ui/TextInput/README.md +197 -0
- package/src/ui/TextInput/TextInputPrimitive.tsx +44 -0
- package/src/ui/TextInput/index.tsx +942 -0
- package/src/ui/WheelColumn/README.md +104 -0
- package/src/ui/WheelColumn/ZKitWheelPickerNativeComponent.tsx +68 -0
- package/src/ui/WheelColumn/constants.ts +1 -0
- package/src/ui/WheelColumn/index.tsx +826 -0
- package/zkit-ui.podspec +19 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import dayjs, { type Dayjs } from 'dayjs';
|
|
2
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
3
|
+
import type { PickerSelection } from '../Picker';
|
|
4
|
+
import type {
|
|
5
|
+
DatePickerColumn,
|
|
6
|
+
DatePickerInput,
|
|
7
|
+
DatePickerLabelFormat,
|
|
8
|
+
DatePickerOption,
|
|
9
|
+
DatePickerParts,
|
|
10
|
+
DatePickerPrecision,
|
|
11
|
+
DatePickerSelection,
|
|
12
|
+
DatePickerValue,
|
|
13
|
+
DatePickerProps,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
dayjs.extend(customParseFormat);
|
|
17
|
+
|
|
18
|
+
export const DATE_PICKER_COLUMNS = ['year', 'month', 'day'] as const satisfies readonly DatePickerColumn[];
|
|
19
|
+
|
|
20
|
+
const DEFAULT_MIN = dayjs('1900-01-01', 'YYYY-MM-DD', true).startOf('day');
|
|
21
|
+
const DEFAULT_MAX = dayjs('2100-12-31', 'YYYY-MM-DD', true).endOf('day');
|
|
22
|
+
const VALUE_FORMAT: Record<DatePickerPrecision, string> = {
|
|
23
|
+
year: 'YYYY',
|
|
24
|
+
month: 'YYYY-MM',
|
|
25
|
+
day: 'YYYY-MM-DD',
|
|
26
|
+
};
|
|
27
|
+
const PARSE_FORMATS: Record<DatePickerPrecision, string | string[]> = {
|
|
28
|
+
year: 'YYYY',
|
|
29
|
+
month: ['YYYY-M', 'YYYY-MM'],
|
|
30
|
+
day: ['YYYY-M-D', 'YYYY-MM-DD'],
|
|
31
|
+
};
|
|
32
|
+
const PRECISION_COLUMN_COUNT: Record<DatePickerPrecision, number> = {
|
|
33
|
+
year: 1,
|
|
34
|
+
month: 2,
|
|
35
|
+
day: 3,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type DateBounds = {
|
|
39
|
+
min: Dayjs;
|
|
40
|
+
max: Dayjs;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type ParsedDate = {
|
|
44
|
+
date: Dayjs;
|
|
45
|
+
inputPrecision: DatePickerPrecision;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type BuildOptionsConfig = {
|
|
49
|
+
bounds: DateBounds;
|
|
50
|
+
precision: DatePickerPrecision;
|
|
51
|
+
isDateDisabled?: DatePickerProps['isDateDisabled'];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function pad2(n: number) {
|
|
55
|
+
return String(n).padStart(2, '0');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isDatePickerPrecision(input: unknown): input is DatePickerPrecision {
|
|
59
|
+
return input === 'year' || input === 'month' || input === 'day';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function normalizePrecision(input: DatePickerPrecision | undefined): DatePickerPrecision {
|
|
63
|
+
return isDatePickerPrecision(input) ? input : 'day';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getColumnCount(precision: DatePickerPrecision) {
|
|
67
|
+
return PRECISION_COLUMN_COUNT[precision];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function formatDatePickerValue(date: Dayjs, precision: DatePickerPrecision): DatePickerValue {
|
|
71
|
+
return date.format(VALUE_FORMAT[precision]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function toDatePickerParts(date: Dayjs, precision: DatePickerPrecision): DatePickerParts {
|
|
75
|
+
if (precision === 'year') return { year: date.year() };
|
|
76
|
+
if (precision === 'month') return { year: date.year(), month: date.month() + 1 };
|
|
77
|
+
return { year: date.year(), month: date.month() + 1, day: date.date() };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function pathFromDate(date: Dayjs, precision: DatePickerPrecision): number[] {
|
|
81
|
+
const parts = toDatePickerParts(date, precision);
|
|
82
|
+
if (precision === 'year') return [parts.year];
|
|
83
|
+
if (precision === 'month') return [parts.year, parts.month ?? 1];
|
|
84
|
+
return [parts.year, parts.month ?? 1, parts.day ?? 1];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeDateByPrecision(date: Dayjs, precision: DatePickerPrecision) {
|
|
88
|
+
if (precision === 'year') return date.startOf('year');
|
|
89
|
+
if (precision === 'month') return date.startOf('month');
|
|
90
|
+
return date.startOf('day');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseStringInput(input: string, fieldName: string): ParsedDate | undefined {
|
|
94
|
+
const value = input.trim();
|
|
95
|
+
if (!value) return undefined;
|
|
96
|
+
|
|
97
|
+
const inputPrecision: DatePickerPrecision | null =
|
|
98
|
+
/^\d{4}$/.test(value)
|
|
99
|
+
? 'year'
|
|
100
|
+
: /^\d{4}-\d{1,2}$/.test(value)
|
|
101
|
+
? 'month'
|
|
102
|
+
: /^\d{4}-\d{1,2}-\d{1,2}$/.test(value)
|
|
103
|
+
? 'day'
|
|
104
|
+
: null;
|
|
105
|
+
|
|
106
|
+
if (!inputPrecision) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`[zkit-ui][DatePicker] Invalid ${fieldName}: "${input}". Expected YYYY, YYYY-MM, or YYYY-MM-DD.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const format = PARSE_FORMATS[inputPrecision];
|
|
113
|
+
const date = dayjs(value, format, true);
|
|
114
|
+
if (!date.isValid()) {
|
|
115
|
+
throw new Error(`[zkit-ui][DatePicker] Invalid ${fieldName}: "${input}".`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { date, inputPrecision };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parseDatePickerInput(
|
|
122
|
+
input: DatePickerInput | null | undefined,
|
|
123
|
+
fieldName: string
|
|
124
|
+
): ParsedDate | undefined {
|
|
125
|
+
if (input == null) return undefined;
|
|
126
|
+
if (typeof input === 'string') return parseStringInput(input, fieldName);
|
|
127
|
+
|
|
128
|
+
const date = dayjs.isDayjs(input) ? input : dayjs(input);
|
|
129
|
+
if (!date.isValid()) {
|
|
130
|
+
throw new Error(`[zkit-ui][DatePicker] Invalid ${fieldName}.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { date, inputPrecision: 'day' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveBoundary(input: DatePickerInput | null | undefined, kind: 'min' | 'max') {
|
|
137
|
+
const parsed = parseDatePickerInput(input, kind);
|
|
138
|
+
if (!parsed) return kind === 'min' ? DEFAULT_MIN : DEFAULT_MAX;
|
|
139
|
+
|
|
140
|
+
if (kind === 'min') {
|
|
141
|
+
return normalizeDateByPrecision(parsed.date, parsed.inputPrecision);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (parsed.inputPrecision === 'year') return parsed.date.endOf('year');
|
|
145
|
+
if (parsed.inputPrecision === 'month') return parsed.date.endOf('month');
|
|
146
|
+
return parsed.date.endOf('day');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveDatePickerBounds(
|
|
150
|
+
min: DatePickerInput | null | undefined,
|
|
151
|
+
max: DatePickerInput | null | undefined
|
|
152
|
+
): DateBounds {
|
|
153
|
+
const bounds = {
|
|
154
|
+
min: resolveBoundary(min, 'min'),
|
|
155
|
+
max: resolveBoundary(max, 'max'),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (bounds.min.isAfter(bounds.max)) {
|
|
159
|
+
throw new Error('[zkit-ui][DatePicker] Invalid bounds: min is after max.');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return bounds;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function clampToBounds(date: Dayjs, bounds: DateBounds) {
|
|
166
|
+
if (date.isBefore(bounds.min)) return bounds.min;
|
|
167
|
+
if (date.isAfter(bounds.max)) return bounds.max;
|
|
168
|
+
return date;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function resolveCommittedDate(
|
|
172
|
+
value: DatePickerValue | null | undefined,
|
|
173
|
+
bounds: DateBounds,
|
|
174
|
+
precision: DatePickerPrecision
|
|
175
|
+
) {
|
|
176
|
+
const parsed = parseDatePickerInput(value, 'value');
|
|
177
|
+
if (!parsed) return null;
|
|
178
|
+
return normalizeDateByPrecision(clampToBounds(parsed.date, bounds), precision);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function resolvePickerDate(
|
|
182
|
+
committedDate: Dayjs | null,
|
|
183
|
+
defaultPickerValue: DatePickerInput | null | undefined,
|
|
184
|
+
bounds: DateBounds,
|
|
185
|
+
precision: DatePickerPrecision
|
|
186
|
+
) {
|
|
187
|
+
if (committedDate) return committedDate;
|
|
188
|
+
|
|
189
|
+
const parsed = parseDatePickerInput(defaultPickerValue, 'defaultPickerValue');
|
|
190
|
+
const seed = parsed?.date ?? dayjs();
|
|
191
|
+
return normalizeDateByPrecision(clampToBounds(seed, bounds), precision);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function optionDate(parts: DatePickerParts) {
|
|
195
|
+
const month = parts.month ?? 1;
|
|
196
|
+
const day = parts.day ?? 1;
|
|
197
|
+
return dayjs(`${parts.year}-${pad2(month)}-${pad2(day)}`, 'YYYY-MM-DD', true).startOf('day');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function createOption(column: DatePickerColumn, parts: DatePickerParts, disabled = false): DatePickerOption {
|
|
201
|
+
const value = column === 'year' ? parts.year : column === 'month' ? parts.month ?? 1 : parts.day ?? 1;
|
|
202
|
+
return {
|
|
203
|
+
value,
|
|
204
|
+
label: column === 'year' ? String(value) : pad2(value),
|
|
205
|
+
disabled,
|
|
206
|
+
column,
|
|
207
|
+
year: parts.year,
|
|
208
|
+
month: parts.month,
|
|
209
|
+
day: parts.day,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function resolveDisabled(
|
|
214
|
+
date: Dayjs,
|
|
215
|
+
parts: DatePickerParts,
|
|
216
|
+
column: DatePickerColumn,
|
|
217
|
+
precision: DatePickerPrecision,
|
|
218
|
+
isDateDisabled: DatePickerProps['isDateDisabled']
|
|
219
|
+
) {
|
|
220
|
+
return !!isDateDisabled?.(date, { column, precision, parts });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildDays(year: number, month: number, config: BuildOptionsConfig) {
|
|
224
|
+
const { bounds, isDateDisabled, precision } = config;
|
|
225
|
+
const isMinMonth = year === bounds.min.year() && month === bounds.min.month() + 1;
|
|
226
|
+
const isMaxMonth = year === bounds.max.year() && month === bounds.max.month() + 1;
|
|
227
|
+
const monthStart = dayjs(`${year}-${pad2(month)}-01`, 'YYYY-MM-DD', true);
|
|
228
|
+
const dayMin = isMinMonth ? bounds.min.date() : 1;
|
|
229
|
+
const dayMax = isMaxMonth ? bounds.max.date() : monthStart.daysInMonth();
|
|
230
|
+
const days: DatePickerOption[] = [];
|
|
231
|
+
|
|
232
|
+
for (let day = dayMin; day <= dayMax; day += 1) {
|
|
233
|
+
const parts = { year, month, day };
|
|
234
|
+
days.push(
|
|
235
|
+
createOption(
|
|
236
|
+
'day',
|
|
237
|
+
parts,
|
|
238
|
+
resolveDisabled(optionDate(parts), parts, 'day', precision, isDateDisabled)
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return days;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function buildMonths(year: number, config: BuildOptionsConfig) {
|
|
247
|
+
const { bounds, isDateDisabled, precision } = config;
|
|
248
|
+
const isMinYear = year === bounds.min.year();
|
|
249
|
+
const isMaxYear = year === bounds.max.year();
|
|
250
|
+
const monthMin = isMinYear ? bounds.min.month() + 1 : 1;
|
|
251
|
+
const monthMax = isMaxYear ? bounds.max.month() + 1 : 12;
|
|
252
|
+
const months: DatePickerOption[] = [];
|
|
253
|
+
|
|
254
|
+
for (let month = monthMin; month <= monthMax; month += 1) {
|
|
255
|
+
const parts = { year, month };
|
|
256
|
+
const option = createOption(
|
|
257
|
+
'month',
|
|
258
|
+
parts,
|
|
259
|
+
precision === 'month'
|
|
260
|
+
? resolveDisabled(optionDate(parts), parts, 'month', precision, isDateDisabled)
|
|
261
|
+
: false
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (precision === 'day') {
|
|
265
|
+
option.children = buildDays(year, month, config);
|
|
266
|
+
option.disabled = option.children.length > 0 && option.children.every((child) => child.disabled);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
months.push(option);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return months;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function buildDatePickerOptions(config: BuildOptionsConfig) {
|
|
276
|
+
const { bounds, isDateDisabled, precision } = config;
|
|
277
|
+
const years: DatePickerOption[] = [];
|
|
278
|
+
|
|
279
|
+
for (let year = bounds.min.year(); year <= bounds.max.year(); year += 1) {
|
|
280
|
+
const parts = { year };
|
|
281
|
+
const option = createOption(
|
|
282
|
+
'year',
|
|
283
|
+
parts,
|
|
284
|
+
precision === 'year'
|
|
285
|
+
? resolveDisabled(optionDate(parts), parts, 'year', precision, isDateDisabled)
|
|
286
|
+
: false
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (precision !== 'year') {
|
|
290
|
+
option.children = buildMonths(year, config);
|
|
291
|
+
option.disabled = option.children.length > 0 && option.children.every((child) => child.disabled);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
years.push(option);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return years;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function numberValues(values: PickerSelection<DatePickerOption>['values']) {
|
|
301
|
+
return values.map((value) => Number(value)).filter((value) => Number.isFinite(value));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function dateFromPath(values: number[], precision: DatePickerPrecision) {
|
|
305
|
+
const year = values[0];
|
|
306
|
+
if (!Number.isFinite(year)) return null;
|
|
307
|
+
const month = precision === 'year' ? 1 : values[1];
|
|
308
|
+
const day = precision === 'day' ? values[2] : 1;
|
|
309
|
+
if (!Number.isFinite(month) || !Number.isFinite(day)) return null;
|
|
310
|
+
|
|
311
|
+
const date = dayjs(`${year}-${pad2(month)}-${pad2(day)}`, 'YYYY-MM-DD', true);
|
|
312
|
+
return date.isValid() ? normalizeDateByPrecision(date, precision) : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function resolveSelectionLabel(selection: DatePickerSelection, labelFormat: DatePickerLabelFormat | undefined) {
|
|
316
|
+
if (typeof labelFormat === 'function') return labelFormat(selection);
|
|
317
|
+
if (typeof labelFormat === 'string' && labelFormat.trim()) return selection.date.format(labelFormat);
|
|
318
|
+
return selection.value;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function createDatePickerSelection(
|
|
322
|
+
pickerSelection: PickerSelection<DatePickerOption>,
|
|
323
|
+
precision: DatePickerPrecision,
|
|
324
|
+
labelFormat?: DatePickerLabelFormat,
|
|
325
|
+
labelOverride?: string
|
|
326
|
+
): DatePickerSelection | null {
|
|
327
|
+
const values = numberValues(pickerSelection.values).slice(0, getColumnCount(precision));
|
|
328
|
+
const date = dateFromPath(values, precision);
|
|
329
|
+
if (!date) return null;
|
|
330
|
+
|
|
331
|
+
const base: DatePickerSelection = {
|
|
332
|
+
value: formatDatePickerValue(date, precision),
|
|
333
|
+
date,
|
|
334
|
+
precision,
|
|
335
|
+
parts: toDatePickerParts(date, precision),
|
|
336
|
+
values,
|
|
337
|
+
label: '',
|
|
338
|
+
labels: pickerSelection.labels.slice(0, getColumnCount(precision)),
|
|
339
|
+
items: pickerSelection.items.slice(0, getColumnCount(precision)),
|
|
340
|
+
columns: pickerSelection.columns.slice(0, getColumnCount(precision)),
|
|
341
|
+
indices: pickerSelection.indices.slice(0, getColumnCount(precision)),
|
|
342
|
+
isComplete: pickerSelection.isComplete,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
...base,
|
|
347
|
+
label: labelOverride ?? resolveSelectionLabel(base, labelFormat),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function findOptionPath(options: DatePickerOption[], values: number[]) {
|
|
352
|
+
const items: DatePickerOption[] = [];
|
|
353
|
+
const columns: DatePickerOption[][] = [];
|
|
354
|
+
const indices: number[] = [];
|
|
355
|
+
let current = options;
|
|
356
|
+
|
|
357
|
+
for (const value of values) {
|
|
358
|
+
columns.push(current);
|
|
359
|
+
const index = current.findIndex((option) => option.value === value);
|
|
360
|
+
if (index < 0) break;
|
|
361
|
+
|
|
362
|
+
const item = current[index];
|
|
363
|
+
if (!item) break;
|
|
364
|
+
|
|
365
|
+
items.push(item);
|
|
366
|
+
indices.push(index);
|
|
367
|
+
current = Array.isArray(item.children) ? item.children : [];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return { items, columns, indices };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function createDatePickerSelectionFromDate(
|
|
374
|
+
date: Dayjs,
|
|
375
|
+
precision: DatePickerPrecision,
|
|
376
|
+
options: DatePickerOption[],
|
|
377
|
+
labelFormat?: DatePickerLabelFormat
|
|
378
|
+
): DatePickerSelection {
|
|
379
|
+
const values = pathFromDate(date, precision);
|
|
380
|
+
const { items, columns, indices } = findOptionPath(options, values);
|
|
381
|
+
const labels = values.map((value, index) => (index === 0 ? String(value) : pad2(value)));
|
|
382
|
+
const base: DatePickerSelection = {
|
|
383
|
+
value: formatDatePickerValue(date, precision),
|
|
384
|
+
date,
|
|
385
|
+
precision,
|
|
386
|
+
parts: toDatePickerParts(date, precision),
|
|
387
|
+
values,
|
|
388
|
+
label: '',
|
|
389
|
+
labels,
|
|
390
|
+
items,
|
|
391
|
+
columns,
|
|
392
|
+
indices,
|
|
393
|
+
isComplete: true,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
...base,
|
|
398
|
+
label: resolveSelectionLabel(base, labelFormat),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function dateFromOption(option: DatePickerOption | undefined, precision: DatePickerPrecision) {
|
|
403
|
+
if (!option) return undefined;
|
|
404
|
+
return normalizeDateByPrecision(optionDate(option), precision);
|
|
405
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# LinkedScroll
|
|
2
|
+
|
|
3
|
+
`LinkedScroll` 是一个通用的菜单/内容滚动联动容器,适合分类、锚点导航、分区内容浏览等无业务语义场景。
|
|
4
|
+
|
|
5
|
+
组件内部基于 `@shopify/flash-list`,应用侧需要安装并满足该 peer dependency。
|
|
6
|
+
|
|
7
|
+
## 基础用法
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { View } from 'react-native';
|
|
11
|
+
import { LinkedScroll, Text } from 'zkit-ui';
|
|
12
|
+
import { wp } from 'zkit-tools';
|
|
13
|
+
|
|
14
|
+
const items = [
|
|
15
|
+
{ value: 'jiangxi', label: '江西省9' },
|
|
16
|
+
{ value: 'anhui', label: '安徽10' },
|
|
17
|
+
{ value: 'fujian', label: '福建11' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
<LinkedScroll
|
|
21
|
+
items={items}
|
|
22
|
+
defaultValue="jiangxi"
|
|
23
|
+
onChange={(value, item, meta) => {
|
|
24
|
+
console.log(value, item.label, meta.source);
|
|
25
|
+
}}
|
|
26
|
+
renderSection={({ item }) => (
|
|
27
|
+
<View
|
|
28
|
+
style={{
|
|
29
|
+
minHeight: wp(260),
|
|
30
|
+
borderRadius: wp(18),
|
|
31
|
+
backgroundColor: '#F4F5FA',
|
|
32
|
+
padding: wp(18),
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<Text>{item.label}</Text>
|
|
36
|
+
</View>
|
|
37
|
+
)}
|
|
38
|
+
/>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API 设计
|
|
42
|
+
|
|
43
|
+
- `items`:菜单和内容分区的数据源,`value` 必须在列表内唯一。
|
|
44
|
+
- `value` / `defaultValue`:当前选中分区,支持受控和非受控模式。
|
|
45
|
+
- `onChange(value, item, meta)`:选中分区变化;`meta.source` 为 `menu` 或 `content`。
|
|
46
|
+
- `renderSection`:必填,渲染右侧内容分区。
|
|
47
|
+
- `renderMenuItem`:可选,完全自定义菜单项;回调参数内的 `press` 用于触发联动滚动。
|
|
48
|
+
- `getMenuItemType` / `getSectionType`:为不同形态的菜单项或内容分区提供回收类型,语义与 `FlashList.getItemType` 一致。
|
|
49
|
+
- `menuListProps` / `contentListProps`:列表 escape hatch,核心联动 props 由组件内部接管。
|
|
50
|
+
|
|
51
|
+
## 性能说明
|
|
52
|
+
|
|
53
|
+
- 两侧均基于 `FlashList`,避免长列表一次性渲染,并优先保证复杂内容块的回收性能。
|
|
54
|
+
- 内容联动依赖 `onViewableItemsChanged`,只在可见分区变化时更新选中态,不做每帧 `onScroll` 计算。
|
|
55
|
+
- 菜单项默认固定高度;复杂内容类型差异较大时建议传入 `getSectionType`,提升回收稳定性。
|