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 commit 0627777693.

* Revert "feat:n-input Support hidden password"

This reverts commit ea6491783d.

* 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:
doom-9 2021-10-06 11:45:12 +08:00 committed by GitHub
parent bacb7a3fc7
commit 3ea326e7a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 523 additions and 16 deletions

View File

@ -52,6 +52,9 @@
### i18n ### i18n
- Add ukUA locale. - Add ukUA locale.
### Feats
- `n-date-picker`'s `type` prop support `month` option.
### Fixes ### Fixes

View File

@ -52,6 +52,9 @@
### i18n ### i18n
- 新增 ukUA locale - 新增 ukUA locale
### Feats
- `n-date-picker` 属性 `type` 支持 `month` 选项
### Fixes ### Fixes

View File

@ -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. |

View 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)
}
}
})
```

View File

@ -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 时执行的回调 |

View 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)
}
}
})
```

View File

@ -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} />
), ),

View File

@ -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

View 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>
)
}
})

View File

@ -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
} }
} }

View File

@ -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'
}), }),

View File

@ -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
}

View File

@ -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'
} }