mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
refactor(components): [date-picker] basic-date-table (#8095)
* Refactor `basic-date-table` to script setup. Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com>
This commit is contained in:
@@ -21,17 +21,17 @@
|
||||
</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(row, key) in rows"
|
||||
:key="key"
|
||||
v-for="(row, rowKey) in rows"
|
||||
:key="rowKey"
|
||||
:class="[ns.e('row'), { current: isWeekActive(row[1]) }]"
|
||||
>
|
||||
<td
|
||||
v-for="(cell, key_) in row"
|
||||
:key="key_"
|
||||
:ref="(el) => isSelectedCell(cell) && (currentCellRef = el)"
|
||||
v-for="(cell, columnKey) in row"
|
||||
:key="`${rowKey}.${columnKey}`"
|
||||
:ref="(el) => { isSelectedCell(cell) && (currentCellRef = el as HTMLElement) }"
|
||||
:class="getCellClasses(cell)"
|
||||
:aria-current="cell.isCurrent ? 'date' : undefined"
|
||||
:aria-selected="`${cell.isCurrent}`"
|
||||
:aria-selected="cell.isCurrent"
|
||||
:tabindex="isSelectedCell(cell) ? 0 : -1"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
@@ -42,407 +42,407 @@
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, nextTick, ref, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref, unref, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useLocale, useNamespace } from '@element-plus/hooks'
|
||||
import { castArray } from '@element-plus/utils'
|
||||
import { basicDateTableProps } from '../props/basic-date-table'
|
||||
import { buildPickerTable } from '../utils'
|
||||
import ElDatePickerCell from './basic-cell-render'
|
||||
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type { DateCell } from '../date-picker.type'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ElDatePickerCell,
|
||||
const props = defineProps(basicDateTableProps)
|
||||
const emit = defineEmits(['changerange', 'pick', 'select'])
|
||||
|
||||
const ns = useNamespace('date-table')
|
||||
|
||||
const { t, lang } = useLocale()
|
||||
|
||||
const tbodyRef = ref<HTMLElement>()
|
||||
const currentCellRef = ref<HTMLElement>()
|
||||
// data
|
||||
const lastRow = ref<number>()
|
||||
const lastColumn = ref<number>()
|
||||
const tableRows = ref<DateCell[][]>([[], [], [], [], [], []])
|
||||
|
||||
// todo better way to get Day.js locale object
|
||||
const firstDayOfWeek = (props.date as any).$locale().weekStart || 7
|
||||
const WEEKS_CONSTANT = props.date
|
||||
.locale('en')
|
||||
.localeData()
|
||||
.weekdaysShort()
|
||||
.map((_) => _.toLowerCase())
|
||||
|
||||
const offsetDay = computed(() => {
|
||||
// Sunday 7(0), cal the left and right offset days, 3217654, such as Monday is -1, the is to adjust the position of the first two rows of dates
|
||||
return firstDayOfWeek > 3 ? 7 - firstDayOfWeek : -firstDayOfWeek
|
||||
})
|
||||
|
||||
const startDate = computed(() => {
|
||||
const startDayOfMonth = props.date.startOf('month')
|
||||
return startDayOfMonth.subtract(startDayOfMonth.day() || 7, 'day')
|
||||
})
|
||||
|
||||
const WEEKS = computed(() => {
|
||||
return WEEKS_CONSTANT.concat(WEEKS_CONSTANT).slice(
|
||||
firstDayOfWeek,
|
||||
firstDayOfWeek + 7
|
||||
)
|
||||
})
|
||||
|
||||
const hasCurrent = computed<boolean>(() => {
|
||||
return rows.value.flat().some((row) => {
|
||||
return row.isCurrent
|
||||
})
|
||||
})
|
||||
|
||||
const days = computed(() => {
|
||||
const startOfMonth = props.date.startOf('month')
|
||||
const startOfMonthDay = startOfMonth.day() || 7 // day of first day
|
||||
const dateCountOfMonth = startOfMonth.daysInMonth()
|
||||
|
||||
const dateCountOfLastMonth = startOfMonth.subtract(1, 'month').daysInMonth()
|
||||
|
||||
return {
|
||||
startOfMonthDay,
|
||||
dateCountOfMonth,
|
||||
dateCountOfLastMonth,
|
||||
}
|
||||
})
|
||||
|
||||
const selectedDate = computed(() => {
|
||||
return props.selectionMode === 'dates'
|
||||
? (castArray(props.parsedValue) as Dayjs[])
|
||||
: ([] as Dayjs[])
|
||||
})
|
||||
|
||||
// Return value indicates should the counter be incremented
|
||||
const setDateText = (
|
||||
cell: DateCell,
|
||||
{
|
||||
count,
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
}: {
|
||||
count: number
|
||||
rowIndex: number
|
||||
columnIndex: number
|
||||
}
|
||||
): boolean => {
|
||||
const { startOfMonthDay, dateCountOfMonth, dateCountOfLastMonth } =
|
||||
unref(days)
|
||||
const offset = unref(offsetDay)
|
||||
if (rowIndex >= 0 && rowIndex <= 1) {
|
||||
const numberOfDaysFromPreviousMonth =
|
||||
startOfMonthDay + offset < 0
|
||||
? 7 + startOfMonthDay + offset
|
||||
: startOfMonthDay + offset
|
||||
|
||||
if (columnIndex + rowIndex * 7 >= numberOfDaysFromPreviousMonth) {
|
||||
cell.text = count
|
||||
return true
|
||||
} else {
|
||||
cell.text =
|
||||
dateCountOfLastMonth -
|
||||
(numberOfDaysFromPreviousMonth - (columnIndex % 7)) +
|
||||
1 +
|
||||
rowIndex * 7
|
||||
cell.type = 'prev-month'
|
||||
}
|
||||
} else {
|
||||
if (count <= dateCountOfMonth) {
|
||||
cell.text = count
|
||||
} else {
|
||||
cell.text = count - dateCountOfMonth
|
||||
cell.type = 'next-month'
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const setCellMetadata = (
|
||||
cell: DateCell,
|
||||
{
|
||||
columnIndex,
|
||||
rowIndex,
|
||||
}: {
|
||||
columnIndex: number
|
||||
rowIndex: number
|
||||
},
|
||||
props: basicDateTableProps,
|
||||
emits: ['changerange', 'pick', 'select'],
|
||||
expose: ['focus'],
|
||||
setup(props, ctx) {
|
||||
const ns = useNamespace('date-table')
|
||||
count: number
|
||||
) => {
|
||||
const { disabledDate, cellClassName } = props
|
||||
const _selectedDate = unref(selectedDate)
|
||||
const shouldIncrement = setDateText(cell, { count, rowIndex, columnIndex })
|
||||
|
||||
const { t, lang } = useLocale()
|
||||
const cellDate = cell.dayjs!.toDate()
|
||||
cell.selected = _selectedDate.find(
|
||||
(d) => d.valueOf() === cell.dayjs!.valueOf()
|
||||
)
|
||||
cell.isSelected = !!cell.selected
|
||||
cell.isCurrent = isCurrent(cell)
|
||||
cell.disabled = disabledDate?.(cellDate)
|
||||
cell.customClass = cellClassName?.(cellDate)
|
||||
return shouldIncrement
|
||||
}
|
||||
|
||||
const tbodyRef = ref<HTMLElement>()
|
||||
const currentCellRef = ref<HTMLElement>()
|
||||
// data
|
||||
const lastRow = ref(null)
|
||||
const lastColumn = ref(null)
|
||||
const tableRows = ref<DateCell[][]>([[], [], [], [], [], []])
|
||||
const setRowMetadata = (row: DateCell[]) => {
|
||||
if (props.selectionMode === 'week') {
|
||||
const [start, end] = props.showWeekNumber ? [1, 7] : [0, 6]
|
||||
const isActive = isWeekActive(row[start + 1])
|
||||
row[start].inRange = isActive
|
||||
row[start].start = isActive
|
||||
row[end].inRange = isActive
|
||||
row[end].end = isActive
|
||||
}
|
||||
}
|
||||
|
||||
// todo better way to get Day.js locale object
|
||||
const firstDayOfWeek = (props.date as any).$locale().weekStart || 7
|
||||
const WEEKS_CONSTANT = props.date
|
||||
.locale('en')
|
||||
.localeData()
|
||||
.weekdaysShort()
|
||||
.map((_) => _.toLowerCase())
|
||||
const rows = computed(() => {
|
||||
const { minDate, maxDate, rangeState, showWeekNumber } = props
|
||||
|
||||
const offsetDay = computed(() => {
|
||||
// Sunday 7(0), cal the left and right offset days, 3217654, such as Monday is -1, the is to adjust the position of the first two rows of dates
|
||||
return firstDayOfWeek > 3 ? 7 - firstDayOfWeek : -firstDayOfWeek
|
||||
})
|
||||
const offset = offsetDay.value
|
||||
const rows_ = tableRows.value
|
||||
const dateUnit = 'day'
|
||||
let count = 1
|
||||
|
||||
const startDate = computed(() => {
|
||||
const startDayOfMonth = props.date.startOf('month')
|
||||
return startDayOfMonth.subtract(startDayOfMonth.day() || 7, 'day')
|
||||
})
|
||||
|
||||
const WEEKS = computed(() => {
|
||||
return WEEKS_CONSTANT.concat(WEEKS_CONSTANT).slice(
|
||||
firstDayOfWeek,
|
||||
firstDayOfWeek + 7
|
||||
)
|
||||
})
|
||||
|
||||
const hasCurrent = computed<boolean>(() => {
|
||||
return rows.value.flat().some((row) => {
|
||||
return row.isCurrent
|
||||
})
|
||||
})
|
||||
|
||||
const rows = computed(() => {
|
||||
// TODO: refactory rows / getCellClasses
|
||||
const startOfMonth = props.date.startOf('month')
|
||||
const startOfMonthDay = startOfMonth.day() || 7 // day of first day
|
||||
const dateCountOfMonth = startOfMonth.daysInMonth()
|
||||
const dateCountOfLastMonth = startOfMonth
|
||||
.subtract(1, 'month')
|
||||
.daysInMonth()
|
||||
|
||||
const offset = offsetDay.value
|
||||
const rows_ = tableRows.value
|
||||
let count = 1
|
||||
|
||||
const selectedDate: Dayjs[] =
|
||||
props.selectionMode === 'dates' ? castArray(props.parsedValue) : []
|
||||
|
||||
const calNow = dayjs().locale(lang.value).startOf('day')
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const row = rows_[i]
|
||||
|
||||
if (props.showWeekNumber) {
|
||||
if (!row[0]) {
|
||||
row[0] = {
|
||||
type: 'week',
|
||||
text: startDate.value.add(i * 7 + 1, 'day').week(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < 7; j++) {
|
||||
let cell = row[props.showWeekNumber ? j + 1 : j]
|
||||
if (!cell) {
|
||||
cell = {
|
||||
row: i,
|
||||
column: j,
|
||||
type: 'normal',
|
||||
inRange: false,
|
||||
start: false,
|
||||
end: false,
|
||||
}
|
||||
}
|
||||
const index = i * 7 + j
|
||||
const calTime = startDate.value.add(index - offset, 'day')
|
||||
cell.dayjs = calTime
|
||||
cell.date = calTime.toDate()
|
||||
cell.timestamp = calTime.valueOf()
|
||||
cell.type = 'normal'
|
||||
|
||||
const calEndDate =
|
||||
props.rangeState.endDate ||
|
||||
props.maxDate ||
|
||||
(props.rangeState.selecting && props.minDate)
|
||||
|
||||
cell.inRange =
|
||||
(props.minDate &&
|
||||
calTime.isSameOrAfter(props.minDate, 'day') &&
|
||||
calEndDate &&
|
||||
calTime.isSameOrBefore(calEndDate, 'day')) ||
|
||||
(props.minDate &&
|
||||
calTime.isSameOrBefore(props.minDate, 'day') &&
|
||||
calEndDate &&
|
||||
calTime.isSameOrAfter(calEndDate, 'day'))
|
||||
|
||||
if (props.minDate?.isSameOrAfter(calEndDate)) {
|
||||
cell.start = calEndDate && calTime.isSame(calEndDate, 'day')
|
||||
cell.end = props.minDate && calTime.isSame(props.minDate, 'day')
|
||||
} else {
|
||||
cell.start = props.minDate && calTime.isSame(props.minDate, 'day')
|
||||
cell.end = calEndDate && calTime.isSame(calEndDate, 'day')
|
||||
}
|
||||
|
||||
const isToday = calTime.isSame(calNow, 'day')
|
||||
|
||||
if (isToday) {
|
||||
cell.type = 'today'
|
||||
}
|
||||
|
||||
if (i >= 0 && i <= 1) {
|
||||
const numberOfDaysFromPreviousMonth =
|
||||
startOfMonthDay + offset < 0
|
||||
? 7 + startOfMonthDay + offset
|
||||
: startOfMonthDay + offset
|
||||
|
||||
if (j + i * 7 >= numberOfDaysFromPreviousMonth) {
|
||||
cell.text = count++
|
||||
} else {
|
||||
cell.text =
|
||||
dateCountOfLastMonth -
|
||||
(numberOfDaysFromPreviousMonth - (j % 7)) +
|
||||
1 +
|
||||
i * 7
|
||||
cell.type = 'prev-month'
|
||||
}
|
||||
} else {
|
||||
if (count <= dateCountOfMonth) {
|
||||
cell.text = count++
|
||||
} else {
|
||||
cell.text = count++ - dateCountOfMonth
|
||||
cell.type = 'next-month'
|
||||
}
|
||||
}
|
||||
|
||||
const cellDate = calTime.toDate()
|
||||
cell.selected = selectedDate.find(
|
||||
(_) => _.valueOf() === calTime.valueOf()
|
||||
)
|
||||
cell.isSelected = !!cell.selected
|
||||
cell.isCurrent = isCurrent(cell)
|
||||
cell.disabled = props.disabledDate && props.disabledDate(cellDate)
|
||||
cell.customClass =
|
||||
props.cellClassName && props.cellClassName(cellDate)
|
||||
row[props.showWeekNumber ? j + 1 : j] = cell
|
||||
}
|
||||
|
||||
if (props.selectionMode === 'week') {
|
||||
const start = props.showWeekNumber ? 1 : 0
|
||||
const end = props.showWeekNumber ? 7 : 6
|
||||
const isActive = isWeekActive(row[start + 1])
|
||||
row[start].inRange = isActive
|
||||
row[start].start = isActive
|
||||
row[end].inRange = isActive
|
||||
row[end].end = isActive
|
||||
if (showWeekNumber) {
|
||||
for (let rowIndex = 0; rowIndex < 6; rowIndex++) {
|
||||
if (!rows_[rowIndex][0]) {
|
||||
rows_[rowIndex][0] = {
|
||||
type: 'week',
|
||||
text: startDate.value.add(rowIndex * 7 + 1, dateUnit).week(),
|
||||
}
|
||||
}
|
||||
return rows_
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.date,
|
||||
async () => {
|
||||
if (tbodyRef.value?.contains(document.activeElement)) {
|
||||
await nextTick()
|
||||
currentCellRef.value?.focus()
|
||||
}
|
||||
buildPickerTable({ row: 6, column: 7 }, rows_, {
|
||||
startDate: minDate,
|
||||
columnIndexOffset: showWeekNumber ? 1 : 0,
|
||||
nextEndDate:
|
||||
rangeState.endDate ||
|
||||
maxDate ||
|
||||
(rangeState.selecting && minDate) ||
|
||||
null,
|
||||
now: dayjs().locale(unref(lang)).startOf(dateUnit),
|
||||
unit: dateUnit,
|
||||
relativeDateGetter: (idx: number) =>
|
||||
startDate.value.add(idx - offset, dateUnit),
|
||||
setCellMetadata: (...args) => {
|
||||
if (setCellMetadata(...args, count)) {
|
||||
count += 1
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
const focus = async () => {
|
||||
setRowMetadata,
|
||||
})
|
||||
|
||||
return rows_
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.date,
|
||||
async () => {
|
||||
if (tbodyRef.value?.contains(document.activeElement)) {
|
||||
await nextTick()
|
||||
currentCellRef.value?.focus()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isCurrent = (cell): boolean => {
|
||||
return (
|
||||
props.selectionMode === 'date' &&
|
||||
(cell.type === 'normal' || cell.type === 'today') &&
|
||||
cellMatchesDate(cell, props.parsedValue)
|
||||
)
|
||||
const focus = async () => {
|
||||
currentCellRef.value?.focus()
|
||||
}
|
||||
|
||||
const isNormalDay = (type = '') => {
|
||||
return ['normal', 'today'].includes(type)
|
||||
}
|
||||
|
||||
const isCurrent = (cell: DateCell): boolean => {
|
||||
return (
|
||||
props.selectionMode === 'date' &&
|
||||
isNormalDay(cell.type) &&
|
||||
cellMatchesDate(cell, props.parsedValue as Dayjs)
|
||||
)
|
||||
}
|
||||
|
||||
const cellMatchesDate = (cell: DateCell, date: Dayjs) => {
|
||||
if (!date) return false
|
||||
return dayjs(date)
|
||||
.locale(lang.value)
|
||||
.isSame(props.date.date(Number(cell.text)), 'day')
|
||||
}
|
||||
|
||||
const getCellClasses = (cell: DateCell) => {
|
||||
const classes: string[] = []
|
||||
if (isNormalDay(cell.type) && !cell.disabled) {
|
||||
classes.push('available')
|
||||
if (cell.type === 'today') {
|
||||
classes.push('today')
|
||||
}
|
||||
} else {
|
||||
classes.push(cell.type!)
|
||||
}
|
||||
|
||||
if (isCurrent(cell)) {
|
||||
classes.push('current')
|
||||
}
|
||||
|
||||
if (
|
||||
cell.inRange &&
|
||||
(isNormalDay(cell.type) || props.selectionMode === 'week')
|
||||
) {
|
||||
classes.push('in-range')
|
||||
|
||||
if (cell.start) {
|
||||
classes.push('start-date')
|
||||
}
|
||||
|
||||
const cellMatchesDate = (cell, date) => {
|
||||
if (!date) return false
|
||||
return dayjs(date)
|
||||
.locale(lang.value)
|
||||
.isSame(props.date.date(Number(cell.text)), 'day')
|
||||
if (cell.end) {
|
||||
classes.push('end-date')
|
||||
}
|
||||
}
|
||||
|
||||
const getCellClasses = (cell) => {
|
||||
const classes: string[] = []
|
||||
if ((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) {
|
||||
classes.push('available')
|
||||
if (cell.type === 'today') {
|
||||
classes.push('today')
|
||||
}
|
||||
if (cell.disabled) {
|
||||
classes.push('disabled')
|
||||
}
|
||||
|
||||
if (cell.selected) {
|
||||
classes.push('selected')
|
||||
}
|
||||
|
||||
if (cell.customClass) {
|
||||
classes.push(cell.customClass)
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
}
|
||||
|
||||
const getDateOfCell = (row: number, column: number) => {
|
||||
const offsetFromStart =
|
||||
row * 7 + (column - (props.showWeekNumber ? 1 : 0)) - offsetDay.value
|
||||
return startDate.value.add(offsetFromStart, 'day')
|
||||
}
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!props.rangeState.selecting) return
|
||||
|
||||
let target = event.target as HTMLElement
|
||||
if (target.tagName === 'SPAN') {
|
||||
target = target.parentNode?.parentNode as HTMLElement
|
||||
}
|
||||
if (target.tagName === 'DIV') {
|
||||
target = target.parentNode as HTMLElement
|
||||
}
|
||||
if (target.tagName !== 'TD') return
|
||||
|
||||
const row = (target.parentNode as HTMLTableRowElement).rowIndex - 1
|
||||
const column = (target as HTMLTableCellElement).cellIndex
|
||||
|
||||
// can not select disabled date
|
||||
if (rows.value[row][column].disabled) return
|
||||
|
||||
// only update rangeState when mouse moves to a new cell
|
||||
// this avoids frequent Date object creation and improves performance
|
||||
if (row !== lastRow.value || column !== lastColumn.value) {
|
||||
lastRow.value = row
|
||||
lastColumn.value = column
|
||||
emit('changerange', {
|
||||
selecting: true,
|
||||
endDate: getDateOfCell(row, column),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const isSelectedCell = (cell: DateCell) => {
|
||||
return (
|
||||
(!hasCurrent.value && cell?.text === 1 && cell.type === 'normal') ||
|
||||
cell.isCurrent
|
||||
)
|
||||
}
|
||||
|
||||
const handleFocus = (event: Event) => {
|
||||
if (!hasCurrent.value && props.selectionMode === 'date') {
|
||||
handlePickDate(event, true)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePickDate = (event: Event, isKeyboardMovement = false) => {
|
||||
const target = (event.target as HTMLElement).closest('td')
|
||||
|
||||
if (!target || target.tagName !== 'TD') return
|
||||
|
||||
const row = (target.parentNode as HTMLTableRowElement).rowIndex - 1
|
||||
const column = (target as HTMLTableCellElement).cellIndex
|
||||
const cell = rows.value[row][column]
|
||||
|
||||
if (cell.disabled || cell.type === 'week') return
|
||||
|
||||
const newDate = getDateOfCell(row, column)
|
||||
|
||||
if (props.selectionMode === 'range') {
|
||||
if (!props.rangeState.selecting || !props.minDate) {
|
||||
emit('pick', { minDate: newDate, maxDate: null })
|
||||
emit('select', true)
|
||||
} else {
|
||||
if (newDate >= props.minDate) {
|
||||
emit('pick', { minDate: props.minDate, maxDate: newDate })
|
||||
} else {
|
||||
classes.push(cell.type)
|
||||
emit('pick', { minDate: newDate, maxDate: props.minDate })
|
||||
}
|
||||
|
||||
if (isCurrent(cell)) {
|
||||
classes.push('current')
|
||||
}
|
||||
|
||||
if (
|
||||
cell.inRange &&
|
||||
(cell.type === 'normal' ||
|
||||
cell.type === 'today' ||
|
||||
props.selectionMode === 'week')
|
||||
) {
|
||||
classes.push('in-range')
|
||||
|
||||
if (cell.start) {
|
||||
classes.push('start-date')
|
||||
}
|
||||
|
||||
if (cell.end) {
|
||||
classes.push('end-date')
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.disabled) {
|
||||
classes.push('disabled')
|
||||
}
|
||||
|
||||
if (cell.selected) {
|
||||
classes.push('selected')
|
||||
}
|
||||
|
||||
if (cell.customClass) {
|
||||
classes.push(cell.customClass)
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
emit('select', false)
|
||||
}
|
||||
} else if (props.selectionMode === 'date') {
|
||||
emit('pick', newDate, isKeyboardMovement)
|
||||
} else if (props.selectionMode === 'week') {
|
||||
const weekNumber = newDate.week()
|
||||
const value = `${newDate.year()}w${weekNumber}`
|
||||
emit('pick', {
|
||||
year: newDate.year(),
|
||||
week: weekNumber,
|
||||
value,
|
||||
date: newDate.startOf('week'),
|
||||
})
|
||||
} else if (props.selectionMode === 'dates') {
|
||||
const newValue = cell.selected
|
||||
? castArray(props.parsedValue).filter(
|
||||
(d) => d?.valueOf() !== newDate.valueOf()
|
||||
)
|
||||
: castArray(props.parsedValue).concat([newDate])
|
||||
emit('pick', newValue)
|
||||
}
|
||||
}
|
||||
|
||||
const getDateOfCell = (row, column) => {
|
||||
const offsetFromStart =
|
||||
row * 7 + (column - (props.showWeekNumber ? 1 : 0)) - offsetDay.value
|
||||
return startDate.value.add(offsetFromStart, 'day')
|
||||
}
|
||||
const isWeekActive = (cell: DateCell) => {
|
||||
if (props.selectionMode !== 'week') return false
|
||||
let newDate = props.date.startOf('day')
|
||||
|
||||
const handleMouseMove = (event) => {
|
||||
if (!props.rangeState.selecting) return
|
||||
if (cell.type === 'prev-month') {
|
||||
newDate = newDate.subtract(1, 'month')
|
||||
}
|
||||
|
||||
let target = event.target
|
||||
if (target.tagName === 'SPAN') {
|
||||
target = target.parentNode.parentNode
|
||||
}
|
||||
if (target.tagName === 'DIV') {
|
||||
target = target.parentNode
|
||||
}
|
||||
if (target.tagName !== 'TD') return
|
||||
if (cell.type === 'next-month') {
|
||||
newDate = newDate.add(1, 'month')
|
||||
}
|
||||
|
||||
const row = target.parentNode.rowIndex - 1
|
||||
const column = target.cellIndex
|
||||
newDate = newDate.date(Number.parseInt(cell.text as any, 10))
|
||||
|
||||
// can not select disabled date
|
||||
if (rows.value[row][column].disabled) return
|
||||
if (props.parsedValue && !Array.isArray(props.parsedValue)) {
|
||||
const dayOffset = ((props.parsedValue.day() - firstDayOfWeek + 7) % 7) - 1
|
||||
const weekDate = props.parsedValue.subtract(dayOffset, 'day')
|
||||
return weekDate.isSame(newDate, 'day')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// only update rangeState when mouse moves to a new cell
|
||||
// this avoids frequent Date object creation and improves performance
|
||||
if (row !== lastRow.value || column !== lastColumn.value) {
|
||||
lastRow.value = row
|
||||
lastColumn.value = column
|
||||
ctx.emit('changerange', {
|
||||
selecting: true,
|
||||
endDate: getDateOfCell(row, column),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const isSelectedCell = (cell: DateCell) => {
|
||||
return (
|
||||
(!hasCurrent.value && cell?.text === 1 && cell.type === 'normal') ||
|
||||
cell.isCurrent
|
||||
)
|
||||
}
|
||||
|
||||
const handleFocus = (event: Event) => {
|
||||
if (!hasCurrent.value && props.selectionMode === 'date') {
|
||||
handlePickDate(event, true)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePickDate = (event: Event, isKeyboardMovement = false) => {
|
||||
let target = event.target as HTMLElement
|
||||
|
||||
target = target?.closest('td')
|
||||
|
||||
if (!target || target.tagName !== 'TD') return
|
||||
|
||||
const row = (target.parentNode as HTMLTableRowElement).rowIndex - 1
|
||||
const column = (target as HTMLTableCellElement).cellIndex
|
||||
const cell = rows.value[row][column]
|
||||
|
||||
if (cell.disabled || cell.type === 'week') return
|
||||
|
||||
const newDate = getDateOfCell(row, column)
|
||||
|
||||
if (props.selectionMode === 'range') {
|
||||
if (!props.rangeState.selecting || !props.minDate) {
|
||||
ctx.emit('pick', { minDate: newDate, maxDate: null })
|
||||
ctx.emit('select', true)
|
||||
} else {
|
||||
if (newDate >= props.minDate) {
|
||||
ctx.emit('pick', { minDate: props.minDate, maxDate: newDate })
|
||||
} else {
|
||||
ctx.emit('pick', { minDate: newDate, maxDate: props.minDate })
|
||||
}
|
||||
ctx.emit('select', false)
|
||||
}
|
||||
} else if (props.selectionMode === 'date') {
|
||||
ctx.emit('pick', newDate, isKeyboardMovement)
|
||||
} else if (props.selectionMode === 'week') {
|
||||
const weekNumber = newDate.week()
|
||||
const value = `${newDate.year()}w${weekNumber}`
|
||||
ctx.emit('pick', {
|
||||
year: newDate.year(),
|
||||
week: weekNumber,
|
||||
value,
|
||||
date: newDate.startOf('week'),
|
||||
})
|
||||
} else if (props.selectionMode === 'dates') {
|
||||
const newValue = cell.selected
|
||||
? castArray(props.parsedValue).filter(
|
||||
(_) => _.valueOf() !== newDate.valueOf()
|
||||
)
|
||||
: castArray(props.parsedValue).concat([newDate])
|
||||
ctx.emit('pick', newValue)
|
||||
}
|
||||
}
|
||||
|
||||
const isWeekActive = (cell) => {
|
||||
if (props.selectionMode !== 'week') return false
|
||||
let newDate = props.date.startOf('day')
|
||||
|
||||
if (cell.type === 'prev-month') {
|
||||
newDate = newDate.subtract(1, 'month')
|
||||
}
|
||||
|
||||
if (cell.type === 'next-month') {
|
||||
newDate = newDate.add(1, 'month')
|
||||
}
|
||||
|
||||
newDate = newDate.date(Number.parseInt(cell.text, 10))
|
||||
|
||||
if (props.parsedValue && !Array.isArray(props.parsedValue)) {
|
||||
const dayOffset =
|
||||
((props.parsedValue.day() - firstDayOfWeek + 7) % 7) - 1
|
||||
const weekDate = props.parsedValue.subtract(dayOffset, 'day')
|
||||
return weekDate.isSame(newDate, 'day')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
ns,
|
||||
tbodyRef,
|
||||
currentCellRef,
|
||||
handleMouseMove,
|
||||
t,
|
||||
hasCurrent,
|
||||
rows,
|
||||
isSelectedCell,
|
||||
isWeekActive,
|
||||
getCellClasses,
|
||||
WEEKS,
|
||||
handleFocus,
|
||||
handlePickDate,
|
||||
focus,
|
||||
}
|
||||
},
|
||||
defineExpose({
|
||||
/**
|
||||
* @description focus on current cell
|
||||
*/
|
||||
focus,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -79,6 +79,7 @@ const lastRow = ref<number>()
|
||||
const lastColumn = ref<number>()
|
||||
const rows = computed<MonthCell[][]>(() => {
|
||||
const rows = tableRows.value
|
||||
|
||||
const now = dayjs().locale(lang.value).startOf('month')
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { default as dayjs, isDayjs } from 'dayjs'
|
||||
import { isArray } from '@element-plus/utils'
|
||||
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type { DateCell } from './date-picker.type'
|
||||
|
||||
type DayRange = [Dayjs | undefined, Dayjs | undefined]
|
||||
|
||||
@@ -40,3 +42,91 @@ export const getDefaultValue = (
|
||||
start = start.locale(lang)
|
||||
return [start, start.add(1, unit)]
|
||||
}
|
||||
|
||||
type Dimension = {
|
||||
row: number
|
||||
column: number
|
||||
}
|
||||
|
||||
type BuildPickerTableMetadata = {
|
||||
startDate?: Dayjs | null
|
||||
unit: 'month' | 'day'
|
||||
columnIndexOffset: number
|
||||
now: Dayjs
|
||||
nextEndDate: Dayjs | null
|
||||
relativeDateGetter: (index: number) => Dayjs
|
||||
setCellMetadata?: (
|
||||
cell: DateCell,
|
||||
dimension: { rowIndex: number; columnIndex: number }
|
||||
) => void
|
||||
setRowMetadata?: (row: DateCell[]) => void
|
||||
}
|
||||
|
||||
export const buildPickerTable = (
|
||||
dimension: Dimension,
|
||||
rows: DateCell[][],
|
||||
{
|
||||
columnIndexOffset,
|
||||
startDate,
|
||||
nextEndDate,
|
||||
now,
|
||||
unit,
|
||||
relativeDateGetter,
|
||||
setCellMetadata,
|
||||
setRowMetadata,
|
||||
}: BuildPickerTableMetadata
|
||||
) => {
|
||||
for (let rowIndex = 0; rowIndex < dimension.row; rowIndex++) {
|
||||
const row = rows[rowIndex]
|
||||
for (let columnIndex = 0; columnIndex < dimension.column; columnIndex++) {
|
||||
let cell = row[columnIndex + columnIndexOffset]
|
||||
if (!cell) {
|
||||
cell = {
|
||||
row: rowIndex,
|
||||
column: columnIndex,
|
||||
type: 'normal',
|
||||
inRange: false,
|
||||
start: false,
|
||||
end: false,
|
||||
}
|
||||
}
|
||||
const index = rowIndex * dimension.column + columnIndex
|
||||
const nextStartDate = relativeDateGetter(index)
|
||||
cell.dayjs = nextStartDate
|
||||
cell.date = nextStartDate.toDate()
|
||||
cell.timestamp = nextStartDate.valueOf()
|
||||
cell.type = 'normal'
|
||||
|
||||
cell.inRange =
|
||||
!!(
|
||||
startDate &&
|
||||
nextStartDate.isSameOrAfter(startDate, unit) &&
|
||||
nextEndDate &&
|
||||
nextStartDate.isSameOrBefore(nextEndDate, unit)
|
||||
) ||
|
||||
!!(
|
||||
startDate &&
|
||||
nextStartDate.isSameOrBefore(startDate, unit) &&
|
||||
nextEndDate &&
|
||||
nextStartDate.isSameOrAfter(nextEndDate, unit)
|
||||
)
|
||||
|
||||
if (startDate?.isSameOrAfter(nextEndDate)) {
|
||||
cell.start = !!nextEndDate && nextStartDate.isSame(nextEndDate, unit)
|
||||
cell.end = startDate && nextStartDate.isSame(startDate, unit)
|
||||
} else {
|
||||
cell.start = !!startDate && nextStartDate.isSame(startDate, unit)
|
||||
cell.end = !!nextEndDate && nextStartDate.isSame(nextEndDate, unit)
|
||||
}
|
||||
|
||||
const isToday = nextStartDate.isSame(now, unit)
|
||||
|
||||
if (isToday) {
|
||||
cell.type = 'today'
|
||||
}
|
||||
setCellMetadata?.(cell, { rowIndex, columnIndex })
|
||||
row[columnIndex + columnIndexOffset] = cell
|
||||
}
|
||||
setRowMetadata?.(row)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user