mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-12-21 04:50:14 +08:00
feat(date-picker): support month type (#1202)
* feat:n-input Support hidden password * feat(form): support require-mark-placement(#171) * Revert "feat(form): support require-mark-placement(#171)" This reverts commit0627777693
. * Revert "feat:n-input Support hidden password" This reverts commitea6491783d
. * feat(date-picker): wip * feat(date-picker): wip * feat(date-picker): support VirtualList * wip * 添加 NScrollbar * Update use-calendar.ts * changelog * 抽取公共代码 * 完善代码
This commit is contained in:
parent
bacb7a3fc7
commit
3ea326e7a2
@ -52,6 +52,9 @@
|
|||||||
### i18n
|
### i18n
|
||||||
|
|
||||||
- Add ukUA locale.
|
- Add ukUA locale.
|
||||||
|
### Feats
|
||||||
|
|
||||||
|
- `n-date-picker`'s `type` prop support `month` option.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
@ -52,6 +52,9 @@
|
|||||||
### i18n
|
### i18n
|
||||||
|
|
||||||
- 新增 ukUA locale
|
- 新增 ukUA locale
|
||||||
|
### Feats
|
||||||
|
|
||||||
|
- `n-date-picker` 属性 `type` 支持 `month` 选项
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ date
|
|||||||
datetime
|
datetime
|
||||||
daterange
|
daterange
|
||||||
datetimerange
|
datetimerange
|
||||||
|
month
|
||||||
size
|
size
|
||||||
disabled
|
disabled
|
||||||
disabled-time
|
disabled-time
|
||||||
@ -33,7 +34,7 @@ update-on-close
|
|||||||
| input-readonly | `boolean` | `false` | Set the `readonly` attribute of the input (avoids virtual keyboard on touch devices). |
|
| input-readonly | `boolean` | `false` | Set the `readonly` attribute of the input (avoids virtual keyboard on touch devices). |
|
||||||
| shortcuts | `Record<string, number \| [number, number]>` | `undefined` | Shortcut button customizations. |
|
| shortcuts | `Record<string, number \| [number, number]>` | `undefined` | Shortcut button customizations. |
|
||||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Date picker size. |
|
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Date picker size. |
|
||||||
| type | `'date' \| 'datetime' \| 'daterange' \|'datetimerange'` | `'date'` | Date picker type. |
|
| type | `'date' \| 'datetime' \| 'daterange' \|'datetimerange' \|'month'` | `'date'` | Date picker type. |
|
||||||
| value | `number \| [number, number] \| null` | `undefined` | Value of the date picker when being manually set. |
|
| value | `number \| [number, number] \| null` | `undefined` | Value of the date picker when being manually set. |
|
||||||
| on-blur | `() => void` | `undefined` | On blur callback. |
|
| on-blur | `() => void` | `undefined` | On blur callback. |
|
||||||
| on-focus | `() => void` | `undefined` | On focus callback. |
|
| on-focus | `() => void` | `undefined` | On focus callback. |
|
||||||
|
18
src/date-picker/demos/enUS/month.demo.md
Normal file
18
src/date-picker/demos/enUS/month.demo.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Month
|
||||||
|
|
||||||
|
```html
|
||||||
|
<n-date-picker v-model:value="timestamp" type="month" clearable />
|
||||||
|
<pre>{{ JSON.stringify(timestamp) }}</pre>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineComponent, ref } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup () {
|
||||||
|
return {
|
||||||
|
timestamp: ref(1183135260000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
@ -9,6 +9,7 @@ date
|
|||||||
datetime
|
datetime
|
||||||
daterange
|
daterange
|
||||||
datetimerange
|
datetimerange
|
||||||
|
month
|
||||||
size
|
size
|
||||||
disabled
|
disabled
|
||||||
disabled-time
|
disabled-time
|
||||||
@ -33,7 +34,7 @@ update-on-close
|
|||||||
| input-readonly | `boolean` | `false` | 设置输入框为只读(避免在移动设备上打开虚拟键盘) |
|
| input-readonly | `boolean` | `false` | 设置输入框为只读(避免在移动设备上打开虚拟键盘) |
|
||||||
| shortcuts | `Record<string, number \| [number, number]>` | `undefined` | 自定义快捷按钮 |
|
| shortcuts | `Record<string, number \| [number, number]>` | `undefined` | 自定义快捷按钮 |
|
||||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 尺寸 |
|
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 尺寸 |
|
||||||
| type | `'date' \| 'datetime' \| 'daterange' \|'datetimerange'` | `'date'` | Date Picker 的类型 |
|
| type | `'date' \| 'datetime' \| 'daterange' \|'datetimerange' \|'month'` | `'date'` | Date Picker 的类型 |
|
||||||
| value | `number \| [number, number] \| null` | `undefined` | Date Picker 的值 |
|
| value | `number \| [number, number] \| null` | `undefined` | Date Picker 的值 |
|
||||||
| on-blur | `() => void` | `undefined` | 用户 blur 时执行的回调 |
|
| on-blur | `() => void` | `undefined` | 用户 blur 时执行的回调 |
|
||||||
| on-focus | `() => void` | `undefined` | 用户 focus 时执行的回调 |
|
| on-focus | `() => void` | `undefined` | 用户 focus 时执行的回调 |
|
||||||
|
18
src/date-picker/demos/zhCN/month.demo.md
Normal file
18
src/date-picker/demos/zhCN/month.demo.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 月份
|
||||||
|
|
||||||
|
```html
|
||||||
|
<n-date-picker v-model:value="timestamp" type="month" clearable />
|
||||||
|
<pre>{{ JSON.stringify(timestamp) }}</pre>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineComponent, ref } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup () {
|
||||||
|
return {
|
||||||
|
timestamp: ref(1183135260000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
@ -12,11 +12,12 @@ import {
|
|||||||
CSSProperties,
|
CSSProperties,
|
||||||
toRef,
|
toRef,
|
||||||
Ref,
|
Ref,
|
||||||
watchEffect
|
watchEffect,
|
||||||
|
nextTick
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { VBinder, VTarget, VFollower, FollowerPlacement } from 'vueuc'
|
import { VBinder, VTarget, VFollower, FollowerPlacement } from 'vueuc'
|
||||||
import { clickoutside } from 'vdirs'
|
import { clickoutside } from 'vdirs'
|
||||||
import { format, getTime, isValid } from 'date-fns'
|
import { format, getTime, isValid, getYear } from 'date-fns'
|
||||||
import { useIsMounted, useMergedState } from 'vooks'
|
import { useIsMounted, useMergedState } from 'vooks'
|
||||||
import { happensIn } from 'seemly'
|
import { happensIn } from 'seemly'
|
||||||
import { InputInst, InputProps, NInput } from '../../input'
|
import { InputInst, InputProps, NInput } from '../../input'
|
||||||
@ -37,6 +38,7 @@ import DatetimePanel from './panel/datetime'
|
|||||||
import DatetimerangePanel from './panel/datetimerange'
|
import DatetimerangePanel from './panel/datetimerange'
|
||||||
import DatePanel from './panel/date'
|
import DatePanel from './panel/date'
|
||||||
import DaterangePanel from './panel/daterange'
|
import DaterangePanel from './panel/daterange'
|
||||||
|
import MonthPanel from './panel/month'
|
||||||
import style from './styles/index.cssr'
|
import style from './styles/index.cssr'
|
||||||
import { DatePickerTheme } from '../styles/light'
|
import { DatePickerTheme } from '../styles/light'
|
||||||
import {
|
import {
|
||||||
@ -56,7 +58,8 @@ const DATE_FORMAT = {
|
|||||||
date: 'yyyy-MM-dd',
|
date: 'yyyy-MM-dd',
|
||||||
datetime: 'yyyy-MM-dd HH:mm:ss',
|
datetime: 'yyyy-MM-dd HH:mm:ss',
|
||||||
daterange: 'yyyy-MM-dd',
|
daterange: 'yyyy-MM-dd',
|
||||||
datetimerange: 'yyyy-MM-dd HH:mm:ss'
|
datetimerange: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
month: 'yyyy-MM'
|
||||||
}
|
}
|
||||||
|
|
||||||
const datePickerProps = {
|
const datePickerProps = {
|
||||||
@ -90,7 +93,7 @@ const datePickerProps = {
|
|||||||
size: String as PropType<'small' | 'medium' | 'large'>,
|
size: String as PropType<'small' | 'medium' | 'large'>,
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<
|
type: String as PropType<
|
||||||
'date' | 'datetime' | 'daterange' | 'datetimerange'
|
'date' | 'datetime' | 'daterange' | 'datetimerange' | 'month'
|
||||||
>,
|
>,
|
||||||
default: 'date'
|
default: 'date'
|
||||||
},
|
},
|
||||||
@ -335,6 +338,24 @@ export default defineComponent({
|
|||||||
disableUpdateOnClose
|
disableUpdateOnClose
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function scrollTimer (): void {
|
||||||
|
if (!panelInstRef.value) return
|
||||||
|
const { monthScrollRef, yearScrollRef } = panelInstRef.value
|
||||||
|
if (monthScrollRef) {
|
||||||
|
const month = monthScrollRef.contentRef?.querySelector(
|
||||||
|
'[data-selected]'
|
||||||
|
) as HTMLElement
|
||||||
|
if (month) {
|
||||||
|
monthScrollRef.scrollTo({ top: month.offsetTop })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (yearScrollRef) {
|
||||||
|
if (mergedValueRef.value) {
|
||||||
|
const yearIndex = getYear(mergedValueRef.value as number) - 1900
|
||||||
|
yearScrollRef.scrollTo({ top: yearIndex * 40 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// --- Panel update value
|
// --- Panel update value
|
||||||
function handlePanelUpdateValue (
|
function handlePanelUpdateValue (
|
||||||
value: Value | null,
|
value: Value | null,
|
||||||
@ -476,6 +497,9 @@ export default defineComponent({
|
|||||||
function openCalendar (): void {
|
function openCalendar (): void {
|
||||||
if (mergedDisabledRef.value || mergedShowRef.value) return
|
if (mergedDisabledRef.value || mergedShowRef.value) return
|
||||||
doUpdateShow(true)
|
doUpdateShow(true)
|
||||||
|
if (props.type === 'month') {
|
||||||
|
void nextTick(scrollTimer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function closeCalendar ({
|
function closeCalendar ({
|
||||||
returnFocus,
|
returnFocus,
|
||||||
@ -616,6 +640,8 @@ export default defineComponent({
|
|||||||
itemSize,
|
itemSize,
|
||||||
itemCellWidth,
|
itemCellWidth,
|
||||||
itemCellHeight,
|
itemCellHeight,
|
||||||
|
itemMonthCellWidth,
|
||||||
|
itemMonthCellHeight,
|
||||||
calendarTitlePadding,
|
calendarTitlePadding,
|
||||||
calendarTitleHeight,
|
calendarTitleHeight,
|
||||||
calendarDaysHeight,
|
calendarDaysHeight,
|
||||||
@ -669,6 +695,8 @@ export default defineComponent({
|
|||||||
'--item-size': itemSize,
|
'--item-size': itemSize,
|
||||||
'--item-cell-width': itemCellWidth,
|
'--item-cell-width': itemCellWidth,
|
||||||
'--item-cell-height': itemCellHeight,
|
'--item-cell-height': itemCellHeight,
|
||||||
|
'--item-month-cell-width': itemMonthCellWidth,
|
||||||
|
'--item-month-cell-height': itemMonthCellHeight,
|
||||||
'--item-text-color': itemTextColor,
|
'--item-text-color': itemTextColor,
|
||||||
'--item-color-included': itemColorIncluded,
|
'--item-color-included': itemColorIncluded,
|
||||||
'--item-color-disabled': itemColorDisabled,
|
'--item-color-disabled': itemColorDisabled,
|
||||||
@ -828,6 +856,8 @@ export default defineComponent({
|
|||||||
<DaterangePanel {...commonPanelProps} />
|
<DaterangePanel {...commonPanelProps} />
|
||||||
) : this.type === 'datetimerange' ? (
|
) : this.type === 'datetimerange' ? (
|
||||||
<DatetimerangePanel {...commonPanelProps} />
|
<DatetimerangePanel {...commonPanelProps} />
|
||||||
|
) : this.type === 'month' ? (
|
||||||
|
<MonthPanel {...commonPanelProps} />
|
||||||
) : (
|
) : (
|
||||||
<DatePanel {...commonPanelProps} />
|
<DatePanel {...commonPanelProps} />
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { InjectionKey, Ref, Slots } from 'vue'
|
import { InjectionKey, Ref, Slots } from 'vue'
|
||||||
|
import { VirtualListInst } from 'vueuc'
|
||||||
import { NLocale, NDateLocale } from '../../locales'
|
import { NLocale, NDateLocale } from '../../locales'
|
||||||
|
import { ScrollbarInst } from '../../scrollbar'
|
||||||
import {
|
import {
|
||||||
IsHourDisabled,
|
IsHourDisabled,
|
||||||
IsMinuteDisabled,
|
IsMinuteDisabled,
|
||||||
@ -41,6 +43,8 @@ export type OnClose = (disableUpdateOnClose: boolean) => void
|
|||||||
|
|
||||||
export interface PanelRef {
|
export interface PanelRef {
|
||||||
$el: HTMLElement
|
$el: HTMLElement
|
||||||
|
monthScrollRef: ScrollbarInst | null
|
||||||
|
yearScrollRef: VirtualListInst | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 is Monday
|
// 0 is Monday
|
||||||
|
190
src/date-picker/src/panel/month.tsx
Normal file
190
src/date-picker/src/panel/month.tsx
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import { h, defineComponent, renderSlot, watchEffect, VNode } from 'vue'
|
||||||
|
import { NButton, NxButton } from '../../../button'
|
||||||
|
import { NBaseFocusDetector } from '../../../_internal'
|
||||||
|
import { useCalendar } from './use-calendar'
|
||||||
|
import { warnOnce } from '../../../_utils'
|
||||||
|
import { NScrollbar } from '../../../scrollbar'
|
||||||
|
import { VirtualList } from 'vueuc'
|
||||||
|
import { MonthItem, YearItem } from '../utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Month Panel
|
||||||
|
* Update picker value on:
|
||||||
|
* 1. item click
|
||||||
|
* 2. clear click
|
||||||
|
*/
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MonthPanel',
|
||||||
|
props: useCalendar.props,
|
||||||
|
setup (props) {
|
||||||
|
if (__DEV__) {
|
||||||
|
watchEffect(() => {
|
||||||
|
if (props.actions?.includes('confirm')) {
|
||||||
|
warnOnce(
|
||||||
|
'date-picker',
|
||||||
|
'The `confirm` action is not supported for n-date-picker of `date` type'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const useCalendarRef = useCalendar(props, 'month')
|
||||||
|
const itemRenderer = (
|
||||||
|
item: YearItem | MonthItem,
|
||||||
|
i: number,
|
||||||
|
mergedClsPrefix: string
|
||||||
|
): VNode => {
|
||||||
|
const { mergedIsDateDisabled, handleDateClick } = useCalendarRef
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-n-date
|
||||||
|
key={i}
|
||||||
|
data-selected={item.selected ? '' : null}
|
||||||
|
class={[
|
||||||
|
`${mergedClsPrefix}-date-panel-month-calendar__picker-col-item`,
|
||||||
|
{
|
||||||
|
[`${mergedClsPrefix}-date-panel-month-calendar__picker-col-item--current`]:
|
||||||
|
item.type === 'month'
|
||||||
|
? item.inCurrentMonth
|
||||||
|
: item.inCurrentYear,
|
||||||
|
[`${mergedClsPrefix}-date-panel-month-calendar__picker-col-item--selected`]:
|
||||||
|
item.selected,
|
||||||
|
[`${mergedClsPrefix}-date-panel-month-calendar__picker-col-item--disabled`]:
|
||||||
|
mergedIsDateDisabled(item.ts)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onClick={() => handleDateClick(item)}
|
||||||
|
>
|
||||||
|
{item.type === 'month' ? item.formattedText : item.dateObject.year}
|
||||||
|
{(
|
||||||
|
item.type === 'month' ? item.inCurrentMonth : item.inCurrentYear
|
||||||
|
) ? (
|
||||||
|
<div class={`${mergedClsPrefix}-date-panel-month-calendar__sup`} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { ...useCalendarRef, itemRenderer }
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const { mergedClsPrefix, mergedTheme, shortcuts, itemRenderer } = this
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref="selfRef"
|
||||||
|
tabindex={0}
|
||||||
|
class={`${mergedClsPrefix}-date-panel ${mergedClsPrefix}-date-panel--month`}
|
||||||
|
onFocus={this.handlePanelFocus}
|
||||||
|
onKeydown={this.handlePanelKeyDown}
|
||||||
|
>
|
||||||
|
<div class={`${mergedClsPrefix}-date-panel-month-calendar`}>
|
||||||
|
<NScrollbar
|
||||||
|
ref="scrollbarInstRef"
|
||||||
|
class={`${mergedClsPrefix}-date-panel-month-calendar__picker-col`}
|
||||||
|
// theme={mergedTheme.peers.Scrollbar}
|
||||||
|
// themeOverrides={mergedTheme.peerOverrides.Scrollbar}
|
||||||
|
container={this.virtualListContainer}
|
||||||
|
content={this.virtualListContent}
|
||||||
|
horizontalRailStyle={{ zIndex: 3 }}
|
||||||
|
verticalRailStyle={{ zIndex: 3 }}
|
||||||
|
// internalOnUpdateScrollLeft={setHeaderScrollLeft}
|
||||||
|
>
|
||||||
|
<VirtualList
|
||||||
|
ref="yearScrollRef"
|
||||||
|
items={this.yearArray.map((yearItem, i) =>
|
||||||
|
itemRenderer(yearItem, i, mergedClsPrefix)
|
||||||
|
)}
|
||||||
|
itemSize={40}
|
||||||
|
showScrollbar={false}
|
||||||
|
onScroll={this.handleVirtualListScroll}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: ({ item }: { item: VNode }) => {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</VirtualList>
|
||||||
|
</NScrollbar>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={`${mergedClsPrefix}-date-panel-month-calendar__picker-col`}
|
||||||
|
>
|
||||||
|
<NScrollbar
|
||||||
|
ref="monthScrollRef"
|
||||||
|
// theme={mergedTheme.peers.Scrollbar}
|
||||||
|
// themeOverrides={mergedTheme.peerOverrides.Scrollbar}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () => [
|
||||||
|
...this.monthArray.map((monthItem, i) =>
|
||||||
|
itemRenderer(monthItem, i, mergedClsPrefix)
|
||||||
|
),
|
||||||
|
<div
|
||||||
|
class={`${mergedClsPrefix}-date-panel-month-calendar__padding`}
|
||||||
|
/>
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
</NScrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.datePickerSlots.footer ? (
|
||||||
|
<div class={`${mergedClsPrefix}-date-panel-footer`}>
|
||||||
|
{renderSlot(this.datePickerSlots, 'footer')}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.actions?.length || shortcuts ? (
|
||||||
|
<div class={`${mergedClsPrefix}-date-panel-actions`}>
|
||||||
|
<div class={`${mergedClsPrefix}-date-panel-actions__prefix`}>
|
||||||
|
{shortcuts &&
|
||||||
|
Object.keys(shortcuts).map((key) => {
|
||||||
|
const shortcut = shortcuts[key]
|
||||||
|
return Array.isArray(shortcut) ? null : (
|
||||||
|
<NxButton
|
||||||
|
size="tiny"
|
||||||
|
onMouseenter={() => {
|
||||||
|
this.cachePendingValue()
|
||||||
|
this.doUpdateValue(shortcut, false)
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
this.doUpdateValue(shortcut, false)
|
||||||
|
this.clearPendingValue()
|
||||||
|
this.handleConfirmClick()
|
||||||
|
}}
|
||||||
|
onMouseleave={() => {
|
||||||
|
this.restorePendingValue()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{{ default: () => key }}
|
||||||
|
</NxButton>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div class={`${mergedClsPrefix}-date-panel-actions__suffix`}>
|
||||||
|
{this.actions?.includes('clear') ? (
|
||||||
|
<NButton
|
||||||
|
theme={mergedTheme.peers.Button}
|
||||||
|
themeOverrides={mergedTheme.peerOverrides.Button}
|
||||||
|
size="tiny"
|
||||||
|
onClick={this.handleClearClick}
|
||||||
|
>
|
||||||
|
{{ default: () => this.locale.clear }}
|
||||||
|
</NButton>
|
||||||
|
) : null}
|
||||||
|
{this.actions?.includes('now') ? (
|
||||||
|
<NButton
|
||||||
|
theme={mergedTheme.peers.Button}
|
||||||
|
themeOverrides={mergedTheme.peerOverrides.Button}
|
||||||
|
size="tiny"
|
||||||
|
onClick={this.handleNowClick}
|
||||||
|
>
|
||||||
|
{{ default: () => this.locale.now }}
|
||||||
|
</NButton>
|
||||||
|
) : null}
|
||||||
|
{/** we don't need a confirm button for date picking */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<NBaseFocusDetector onFocus={this.handleFocusDetectorFocus} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
@ -11,12 +11,15 @@ import {
|
|||||||
getDate,
|
getDate,
|
||||||
isValid,
|
isValid,
|
||||||
startOfDay,
|
startOfDay,
|
||||||
startOfSecond
|
startOfSecond,
|
||||||
|
startOfMonth
|
||||||
} from 'date-fns'
|
} from 'date-fns'
|
||||||
import { dateArray, strictParse } from '../utils'
|
import { dateArray, monthArray, strictParse, yearArray } from '../utils'
|
||||||
import { usePanelCommon } from './use-panel-common'
|
import { usePanelCommon } from './use-panel-common'
|
||||||
import { IsSingleDateDisabled, datePickerInjectionKey } from '../interface'
|
import { IsSingleDateDisabled, datePickerInjectionKey } from '../interface'
|
||||||
import type { DateItem } from '../utils'
|
import type { DateItem, MonthItem, YearItem } from '../utils'
|
||||||
|
import { VirtualListInst } from 'vueuc'
|
||||||
|
import { ScrollbarInst } from '../../../scrollbar'
|
||||||
|
|
||||||
const useCalendarProps = {
|
const useCalendarProps = {
|
||||||
...usePanelCommon.props,
|
...usePanelCommon.props,
|
||||||
@ -29,7 +32,7 @@ const useCalendarProps = {
|
|||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
function useCalendar (
|
function useCalendar (
|
||||||
props: ExtractPropTypes<typeof useCalendarProps>,
|
props: ExtractPropTypes<typeof useCalendarProps>,
|
||||||
type: 'date' | 'datetime'
|
type: 'date' | 'datetime' | 'month'
|
||||||
) {
|
) {
|
||||||
const panelCommon = usePanelCommon(props)
|
const panelCommon = usePanelCommon(props)
|
||||||
const {
|
const {
|
||||||
@ -66,6 +69,8 @@ function useCalendar (
|
|||||||
? Date.now()
|
? Date.now()
|
||||||
: props.value
|
: props.value
|
||||||
)
|
)
|
||||||
|
const yearScrollRef = ref<VirtualListInst | null>(null)
|
||||||
|
const scrollbarInstRef = ref<ScrollbarInst | null>(null)
|
||||||
const nowRef = ref(Date.now())
|
const nowRef = ref(Date.now())
|
||||||
const dateArrayRef = computed(() => {
|
const dateArrayRef = computed(() => {
|
||||||
return dateArray(
|
return dateArray(
|
||||||
@ -75,6 +80,21 @@ function useCalendar (
|
|||||||
firstDayOfWeekRef.value ?? localeRef.value.firstDayOfWeek
|
firstDayOfWeekRef.value ?? localeRef.value.firstDayOfWeek
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
const monthArrayRef = computed(() => {
|
||||||
|
return monthArray(calendarValueRef.value, props.value, nowRef.value).map(
|
||||||
|
(item) => {
|
||||||
|
item.formattedText = format(
|
||||||
|
item.ts,
|
||||||
|
localeRef.value.monthFormat,
|
||||||
|
panelCommon.dateFnsOptions.value
|
||||||
|
)
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const yearArrayRef = computed(() => {
|
||||||
|
return yearArray(calendarValueRef.value, props.value, nowRef.value)
|
||||||
|
})
|
||||||
const weekdaysRef = computed(() => {
|
const weekdaysRef = computed(() => {
|
||||||
return dateArrayRef.value.slice(0, 7).map((dateItem) => {
|
return dateArrayRef.value.slice(0, 7).map((dateItem) => {
|
||||||
const { ts } = dateItem
|
const { ts } = dateItem
|
||||||
@ -121,6 +141,7 @@ function useCalendar (
|
|||||||
)
|
)
|
||||||
function sanitizeValue (value: number): number {
|
function sanitizeValue (value: number): number {
|
||||||
if (type === 'datetime') return getTime(startOfSecond(value))
|
if (type === 'datetime') return getTime(startOfSecond(value))
|
||||||
|
if (type === 'month') return getTime(startOfMonth(value))
|
||||||
return getTime(startOfDay(value))
|
return getTime(startOfDay(value))
|
||||||
}
|
}
|
||||||
function mergedIsDateDisabled (ts: number): boolean {
|
function mergedIsDateDisabled (ts: number): boolean {
|
||||||
@ -190,7 +211,7 @@ function useCalendar (
|
|||||||
calendarValueRef.value = Date.now()
|
calendarValueRef.value = Date.now()
|
||||||
panelCommon.doClose(true)
|
panelCommon.doClose(true)
|
||||||
}
|
}
|
||||||
function handleDateClick (dateItem: DateItem): void {
|
function handleDateClick (dateItem: DateItem | MonthItem | YearItem): void {
|
||||||
if (mergedIsDateDisabled(dateItem.ts)) {
|
if (mergedIsDateDisabled(dateItem.ts)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -201,8 +222,11 @@ function useCalendar (
|
|||||||
newValue = Date.now()
|
newValue = Date.now()
|
||||||
}
|
}
|
||||||
newValue = getTime(set(newValue, dateItem.dateObject))
|
newValue = getTime(set(newValue, dateItem.dateObject))
|
||||||
panelCommon.doUpdateValue(getTime(sanitizeValue(newValue)), type === 'date')
|
panelCommon.doUpdateValue(
|
||||||
if (type === 'date') {
|
getTime(sanitizeValue(newValue)),
|
||||||
|
type === 'month' || type === 'date'
|
||||||
|
)
|
||||||
|
if (type === 'date' || (type === 'month' && dateItem.type === 'month')) {
|
||||||
panelCommon.doClose()
|
panelCommon.doClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,11 +270,24 @@ function useCalendar (
|
|||||||
function prevMonth (): void {
|
function prevMonth (): void {
|
||||||
calendarValueRef.value = getTime(addMonths(calendarValueRef.value, -1))
|
calendarValueRef.value = getTime(addMonths(calendarValueRef.value, -1))
|
||||||
}
|
}
|
||||||
|
function virtualListContainer (): HTMLElement {
|
||||||
|
const { value } = yearScrollRef
|
||||||
|
return value?.listElRef as HTMLElement
|
||||||
|
}
|
||||||
|
function virtualListContent (): HTMLElement {
|
||||||
|
const { value } = yearScrollRef
|
||||||
|
return value?.itemsElRef as HTMLElement
|
||||||
|
}
|
||||||
|
function handleVirtualListScroll (e: Event): void {
|
||||||
|
scrollbarInstRef.value?.sync()
|
||||||
|
}
|
||||||
function handleTimePickerChange (value: number): void {
|
function handleTimePickerChange (value: number): void {
|
||||||
panelCommon.doUpdateValue(value, false)
|
panelCommon.doUpdateValue(value, false)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
dateArray: dateArrayRef,
|
dateArray: dateArrayRef,
|
||||||
|
monthArray: monthArrayRef,
|
||||||
|
yearArray: yearArrayRef,
|
||||||
calendarYear: calendarYearRef,
|
calendarYear: calendarYearRef,
|
||||||
calendarMonth: calendarMonthRef,
|
calendarMonth: calendarMonthRef,
|
||||||
weekdays: weekdaysRef,
|
weekdays: weekdaysRef,
|
||||||
@ -269,9 +306,15 @@ function useCalendar (
|
|||||||
handleDateInput,
|
handleDateInput,
|
||||||
handleTimePickerChange,
|
handleTimePickerChange,
|
||||||
clearSelectedDateTime,
|
clearSelectedDateTime,
|
||||||
|
virtualListContainer,
|
||||||
|
virtualListContent,
|
||||||
|
handleVirtualListScroll,
|
||||||
timePickerSize: panelCommon.timePickerSize,
|
timePickerSize: panelCommon.timePickerSize,
|
||||||
dateInputValue: dateInputValueRef,
|
dateInputValue: dateInputValueRef,
|
||||||
datePickerSlots
|
datePickerSlots,
|
||||||
|
monthScrollRef: ref(null),
|
||||||
|
yearScrollRef,
|
||||||
|
scrollbarInstRef
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ import fadeInScaleUpTransition from '../../../_styles/transitions/fade-in-scale-
|
|||||||
// --item-size
|
// --item-size
|
||||||
// --item-cell-width
|
// --item-cell-width
|
||||||
// --item-cell-height
|
// --item-cell-height
|
||||||
|
// --item-month-cell-width
|
||||||
|
// --item-month-cell-height
|
||||||
// --item-text-color
|
// --item-text-color
|
||||||
// --item-color-included
|
// --item-color-included
|
||||||
// --item-color-disabled
|
// --item-color-disabled
|
||||||
@ -89,6 +91,65 @@ export default c([
|
|||||||
gridArea: 'right-calendar'
|
gridArea: 'right-calendar'
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
|
cB('date-panel-month-calendar', {
|
||||||
|
padding: 'var(--calendar-left-padding)',
|
||||||
|
display: 'flex',
|
||||||
|
gridArea: 'left-calendar'
|
||||||
|
}, [
|
||||||
|
cE('picker-col', `
|
||||||
|
min-width: var(--item-month-cell-width);
|
||||||
|
height: calc(var(--item-month-cell-height) * 7);;
|
||||||
|
`, [
|
||||||
|
cE('padding', `
|
||||||
|
height: calc(var(--item-month-cell-height) * 6)
|
||||||
|
`)
|
||||||
|
]),
|
||||||
|
cE('picker-col-item', `
|
||||||
|
cursor: pointer;
|
||||||
|
height: var(--item-month-cell-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
transition:
|
||||||
|
color .3s var(--bezier),
|
||||||
|
background-color .3s var(--bezier),
|
||||||
|
background: #0000;
|
||||||
|
color: var(--item-text-color);
|
||||||
|
`, [
|
||||||
|
cNotM('disabled', [
|
||||||
|
c('&:hover', {
|
||||||
|
backgroundColor: 'var(--item-color-hover)'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cM('current', [
|
||||||
|
cE('sup', `
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
content: "";
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: var(--item-color-active);
|
||||||
|
transition:
|
||||||
|
background-color .2s var(--bezier);
|
||||||
|
`)
|
||||||
|
]),
|
||||||
|
cM('selected', `
|
||||||
|
background-color: var(--item-color-hover);
|
||||||
|
color: var(--item-color-active);
|
||||||
|
`),
|
||||||
|
cM('disabled', `
|
||||||
|
background-color: var(--item-color-disabled);
|
||||||
|
cursor: not-allowed;
|
||||||
|
`)
|
||||||
|
]),
|
||||||
|
cM('end', {
|
||||||
|
padding: 'var(--calendar-right-padding)',
|
||||||
|
gridArea: 'right-calendar'
|
||||||
|
})
|
||||||
|
]),
|
||||||
cM('date', {
|
cM('date', {
|
||||||
gridTemplateAreas: `
|
gridTemplateAreas: `
|
||||||
"left-calendar"
|
"left-calendar"
|
||||||
@ -119,6 +180,13 @@ export default c([
|
|||||||
"action action action"
|
"action action action"
|
||||||
`
|
`
|
||||||
}),
|
}),
|
||||||
|
cM('month', {
|
||||||
|
gridTemplateAreas: `
|
||||||
|
"left-calendar"
|
||||||
|
"footer"
|
||||||
|
"action"
|
||||||
|
`
|
||||||
|
}),
|
||||||
cB('date-panel-footer', {
|
cB('date-panel-footer', {
|
||||||
gridArea: 'footer'
|
gridArea: 'footer'
|
||||||
}),
|
}),
|
||||||
|
@ -5,9 +5,12 @@ import {
|
|||||||
getMonth,
|
getMonth,
|
||||||
getYear,
|
getYear,
|
||||||
isSameMonth,
|
isSameMonth,
|
||||||
|
isSameYear,
|
||||||
getTime,
|
getTime,
|
||||||
startOfMonth,
|
startOfMonth,
|
||||||
addDays,
|
addDays,
|
||||||
|
addMonths,
|
||||||
|
addYears,
|
||||||
getDay,
|
getDay,
|
||||||
parse,
|
parse,
|
||||||
format,
|
format,
|
||||||
@ -44,7 +47,30 @@ function matchDate (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchMonth (
|
||||||
|
sourceTime: number[] | number,
|
||||||
|
patternTime: number | Date
|
||||||
|
): boolean {
|
||||||
|
if (Array.isArray(sourceTime)) {
|
||||||
|
return sourceTime.some((time) => isSameMonth(time, patternTime))
|
||||||
|
} else {
|
||||||
|
return isSameMonth(sourceTime, patternTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchYear (
|
||||||
|
sourceTime: number[] | number,
|
||||||
|
patternTime: number | Date
|
||||||
|
): boolean {
|
||||||
|
if (Array.isArray(sourceTime)) {
|
||||||
|
return sourceTime.some((time) => isSameYear(time, patternTime))
|
||||||
|
} else {
|
||||||
|
return isSameYear(sourceTime, patternTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface DateItem {
|
export interface DateItem {
|
||||||
|
type: 'day'
|
||||||
dateObject: {
|
dateObject: {
|
||||||
date: number
|
date: number
|
||||||
month: number
|
month: number
|
||||||
@ -59,6 +85,28 @@ export interface DateItem {
|
|||||||
ts: number
|
ts: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MonthItem {
|
||||||
|
type: 'month'
|
||||||
|
dateObject: {
|
||||||
|
month: number
|
||||||
|
year: number
|
||||||
|
}
|
||||||
|
inCurrentMonth: boolean
|
||||||
|
selected: boolean
|
||||||
|
ts: number
|
||||||
|
formattedText?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YearItem {
|
||||||
|
type: 'year'
|
||||||
|
dateObject: {
|
||||||
|
year: number
|
||||||
|
}
|
||||||
|
inCurrentYear: boolean
|
||||||
|
selected: boolean
|
||||||
|
ts: number
|
||||||
|
}
|
||||||
|
|
||||||
function dateItem (
|
function dateItem (
|
||||||
time: number,
|
time: number,
|
||||||
monthTs: number,
|
monthTs: number,
|
||||||
@ -76,6 +124,7 @@ function dateItem (
|
|||||||
if (matchDate(valueTs[1], time)) endOfSpan = true
|
if (matchDate(valueTs[1], time)) endOfSpan = true
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
type: 'day',
|
||||||
dateObject: {
|
dateObject: {
|
||||||
date: getDate(time),
|
date: getDate(time),
|
||||||
month: getMonth(time),
|
month: getMonth(time),
|
||||||
@ -91,6 +140,39 @@ function dateItem (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function monthItem (
|
||||||
|
monthTs: number,
|
||||||
|
valueTs: number | [number, number] | null,
|
||||||
|
currentTs: number
|
||||||
|
): MonthItem {
|
||||||
|
return {
|
||||||
|
type: 'month',
|
||||||
|
dateObject: {
|
||||||
|
month: getMonth(monthTs),
|
||||||
|
year: getYear(monthTs)
|
||||||
|
},
|
||||||
|
inCurrentMonth: isSameMonth(currentTs, monthTs),
|
||||||
|
selected: valueTs !== null && matchMonth(valueTs, monthTs),
|
||||||
|
ts: getTime(monthTs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function yearItem (
|
||||||
|
yearTs: number,
|
||||||
|
valueTs: number | [number, number] | null,
|
||||||
|
currentTs: number
|
||||||
|
): YearItem {
|
||||||
|
return {
|
||||||
|
type: 'year',
|
||||||
|
dateObject: {
|
||||||
|
year: getYear(yearTs)
|
||||||
|
},
|
||||||
|
inCurrentYear: isSameYear(currentTs, yearTs),
|
||||||
|
selected: valueTs !== null && matchYear(valueTs, yearTs),
|
||||||
|
ts: getTime(yearTs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given time to display calendar, given the selected time, given current time,
|
* Given time to display calendar, given the selected time, given current time,
|
||||||
* return the date array of display time's month.
|
* return the date array of display time's month.
|
||||||
@ -141,6 +223,42 @@ function dateArray (
|
|||||||
return calendarDays
|
return calendarDays
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function monthArray (
|
||||||
|
monthTs: number,
|
||||||
|
valueTs: number | [number, number] | null,
|
||||||
|
currentTs: number
|
||||||
|
): MonthItem[] {
|
||||||
|
const calendarMonths = []
|
||||||
|
const cachedMonth = getMonth(monthTs)
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
calendarMonths.push(
|
||||||
|
monthItem(
|
||||||
|
getTime(addMonths(monthTs, i - cachedMonth)),
|
||||||
|
valueTs,
|
||||||
|
currentTs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return calendarMonths
|
||||||
|
}
|
||||||
|
|
||||||
|
function yearArray (
|
||||||
|
yearTs: number,
|
||||||
|
valueTs: number | [number, number] | null,
|
||||||
|
currentTs: number
|
||||||
|
): YearItem[] {
|
||||||
|
const calendarYears = []
|
||||||
|
const cachedYear = getYear(yearTs)
|
||||||
|
|
||||||
|
for (let i = 1900; i < 2100; i++) {
|
||||||
|
calendarYears.push(
|
||||||
|
yearItem(getTime(addYears(yearTs, i - cachedYear)), valueTs, currentTs)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return calendarYears
|
||||||
|
}
|
||||||
|
|
||||||
function strictParse (
|
function strictParse (
|
||||||
string: string,
|
string: string,
|
||||||
pattern: string,
|
pattern: string,
|
||||||
@ -155,4 +273,10 @@ function strictParse (
|
|||||||
else return new Date(NaN)
|
else return new Date(NaN)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { dateArray, strictParse, getDerivedTimeFromKeyboardEvent }
|
export {
|
||||||
|
dateArray,
|
||||||
|
monthArray,
|
||||||
|
yearArray,
|
||||||
|
strictParse,
|
||||||
|
getDerivedTimeFromKeyboardEvent
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@ export default {
|
|||||||
itemSize: '24px',
|
itemSize: '24px',
|
||||||
itemCellWidth: '38px',
|
itemCellWidth: '38px',
|
||||||
itemCellHeight: '32px',
|
itemCellHeight: '32px',
|
||||||
|
itemMonthCellWidth: '80px',
|
||||||
|
itemMonthCellHeight: '40px',
|
||||||
panelExtraFooterPadding: '8px 12px',
|
panelExtraFooterPadding: '8px 12px',
|
||||||
panelActionPadding: '8px 12px',
|
panelActionPadding: '8px 12px',
|
||||||
calendarTitlePadding: '0',
|
calendarTitlePadding: '0',
|
||||||
@ -15,8 +17,10 @@ export default {
|
|||||||
calendarLeftPaddingDatetime: '4px 12px',
|
calendarLeftPaddingDatetime: '4px 12px',
|
||||||
calendarLeftPaddingDaterange: '6px 12px 4px 12px',
|
calendarLeftPaddingDaterange: '6px 12px 4px 12px',
|
||||||
calendarLeftPaddingDatetimerange: '4px 12px',
|
calendarLeftPaddingDatetimerange: '4px 12px',
|
||||||
|
calendarLeftPaddingMonth: '4px 4px',
|
||||||
calendarRightPaddingDate: '6px 12px 4px 12px',
|
calendarRightPaddingDate: '6px 12px 4px 12px',
|
||||||
calendarRightPaddingDatetime: '4px 12px',
|
calendarRightPaddingDatetime: '4px 12px',
|
||||||
calendarRightPaddingDaterange: '6px 12px 4px 12px',
|
calendarRightPaddingDaterange: '6px 12px 4px 12px',
|
||||||
calendarRightPaddingDatetimerange: '4px 12px'
|
calendarRightPaddingDatetimerange: '4px 12px',
|
||||||
|
calendarRightPaddingMonth: '4px 12px'
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user