refactor(components): [date-picker] basic-month-table (#8053)

* Refactor `basic-month-table` to script setup.

Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com>
This commit is contained in:
Jeremy
2022-06-04 21:40:52 +08:00
committed by GitHub
parent 074c94ff3b
commit 4662a37bbe

View File

@@ -11,7 +11,7 @@
<td
v-for="(cell, key_) in row"
:key="key_"
:ref="(el) => isSelectedCell(cell) && (currentCellRef = el)"
:ref="(el) => isSelectedCell(cell) && (currentCellRef = el as HTMLElement)"
:class="getCellStyle(cell)"
:aria-selected="`${isSelectedCell(cell)}`"
:aria-label="t(`el.datepicker.month${+cell.text + 1}`)"
@@ -30,224 +30,229 @@
</table>
</template>
<script lang="ts">
import { computed, defineComponent, nextTick, ref, watch } from 'vue'
<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue'
import dayjs from 'dayjs'
import { useLocale, useNamespace } from '@element-plus/hooks'
import { rangeArr } from '@element-plus/components/time-picker'
import { castArray, hasClass } from '@element-plus/utils'
import { basicMonthTableProps } from '../props/basic-month-table'
type MonthCell = {
column: number
row: number
disabled: boolean
start: boolean
end: boolean
text: number
type: 'normal' | 'today'
inRange: boolean
}
const datesInMonth = (year: number, month: number, lang: string) => {
const firstDay = dayjs().locale(lang).startOf('month').month(month).year(year)
const numOfDays = firstDay.daysInMonth()
return rangeArr(numOfDays).map((n) => firstDay.add(n, 'day').toDate())
}
export default defineComponent({
props: basicMonthTableProps,
const props = defineProps(basicMonthTableProps)
const emit = defineEmits(['changerange', 'pick', 'select'])
emits: ['changerange', 'pick', 'select'],
expose: ['focus'],
const ns = useNamespace('month-table')
setup(props, ctx) {
const ns = useNamespace('month-table')
const { t, lang } = useLocale()
const tbodyRef = ref<HTMLElement>()
const currentCellRef = ref<HTMLElement>()
const months = ref(
props.date
.locale('en')
.localeData()
.monthsShort()
.map((_) => _.toLowerCase())
)
const tableRows = ref<MonthCell[][]>([
[] as MonthCell[],
[] as MonthCell[],
[] as MonthCell[],
])
const lastRow = ref<number>()
const lastColumn = ref<number>()
const rows = computed<MonthCell[][]>(() => {
const rows = tableRows.value
const now = dayjs().locale(lang.value).startOf('month')
const { t, lang } = useLocale()
const tbodyRef = ref<HTMLElement>()
const currentCellRef = ref<HTMLElement>()
const months = ref(
props.date
.locale('en')
.localeData()
.monthsShort()
.map((_) => _.toLowerCase())
)
const tableRows = ref([[], [], []])
const lastRow = ref(null)
const lastColumn = ref(null)
const rows = computed(() => {
// TODO: refactory rows / getCellClasses
const rows = tableRows.value
const now = dayjs().locale(lang.value).startOf('month')
for (let i = 0; i < 3; i++) {
const row = rows[i]
for (let j = 0; j < 4; j++) {
const cell = (row[j] ||= {
row: i,
column: j,
type: 'normal',
inRange: false,
start: false,
end: false,
text: -1,
disabled: false,
})
for (let i = 0; i < 3; i++) {
const row = rows[i]
for (let j = 0; j < 4; j++) {
let cell = row[j]
if (!cell) {
cell = {
row: i,
column: j,
type: 'normal',
inRange: false,
start: false,
end: false,
}
}
cell.type = 'normal'
cell.type = 'normal'
const index = i * 4 + j
const calTime = props.date.startOf('year').month(index)
const index = i * 4 + j
const calTime = props.date.startOf('year').month(index)
const calEndDate =
props.rangeState.endDate ||
props.maxDate ||
(props.rangeState.selecting && props.minDate) ||
null
const calEndDate =
props.rangeState.endDate ||
props.maxDate ||
(props.rangeState.selecting && props.minDate)
cell.inRange =
!!(
props.minDate &&
calTime.isSameOrAfter(props.minDate, 'month') &&
calEndDate &&
calTime.isSameOrBefore(calEndDate, 'month')
) ||
!!(
props.minDate &&
calTime.isSameOrBefore(props.minDate, 'month') &&
calEndDate &&
calTime.isSameOrAfter(calEndDate, 'month')
)
cell.inRange =
(props.minDate &&
calTime.isSameOrAfter(props.minDate, 'month') &&
calEndDate &&
calTime.isSameOrBefore(calEndDate, 'month')) ||
(props.minDate &&
calTime.isSameOrBefore(props.minDate, 'month') &&
calEndDate &&
calTime.isSameOrAfter(calEndDate, 'month'))
if (props.minDate?.isSameOrAfter(calEndDate)) {
cell.start = calEndDate && calTime.isSame(calEndDate, 'month')
cell.end = props.minDate && calTime.isSame(props.minDate, 'month')
} else {
cell.start = props.minDate && calTime.isSame(props.minDate, 'month')
cell.end = calEndDate && calTime.isSame(calEndDate, 'month')
}
const isToday = now.isSame(calTime)
if (isToday) {
cell.type = 'today'
}
cell.text = index
const cellDate = calTime.toDate()
cell.disabled = props.disabledDate && props.disabledDate(cellDate)
row[j] = cell
}
if (props.minDate?.isSameOrAfter(calEndDate)) {
cell.start = !!(calEndDate && calTime.isSame(calEndDate, 'month'))
cell.end = props.minDate && calTime.isSame(props.minDate, 'month')
} else {
cell.start = !!(props.minDate && calTime.isSame(props.minDate, 'month'))
cell.end = !!(calEndDate && calTime.isSame(calEndDate, 'month'))
}
return rows
const isToday = now.isSame(calTime)
if (isToday) {
cell.type = 'today'
}
cell.text = index
cell.disabled = props.disabledDate?.(calTime.toDate()) || false
}
}
return rows
})
const focus = () => {
currentCellRef.value?.focus()
}
const getCellStyle = (cell: MonthCell) => {
const style = {} as any
const year = props.date.year()
const today = new Date()
const month = cell.text
style.disabled = props.disabledDate
? datesInMonth(year, month, lang.value).every(props.disabledDate)
: false
style.current =
castArray(props.parsedValue).findIndex(
(date) =>
dayjs.isDayjs(date) && date.year() === year && date.month() === month
) >= 0
style.today = today.getFullYear() === year && today.getMonth() === month
if (cell.inRange) {
style['in-range'] = true
if (cell.start) {
style['start-date'] = true
}
if (cell.end) {
style['end-date'] = true
}
}
return style
}
const isSelectedCell = (cell: MonthCell) => {
const year = props.date.year()
const month = cell.text
return (
castArray(props.date).findIndex(
(date) => date.year() === year && date.month() === month
) >= 0
)
}
const handleMouseMove = (event: MouseEvent) => {
if (!props.rangeState.selecting) return
let target = event.target as HTMLElement
if (target.tagName === 'A') {
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
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: props.date.startOf('year').month(row * 4 + column),
})
watch(
() => props.date,
async () => {
if (tbodyRef.value?.contains(document.activeElement)) {
await nextTick()
currentCellRef.value?.focus()
}
}
}
const handleMonthTableClick = (event: MouseEvent | KeyboardEvent) => {
const target = (event.target as HTMLElement)?.closest(
'td'
) as HTMLTableCellElement
if (target?.tagName !== 'TD') return
if (hasClass(target, 'disabled')) return
const column = target.cellIndex
const row = (target.parentNode as HTMLTableRowElement).rowIndex
const month = row * 4 + column
const newDate = props.date.startOf('year').month(month)
if (props.selectionMode === 'range') {
if (!props.rangeState.selecting) {
emit('pick', { minDate: newDate, maxDate: null })
emit('select', true)
} else {
if (props.minDate && newDate >= props.minDate) {
emit('pick', { minDate: props.minDate, maxDate: newDate })
} else {
emit('pick', { minDate: newDate, maxDate: props.minDate })
}
)
emit('select', false)
}
} else {
emit('pick', month)
}
}
const focus = () => {
watch(
() => props.date,
async () => {
if (tbodyRef.value?.contains(document.activeElement)) {
await nextTick()
currentCellRef.value?.focus()
}
}
)
const getCellStyle = (cell) => {
const style = {} as any
const year = props.date.year()
const today = new Date()
const month = cell.text
style.disabled = props.disabledDate
? datesInMonth(year, month, lang.value).every(props.disabledDate)
: false
style.current =
castArray(props.parsedValue).findIndex(
(date) => date.year() === year && date.month() === month
) >= 0
style.today = today.getFullYear() === year && today.getMonth() === month
if (cell.inRange) {
style['in-range'] = true
if (cell.start) {
style['start-date'] = true
}
if (cell.end) {
style['end-date'] = true
}
}
return style
}
const isSelectedCell = (cell) => {
const year = props.date.year()
const month = cell.text
return (
castArray(props.date).findIndex(
(date) => date.year() === year && date.month() === month
) >= 0
)
}
const handleMouseMove = (event) => {
if (!props.rangeState.selecting) return
let target = event.target
if (target.tagName === 'A') {
target = target.parentNode.parentNode
}
if (target.tagName === 'DIV') {
target = target.parentNode
}
if (target.tagName !== 'TD') return
const row = target.parentNode.rowIndex
const column = target.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
ctx.emit('changerange', {
selecting: true,
endDate: props.date.startOf('year').month(row * 4 + column),
})
}
}
const handleMonthTableClick = (event) => {
let target = event.target
target = target?.closest('td')
if (target?.tagName !== 'TD') return
if (hasClass(target, 'disabled')) return
const column = target.cellIndex
const row = target.parentNode.rowIndex
const month = row * 4 + column
const newDate = props.date.startOf('year').month(month)
if (props.selectionMode === 'range') {
if (!props.rangeState.selecting) {
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 {
ctx.emit('pick', month)
}
}
return {
ns,
tbodyRef,
currentCellRef,
handleMouseMove,
handleMonthTableClick,
focus,
isSelectedCell,
rows,
getCellStyle,
t,
months,
}
},
defineExpose({
/**
* @description focus current cell
*/
focus,
})
</script>