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
|
||||
|
||||
- Add ukUA locale.
|
||||
### Feats
|
||||
|
||||
- `n-date-picker`'s `type` prop support `month` option.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
@ -52,6 +52,9 @@
|
||||
### i18n
|
||||
|
||||
- 新增 ukUA locale
|
||||
### Feats
|
||||
|
||||
- `n-date-picker` 属性 `type` 支持 `month` 选项
|
||||
|
||||
### Fixes
|
||||
|
||||
|
@ -9,6 +9,7 @@ date
|
||||
datetime
|
||||
daterange
|
||||
datetimerange
|
||||
month
|
||||
size
|
||||
disabled
|
||||
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). |
|
||||
| shortcuts | `Record<string, number \| [number, number]>` | `undefined` | Shortcut button customizations. |
|
||||
| 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. |
|
||||
| on-blur | `() => void` | `undefined` | On blur 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
|
||||
daterange
|
||||
datetimerange
|
||||
month
|
||||
size
|
||||
disabled
|
||||
disabled-time
|
||||
@ -33,7 +34,7 @@ update-on-close
|
||||
| input-readonly | `boolean` | `false` | 设置输入框为只读(避免在移动设备上打开虚拟键盘) |
|
||||
| shortcuts | `Record<string, number \| [number, number]>` | `undefined` | 自定义快捷按钮 |
|
||||
| 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 的值 |
|
||||
| on-blur | `() => void` | `undefined` | 用户 blur 时执行的回调 |
|
||||
| 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,
|
||||
toRef,
|
||||
Ref,
|
||||
watchEffect
|
||||
watchEffect,
|
||||
nextTick
|
||||
} from 'vue'
|
||||
import { VBinder, VTarget, VFollower, FollowerPlacement } from 'vueuc'
|
||||
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 { happensIn } from 'seemly'
|
||||
import { InputInst, InputProps, NInput } from '../../input'
|
||||
@ -37,6 +38,7 @@ import DatetimePanel from './panel/datetime'
|
||||
import DatetimerangePanel from './panel/datetimerange'
|
||||
import DatePanel from './panel/date'
|
||||
import DaterangePanel from './panel/daterange'
|
||||
import MonthPanel from './panel/month'
|
||||
import style from './styles/index.cssr'
|
||||
import { DatePickerTheme } from '../styles/light'
|
||||
import {
|
||||
@ -56,7 +58,8 @@ const DATE_FORMAT = {
|
||||
date: 'yyyy-MM-dd',
|
||||
datetime: 'yyyy-MM-dd HH:mm:ss',
|
||||
daterange: 'yyyy-MM-dd',
|
||||
datetimerange: 'yyyy-MM-dd HH:mm:ss'
|
||||
datetimerange: 'yyyy-MM-dd HH:mm:ss',
|
||||
month: 'yyyy-MM'
|
||||
}
|
||||
|
||||
const datePickerProps = {
|
||||
@ -90,7 +93,7 @@ const datePickerProps = {
|
||||
size: String as PropType<'small' | 'medium' | 'large'>,
|
||||
type: {
|
||||
type: String as PropType<
|
||||
'date' | 'datetime' | 'daterange' | 'datetimerange'
|
||||
'date' | 'datetime' | 'daterange' | 'datetimerange' | 'month'
|
||||
>,
|
||||
default: 'date'
|
||||
},
|
||||
@ -335,6 +338,24 @@ export default defineComponent({
|
||||
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
|
||||
function handlePanelUpdateValue (
|
||||
value: Value | null,
|
||||
@ -476,6 +497,9 @@ export default defineComponent({
|
||||
function openCalendar (): void {
|
||||
if (mergedDisabledRef.value || mergedShowRef.value) return
|
||||
doUpdateShow(true)
|
||||
if (props.type === 'month') {
|
||||
void nextTick(scrollTimer)
|
||||
}
|
||||
}
|
||||
function closeCalendar ({
|
||||
returnFocus,
|
||||
@ -616,6 +640,8 @@ export default defineComponent({
|
||||
itemSize,
|
||||
itemCellWidth,
|
||||
itemCellHeight,
|
||||
itemMonthCellWidth,
|
||||
itemMonthCellHeight,
|
||||
calendarTitlePadding,
|
||||
calendarTitleHeight,
|
||||
calendarDaysHeight,
|
||||
@ -669,6 +695,8 @@ export default defineComponent({
|
||||
'--item-size': itemSize,
|
||||
'--item-cell-width': itemCellWidth,
|
||||
'--item-cell-height': itemCellHeight,
|
||||
'--item-month-cell-width': itemMonthCellWidth,
|
||||
'--item-month-cell-height': itemMonthCellHeight,
|
||||
'--item-text-color': itemTextColor,
|
||||
'--item-color-included': itemColorIncluded,
|
||||
'--item-color-disabled': itemColorDisabled,
|
||||
@ -828,6 +856,8 @@ export default defineComponent({
|
||||
<DaterangePanel {...commonPanelProps} />
|
||||
) : this.type === 'datetimerange' ? (
|
||||
<DatetimerangePanel {...commonPanelProps} />
|
||||
) : this.type === 'month' ? (
|
||||
<MonthPanel {...commonPanelProps} />
|
||||
) : (
|
||||
<DatePanel {...commonPanelProps} />
|
||||
),
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { InjectionKey, Ref, Slots } from 'vue'
|
||||
import { VirtualListInst } from 'vueuc'
|
||||
import { NLocale, NDateLocale } from '../../locales'
|
||||
import { ScrollbarInst } from '../../scrollbar'
|
||||
import {
|
||||
IsHourDisabled,
|
||||
IsMinuteDisabled,
|
||||
@ -41,6 +43,8 @@ export type OnClose = (disableUpdateOnClose: boolean) => void
|
||||
|
||||
export interface PanelRef {
|
||||
$el: HTMLElement
|
||||
monthScrollRef: ScrollbarInst | null
|
||||
yearScrollRef: VirtualListInst | null
|
||||
}
|
||||
|
||||
// 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,
|
||||
isValid,
|
||||
startOfDay,
|
||||
startOfSecond
|
||||
startOfSecond,
|
||||
startOfMonth
|
||||
} from 'date-fns'
|
||||
import { dateArray, strictParse } from '../utils'
|
||||
import { dateArray, monthArray, strictParse, yearArray } from '../utils'
|
||||
import { usePanelCommon } from './use-panel-common'
|
||||
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 = {
|
||||
...usePanelCommon.props,
|
||||
@ -29,7 +32,7 @@ const useCalendarProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
function useCalendar (
|
||||
props: ExtractPropTypes<typeof useCalendarProps>,
|
||||
type: 'date' | 'datetime'
|
||||
type: 'date' | 'datetime' | 'month'
|
||||
) {
|
||||
const panelCommon = usePanelCommon(props)
|
||||
const {
|
||||
@ -66,6 +69,8 @@ function useCalendar (
|
||||
? Date.now()
|
||||
: props.value
|
||||
)
|
||||
const yearScrollRef = ref<VirtualListInst | null>(null)
|
||||
const scrollbarInstRef = ref<ScrollbarInst | null>(null)
|
||||
const nowRef = ref(Date.now())
|
||||
const dateArrayRef = computed(() => {
|
||||
return dateArray(
|
||||
@ -75,6 +80,21 @@ function useCalendar (
|
||||
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(() => {
|
||||
return dateArrayRef.value.slice(0, 7).map((dateItem) => {
|
||||
const { ts } = dateItem
|
||||
@ -121,6 +141,7 @@ function useCalendar (
|
||||
)
|
||||
function sanitizeValue (value: number): number {
|
||||
if (type === 'datetime') return getTime(startOfSecond(value))
|
||||
if (type === 'month') return getTime(startOfMonth(value))
|
||||
return getTime(startOfDay(value))
|
||||
}
|
||||
function mergedIsDateDisabled (ts: number): boolean {
|
||||
@ -190,7 +211,7 @@ function useCalendar (
|
||||
calendarValueRef.value = Date.now()
|
||||
panelCommon.doClose(true)
|
||||
}
|
||||
function handleDateClick (dateItem: DateItem): void {
|
||||
function handleDateClick (dateItem: DateItem | MonthItem | YearItem): void {
|
||||
if (mergedIsDateDisabled(dateItem.ts)) {
|
||||
return
|
||||
}
|
||||
@ -201,8 +222,11 @@ function useCalendar (
|
||||
newValue = Date.now()
|
||||
}
|
||||
newValue = getTime(set(newValue, dateItem.dateObject))
|
||||
panelCommon.doUpdateValue(getTime(sanitizeValue(newValue)), type === 'date')
|
||||
if (type === 'date') {
|
||||
panelCommon.doUpdateValue(
|
||||
getTime(sanitizeValue(newValue)),
|
||||
type === 'month' || type === 'date'
|
||||
)
|
||||
if (type === 'date' || (type === 'month' && dateItem.type === 'month')) {
|
||||
panelCommon.doClose()
|
||||
}
|
||||
}
|
||||
@ -246,11 +270,24 @@ function useCalendar (
|
||||
function prevMonth (): void {
|
||||
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 {
|
||||
panelCommon.doUpdateValue(value, false)
|
||||
}
|
||||
return {
|
||||
dateArray: dateArrayRef,
|
||||
monthArray: monthArrayRef,
|
||||
yearArray: yearArrayRef,
|
||||
calendarYear: calendarYearRef,
|
||||
calendarMonth: calendarMonthRef,
|
||||
weekdays: weekdaysRef,
|
||||
@ -269,9 +306,15 @@ function useCalendar (
|
||||
handleDateInput,
|
||||
handleTimePickerChange,
|
||||
clearSelectedDateTime,
|
||||
virtualListContainer,
|
||||
virtualListContent,
|
||||
handleVirtualListScroll,
|
||||
timePickerSize: panelCommon.timePickerSize,
|
||||
dateInputValue: dateInputValueRef,
|
||||
datePickerSlots
|
||||
datePickerSlots,
|
||||
monthScrollRef: ref(null),
|
||||
yearScrollRef,
|
||||
scrollbarInstRef
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,8 @@ import fadeInScaleUpTransition from '../../../_styles/transitions/fade-in-scale-
|
||||
// --item-size
|
||||
// --item-cell-width
|
||||
// --item-cell-height
|
||||
// --item-month-cell-width
|
||||
// --item-month-cell-height
|
||||
// --item-text-color
|
||||
// --item-color-included
|
||||
// --item-color-disabled
|
||||
@ -89,6 +91,65 @@ export default c([
|
||||
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', {
|
||||
gridTemplateAreas: `
|
||||
"left-calendar"
|
||||
@ -119,6 +180,13 @@ export default c([
|
||||
"action action action"
|
||||
`
|
||||
}),
|
||||
cM('month', {
|
||||
gridTemplateAreas: `
|
||||
"left-calendar"
|
||||
"footer"
|
||||
"action"
|
||||
`
|
||||
}),
|
||||
cB('date-panel-footer', {
|
||||
gridArea: 'footer'
|
||||
}),
|
||||
|
@ -5,9 +5,12 @@ import {
|
||||
getMonth,
|
||||
getYear,
|
||||
isSameMonth,
|
||||
isSameYear,
|
||||
getTime,
|
||||
startOfMonth,
|
||||
addDays,
|
||||
addMonths,
|
||||
addYears,
|
||||
getDay,
|
||||
parse,
|
||||
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 {
|
||||
type: 'day'
|
||||
dateObject: {
|
||||
date: number
|
||||
month: number
|
||||
@ -59,6 +85,28 @@ export interface DateItem {
|
||||
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 (
|
||||
time: number,
|
||||
monthTs: number,
|
||||
@ -76,6 +124,7 @@ function dateItem (
|
||||
if (matchDate(valueTs[1], time)) endOfSpan = true
|
||||
}
|
||||
return {
|
||||
type: 'day',
|
||||
dateObject: {
|
||||
date: getDate(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,
|
||||
* return the date array of display time's month.
|
||||
@ -141,6 +223,42 @@ function dateArray (
|
||||
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 (
|
||||
string: string,
|
||||
pattern: string,
|
||||
@ -155,4 +273,10 @@ function strictParse (
|
||||
else return new Date(NaN)
|
||||
}
|
||||
|
||||
export { dateArray, strictParse, getDerivedTimeFromKeyboardEvent }
|
||||
export {
|
||||
dateArray,
|
||||
monthArray,
|
||||
yearArray,
|
||||
strictParse,
|
||||
getDerivedTimeFromKeyboardEvent
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ export default {
|
||||
itemSize: '24px',
|
||||
itemCellWidth: '38px',
|
||||
itemCellHeight: '32px',
|
||||
itemMonthCellWidth: '80px',
|
||||
itemMonthCellHeight: '40px',
|
||||
panelExtraFooterPadding: '8px 12px',
|
||||
panelActionPadding: '8px 12px',
|
||||
calendarTitlePadding: '0',
|
||||
@ -15,8 +17,10 @@ export default {
|
||||
calendarLeftPaddingDatetime: '4px 12px',
|
||||
calendarLeftPaddingDaterange: '6px 12px 4px 12px',
|
||||
calendarLeftPaddingDatetimerange: '4px 12px',
|
||||
calendarLeftPaddingMonth: '4px 4px',
|
||||
calendarRightPaddingDate: '6px 12px 4px 12px',
|
||||
calendarRightPaddingDatetime: '4px 12px',
|
||||
calendarRightPaddingDaterange: '6px 12px 4px 12px',
|
||||
calendarRightPaddingDatetimerange: '4px 12px'
|
||||
calendarRightPaddingDatetimerange: '4px 12px',
|
||||
calendarRightPaddingMonth: '4px 12px'
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user