UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

563 lines (562 loc) 22.1 kB
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 };