use-basic-date-table.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import { ref, computed, unref, watch, nextTick } from 'vue';
  2. import dayjs from 'dayjs';
  3. import { flatten } from 'lodash-unified';
  4. import { buildPickerTable } from '../utils.mjs';
  5. import { useLocale } from '../../../../hooks/use-locale/index.mjs';
  6. import { castArray } from '../../../../utils/arrays.mjs';
  7. import { useNamespace } from '../../../../hooks/use-namespace/index.mjs';
  8. import { isArray } from '@vue/shared';
  9. const isNormalDay = (type = "") => {
  10. return ["normal", "today"].includes(type);
  11. };
  12. const useBasicDateTable = (props, emit) => {
  13. const { lang } = useLocale();
  14. const tbodyRef = ref();
  15. const currentCellRef = ref();
  16. const lastRow = ref();
  17. const lastColumn = ref();
  18. const tableRows = ref([[], [], [], [], [], []]);
  19. let focusWithClick = false;
  20. const firstDayOfWeek = props.date.$locale().weekStart || 7;
  21. const WEEKS_CONSTANT = props.date.locale("en").localeData().weekdaysShort().map((_) => _.toLowerCase());
  22. const offsetDay = computed(() => {
  23. return firstDayOfWeek > 3 ? 7 - firstDayOfWeek : -firstDayOfWeek;
  24. });
  25. const startDate = computed(() => {
  26. const startDayOfMonth = props.date.startOf("month");
  27. return startDayOfMonth.subtract(startDayOfMonth.day() || 7, "day");
  28. });
  29. const WEEKS = computed(() => {
  30. return WEEKS_CONSTANT.concat(WEEKS_CONSTANT).slice(firstDayOfWeek, firstDayOfWeek + 7);
  31. });
  32. const hasCurrent = computed(() => {
  33. return flatten(unref(rows)).some((row) => {
  34. return row.isCurrent;
  35. });
  36. });
  37. const days = computed(() => {
  38. const startOfMonth = props.date.startOf("month");
  39. const startOfMonthDay = startOfMonth.day() || 7;
  40. const dateCountOfMonth = startOfMonth.daysInMonth();
  41. const dateCountOfLastMonth = startOfMonth.subtract(1, "month").daysInMonth();
  42. return {
  43. startOfMonthDay,
  44. dateCountOfMonth,
  45. dateCountOfLastMonth
  46. };
  47. });
  48. const selectedDate = computed(() => {
  49. return props.selectionMode === "dates" ? castArray(props.parsedValue) : [];
  50. });
  51. const setDateText = (cell, { count, rowIndex, columnIndex }) => {
  52. const { startOfMonthDay, dateCountOfMonth, dateCountOfLastMonth } = unref(days);
  53. const offset = unref(offsetDay);
  54. if (rowIndex >= 0 && rowIndex <= 1) {
  55. const numberOfDaysFromPreviousMonth = startOfMonthDay + offset < 0 ? 7 + startOfMonthDay + offset : startOfMonthDay + offset;
  56. if (columnIndex + rowIndex * 7 >= numberOfDaysFromPreviousMonth) {
  57. cell.text = count;
  58. return true;
  59. } else {
  60. cell.text = dateCountOfLastMonth - (numberOfDaysFromPreviousMonth - columnIndex % 7) + 1 + rowIndex * 7;
  61. cell.type = "prev-month";
  62. }
  63. } else {
  64. if (count <= dateCountOfMonth) {
  65. cell.text = count;
  66. } else {
  67. cell.text = count - dateCountOfMonth;
  68. cell.type = "next-month";
  69. }
  70. return true;
  71. }
  72. return false;
  73. };
  74. const setCellMetadata = (cell, { columnIndex, rowIndex }, count) => {
  75. const { disabledDate, cellClassName } = props;
  76. const _selectedDate = unref(selectedDate);
  77. const shouldIncrement = setDateText(cell, { count, rowIndex, columnIndex });
  78. const cellDate = cell.dayjs.toDate();
  79. cell.selected = _selectedDate.find((d) => d.isSame(cell.dayjs, "day"));
  80. cell.isSelected = !!cell.selected;
  81. cell.isCurrent = isCurrent(cell);
  82. cell.disabled = disabledDate == null ? void 0 : disabledDate(cellDate);
  83. cell.customClass = cellClassName == null ? void 0 : cellClassName(cellDate);
  84. return shouldIncrement;
  85. };
  86. const setRowMetadata = (row) => {
  87. if (props.selectionMode === "week") {
  88. const [start, end] = props.showWeekNumber ? [1, 7] : [0, 6];
  89. const isActive = isWeekActive(row[start + 1]);
  90. row[start].inRange = isActive;
  91. row[start].start = isActive;
  92. row[end].inRange = isActive;
  93. row[end].end = isActive;
  94. }
  95. };
  96. const rows = computed(() => {
  97. const { minDate, maxDate, rangeState, showWeekNumber } = props;
  98. const offset = unref(offsetDay);
  99. const rows_ = unref(tableRows);
  100. const dateUnit = "day";
  101. let count = 1;
  102. if (showWeekNumber) {
  103. for (let rowIndex = 0; rowIndex < 6; rowIndex++) {
  104. if (!rows_[rowIndex][0]) {
  105. rows_[rowIndex][0] = {
  106. type: "week",
  107. text: unref(startDate).add(rowIndex * 7 + 1, dateUnit).week()
  108. };
  109. }
  110. }
  111. }
  112. buildPickerTable({ row: 6, column: 7 }, rows_, {
  113. startDate: minDate,
  114. columnIndexOffset: showWeekNumber ? 1 : 0,
  115. nextEndDate: rangeState.endDate || maxDate || rangeState.selecting && minDate || null,
  116. now: dayjs().locale(unref(lang)).startOf(dateUnit),
  117. unit: dateUnit,
  118. relativeDateGetter: (idx) => unref(startDate).add(idx - offset, dateUnit),
  119. setCellMetadata: (...args) => {
  120. if (setCellMetadata(...args, count)) {
  121. count += 1;
  122. }
  123. },
  124. setRowMetadata
  125. });
  126. return rows_;
  127. });
  128. watch(() => props.date, async () => {
  129. var _a;
  130. if ((_a = unref(tbodyRef)) == null ? void 0 : _a.contains(document.activeElement)) {
  131. await nextTick();
  132. await focus();
  133. }
  134. });
  135. const focus = async () => {
  136. var _a;
  137. return (_a = unref(currentCellRef)) == null ? void 0 : _a.focus();
  138. };
  139. const isCurrent = (cell) => {
  140. return props.selectionMode === "date" && isNormalDay(cell.type) && cellMatchesDate(cell, props.parsedValue);
  141. };
  142. const cellMatchesDate = (cell, date) => {
  143. if (!date)
  144. return false;
  145. return dayjs(date).locale(unref(lang)).isSame(props.date.date(Number(cell.text)), "day");
  146. };
  147. const getDateOfCell = (row, column) => {
  148. const offsetFromStart = row * 7 + (column - (props.showWeekNumber ? 1 : 0)) - unref(offsetDay);
  149. return unref(startDate).add(offsetFromStart, "day");
  150. };
  151. const handleMouseMove = (event) => {
  152. var _a;
  153. if (!props.rangeState.selecting)
  154. return;
  155. let target = event.target;
  156. if (target.tagName === "SPAN") {
  157. target = (_a = target.parentNode) == null ? void 0 : _a.parentNode;
  158. }
  159. if (target.tagName === "DIV") {
  160. target = target.parentNode;
  161. }
  162. if (target.tagName !== "TD")
  163. return;
  164. const row = target.parentNode.rowIndex - 1;
  165. const column = target.cellIndex;
  166. if (unref(rows)[row][column].disabled)
  167. return;
  168. if (row !== unref(lastRow) || column !== unref(lastColumn)) {
  169. lastRow.value = row;
  170. lastColumn.value = column;
  171. emit("changerange", {
  172. selecting: true,
  173. endDate: getDateOfCell(row, column)
  174. });
  175. }
  176. };
  177. const isSelectedCell = (cell) => {
  178. return !unref(hasCurrent) && (cell == null ? void 0 : cell.text) === 1 && cell.type === "normal" || cell.isCurrent;
  179. };
  180. const handleFocus = (event) => {
  181. if (focusWithClick || unref(hasCurrent) || props.selectionMode !== "date")
  182. return;
  183. handlePickDate(event, true);
  184. };
  185. const handleMouseDown = (event) => {
  186. const target = event.target.closest("td");
  187. if (!target)
  188. return;
  189. focusWithClick = true;
  190. };
  191. const handleMouseUp = (event) => {
  192. const target = event.target.closest("td");
  193. if (!target)
  194. return;
  195. focusWithClick = false;
  196. };
  197. const handleRangePick = (newDate) => {
  198. if (!props.rangeState.selecting || !props.minDate) {
  199. emit("pick", { minDate: newDate, maxDate: null });
  200. emit("select", true);
  201. } else {
  202. if (newDate >= props.minDate) {
  203. emit("pick", { minDate: props.minDate, maxDate: newDate });
  204. } else {
  205. emit("pick", { minDate: newDate, maxDate: props.minDate });
  206. }
  207. emit("select", false);
  208. }
  209. };
  210. const handleWeekPick = (newDate) => {
  211. const weekNumber = newDate.week();
  212. const value = `${newDate.year()}w${weekNumber}`;
  213. emit("pick", {
  214. year: newDate.year(),
  215. week: weekNumber,
  216. value,
  217. date: newDate.startOf("week")
  218. });
  219. };
  220. const handleDatesPick = (newDate, selected) => {
  221. const newValue = selected ? castArray(props.parsedValue).filter((d) => (d == null ? void 0 : d.valueOf()) !== newDate.valueOf()) : castArray(props.parsedValue).concat([newDate]);
  222. emit("pick", newValue);
  223. };
  224. const handlePickDate = (event, isKeyboardMovement = false) => {
  225. const target = event.target.closest("td");
  226. if (!target)
  227. return;
  228. const row = target.parentNode.rowIndex - 1;
  229. const column = target.cellIndex;
  230. const cell = unref(rows)[row][column];
  231. if (cell.disabled || cell.type === "week")
  232. return;
  233. const newDate = getDateOfCell(row, column);
  234. switch (props.selectionMode) {
  235. case "range": {
  236. handleRangePick(newDate);
  237. break;
  238. }
  239. case "date": {
  240. emit("pick", newDate, isKeyboardMovement);
  241. break;
  242. }
  243. case "week": {
  244. handleWeekPick(newDate);
  245. break;
  246. }
  247. case "dates": {
  248. handleDatesPick(newDate, !!cell.selected);
  249. break;
  250. }
  251. }
  252. };
  253. const isWeekActive = (cell) => {
  254. if (props.selectionMode !== "week")
  255. return false;
  256. let newDate = props.date.startOf("day");
  257. if (cell.type === "prev-month") {
  258. newDate = newDate.subtract(1, "month");
  259. }
  260. if (cell.type === "next-month") {
  261. newDate = newDate.add(1, "month");
  262. }
  263. newDate = newDate.date(Number.parseInt(cell.text, 10));
  264. if (props.parsedValue && !isArray(props.parsedValue)) {
  265. const dayOffset = (props.parsedValue.day() - firstDayOfWeek + 7) % 7 - 1;
  266. const weekDate = props.parsedValue.subtract(dayOffset, "day");
  267. return weekDate.isSame(newDate, "day");
  268. }
  269. return false;
  270. };
  271. return {
  272. WEEKS,
  273. rows,
  274. tbodyRef,
  275. currentCellRef,
  276. focus,
  277. isCurrent,
  278. isWeekActive,
  279. isSelectedCell,
  280. handlePickDate,
  281. handleMouseUp,
  282. handleMouseDown,
  283. handleMouseMove,
  284. handleFocus
  285. };
  286. };
  287. const useBasicDateTableDOM = (props, {
  288. isCurrent,
  289. isWeekActive
  290. }) => {
  291. const ns = useNamespace("date-table");
  292. const { t } = useLocale();
  293. const tableKls = computed(() => [
  294. ns.b(),
  295. { "is-week-mode": props.selectionMode === "week" }
  296. ]);
  297. const tableLabel = computed(() => t("el.datepicker.dateTablePrompt"));
  298. const weekLabel = computed(() => t("el.datepicker.week"));
  299. const getCellClasses = (cell) => {
  300. const classes = [];
  301. if (isNormalDay(cell.type) && !cell.disabled) {
  302. classes.push("available");
  303. if (cell.type === "today") {
  304. classes.push("today");
  305. }
  306. } else {
  307. classes.push(cell.type);
  308. }
  309. if (isCurrent(cell)) {
  310. classes.push("current");
  311. }
  312. if (cell.inRange && (isNormalDay(cell.type) || props.selectionMode === "week")) {
  313. classes.push("in-range");
  314. if (cell.start) {
  315. classes.push("start-date");
  316. }
  317. if (cell.end) {
  318. classes.push("end-date");
  319. }
  320. }
  321. if (cell.disabled) {
  322. classes.push("disabled");
  323. }
  324. if (cell.selected) {
  325. classes.push("selected");
  326. }
  327. if (cell.customClass) {
  328. classes.push(cell.customClass);
  329. }
  330. return classes.join(" ");
  331. };
  332. const getRowKls = (cell) => [
  333. ns.e("row"),
  334. { current: isWeekActive(cell) }
  335. ];
  336. return {
  337. tableKls,
  338. tableLabel,
  339. weekLabel,
  340. getCellClasses,
  341. getRowKls,
  342. t
  343. };
  344. };
  345. export { useBasicDateTable, useBasicDateTableDOM };
  346. //# sourceMappingURL=use-basic-date-table.mjs.map