@nutui/nutui-react
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
563 lines (562 loc) • 22.1 kB
JavaScript
import React__default, { useState, useRef, useEffect } from "react";
import classNames from "classnames";
import { C as ComponentDefaults } from "./typings.js";
import { U as Utils, g as getPreMonthDates, a as getDaysStatus, b as getCurrMonthData } from "./date.js";
import { r as requestAniFrame } from "./raf.js";
import { u as useConfig } from "./configprovider2.js";
import { u as usePropsValue } from "./use-props-value.js";
const splitDate = (date) => {
const split = date.indexOf("-") !== -1 ? "-" : "/";
return date.split(split);
};
const isMultiple = (day, days) => {
if (days.length > 0) {
return days.some((item) => {
return Utils.isEqual(item, day);
});
}
return false;
};
const isCurrDay = (month, day) => {
const date = `${month.curData[0]}/${month.curData[1]}/${day}`;
return Utils.isEqual(date, Utils.date2Str(/* @__PURE__ */ new Date(), "/"));
};
const getCurrDate = (day, month) => {
return `${month.curData[0]}/${month.curData[1]}/${Utils.getNumTwoBit(+day.day)}`;
};
const isStart = (day, days) => {
return Utils.isEqual(days[0], day);
};
const isEnd = (day, days) => {
return Utils.isEqual(days[1], day);
};
const isStartAndEnd = (days) => {
return days.length >= 2 && Utils.isEqual(days[0], days[1]);
};
const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { type: "single", autoBackfill: false, popup: true, title: "", startDate: Utils.getDay(0), endDate: Utils.getDay(365), showToday: true, startText: "", endText: "", confirmText: "", showTitle: true, showSubTitle: true, scrollAnimation: true, firstDayOfWeek: 0, disableDate: (date) => false, renderHeaderButtons: void 0, renderDay: void 0, renderDayTop: void 0, renderDayBottom: void 0, onConfirm: (data) => {
}, onUpdate: () => {
}, onDayClick: (data) => {
}, onPageChange: (data) => {
} });
const CalendarItem = React__default.forwardRef((props, ref) => {
const { locale } = useConfig();
const { style, className, children, popup, type, autoBackfill, title, defaultValue, startDate, endDate, showToday, startText, endText, confirmText, showTitle, showSubTitle, scrollAnimation, firstDayOfWeek, disableDate, renderHeaderButtons, renderDay, renderDayTop, renderDayBottom, value, onConfirm, onUpdate, onDayClick, onPageChange } = Object.assign(Object.assign({}, defaultProps), props);
const weekdays = locale.calendaritem.weekdays;
const weeks = [
...weekdays.slice(firstDayOfWeek, 7),
...weekdays.slice(0, firstDayOfWeek)
];
const monthTitle = locale.calendaritem.monthTitle;
const [yearMonthTitle, setYearMonthTitle] = useState("");
const [monthsData, setMonthsData] = useState([]);
const [monthsNum, setMonthsNum] = useState(0);
const [translateY, setTranslateY] = useState(0);
const [monthDefaultRange, setMonthDefaultRange] = useState([]);
const propStartDate = startDate || Utils.getDay(0);
const propEndDate = endDate || Utils.getDay(365);
const startDates = splitDate(propStartDate);
const endDates = splitDate(propEndDate);
const [state] = useState({
currDateArray: []
});
const getMonthsPanel = () => {
return monthsPanel.current;
};
const getMonthsRef = () => {
return monthsRef.current;
};
const resetDefaultValue = () => {
if (defaultValue || Array.isArray(defaultValue) && defaultValue.length > 0) {
return type !== "single" ? [...defaultValue] : defaultValue;
}
return void 0;
};
const [currentDate, setCurrentDate] = usePropsValue({
value,
defaultValue: resetDefaultValue(),
finalValue: [],
onChange: (val) => {
}
});
const weeksPanel = useRef(null);
const monthsRef = useRef(null);
const monthsPanel = useRef(null);
const viewAreaRef = useRef(null);
const [avgHeight, setAvgHeight] = useState(0);
let viewHeight = 0;
const classPrefix = "nut-calendar";
const dayPrefix = "nut-calendar-day";
const getMonthData = (curData, monthNum, type2) => {
let i = 0;
let date = curData;
const monthData = monthsData;
do {
const y = parseInt(date[0], 10);
const m = parseInt(date[1], 10);
const days = [
...getPreMonthDates("prev", y, m, firstDayOfWeek),
...getDaysStatus("active", y, m)
];
const cssHeight = 39 + (days.length > 35 ? 384 : 320);
let scrollTop = 0;
if (monthData.length > 0) {
const monthEle = monthData[monthData.length - 1];
scrollTop = monthEle.scrollTop + monthEle.cssHeight;
}
const monthInfo = {
curData: date,
title: monthTitle(y, m),
monthData: days,
cssHeight,
scrollTop
};
{
if (!endDates || !Utils.compareDate(`${endDates[0]}/${endDates[1]}/${Utils.getMonthDays(endDates[0], endDates[1])}`, `${curData[0]}/${curData[1]}/${curData[2]}`)) {
monthData.push(monthInfo);
}
}
date = getCurrMonthData("next", y, m);
} while (i++ < monthNum);
setMonthsData(monthData);
};
const setReachedYearMonthInfo = (current) => {
const currentMonthsData = monthsData[current];
const [year, month] = currentMonthsData.curData;
if (currentMonthsData.title === yearMonthTitle)
return;
onPageChange && onPageChange([year, month, `${year}-${month}`]);
setYearMonthTitle(currentMonthsData.title);
};
const setDefaultRange = (monthNum, current) => {
let start = 0;
let end = 0;
if (monthNum >= 3) {
if (current > 0 && current < monthNum) {
start = current - 1;
end = current + 3;
} else if (current === 0) {
start = current;
end = current + 4;
} else if (current === monthNum) {
start = current - 2;
end = current + 2;
}
} else {
start = 0;
end = monthNum + 2;
}
setMonthDefaultRange([start, end]);
setTranslateY(monthsData[start].scrollTop);
setReachedYearMonthInfo(current);
};
const getMonthNum = () => {
let monthNum = Number(endDates[1]) - Number(startDates[1]);
const yearNum = Number(endDates[0]) - Number(startDates[0]);
if (yearNum > 0) {
monthNum += 12 * yearNum;
}
if (monthNum <= 0) {
monthNum = 1;
}
setMonthsNum(monthNum);
return monthNum;
};
const setDefaultDate = () => {
let defaultData = [];
if (type === "range" && Array.isArray(currentDate)) {
if (currentDate.length > 0) {
if (propStartDate && Utils.compareDate(currentDate[0], propStartDate)) {
currentDate.splice(0, 1, propStartDate);
}
if (propEndDate && Utils.compareDate(propEndDate, currentDate[1])) {
currentDate.splice(1, 1, propEndDate);
}
defaultData = [
...splitDate(currentDate[0]),
...splitDate(currentDate[1])
];
}
} else if (type === "multiple" && Array.isArray(currentDate)) {
if (currentDate.length > 0) {
const defaultArr = [];
const obj = {};
currentDate.forEach((item) => {
if (propStartDate && !Utils.compareDate(item, propStartDate) && propEndDate && !Utils.compareDate(propEndDate, item)) {
if (!Object.hasOwnProperty.call(obj, item)) {
defaultArr.push(item);
obj[item] = item;
}
}
});
currentDate.splice(0) && currentDate.push(...defaultArr);
defaultData = [...splitDate(defaultArr[0])];
}
} else if (type === "week" && Array.isArray(currentDate)) {
if (currentDate.length > 0) {
const [y, m, d] = splitDate(currentDate[0]);
const weekArr = Utils.getWeekDate(y, m, d, firstDayOfWeek);
currentDate.splice(0) && currentDate.push(...weekArr);
if (propStartDate && Utils.compareDate(currentDate[0], propStartDate)) {
currentDate.splice(0, 1, propStartDate);
}
if (propEndDate && Utils.compareDate(propEndDate, currentDate[1])) {
currentDate.splice(1, 1, propEndDate);
}
defaultData = [
...splitDate(currentDate[0]),
...splitDate(currentDate[1])
];
}
} else if (currentDate) {
if (currentDate.length > 0) {
if (propStartDate && Utils.compareDate(currentDate, propStartDate)) {
defaultData = [...splitDate(propStartDate)];
} else if (propEndDate && !Utils.compareDate(currentDate, propEndDate)) {
defaultData = [...splitDate(propEndDate)];
} else {
defaultData = [...splitDate(currentDate)];
}
} else {
defaultData = [];
}
}
return defaultData;
};
const getCurrentIndex = (defaultData) => {
let current = 0;
let lastCurrent = 0;
if (defaultData.length > 0) {
monthsData.forEach((item, index) => {
if (item.title === monthTitle(defaultData[0], defaultData[1])) {
current = index;
}
if (type === "range" || type === "week") {
if (item.title === monthTitle(defaultData[3], defaultData[4])) {
lastCurrent = index;
}
}
});
} else {
const date = /* @__PURE__ */ new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1;
const index = monthsData.findIndex((item) => {
return +item.curData[0] === year && +item.curData[1] === month;
});
if (index > -1) {
current = index;
}
}
return {
current,
lastCurrent
};
};
const renderCurrentDate = () => {
const defaultData = setDefaultDate();
const current = getCurrentIndex(defaultData);
if (defaultData.length > 0) {
if (type === "range") {
chooseDay({ day: defaultData[2], type: "active" }, monthsData[current.current], true);
chooseDay({ day: defaultData[5], type: "active" }, monthsData[current.lastCurrent], true);
} else if (type === "week") {
chooseDay({ day: defaultData[2], type: "curr" }, monthsData[current.current], true);
} else if (type === "multiple") {
[...currentDate].forEach((item) => {
const dateArr = splitDate(item);
let currentIndex = current.current;
monthsData.forEach((item2, index) => {
if (item2.title === monthTitle(dateArr[0], dateArr[1])) {
currentIndex = index;
}
});
chooseDay({ day: dateArr[2], type: "active" }, monthsData[currentIndex], true);
});
} else {
chooseDay({ day: defaultData[2], type: "active" }, monthsData[current.current], true);
}
}
return current.current;
};
const requestAniFrameFunc = (current, monthNum) => {
const lastItem = monthsData[monthsData.length - 1];
const containerHeight = lastItem.cssHeight + lastItem.scrollTop;
requestAniFrame(() => {
if (monthsRef && monthsPanel && viewAreaRef) {
viewHeight = getMonthsRef().clientHeight;
getMonthsPanel().style.height = `${containerHeight}px`;
getMonthsRef().scrollTop = monthsData[current].scrollTop;
}
});
setAvgHeight(Math.floor(containerHeight / (monthNum + 1)));
};
const initData = () => {
const monthNum = getMonthNum();
getMonthData(startDates, monthNum);
const current = renderCurrentDate();
setDefaultRange(monthNum, current);
requestAniFrameFunc(current, monthNum);
};
useEffect(() => {
initData();
}, []);
const resetRender = () => {
state.currDateArray.splice(0);
monthsData.splice(0);
initData();
};
useEffect(() => {
setCurrentDate(resetDefaultValue() || []);
}, [defaultValue]);
useEffect(() => {
popup && resetRender();
}, [currentDate]);
const scrollToDate = (date) => {
if (Utils.compareDate(date, propStartDate)) {
date = propStartDate;
} else if (!Utils.compareDate(date, propEndDate)) {
date = propEndDate;
}
const dateArr = splitDate(date);
monthsData.forEach((item, index) => {
if (item.title === monthTitle(dateArr[0], dateArr[1])) {
const currTop = monthsData[index].scrollTop;
if (monthsRef.current) {
const distance = currTop - monthsRef.current.scrollTop;
if (scrollAnimation) {
let flag = 0;
const interval = setInterval(() => {
flag++;
if (monthsRef.current) {
const offset = distance / 10;
monthsRef.current.scrollTop += offset;
}
if (flag >= 10) {
clearInterval(interval);
if (monthsRef.current) {
monthsRef.current.scrollTop = currTop;
}
}
}, 40);
} else {
monthsRef.current.scrollTop = currTop;
}
}
}
});
};
const monthsViewScroll = (e) => {
if (monthsData.length <= 1) {
return;
}
const scrollTop = e.target.scrollTop;
let current = Math.floor(scrollTop / avgHeight);
if (current < 0)
return;
if (!monthsData[current + 1])
return;
const nextTop = monthsData[current + 1].scrollTop;
const nextHeight = monthsData[current + 1].cssHeight;
if (current === 0) {
if (scrollTop >= nextTop) {
current += 1;
}
} else if (current > 0 && current < monthsNum - 1) {
if (scrollTop >= nextTop) {
current += 1;
}
if (scrollTop < monthsData[current].scrollTop) {
current -= 1;
}
} else {
const viewPosition = Math.round(scrollTop + viewHeight);
if (current + 1 <= monthsNum && viewPosition >= nextTop + nextHeight) {
current += 1;
}
if (current >= 1 && scrollTop < monthsData[current - 1].scrollTop) {
current -= 1;
}
}
setDefaultRange(monthsNum, current);
};
React__default.useImperativeHandle(ref, () => ({
scrollToDate
}));
const getClasses = (day, month) => {
const dateStr = getCurrDate(day, month);
if (day.type === "active") {
if (propStartDate && Utils.compareDate(dateStr, propStartDate) || propEndDate && Utils.compareDate(propEndDate, dateStr)) {
return `${dayPrefix}-disabled`;
}
if (type === "range" || type === "week") {
if (isStart(dateStr, currentDate) || isEnd(dateStr, currentDate)) {
return `${dayPrefix}-active ${isStart(dateStr, currentDate) ? "active-start" : ""} ${isEnd(dateStr, currentDate) ? "active-end" : ""}`;
}
if (Array.isArray(currentDate) && Object.values(currentDate).length === 2 && Utils.compareDate(currentDate[0], dateStr) && Utils.compareDate(dateStr, currentDate[1])) {
if (disableDate(day)) {
return `${dayPrefix}-choose-disabled`;
}
return `${dayPrefix}-choose`;
}
} else if (type === "multiple" && isMultiple(dateStr, currentDate) || !Array.isArray(currentDate) && Utils.isEqual(currentDate, dateStr)) {
return `${dayPrefix}-active`;
}
if (disableDate(day)) {
return `${dayPrefix}-disabled`;
}
return null;
}
return `${dayPrefix}-disabled`;
};
const chooseDay = (day, month, isFirst) => {
if (getClasses(day, month) === `${dayPrefix}-disabled`) {
return;
}
const days = [...month.curData];
const [y, m] = month.curData;
days[2] = typeof day.day === "number" ? Utils.getNumTwoBit(day.day) : day.day;
days[3] = `${days[0]}/${days[1]}/${days[2]}`;
days[4] = Utils.getWhatDay(+days[0], +days[1], +days[2]);
if (type === "multiple") {
if (currentDate.length > 0) {
let hasIndex = "";
currentDate.forEach((item, index) => {
if (item === days[3]) {
hasIndex = index;
}
});
if (isFirst) {
state.currDateArray.push([...days]);
} else if (hasIndex !== "") {
currentDate.splice(hasIndex, 1);
state.currDateArray.splice(hasIndex, 1);
} else {
currentDate.push(days[3]);
state.currDateArray.push([...days]);
}
} else {
currentDate.push(days[3]);
state.currDateArray = [[...days]];
}
} else if (type === "range") {
const curDataLength = Object.values(currentDate).length;
if (curDataLength === 2 || curDataLength === 0) {
Array.isArray(currentDate) && currentDate.splice(0) && currentDate.push(days[3]);
state.currDateArray = [[...days]];
} else if (Utils.compareDate(currentDate[0], days[3])) {
Array.isArray(currentDate) && currentDate.push(days[3]);
state.currDateArray = [...state.currDateArray, [...days]];
} else {
Array.isArray(currentDate) && currentDate.unshift(days[3]);
state.currDateArray = [[...days], ...state.currDateArray];
}
} else if (type === "week") {
const weekArr = Utils.getWeekDate(y, m, `${day.day}`, firstDayOfWeek);
if (propStartDate && Utils.compareDate(weekArr[0], propStartDate)) {
weekArr.splice(0, 1, propStartDate);
}
if (propEndDate && Utils.compareDate(propEndDate, weekArr[1])) {
weekArr.splice(1, 1, propEndDate);
}
Array.isArray(currentDate) && currentDate.splice(0) && currentDate.push(...weekArr);
state.currDateArray = [
Utils.formatResultDate(weekArr[0]),
Utils.formatResultDate(weekArr[1])
];
} else {
setCurrentDate(days[3]);
state.currDateArray = [...days];
}
if (!isFirst) {
onDayClick && onDayClick(state.currDateArray);
if (autoBackfill || !popup) {
confirm();
}
}
setMonthsData(monthsData.slice());
};
const confirm = () => {
if (type === "range" && state.currDateArray.length === 2 || type !== "range") {
const chooseData = state.currDateArray.slice(0);
onConfirm && onConfirm(chooseData);
if (popup) {
onUpdate && onUpdate();
}
}
};
const classes = classNames({
[`${classPrefix}-title`]: !popup,
[`${classPrefix}-nofooter`]: !!autoBackfill
}, classPrefix, className);
const headerClasses = classNames({
[`${classPrefix}-header`]: true,
[`${classPrefix}-header-title`]: !popup
});
const isStartTip = (day, month) => {
return (type === "range" || type === "week") && day.type === "active" && isStart(getCurrDate(day, month), currentDate);
};
const isEndTip = (day, month) => {
return currentDate.length >= 2 && (type === "range" || type === "week") && day.type === "active" && isEnd(getCurrDate(day, month), currentDate);
};
const renderHeader = () => {
return React__default.createElement(
"div",
{ className: headerClasses },
showTitle && React__default.createElement("div", { className: `${classPrefix}-title` }, title || locale.calendaritem.title),
renderHeaderButtons && React__default.createElement("div", { className: `${classPrefix}-header-buttons` }, renderHeaderButtons()),
showSubTitle && React__default.createElement("div", { className: `${classPrefix}-sub-title` }, yearMonthTitle),
React__default.createElement("div", { className: `${classPrefix}-weeks`, ref: weeksPanel }, weeks.map((item) => React__default.createElement("div", { className: `${classPrefix}-week-item`, key: item }, item)))
);
};
const renderContent = () => {
return React__default.createElement(
"div",
{ className: `${classPrefix}-content`, onScroll: monthsViewScroll, ref: monthsRef },
React__default.createElement(
"div",
{ className: `${classPrefix}-pannel`, ref: monthsPanel },
React__default.createElement("div", { className: "viewArea", ref: viewAreaRef, style: { transform: `translateY(${translateY}px)` } }, monthsData.slice(monthDefaultRange[0], monthDefaultRange[1]).map((month, key) => {
return React__default.createElement(
"div",
{ className: `${classPrefix}-month`, key },
React__default.createElement("div", { className: `${classPrefix}-month-title` }, month.title),
React__default.createElement("div", { className: `${classPrefix}-days` }, month.monthData.map((day, i) => React__default.createElement(
"div",
{ className: [
`${classPrefix}-day`,
getClasses(day, month)
].join(" "), onClick: () => {
chooseDay(day, month);
}, key: i },
React__default.createElement("div", { className: `${classPrefix}-day-day` }, renderDay ? renderDay(day) : day.day),
!isStartTip(day, month) && renderDayTop && React__default.createElement("div", { className: `${classPrefix}-day-info-top` }, renderDayTop(day)),
!isStartTip(day, month) && !isEndTip(day, month) && renderDayBottom && React__default.createElement("div", { className: `${classPrefix}-day-info-bottom` }, renderDayBottom(day)),
!isStartTip(day, month) && !isEndTip(day, month) && !renderDayBottom && showToday && isCurrDay(month, day.day) && React__default.createElement("div", { className: `${classPrefix}-day-info-curr` }, locale.calendaritem.today),
isStartTip(day, month) && React__default.createElement("div", { className: `${classPrefix}-day-info ${isStartAndEnd(currentDate) ? `${classPrefix}-day-info-top` : ""}` }, startText || locale.calendaritem.start),
isEndTip(day, month) && React__default.createElement("div", { className: `${classPrefix}-day-info` }, endText || locale.calendaritem.end)
)))
);
}))
)
);
};
const renderFooter = () => {
return React__default.createElement(
"div",
{ className: "nut-calendar-footer" },
children,
React__default.createElement("div", { className: "calendar-confirm-btn", onClick: confirm }, confirmText || locale.confirm)
);
};
return React__default.createElement(
"div",
{ className: classes, style },
renderHeader(),
renderContent(),
popup && !autoBackfill ? renderFooter() : ""
);
});
CalendarItem.displayName = "NutCalendarItem";
export {
CalendarItem as default
};