mirror of
https://github.com/element-plus/element-plus.git
synced 2025-02-17 11:49:41 +08:00
feat(components): [date-picker] add type years for year multiple select (#15980)
* feat(components): [date-picker] add type years for year multiple select * fix(components): [date-picker] fix footerVisible while type is not years * style(components): [date-picker] stand out selected item for year/month * style(components): [date-picker] remove the in-range background * docs(components): [date-picker] add 'years' for attribute type
This commit is contained in:
parent
5341166584
commit
42d1738463
@ -158,7 +158,7 @@ Note, date time locale (month name, first day of the week ...) are also configur
|
||||
| placeholder | placeholder in non-range mode | ^[string] | '' |
|
||||
| start-placeholder | placeholder for the start date in range mode | ^[string] | — |
|
||||
| end-placeholder | placeholder for the end date in range mode | ^[string] | — |
|
||||
| type | type of the picker | ^[enum]`'year' \| 'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
|
||||
| type | type of the picker | ^[enum]`'year' \| 'years' \|'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
|
||||
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD |
|
||||
| popper-class | custom class name for DatePicker's dropdown | ^[string] | — |
|
||||
| popper-options | Customized popper option see more at [popper.js](https://popper.js.org/docs/v2/) | ^[object]`Partial<PopperOptions>` | {} |
|
||||
|
@ -21,7 +21,9 @@
|
||||
@keydown.space.prevent.stop="handleYearTableClick"
|
||||
@keydown.enter.prevent.stop="handleYearTableClick"
|
||||
>
|
||||
<span class="cell">{{ startYear + i * 4 + j }}</span>
|
||||
<div>
|
||||
<span class="cell">{{ startYear + i * 4 + j }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td v-else />
|
||||
</template>
|
||||
@ -82,7 +84,8 @@ const isSelectedCell = (year: number) => {
|
||||
(year === startYear.value &&
|
||||
props.date.year() < startYear.value &&
|
||||
props.date.year() > startYear.value + 9) ||
|
||||
castArray(props.date).findIndex((date) => date.year() === year) >= 0
|
||||
castArray(props.date).findIndex((date) => date.year() === year) >= 0 ||
|
||||
castArray(props.parsedValue).findIndex((date) => date?.year() === year) >= 0
|
||||
)
|
||||
}
|
||||
|
||||
@ -92,7 +95,18 @@ const handleYearTableClick = (event: MouseEvent | KeyboardEvent) => {
|
||||
if (target && target.textContent) {
|
||||
if (hasClass(target, 'disabled')) return
|
||||
const year = target.textContent || target.innerText
|
||||
emit('pick', Number(year))
|
||||
if (props.selectionMode === 'years') {
|
||||
if (event.type === 'keydown') {
|
||||
emit('pick', castArray(props.parsedValue), false)
|
||||
return
|
||||
}
|
||||
const newValue = hasClass(target, 'current')
|
||||
? castArray(props.parsedValue).filter((d) => d?.year() !== Number(year))
|
||||
: castArray(props.parsedValue).concat([dayjs(year)])
|
||||
emit('pick', newValue)
|
||||
} else {
|
||||
emit('pick', Number(year))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,6 +142,7 @@
|
||||
<year-table
|
||||
v-if="currentView === 'year'"
|
||||
ref="currentViewRef"
|
||||
:selection-mode="selectionMode"
|
||||
:date="innerDate"
|
||||
:disabled-date="disabledDate"
|
||||
:parsed-value="parsedValue"
|
||||
@ -158,12 +159,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="footerVisible && currentView === 'date'"
|
||||
:class="ppNs.e('footer')"
|
||||
>
|
||||
<div v-show="footerVisible" :class="ppNs.e('footer')">
|
||||
<el-button
|
||||
v-show="selectionMode !== 'dates'"
|
||||
v-show="selectionMode !== 'dates' && selectionMode !== 'years'"
|
||||
text
|
||||
size="small"
|
||||
:class="ppNs.e('link-btn')"
|
||||
@ -228,6 +226,7 @@ import type {
|
||||
DateTableEmits,
|
||||
DatesPickerEmits,
|
||||
WeekPickerEmits,
|
||||
YearsPickerEmits,
|
||||
} from '../props/basic-date-table'
|
||||
|
||||
type DatePickType = PanelDatePickProps['type']
|
||||
@ -394,7 +393,7 @@ const handleShortcutClick = (shortcut: Shortcut) => {
|
||||
|
||||
const selectionMode = computed<DatePickType>(() => {
|
||||
const { type } = props
|
||||
if (['week', 'month', 'year', 'dates'].includes(type)) return type
|
||||
if (['week', 'month', 'year', 'years', 'dates'].includes(type)) return type
|
||||
return 'date' as DatePickType
|
||||
})
|
||||
|
||||
@ -421,12 +420,17 @@ const handleMonthPick = async (month: number) => {
|
||||
handlePanelChange('month')
|
||||
}
|
||||
|
||||
const handleYearPick = async (year: number) => {
|
||||
const handleYearPick = async (
|
||||
year: number | YearsPickerEmits,
|
||||
keepOpen?: boolean
|
||||
) => {
|
||||
if (selectionMode.value === 'year') {
|
||||
innerDate.value = innerDate.value.startOf('year').year(year)
|
||||
innerDate.value = innerDate.value.startOf('year').year(year as number)
|
||||
emit(innerDate.value, false)
|
||||
} else if (selectionMode.value === 'years') {
|
||||
emit(year as YearsPickerEmits, keepOpen ?? true)
|
||||
} else {
|
||||
innerDate.value = innerDate.value.year(year)
|
||||
innerDate.value = innerDate.value.year(year as number)
|
||||
currentView.value = 'month'
|
||||
if (['month', 'year', 'date', 'week'].includes(selectionMode.value)) {
|
||||
emit(innerDate.value, true)
|
||||
@ -448,7 +452,11 @@ const showTime = computed(
|
||||
)
|
||||
|
||||
const footerVisible = computed(() => {
|
||||
return showTime.value || selectionMode.value === 'dates'
|
||||
const showDateFooter = showTime.value || selectionMode.value === 'dates'
|
||||
const showYearFooter = selectionMode.value === 'years'
|
||||
const isDateView = currentView.value === 'date'
|
||||
const isYearView = currentView.value === 'year'
|
||||
return (showDateFooter && isDateView) || (showYearFooter && isYearView)
|
||||
})
|
||||
|
||||
const disabledConfirm = computed(() => {
|
||||
@ -460,7 +468,7 @@ const disabledConfirm = computed(() => {
|
||||
return disabledDate(props.parsedValue.toDate())
|
||||
})
|
||||
const onConfirm = () => {
|
||||
if (selectionMode.value === 'dates') {
|
||||
if (selectionMode.value === 'dates' || selectionMode.value === 'years') {
|
||||
emit(props.parsedValue as Dayjs[])
|
||||
} else {
|
||||
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
|
||||
@ -585,10 +593,9 @@ const isValidValue = (date: unknown) => {
|
||||
}
|
||||
|
||||
const formatToString = (value: Dayjs | Dayjs[]) => {
|
||||
if (selectionMode.value === 'dates') {
|
||||
return (value as Dayjs[]).map((_) => _.format(props.format))
|
||||
}
|
||||
return (value as Dayjs).format(props.format)
|
||||
return Array.isArray(value)
|
||||
? (value as Dayjs[]).map((_) => _.format(props.format))
|
||||
: (value as Dayjs).format(props.format)
|
||||
}
|
||||
|
||||
const parseUserInput = (value: Dayjs) => {
|
||||
@ -730,6 +737,9 @@ watch(
|
||||
if (['month', 'year'].includes(val)) {
|
||||
currentView.value = val
|
||||
return
|
||||
} else if (val === 'years') {
|
||||
currentView.value = 'year'
|
||||
return
|
||||
}
|
||||
currentView.value = 'date'
|
||||
},
|
||||
@ -757,7 +767,8 @@ watch(
|
||||
() => props.parsedValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (selectionMode.value === 'dates') return
|
||||
if (selectionMode.value === 'dates' || selectionMode.value === 'years')
|
||||
return
|
||||
if (Array.isArray(val)) return
|
||||
innerDate.value = val
|
||||
} else {
|
||||
|
@ -2,6 +2,7 @@ import type { Dayjs } from 'dayjs'
|
||||
|
||||
export declare type IDatePickerType =
|
||||
| 'year'
|
||||
| 'years'
|
||||
| 'month'
|
||||
| 'date'
|
||||
| 'dates'
|
||||
|
@ -21,6 +21,7 @@ export type BasicDateTableEmits = typeof basicDateTableEmits
|
||||
export type RangePickerEmits = { minDate: Dayjs; maxDate: null }
|
||||
export type DatePickerEmits = Dayjs
|
||||
export type DatesPickerEmits = Dayjs[]
|
||||
export type YearsPickerEmits = Dayjs[]
|
||||
export type WeekPickerEmits = {
|
||||
year: number
|
||||
week: number
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { buildProps } from '@element-plus/utils'
|
||||
import { datePickerSharedProps } from './shared'
|
||||
import { datePickerSharedProps, selectionModeWithDefault } from './shared'
|
||||
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
|
||||
@ -9,6 +9,7 @@ export const basicYearTableProps = buildProps({
|
||||
date,
|
||||
disabledDate,
|
||||
parsedValue,
|
||||
})
|
||||
selectionMode: selectionModeWithDefault('year'),
|
||||
} as const)
|
||||
|
||||
export type BasicYearTableProps = ExtractPropTypes<typeof basicYearTableProps>
|
||||
|
@ -5,7 +5,15 @@ import type { ExtractPropTypes } from 'vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type { DatePickType } from '@element-plus/constants'
|
||||
|
||||
const selectionModes = ['date', 'dates', 'year', 'month', 'week', 'range']
|
||||
const selectionModes = [
|
||||
'date',
|
||||
'dates',
|
||||
'year',
|
||||
'years',
|
||||
'month',
|
||||
'week',
|
||||
'range',
|
||||
]
|
||||
|
||||
export type RangeState = {
|
||||
endDate: null | Dayjs
|
||||
|
@ -33,7 +33,13 @@
|
||||
:placeholder="placeholder"
|
||||
:class="[nsDate.b('editor'), nsDate.bm('editor', type), $attrs.class]"
|
||||
:style="$attrs.style"
|
||||
:readonly="!editable || readonly || isDatesPicker || type === 'week'"
|
||||
:readonly="
|
||||
!editable ||
|
||||
readonly ||
|
||||
isDatesPicker ||
|
||||
isYearsPicker ||
|
||||
type === 'week'
|
||||
"
|
||||
:label="label"
|
||||
:tabindex="tabindex"
|
||||
:validate-event="false"
|
||||
@ -470,7 +476,7 @@ const displayValue = computed<UserInput>(() => {
|
||||
if (!isTimePicker.value && valueIsEmpty.value) return ''
|
||||
if (!pickerVisible.value && valueIsEmpty.value) return ''
|
||||
if (formattedValue) {
|
||||
return isDatesPicker.value
|
||||
return isDatesPicker.value || isYearsPicker.value
|
||||
? (formattedValue as Array<string>).join(', ')
|
||||
: formattedValue
|
||||
}
|
||||
@ -483,6 +489,8 @@ const isTimePicker = computed(() => props.type.startsWith('time'))
|
||||
|
||||
const isDatesPicker = computed(() => props.type === 'dates')
|
||||
|
||||
const isYearsPicker = computed(() => props.type === 'years')
|
||||
|
||||
const triggerIcon = computed(
|
||||
() => props.prefixIcon || (isTimeLikePicker.value ? Clock : Calendar)
|
||||
)
|
||||
|
@ -7,6 +7,7 @@ export const DEFAULT_FORMATS_DATEPICKER = {
|
||||
dates: DEFAULT_FORMATS_DATE,
|
||||
week: 'gggg[w]ww',
|
||||
year: 'YYYY',
|
||||
years: 'YYYY',
|
||||
month: 'YYYY-MM',
|
||||
datetime: `${DEFAULT_FORMATS_DATE} ${DEFAULT_FORMATS_TIME}`,
|
||||
monthrange: 'YYYY-MM',
|
||||
|
@ -1,5 +1,6 @@
|
||||
export const datePickTypes = [
|
||||
'year',
|
||||
'years',
|
||||
'month',
|
||||
'date',
|
||||
'dates',
|
||||
|
@ -130,11 +130,7 @@
|
||||
&.selected .#{$namespace}-date-table-cell {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
background-color: getCssVar('datepicker-inrange-bg-color');
|
||||
border-radius: 15px;
|
||||
&:hover {
|
||||
background-color: getCssVar('datepicker-inrange-hover-bg-color');
|
||||
}
|
||||
}
|
||||
|
||||
&.selected .#{$namespace}-date-table-cell__text {
|
||||
|
@ -7,9 +7,11 @@
|
||||
border-collapse: collapse;
|
||||
|
||||
td {
|
||||
width: 68px;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
& div {
|
||||
height: 48px;
|
||||
padding: 6px 0;
|
||||
@ -37,13 +39,16 @@
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 60px;
|
||||
width: 54px;
|
||||
height: 36px;
|
||||
display: block;
|
||||
line-height: 36px;
|
||||
color: getCssVar('datepicker-text-color');
|
||||
margin: 0 auto;
|
||||
border-radius: 18px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
&:hover {
|
||||
color: getCssVar('datepicker-hover-text-color');
|
||||
}
|
||||
@ -67,23 +72,33 @@
|
||||
}
|
||||
|
||||
&.start-date div {
|
||||
margin-left: 3px;
|
||||
border-top-left-radius: 24px;
|
||||
border-bottom-left-radius: 24px;
|
||||
}
|
||||
|
||||
&.end-date div {
|
||||
margin-right: 3px;
|
||||
border-top-right-radius: 24px;
|
||||
border-bottom-right-radius: 24px;
|
||||
}
|
||||
|
||||
&.current:not(.disabled) div {
|
||||
border-radius: 24px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
&.current:not(.disabled) .cell {
|
||||
color: getCssVar('datepicker-active-color');
|
||||
color: $color-white;
|
||||
background-color: getCssVar('datepicker-active-color');
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
.cell {
|
||||
outline: 2px solid getCssVar('datepicker-active-color');
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,17 @@
|
||||
}
|
||||
|
||||
td {
|
||||
width: 68px;
|
||||
text-align: center;
|
||||
padding: 20px 3px;
|
||||
padding: 8px 0px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
& div {
|
||||
height: 48px;
|
||||
padding: 6px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&.today {
|
||||
.cell {
|
||||
@ -33,27 +41,38 @@
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 48px;
|
||||
width: 54px;
|
||||
height: 36px;
|
||||
display: block;
|
||||
line-height: 36px;
|
||||
color: getCssVar('datepicker-text-color');
|
||||
border-radius: 18px;
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
&:hover {
|
||||
color: getCssVar('datepicker-hover-text-color');
|
||||
}
|
||||
}
|
||||
|
||||
&.current:not(.disabled) div {
|
||||
border-radius: 24px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
&.current:not(.disabled) .cell {
|
||||
color: getCssVar('datepicker-active-color');
|
||||
color: $color-white;
|
||||
background-color: getCssVar('datepicker-active-color');
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
.cell {
|
||||
outline: 2px solid getCssVar('datepicker-active-color');
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user