refactor: date

pull/4499/head
tangjinzhou 2021-06-15 15:26:53 +08:00
parent a448e57475
commit a020c2f681
107 changed files with 8462 additions and 6 deletions

View File

@ -0,0 +1,38 @@
import { inject, InjectionKey, provide, Ref } from 'vue';
import type { OnSelect, PanelMode } from './interface';
export type ContextOperationRefProps = {
onKeyDown?: (e: KeyboardEvent) => boolean;
onClose?: () => void;
};
export type PanelContextProps = {
operationRef?: Ref<ContextOperationRefProps | null>;
/** Only work with time panel */
hideHeader?: boolean;
panelRef?: Ref<HTMLDivElement>;
hidePrevBtn?: boolean;
hideNextBtn?: boolean;
onDateMouseEnter?: (date: any) => void;
onDateMouseLeave?: (date: any) => void;
onSelect?: OnSelect<any>;
hideRanges?: boolean;
open?: boolean;
mode?: PanelMode;
/** Only used for TimePicker and this is a deprecated prop */
defaultOpenValue?: any;
};
const PanelContextKey: InjectionKey<PanelContextProps> = Symbol('PanelContextProps');
export const useProvidePanel = (props: PanelContextProps) => {
provide(PanelContextKey, props);
};
export const useInjectPanel = () => {
return inject(PanelContextKey);
};
export default PanelContextKey;

View File

@ -0,0 +1,561 @@
/**
* Removed:
* - getCalendarContainer: use `getPopupContainer` instead
* - onOk
*
* New Feature:
* - picker
* - allowEmpty
* - selectable
*
* Tips: Should add faq about `datetime` mode with `defaultValue`
*/
import * as React from 'react';
import classNames from 'classnames';
import type { AlignType } from 'rc-trigger/lib/interface';
import warning from 'rc-util/lib/warning';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type {
PickerPanelBaseProps,
PickerPanelDateProps,
PickerPanelTimeProps,
} from './PickerPanel';
import PickerPanel from './PickerPanel';
import PickerTrigger from './PickerTrigger';
import { formatValue, isEqual, parseValue } from './utils/dateUtil';
import getDataOrAriaProps, { toArray } from './utils/miscUtil';
import type { ContextOperationRefProps } from './PanelContext';
import PanelContext from './PanelContext';
import type { CustomFormat, PickerMode } from './interface';
import { getDefaultFormat, getInputSize, elementsContains } from './utils/uiUtil';
import usePickerInput from './hooks/usePickerInput';
import useTextValueMapping from './hooks/useTextValueMapping';
import useValueTexts from './hooks/useValueTexts';
import useHoverValue from './hooks/useHoverValue';
export type PickerRefConfig = {
focus: () => void;
blur: () => void;
};
export type PickerSharedProps<DateType> = {
dropdownClassName?: string;
dropdownAlign?: AlignType;
popupStyle?: React.CSSProperties;
transitionName?: string;
placeholder?: string;
allowClear?: boolean;
autoFocus?: boolean;
disabled?: boolean;
tabIndex?: number;
open?: boolean;
defaultOpen?: boolean;
/** Make input readOnly to avoid popup keyboard in mobile */
inputReadOnly?: boolean;
id?: string;
// Value
format?: string | CustomFormat<DateType> | (string | CustomFormat<DateType>)[];
// Render
suffixIcon?: React.ReactNode;
clearIcon?: React.ReactNode;
prevIcon?: React.ReactNode;
nextIcon?: React.ReactNode;
superPrevIcon?: React.ReactNode;
superNextIcon?: React.ReactNode;
getPopupContainer?: (node: HTMLElement) => HTMLElement;
panelRender?: (originPanel: React.ReactNode) => React.ReactNode;
// Events
onChange?: (value: DateType | null, dateString: string) => void;
onOpenChange?: (open: boolean) => void;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseUp?: React.MouseEventHandler<HTMLDivElement>;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>, preventDefault: () => void) => void;
// Internal
/** @private Internal usage, do not use in production mode!!! */
pickerRef?: React.MutableRefObject<PickerRefConfig>;
// WAI-ARIA
role?: string;
name?: string;
autoComplete?: string;
direction?: 'ltr' | 'rtl';
} & React.AriaAttributes;
type OmitPanelProps<Props> = Omit<
Props,
'onChange' | 'hideHeader' | 'pickerValue' | 'onPickerValueChange'
>;
export type PickerBaseProps<DateType> = {} & PickerSharedProps<DateType> &
OmitPanelProps<PickerPanelBaseProps<DateType>>;
export type PickerDateProps<DateType> = {} & PickerSharedProps<DateType> &
OmitPanelProps<PickerPanelDateProps<DateType>>;
export type PickerTimeProps<DateType> = {
picker: 'time';
/**
* @deprecated Please use `defaultValue` directly instead
* since `defaultOpenValue` will confuse user of current value status
*/
defaultOpenValue?: DateType;
} & PickerSharedProps<DateType> &
Omit<OmitPanelProps<PickerPanelTimeProps<DateType>>, 'format'>;
export type PickerProps<DateType> =
| PickerBaseProps<DateType>
| PickerDateProps<DateType>
| PickerTimeProps<DateType>;
// TMP type to fit for ts 3.9.2
type OmitType<DateType> = Omit<PickerBaseProps<DateType>, 'picker'> &
Omit<PickerDateProps<DateType>, 'picker'> &
Omit<PickerTimeProps<DateType>, 'picker'>;
type MergedPickerProps<DateType> = {
picker?: PickerMode;
} & OmitType<DateType>;
function InnerPicker<DateType>(props: PickerProps<DateType>) {
const {
prefixCls = 'rc-picker',
id,
tabIndex,
style,
className,
dropdownClassName,
dropdownAlign,
popupStyle,
transitionName,
generateConfig,
locale,
inputReadOnly,
allowClear,
autoFocus,
showTime,
picker = 'date',
format,
use12Hours,
value,
defaultValue,
open,
defaultOpen,
defaultOpenValue,
suffixIcon,
clearIcon,
disabled,
disabledDate,
placeholder,
getPopupContainer,
pickerRef,
panelRender,
onChange,
onOpenChange,
onFocus,
onBlur,
onMouseDown,
onMouseUp,
onMouseEnter,
onMouseLeave,
onContextMenu,
onClick,
onKeyDown,
onSelect,
direction,
autoComplete = 'off',
} = props as MergedPickerProps<DateType>;
const inputRef = React.useRef<HTMLInputElement>(null);
const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time';
// ============================= State =============================
const formatList = toArray(getDefaultFormat(format, picker, showTime, use12Hours));
// Panel ref
const panelDivRef = React.useRef<HTMLDivElement>(null);
const inputDivRef = React.useRef<HTMLDivElement>(null);
// Real value
const [mergedValue, setInnerValue] = useMergedState(null, {
value,
defaultValue,
});
// Selected value
const [selectedValue, setSelectedValue] = React.useState<DateType | null>(mergedValue);
// Operation ref
const operationRef: React.MutableRefObject<ContextOperationRefProps | null> =
React.useRef<ContextOperationRefProps>(null);
// Open
const [mergedOpen, triggerInnerOpen] = useMergedState(false, {
value: open,
defaultValue: defaultOpen,
postState: (postOpen) => (disabled ? false : postOpen),
onChange: (newOpen) => {
if (onOpenChange) {
onOpenChange(newOpen);
}
if (!newOpen && operationRef.current && operationRef.current.onClose) {
operationRef.current.onClose();
}
},
});
// ============================= Text ==============================
const [valueTexts, firstValueText] = useValueTexts(selectedValue, {
formatList,
generateConfig,
locale,
});
const [text, triggerTextChange, resetText] = useTextValueMapping({
valueTexts,
onTextChange: (newText) => {
const inputDate = parseValue(newText, {
locale,
formatList,
generateConfig,
});
if (inputDate && (!disabledDate || !disabledDate(inputDate))) {
setSelectedValue(inputDate);
}
},
});
// ============================ Trigger ============================
const triggerChange = (newValue: DateType | null) => {
setSelectedValue(newValue);
setInnerValue(newValue);
if (onChange && !isEqual(generateConfig, mergedValue, newValue)) {
onChange(
newValue,
newValue ? formatValue(newValue, { generateConfig, locale, format: formatList[0] }) : '',
);
}
};
const triggerOpen = (newOpen: boolean) => {
if (disabled && newOpen) {
return;
}
triggerInnerOpen(newOpen);
};
const forwardKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
if (mergedOpen && operationRef.current && operationRef.current.onKeyDown) {
// Let popup panel handle keyboard
return operationRef.current.onKeyDown(e);
}
/* istanbul ignore next */
/* eslint-disable no-lone-blocks */
{
warning(
false,
'Picker not correct forward KeyDown operation. Please help to fire issue about this.',
);
return false;
}
};
const onInternalMouseUp: React.MouseEventHandler<HTMLDivElement> = (...args) => {
if (onMouseUp) {
onMouseUp(...args);
}
if (inputRef.current) {
inputRef.current.focus();
triggerOpen(true);
}
};
// ============================= Input =============================
const [inputProps, { focused, typing }] = usePickerInput({
blurToCancel: needConfirmButton,
open: mergedOpen,
value: text,
triggerOpen,
forwardKeyDown,
isClickOutside: (target) =>
!elementsContains([panelDivRef.current, inputDivRef.current], target as HTMLElement),
onSubmit: () => {
if (disabledDate && disabledDate(selectedValue)) {
return false;
}
triggerChange(selectedValue);
triggerOpen(false);
resetText();
return true;
},
onCancel: () => {
triggerOpen(false);
setSelectedValue(mergedValue);
resetText();
},
onKeyDown: (e, preventDefault) => {
onKeyDown?.(e, preventDefault);
},
onFocus,
onBlur,
});
// ============================= Sync ==============================
// Close should sync back with text value
React.useEffect(() => {
if (!mergedOpen) {
setSelectedValue(mergedValue);
if (!valueTexts.length || valueTexts[0] === '') {
triggerTextChange('');
} else if (firstValueText !== text) {
resetText();
}
}
}, [mergedOpen, valueTexts]);
// Change picker should sync back with text value
React.useEffect(() => {
if (!mergedOpen) {
resetText();
}
}, [picker]);
// Sync innerValue with control mode
React.useEffect(() => {
// Sync select value
setSelectedValue(mergedValue);
}, [mergedValue]);
// ============================ Private ============================
if (pickerRef) {
pickerRef.current = {
focus: () => {
if (inputRef.current) {
inputRef.current.focus();
}
},
blur: () => {
if (inputRef.current) {
inputRef.current.blur();
}
},
};
}
const [hoverValue, onEnter, onLeave] = useHoverValue(text, {
formatList,
generateConfig,
locale,
});
// ============================= Panel =============================
const panelProps = {
// Remove `picker` & `format` here since TimePicker is little different with other panel
...(props as Omit<MergedPickerProps<DateType>, 'picker' | 'format'>),
className: undefined,
style: undefined,
pickerValue: undefined,
onPickerValueChange: undefined,
onChange: null,
};
let panelNode: React.ReactNode = (
<PickerPanel<DateType>
{...panelProps}
generateConfig={generateConfig}
className={classNames({
[`${prefixCls}-panel-focused`]: !typing,
})}
value={selectedValue}
locale={locale}
tabIndex={-1}
onSelect={(date) => {
onSelect?.(date);
setSelectedValue(date);
}}
direction={direction}
onPanelChange={(viewDate, mode) => {
const { onPanelChange } = props;
onLeave(true);
onPanelChange?.(viewDate, mode);
}}
/>
);
if (panelRender) {
panelNode = panelRender(panelNode);
}
const panel = (
<div
className={`${prefixCls}-panel-container`}
onMouseDown={(e) => {
e.preventDefault();
}}
>
{panelNode}
</div>
);
let suffixNode: React.ReactNode;
if (suffixIcon) {
suffixNode = <span className={`${prefixCls}-suffix`}>{suffixIcon}</span>;
}
let clearNode: React.ReactNode;
if (allowClear && mergedValue && !disabled) {
clearNode = (
<span
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseUp={(e) => {
e.preventDefault();
e.stopPropagation();
triggerChange(null);
triggerOpen(false);
}}
className={`${prefixCls}-clear`}
role="button"
>
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
</span>
);
}
// ============================ Warning ============================
if (process.env.NODE_ENV !== 'production') {
warning(
!defaultOpenValue,
'`defaultOpenValue` may confuse user for the current value status. Please use `defaultValue` instead.',
);
}
// ============================ Return =============================
const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => {
if (type === 'submit' || (type !== 'key' && !needConfirmButton)) {
// triggerChange will also update selected values
triggerChange(date);
triggerOpen(false);
}
};
const popupPlacement = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
return (
<PanelContext.Provider
value={{
operationRef,
hideHeader: picker === 'time',
panelRef: panelDivRef,
onSelect: onContextSelect,
open: mergedOpen,
defaultOpenValue,
onDateMouseEnter: onEnter,
onDateMouseLeave: onLeave,
}}
>
<PickerTrigger
visible={mergedOpen}
popupElement={panel}
popupStyle={popupStyle}
prefixCls={prefixCls}
dropdownClassName={dropdownClassName}
dropdownAlign={dropdownAlign}
getPopupContainer={getPopupContainer}
transitionName={transitionName}
popupPlacement={popupPlacement}
direction={direction}
>
<div
className={classNames(prefixCls, className, {
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-focused`]: focused,
[`${prefixCls}-rtl`]: direction === 'rtl',
})}
style={style}
onMouseDown={onMouseDown}
onMouseUp={onInternalMouseUp}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onContextMenu={onContextMenu}
onClick={onClick}
>
<div
className={classNames(`${prefixCls}-input`, {
[`${prefixCls}-input-placeholder`]: !!hoverValue,
})}
ref={inputDivRef}
>
<input
id={id}
tabIndex={tabIndex}
disabled={disabled}
readOnly={inputReadOnly || typeof formatList[0] === 'function' || !typing}
value={hoverValue || text}
onChange={(e) => {
triggerTextChange(e.target.value);
}}
autoFocus={autoFocus}
placeholder={placeholder}
ref={inputRef}
title={text}
{...inputProps}
size={getInputSize(picker, formatList[0], generateConfig)}
{...getDataOrAriaProps(props)}
autoComplete={autoComplete}
/>
{suffixNode}
{clearNode}
</div>
</div>
</PickerTrigger>
</PanelContext.Provider>
);
}
// Wrap with class component to enable pass generic with instance method
class Picker<DateType> extends React.Component<PickerProps<DateType>> {
pickerRef = React.createRef<PickerRefConfig>();
focus = () => {
if (this.pickerRef.current) {
this.pickerRef.current.focus();
}
};
blur = () => {
if (this.pickerRef.current) {
this.pickerRef.current.blur();
}
};
render() {
return (
<InnerPicker<DateType>
{...this.props}
pickerRef={this.pickerRef as React.MutableRefObject<PickerRefConfig>}
/>
);
}
}
export default Picker;

View File

@ -0,0 +1,569 @@
/**
* Logic:
* When `mode` === `picker`,
* click will trigger `onSelect` (if value changed trigger `onChange` also).
* Panel change will not trigger `onSelect` but trigger `onPanelChange`
*/
import * as React from 'react';
import classNames from 'classnames';
import KeyCode from 'rc-util/lib/KeyCode';
import warning from 'rc-util/lib/warning';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { SharedTimeProps } from './panels/TimePanel';
import TimePanel from './panels/TimePanel';
import DatetimePanel from './panels/DatetimePanel';
import DatePanel from './panels/DatePanel';
import WeekPanel from './panels/WeekPanel';
import MonthPanel from './panels/MonthPanel';
import QuarterPanel from './panels/QuarterPanel';
import YearPanel from './panels/YearPanel';
import DecadePanel from './panels/DecadePanel';
import type { GenerateConfig } from './generate';
import type {
Locale,
PanelMode,
PanelRefProps,
PickerMode,
DisabledTime,
OnPanelChange,
Components,
} from './interface';
import { isEqual } from './utils/dateUtil';
import PanelContext from './PanelContext';
import type { DateRender } from './panels/DatePanel/DateBody';
import { PickerModeMap } from './utils/uiUtil';
import type { MonthCellRender } from './panels/MonthPanel/MonthBody';
import RangeContext from './RangeContext';
import getExtraFooter from './utils/getExtraFooter';
import getRanges from './utils/getRanges';
import { getLowerBoundTime, setDateTime, setTime } from './utils/timeUtil';
export type PickerPanelSharedProps<DateType> = {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
/** @deprecated Will be removed in next big version. Please use `mode` instead */
mode?: PanelMode;
tabIndex?: number;
// Locale
locale: Locale;
generateConfig: GenerateConfig<DateType>;
// Value
value?: DateType | null;
defaultValue?: DateType;
/** [Legacy] Set default display picker view date */
pickerValue?: DateType;
/** [Legacy] Set default display picker view date */
defaultPickerValue?: DateType;
// Date
disabledDate?: (date: DateType) => boolean;
// Render
dateRender?: DateRender<DateType>;
monthCellRender?: MonthCellRender<DateType>;
renderExtraFooter?: (mode: PanelMode) => React.ReactNode;
// Event
onSelect?: (value: DateType) => void;
onChange?: (value: DateType) => void;
onPanelChange?: OnPanelChange<DateType>;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onOk?: (date: DateType) => void;
direction?: 'ltr' | 'rtl';
/** @private This is internal usage. Do not use in your production env */
hideHeader?: boolean;
/** @private This is internal usage. Do not use in your production env */
onPickerValueChange?: (date: DateType) => void;
/** @private Internal usage. Do not use in your production env */
components?: Components;
};
export type PickerPanelBaseProps<DateType> = {
picker: Exclude<PickerMode, 'date' | 'time'>;
} & PickerPanelSharedProps<DateType>;
export type PickerPanelDateProps<DateType> = {
picker?: 'date';
showToday?: boolean;
showNow?: boolean;
// Time
showTime?: boolean | SharedTimeProps<DateType>;
disabledTime?: DisabledTime<DateType>;
} & PickerPanelSharedProps<DateType>;
export type PickerPanelTimeProps<DateType> = {
picker: 'time';
} & PickerPanelSharedProps<DateType> & SharedTimeProps<DateType>;
export type PickerPanelProps<DateType> =
| PickerPanelBaseProps<DateType>
| PickerPanelDateProps<DateType>
| PickerPanelTimeProps<DateType>;
// TMP type to fit for ts 3.9.2
type OmitType<DateType> = Omit<PickerPanelBaseProps<DateType>, 'picker'> &
Omit<PickerPanelDateProps<DateType>, 'picker'> &
Omit<PickerPanelTimeProps<DateType>, 'picker'>;
type MergedPickerPanelProps<DateType> = {
picker?: PickerMode;
} & OmitType<DateType>;
function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
const {
prefixCls = 'rc-picker',
className,
style,
locale,
generateConfig,
value,
defaultValue,
pickerValue,
defaultPickerValue,
disabledDate,
mode,
picker = 'date',
tabIndex = 0,
showNow,
showTime,
showToday,
renderExtraFooter,
hideHeader,
onSelect,
onChange,
onPanelChange,
onMouseDown,
onPickerValueChange,
onOk,
components,
direction,
hourStep = 1,
minuteStep = 1,
secondStep = 1,
} = props as MergedPickerPanelProps<DateType>;
const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time';
const isHourStepValid = 24 % hourStep === 0;
const isMinuteStepValid = 60 % minuteStep === 0;
const isSecondStepValid = 60 % secondStep === 0;
if (process.env.NODE_ENV !== 'production') {
warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `value`.');
warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `defaultValue`.');
warning(isHourStepValid, `\`hourStep\` ${hourStep} is invalid. It should be a factor of 24.`);
warning(
isMinuteStepValid,
`\`minuteStep\` ${minuteStep} is invalid. It should be a factor of 60.`,
);
warning(
isSecondStepValid,
`\`secondStep\` ${secondStep} is invalid. It should be a factor of 60.`,
);
}
// ============================ State =============================
const panelContext = React.useContext(PanelContext);
const {
operationRef,
panelRef: panelDivRef,
onSelect: onContextSelect,
hideRanges,
defaultOpenValue,
} = panelContext;
const { inRange, panelPosition, rangedValue, hoverRangedValue } = React.useContext(RangeContext);
const panelRef = React.useRef<PanelRefProps>({});
// Handle init logic
const initRef = React.useRef(true);
// Value
const [mergedValue, setInnerValue] = useMergedState(null, {
value,
defaultValue,
postState: (val) => {
if (!val && defaultOpenValue && picker === 'time') {
return defaultOpenValue;
}
return val;
},
});
// View date control
const [viewDate, setInnerViewDate] = useMergedState<DateType | null, DateType>(null, {
value: pickerValue,
defaultValue: defaultPickerValue || mergedValue,
postState: (date) => {
const now = generateConfig.getNow();
if (!date) return now;
// When value is null and set showTime
if (!mergedValue && showTime) {
if (typeof showTime === 'object') {
return setDateTime(generateConfig, date, showTime.defaultValue || now);
}
if (defaultValue) {
return setDateTime(generateConfig, date, defaultValue);
}
return setDateTime(generateConfig, date, now);
}
return date;
},
});
const setViewDate = (date: DateType) => {
setInnerViewDate(date);
if (onPickerValueChange) {
onPickerValueChange(date);
}
};
// Panel control
const getInternalNextMode = (nextMode: PanelMode): PanelMode => {
const getNextMode = PickerModeMap[picker!];
if (getNextMode) {
return getNextMode(nextMode);
}
return nextMode;
};
// Save panel is changed from which panel
const [mergedMode, setInnerMode] = useMergedState(
() => {
if (picker === 'time') {
return 'time';
}
return getInternalNextMode('date');
},
{
value: mode,
},
);
React.useEffect(() => {
setInnerMode(picker);
}, [picker]);
const [sourceMode, setSourceMode] = React.useState<PanelMode>(() => mergedMode);
const onInternalPanelChange = (newMode: PanelMode | null, viewValue: DateType) => {
const nextMode = getInternalNextMode(newMode || mergedMode);
setSourceMode(mergedMode);
setInnerMode(nextMode);
if (onPanelChange && (mergedMode !== nextMode || isEqual(generateConfig, viewDate, viewDate))) {
onPanelChange(viewValue, nextMode);
}
};
const triggerSelect = (
date: DateType,
type: 'key' | 'mouse' | 'submit',
forceTriggerSelect: boolean = false,
) => {
if (mergedMode === picker || forceTriggerSelect) {
setInnerValue(date);
if (onSelect) {
onSelect(date);
}
if (onContextSelect) {
onContextSelect(date, type);
}
if (onChange && !isEqual(generateConfig, date, mergedValue) && !disabledDate?.(date)) {
onChange(date);
}
}
};
// ========================= Interactive ==========================
const onInternalKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
if (panelRef.current && panelRef.current.onKeyDown) {
if (
[
KeyCode.LEFT,
KeyCode.RIGHT,
KeyCode.UP,
KeyCode.DOWN,
KeyCode.PAGE_UP,
KeyCode.PAGE_DOWN,
KeyCode.ENTER,
].includes(e.which)
) {
e.preventDefault();
}
return panelRef.current.onKeyDown(e);
}
/* istanbul ignore next */
/* eslint-disable no-lone-blocks */
{
warning(
false,
'Panel not correct handle keyDown event. Please help to fire issue about this.',
);
return false;
}
/* eslint-enable no-lone-blocks */
};
const onInternalBlur: React.FocusEventHandler<HTMLElement> = (e) => {
if (panelRef.current && panelRef.current.onBlur) {
panelRef.current.onBlur(e);
}
};
if (operationRef && panelPosition !== 'right') {
operationRef.current = {
onKeyDown: onInternalKeyDown,
onClose: () => {
if (panelRef.current && panelRef.current.onClose) {
panelRef.current.onClose();
}
},
};
}
// ============================ Effect ============================
React.useEffect(() => {
if (value && !initRef.current) {
setInnerViewDate(value);
}
}, [value]);
React.useEffect(() => {
initRef.current = false;
}, []);
// ============================ Panels ============================
let panelNode: React.ReactNode;
const pickerProps = {
...(props as MergedPickerPanelProps<DateType>),
operationRef: panelRef,
prefixCls,
viewDate,
value: mergedValue,
onViewDateChange: setViewDate,
sourceMode,
onPanelChange: onInternalPanelChange,
disabledDate,
};
delete pickerProps.onChange;
delete pickerProps.onSelect;
switch (mergedMode) {
case 'decade':
panelNode = (
<DecadePanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'year':
panelNode = (
<YearPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'month':
panelNode = (
<MonthPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'quarter':
panelNode = (
<QuarterPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'week':
panelNode = (
<WeekPanel
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'time':
delete pickerProps.showTime;
panelNode = (
<TimePanel<DateType>
{...pickerProps}
{...(typeof showTime === 'object' ? showTime : null)}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
default:
if (showTime) {
panelNode = (
<DatetimePanel
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
} else {
panelNode = (
<DatePanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
}
}
// ============================ Footer ============================
let extraFooter: React.ReactNode;
let rangesNode: React.ReactNode;
const onNow = () => {
const now = generateConfig.getNow();
const lowerBoundTime = getLowerBoundTime(
generateConfig.getHour(now),
generateConfig.getMinute(now),
generateConfig.getSecond(now),
isHourStepValid ? hourStep : 1,
isMinuteStepValid ? minuteStep : 1,
isSecondStepValid ? secondStep : 1,
);
const adjustedNow = setTime(
generateConfig,
now,
lowerBoundTime[0], // hour
lowerBoundTime[1], // minute
lowerBoundTime[2], // second
);
triggerSelect(adjustedNow, 'submit');
};
if (!hideRanges) {
extraFooter = getExtraFooter(prefixCls, mergedMode, renderExtraFooter);
rangesNode = getRanges({
prefixCls,
components,
needConfirmButton,
okDisabled: !mergedValue || (disabledDate && disabledDate(mergedValue)),
locale,
showNow,
onNow: needConfirmButton && onNow,
onOk: () => {
if (mergedValue) {
triggerSelect(mergedValue, 'submit', true);
if (onOk) {
onOk(mergedValue);
}
}
},
});
}
let todayNode: React.ReactNode;
if (showToday && mergedMode === 'date' && picker === 'date' && !showTime) {
const now = generateConfig.getNow();
const todayCls = `${prefixCls}-today-btn`;
const disabled = disabledDate && disabledDate(now);
todayNode = (
<a
className={classNames(todayCls, disabled && `${todayCls}-disabled`)}
aria-disabled={disabled}
onClick={() => {
if (!disabled) {
triggerSelect(now, 'mouse', true);
}
}}
>
{locale.today}
</a>
);
}
return (
<PanelContext.Provider
value={{
...panelContext,
mode: mergedMode,
hideHeader: 'hideHeader' in props ? hideHeader : panelContext.hideHeader,
hidePrevBtn: inRange && panelPosition === 'right',
hideNextBtn: inRange && panelPosition === 'left',
}}
>
<div
tabIndex={tabIndex}
className={classNames(`${prefixCls}-panel`, className, {
[`${prefixCls}-panel-has-range`]: rangedValue && rangedValue[0] && rangedValue[1],
[`${prefixCls}-panel-has-range-hover`]:
hoverRangedValue && hoverRangedValue[0] && hoverRangedValue[1],
[`${prefixCls}-panel-rtl`]: direction === 'rtl',
})}
style={style}
onKeyDown={onInternalKeyDown}
onBlur={onInternalBlur}
onMouseDown={onMouseDown}
ref={panelDivRef}
>
{panelNode}
{extraFooter || rangesNode || todayNode ? (
<div className={`${prefixCls}-footer`}>
{extraFooter}
{rangesNode}
{todayNode}
</div>
) : null}
</div>
</PanelContext.Provider>
);
}
export default PickerPanel;
/* eslint-enable */

View File

@ -0,0 +1,104 @@
import * as React from 'react';
import classNames from 'classnames';
import Trigger from 'rc-trigger';
import type { AlignType } from 'rc-trigger/lib/interface';
const BUILT_IN_PLACEMENTS = {
bottomLeft: {
points: ['tl', 'bl'],
offset: [0, 4],
overflow: {
adjustX: 1,
adjustY: 1,
},
},
bottomRight: {
points: ['tr', 'br'],
offset: [0, 4],
overflow: {
adjustX: 1,
adjustY: 1,
},
},
topLeft: {
points: ['bl', 'tl'],
offset: [0, -4],
overflow: {
adjustX: 0,
adjustY: 1,
},
},
topRight: {
points: ['br', 'tr'],
offset: [0, -4],
overflow: {
adjustX: 0,
adjustY: 1,
},
},
};
type Placement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';
export type PickerTriggerProps = {
prefixCls: string;
visible: boolean;
popupElement: React.ReactElement;
popupStyle?: React.CSSProperties;
children: React.ReactElement;
dropdownClassName?: string;
transitionName?: string;
getPopupContainer?: (node: HTMLElement) => HTMLElement;
dropdownAlign?: AlignType;
range?: boolean;
popupPlacement?: Placement;
direction?: 'ltr' | 'rtl';
};
function PickerTrigger({
prefixCls,
popupElement,
popupStyle,
visible,
dropdownClassName,
dropdownAlign,
transitionName,
getPopupContainer,
children,
range,
popupPlacement,
direction,
}: PickerTriggerProps) {
const dropdownPrefixCls = `${prefixCls}-dropdown`;
const getPopupPlacement = () => {
if (popupPlacement !== undefined) {
return popupPlacement;
}
return direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
};
return (
<Trigger
showAction={[]}
hideAction={[]}
popupPlacement={getPopupPlacement()}
builtinPlacements={BUILT_IN_PLACEMENTS}
prefixCls={dropdownPrefixCls}
popupTransitionName={transitionName}
popup={popupElement}
popupAlign={dropdownAlign}
popupVisible={visible}
popupClassName={classNames(dropdownClassName, {
[`${dropdownPrefixCls}-range`]: range,
[`${dropdownPrefixCls}-rtl`]: direction === 'rtl',
})}
popupStyle={popupStyle}
getPopupContainer={getPopupContainer}
>
{children}
</Trigger>
);
}
export default PickerTrigger;

View File

@ -0,0 +1,27 @@
import { inject, InjectionKey, provide } from 'vue';
import type { NullableDateType, RangeValue } from './interface';
export type RangeContextProps = {
/**
* Set displayed range value style.
* Panel only has one value, this is only style effect.
*/
rangedValue?: [NullableDateType<any>, NullableDateType<any>] | null;
hoverRangedValue?: RangeValue<any>;
inRange?: boolean;
panelPosition?: 'left' | 'right' | false;
};
const RangeContextKey: InjectionKey<RangeContextProps> = Symbol('RangeContextProps');
export const useProvideRange = (props: RangeContextProps) => {
provide(RangeContextKey, props);
};
export const useInjectRange = () => {
return inject(RangeContextKey);
};
export default RangeContextKey;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday';
import localeData from 'dayjs/plugin/localeData';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import weekYear from 'dayjs/plugin/weekYear';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import type { GenerateConfig } from '.';
import { noteOnce } from '../../vc-util/warning';
dayjs.extend(customParseFormat);
dayjs.extend(advancedFormat);
dayjs.extend(weekday);
dayjs.extend(localeData);
dayjs.extend(weekOfYear);
dayjs.extend(weekYear);
dayjs.extend((_o, c) => {
// todo support Wo (ISO week)
const proto = c.prototype;
const oldFormat = proto.format;
proto.format = function f(formatStr: string) {
const str = (formatStr || '').replace('Wo', 'wo');
return oldFormat.bind(this)(str);
};
});
type IlocaleMapObject = Record<string, string>;
const localeMap: IlocaleMapObject = {
en_GB: 'en-gb',
en_US: 'en',
zh_CN: 'zh-cn',
zh_TW: 'zh-tw',
};
const parseLocale = (locale: string) => {
const mapLocale = localeMap[locale];
return mapLocale || locale.split('_')[0];
};
const parseNoMatchNotice = () => {
/* istanbul ignore next */
noteOnce(false, 'Not match any format. Please help to fire a issue about this.');
};
const generateConfig: GenerateConfig<Dayjs> = {
// get
getNow: () => dayjs(),
getFixedDate: string => dayjs(string, 'YYYY-MM-DD'),
getEndDate: date => date.endOf('month'),
getWeekDay: date => {
const clone = date.locale('en');
return clone.weekday() + clone.localeData().firstDayOfWeek();
},
getYear: date => date.year(),
getMonth: date => date.month(),
getDate: date => date.date(),
getHour: date => date.hour(),
getMinute: date => date.minute(),
getSecond: date => date.second(),
// set
addYear: (date, diff) => date.add(diff, 'year'),
addMonth: (date, diff) => date.add(diff, 'month'),
addDate: (date, diff) => date.add(diff, 'day'),
setYear: (date, year) => date.year(year),
setMonth: (date, month) => date.month(month),
setDate: (date, num) => date.date(num),
setHour: (date, hour) => date.hour(hour),
setMinute: (date, minute) => date.minute(minute),
setSecond: (date, second) => date.second(second),
// Compare
isAfter: (date1, date2) => date1.isAfter(date2),
isValidate: date => date.isValid(),
locale: {
getWeekFirstDay: locale =>
dayjs()
.locale(parseLocale(locale))
.localeData()
.firstDayOfWeek(),
getWeekFirstDate: (locale, date) => date.locale(parseLocale(locale)).weekday(0),
getWeek: (locale, date) => date.locale(parseLocale(locale)).week(),
getShortWeekDays: locale =>
dayjs()
.locale(parseLocale(locale))
.localeData()
.weekdaysMin(),
getShortMonths: locale =>
dayjs()
.locale(parseLocale(locale))
.localeData()
.monthsShort(),
format: (locale, date, format) => date.locale(parseLocale(locale)).format(format),
parse: (locale, text, formats) => {
const localeStr = parseLocale(locale);
for (let i = 0; i < formats.length; i += 1) {
const format = formats[i];
const formatText = text;
if (format.includes('wo') || format.includes('Wo')) {
// parse Wo
const year = formatText.split('-')[0];
const weekStr = formatText.split('-')[1];
const firstWeek = dayjs(year, 'YYYY')
.startOf('year')
.locale(localeStr);
for (let j = 0; j <= 52; j += 1) {
const nextWeek = firstWeek.add(j, 'week');
if (nextWeek.format('Wo') === weekStr) {
return nextWeek;
}
}
parseNoMatchNotice();
return null;
}
const date = dayjs(formatText, format).locale(localeStr);
if (date.isValid()) {
return date;
}
}
if (text) {
parseNoMatchNotice();
}
return null;
},
},
};
export default generateConfig;

View File

@ -0,0 +1,140 @@
import { noteOnce } from '../../vc-util/warning';
import type { Moment } from 'moment';
import moment from 'moment';
import type { GenerateConfig } from '.';
const generateConfig: GenerateConfig<Moment> = {
// get
getNow: () => moment(),
getFixedDate: string => moment(string, 'YYYY-MM-DD'),
getEndDate: date => {
const clone = date.clone();
return clone.endOf('month');
},
getWeekDay: date => {
const clone = date.clone().locale('en_US');
return clone.weekday() + clone.localeData().firstDayOfWeek();
},
getYear: date => date.year(),
getMonth: date => date.month(),
getDate: date => date.date(),
getHour: date => date.hour(),
getMinute: date => date.minute(),
getSecond: date => date.second(),
// set
addYear: (date, diff) => {
const clone = date.clone();
return clone.add(diff, 'year');
},
addMonth: (date, diff) => {
const clone = date.clone();
return clone.add(diff, 'month');
},
addDate: (date, diff) => {
const clone = date.clone();
return clone.add(diff, 'day');
},
setYear: (date, year) => {
const clone = date.clone();
return clone.year(year);
},
setMonth: (date, month) => {
const clone = date.clone();
return clone.month(month);
},
setDate: (date, num) => {
const clone = date.clone();
return clone.date(num);
},
setHour: (date, hour) => {
const clone = date.clone();
return clone.hour(hour);
},
setMinute: (date, minute) => {
const clone = date.clone();
return clone.minute(minute);
},
setSecond: (date, second) => {
const clone = date.clone();
return clone.second(second);
},
// Compare
isAfter: (date1, date2) => date1.isAfter(date2),
isValidate: date => date.isValid(),
locale: {
getWeekFirstDay: locale => {
const date = moment().locale(locale);
return date.localeData().firstDayOfWeek();
},
getWeekFirstDate: (locale, date) => {
const clone = date.clone();
const result = clone.locale(locale);
return result.weekday(0);
},
getWeek: (locale, date) => {
const clone = date.clone();
const result = clone.locale(locale);
return result.week();
},
getShortWeekDays: locale => {
const date = moment().locale(locale);
return date.localeData().weekdaysMin();
},
getShortMonths: locale => {
const date = moment().locale(locale);
return date.localeData().monthsShort();
},
format: (locale, date, format) => {
const clone = date.clone();
const result = clone.locale(locale);
return result.format(format);
},
parse: (locale, text, formats) => {
const fallbackFormatList: string[] = [];
for (let i = 0; i < formats.length; i += 1) {
let format = formats[i];
let formatText = text;
if (format.includes('wo') || format.includes('Wo')) {
format = format.replace(/wo/g, 'w').replace(/Wo/g, 'W');
const matchFormat = format.match(/[-YyMmDdHhSsWwGg]+/g);
const matchText = formatText.match(/[-\d]+/g);
if (matchFormat && matchText) {
format = matchFormat.join('');
formatText = matchText.join('');
} else {
fallbackFormatList.push(format.replace(/o/g, ''));
}
}
const date = moment(formatText, format, locale, true);
if (date.isValid()) {
return date;
}
}
// Fallback to fuzzy matching, this should always not reach match or need fire a issue
for (let i = 0; i < fallbackFormatList.length; i += 1) {
const date = moment(text, fallbackFormatList[i], locale, false);
/* istanbul ignore next */
if (date.isValid()) {
noteOnce(
false,
'Not match any format strictly and fallback to fuzzy match. Please help to fire a issue about this.',
);
return date;
}
}
return null;
},
},
};
export default generateConfig;

View File

@ -0,0 +1,109 @@
import { isInRange } from '../utils/dateUtil';
import type { GenerateConfig } from '../generate';
import type { RangeValue, NullableDateType } from '../interface';
import { getValue } from '../utils/miscUtil';
export default function useCellClassName<DateType>({
cellPrefixCls,
generateConfig,
rangedValue,
hoverRangedValue,
isInView,
isSameCell,
offsetCell,
today,
value,
}: {
cellPrefixCls: string;
generateConfig: GenerateConfig<DateType>;
isSameCell: (
current: NullableDateType<DateType>,
target: NullableDateType<DateType>,
) => boolean;
offsetCell: (date: DateType, offset: number) => DateType;
isInView: (date: DateType) => boolean;
rangedValue?: RangeValue<DateType>;
hoverRangedValue?: RangeValue<DateType>;
today?: NullableDateType<DateType>;
value?: NullableDateType<DateType>;
}) {
function getClassName(currentDate: DateType) {
const prevDate = offsetCell(currentDate, -1);
const nextDate = offsetCell(currentDate, 1);
const rangeStart = getValue(rangedValue, 0);
const rangeEnd = getValue(rangedValue, 1);
const hoverStart = getValue(hoverRangedValue, 0);
const hoverEnd = getValue(hoverRangedValue, 1);
const isRangeHovered = isInRange(
generateConfig,
hoverStart,
hoverEnd,
currentDate,
);
function isRangeStart(date: DateType) {
return isSameCell(rangeStart, date);
}
function isRangeEnd(date: DateType) {
return isSameCell(rangeEnd, date);
}
const isHoverStart = isSameCell(hoverStart, currentDate);
const isHoverEnd = isSameCell(hoverEnd, currentDate);
const isHoverEdgeStart =
(isRangeHovered || isHoverEnd) &&
(!isInView(prevDate) || isRangeEnd(prevDate));
const isHoverEdgeEnd =
(isRangeHovered || isHoverStart) &&
(!isInView(nextDate) || isRangeStart(nextDate));
return {
// In view
[`${cellPrefixCls}-in-view`]: isInView(currentDate),
// Range
[`${cellPrefixCls}-in-range`]: isInRange<DateType>(
generateConfig,
rangeStart,
rangeEnd,
currentDate,
),
[`${cellPrefixCls}-range-start`]: isRangeStart(currentDate),
[`${cellPrefixCls}-range-end`]: isRangeEnd(currentDate),
[`${cellPrefixCls}-range-start-single`]:
isRangeStart(currentDate) && !rangeEnd,
[`${cellPrefixCls}-range-end-single`]:
isRangeEnd(currentDate) && !rangeStart,
[`${cellPrefixCls}-range-start-near-hover`]:
isRangeStart(currentDate) &&
(isSameCell(prevDate, hoverStart) ||
isInRange(generateConfig, hoverStart, hoverEnd, prevDate)),
[`${cellPrefixCls}-range-end-near-hover`]:
isRangeEnd(currentDate) &&
(isSameCell(nextDate, hoverEnd) ||
isInRange(generateConfig, hoverStart, hoverEnd, nextDate)),
// Range Hover
[`${cellPrefixCls}-range-hover`]: isRangeHovered,
[`${cellPrefixCls}-range-hover-start`]: isHoverStart,
[`${cellPrefixCls}-range-hover-end`]: isHoverEnd,
// Range Edge
[`${cellPrefixCls}-range-hover-edge-start`]: isHoverEdgeStart,
[`${cellPrefixCls}-range-hover-edge-end`]: isHoverEdgeEnd,
[`${cellPrefixCls}-range-hover-edge-start-near-range`]:
isHoverEdgeStart && isSameCell(prevDate, rangeEnd),
[`${cellPrefixCls}-range-hover-edge-end-near-range`]:
isHoverEdgeEnd && isSameCell(nextDate, rangeStart),
// Others
[`${cellPrefixCls}-today`]: isSameCell(today, currentDate),
[`${cellPrefixCls}-selected`]: isSameCell(value, currentDate),
};
}
return getClassName;
}

View File

@ -0,0 +1,44 @@
import { useState, useEffect, useRef } from 'react';
import type { ValueTextConfig } from './useValueTexts';
import useValueTexts from './useValueTexts';
export default function useHoverValue<DateType>(
valueText: string,
{ formatList, generateConfig, locale }: ValueTextConfig<DateType>,
): [string, (date: DateType) => void, (immediately?: boolean) => void] {
const [value, internalSetValue] = useState<DateType>(null);
const raf = useRef(null);
function setValue(val: DateType, immediately: boolean = false) {
cancelAnimationFrame(raf.current);
if (immediately) {
internalSetValue(val);
return;
}
raf.current = requestAnimationFrame(() => {
internalSetValue(val);
});
}
const [, firstText] = useValueTexts(value, {
formatList,
generateConfig,
locale,
});
function onEnter(date: DateType) {
setValue(date);
}
function onLeave(immediately: boolean = false) {
setValue(null, immediately);
}
useEffect(() => {
onLeave(true);
}, [valueText]);
useEffect(() => () => cancelAnimationFrame(raf.current), []);
return [firstText, onEnter, onLeave];
}

View File

@ -0,0 +1,171 @@
import type * as React from 'react';
import { useState, useEffect, useRef } from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
import { addGlobalMouseDownEvent, getTargetFromEvent } from '../utils/uiUtil';
export default function usePickerInput({
open,
value,
isClickOutside,
triggerOpen,
forwardKeyDown,
onKeyDown,
blurToCancel,
onSubmit,
onCancel,
onFocus,
onBlur,
}: {
open: boolean;
value: string;
isClickOutside: (clickElement: EventTarget | null) => boolean;
triggerOpen: (open: boolean) => void;
forwardKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => boolean;
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>, preventDefault: () => void) => void;
blurToCancel?: boolean;
onSubmit: () => void | boolean;
onCancel: () => void;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
}): [React.DOMAttributes<HTMLInputElement>, { focused: boolean; typing: boolean }] {
const [typing, setTyping] = useState(false);
const [focused, setFocused] = useState(false);
/**
* We will prevent blur to handle open event when user click outside,
* since this will repeat trigger `onOpenChange` event.
*/
const preventBlurRef = useRef<boolean>(false);
const valueChangedRef = useRef<boolean>(false);
const preventDefaultRef = useRef<boolean>(false);
const inputProps: React.DOMAttributes<HTMLInputElement> = {
onMouseDown: () => {
setTyping(true);
triggerOpen(true);
},
onKeyDown: (e) => {
const preventDefault = (): void => {
preventDefaultRef.current = true;
};
onKeyDown(e, preventDefault);
if (preventDefaultRef.current) return;
switch (e.which) {
case KeyCode.ENTER: {
if (!open) {
triggerOpen(true);
} else if (onSubmit() !== false) {
setTyping(true);
}
e.preventDefault();
return;
}
case KeyCode.TAB: {
if (typing && open && !e.shiftKey) {
setTyping(false);
e.preventDefault();
} else if (!typing && open) {
if (!forwardKeyDown(e) && e.shiftKey) {
setTyping(true);
e.preventDefault();
}
}
return;
}
case KeyCode.ESC: {
setTyping(true);
onCancel();
return;
}
}
if (!open && ![KeyCode.SHIFT].includes(e.which)) {
triggerOpen(true);
} else if (!typing) {
// Let popup panel handle keyboard
forwardKeyDown(e);
}
},
onFocus: (e) => {
setTyping(true);
setFocused(true);
if (onFocus) {
onFocus(e);
}
},
onBlur: (e) => {
if (preventBlurRef.current || !isClickOutside(document.activeElement)) {
preventBlurRef.current = false;
return;
}
if (blurToCancel) {
setTimeout(() => {
let { activeElement } = document;
while (activeElement && activeElement.shadowRoot) {
activeElement = activeElement.shadowRoot.activeElement;
}
if (isClickOutside(activeElement)) {
onCancel();
}
}, 0);
} else if (open) {
triggerOpen(false);
if (valueChangedRef.current) {
onSubmit();
}
}
setFocused(false);
if (onBlur) {
onBlur(e);
}
},
};
// check if value changed
useEffect(() => {
valueChangedRef.current = false;
}, [open]);
useEffect(() => {
valueChangedRef.current = true;
}, [value]);
// Global click handler
useEffect(() =>
addGlobalMouseDownEvent((e: MouseEvent) => {
const target = getTargetFromEvent(e);
if (open) {
const clickedOutside = isClickOutside(target);
if (!clickedOutside) {
preventBlurRef.current = true;
// Always set back in case `onBlur` prevented by user
requestAnimationFrame(() => {
preventBlurRef.current = false;
});
} else if (!focused || clickedOutside) {
triggerOpen(false);
}
}
}),
);
return [inputProps, { focused, typing }];
}

View File

@ -0,0 +1,113 @@
import * as React from 'react';
import type { RangeValue, PickerMode, Locale } from '../interface';
import { getValue } from '../utils/miscUtil';
import type { GenerateConfig } from '../generate';
import { isSameDate, getQuarter } from '../utils/dateUtil';
export default function useRangeDisabled<DateType>(
{
picker,
locale,
selectedValue,
disabledDate,
disabled,
generateConfig,
}: {
picker: PickerMode;
selectedValue: RangeValue<DateType>;
disabledDate?: (date: DateType) => boolean;
disabled: [boolean, boolean];
locale: Locale;
generateConfig: GenerateConfig<DateType>;
},
disabledStart: boolean,
disabledEnd: boolean,
) {
const startDate = getValue(selectedValue, 0);
const endDate = getValue(selectedValue, 1);
function weekFirstDate(date: DateType) {
return generateConfig.locale.getWeekFirstDate(locale.locale, date);
}
function monthNumber(date: DateType) {
const year = generateConfig.getYear(date);
const month = generateConfig.getMonth(date);
return year * 100 + month;
}
function quarterNumber(date: DateType) {
const year = generateConfig.getYear(date);
const quarter = getQuarter(generateConfig, date);
return year * 10 + quarter;
}
const disabledStartDate = React.useCallback(
(date: DateType) => {
if (disabledDate && disabledDate(date)) {
return true;
}
// Disabled range
if (disabled[1] && endDate) {
return !isSameDate(generateConfig, date, endDate) && generateConfig.isAfter(date, endDate);
}
// Disabled part
if (disabledStart && endDate) {
switch (picker) {
case 'quarter':
return quarterNumber(date) > quarterNumber(endDate);
case 'month':
return monthNumber(date) > monthNumber(endDate);
case 'week':
return weekFirstDate(date) > weekFirstDate(endDate);
default:
return (
!isSameDate(generateConfig, date, endDate) && generateConfig.isAfter(date, endDate)
);
}
}
return false;
},
[disabledDate, disabled[1], endDate, disabledStart],
);
const disabledEndDate = React.useCallback(
(date: DateType) => {
if (disabledDate && disabledDate(date)) {
return true;
}
// Disabled range
if (disabled[0] && startDate) {
return (
!isSameDate(generateConfig, date, endDate) && generateConfig.isAfter(startDate, date)
);
}
// Disabled part
if (disabledEnd && startDate) {
switch (picker) {
case 'quarter':
return quarterNumber(date) < quarterNumber(startDate);
case 'month':
return monthNumber(date) < monthNumber(startDate);
case 'week':
return weekFirstDate(date) < weekFirstDate(startDate);
default:
return (
!isSameDate(generateConfig, date, startDate) &&
generateConfig.isAfter(startDate, date)
);
}
}
return false;
},
[disabledDate, disabled[0], startDate, disabledEnd],
);
return [disabledStartDate, disabledEndDate];
}

View File

@ -0,0 +1,121 @@
import * as React from 'react';
import type { RangeValue, PickerMode } from '../interface';
import type { GenerateConfig } from '../generate';
import { getValue, updateValues } from '../utils/miscUtil';
import { getClosingViewDate, isSameYear, isSameMonth, isSameDecade } from '../utils/dateUtil';
function getStartEndDistance<DateType>(
startDate: DateType,
endDate: DateType,
picker: PickerMode,
generateConfig: GenerateConfig<DateType>,
): 'same' | 'closing' | 'far' {
const startNext = getClosingViewDate(startDate, picker, generateConfig, 1);
function getDistance(compareFunc: (start: DateType | null, end: DateType | null) => boolean) {
if (compareFunc(startDate, endDate)) {
return 'same';
}
if (compareFunc(startNext, endDate)) {
return 'closing';
}
return 'far';
}
switch (picker) {
case 'year':
return getDistance((start, end) => isSameDecade(generateConfig, start, end));
case 'quarter':
case 'month':
return getDistance((start, end) => isSameYear(generateConfig, start, end));
default:
return getDistance((start, end) => isSameMonth(generateConfig, start, end));
}
}
function getRangeViewDate<DateType>(
values: RangeValue<DateType>,
index: 0 | 1,
picker: PickerMode,
generateConfig: GenerateConfig<DateType>,
): DateType | null {
const startDate = getValue(values, 0);
const endDate = getValue(values, 1);
if (index === 0) {
return startDate;
}
if (startDate && endDate) {
const distance = getStartEndDistance(startDate, endDate, picker, generateConfig);
switch (distance) {
case 'same':
return startDate;
case 'closing':
return startDate;
default:
return getClosingViewDate(endDate, picker, generateConfig, -1);
}
}
return startDate;
}
export default function useRangeViewDates<DateType>({
values,
picker,
defaultDates,
generateConfig,
}: {
values: RangeValue<DateType>;
picker: PickerMode;
defaultDates: RangeValue<DateType> | undefined;
generateConfig: GenerateConfig<DateType>;
}): [(activePickerIndex: 0 | 1) => DateType, (viewDate: DateType | null, index: 0 | 1) => void] {
const [defaultViewDates, setDefaultViewDates] = React.useState<
[DateType | null, DateType | null]
>(() => [getValue(defaultDates, 0), getValue(defaultDates, 1)]);
const [viewDates, setInternalViewDates] = React.useState<RangeValue<DateType>>(null);
const startDate = getValue(values, 0);
const endDate = getValue(values, 1);
function getViewDate(index: 0 | 1): DateType {
// If set default view date, use it
if (defaultViewDates[index]) {
return defaultViewDates[index]!;
}
return (
getValue(viewDates, index) ||
getRangeViewDate(values, index, picker, generateConfig) ||
startDate ||
endDate ||
generateConfig.getNow()
);
}
function setViewDate(viewDate: DateType | null, index: 0 | 1) {
if (viewDate) {
let newViewDates = updateValues(viewDates, viewDate, index);
// Set view date will clean up default one
setDefaultViewDates(
// Should always be an array
updateValues(defaultViewDates, null, index) || [null, null],
);
// Reset another one when not have value
const anotherIndex = (index + 1) % 2;
if (!getValue(values, anotherIndex)) {
newViewDates = updateValues(newViewDates, viewDate, anotherIndex);
}
setInternalViewDates(newViewDates);
} else if (startDate || endDate) {
// Reset all when has values when `viewDate` is `null` which means from open trigger
setInternalViewDates(null);
}
}
return [getViewDate, setViewDate];
}

View File

@ -0,0 +1,31 @@
import * as React from 'react';
export default function useTextValueMapping({
valueTexts,
onTextChange,
}: {
/** Must useMemo, to assume that `valueTexts` only match on the first change */
valueTexts: string[];
onTextChange: (text: string) => void;
}): [string, (text: string) => void, () => void] {
const [text, setInnerText] = React.useState('');
const valueTextsRef = React.useRef<string[]>([]);
valueTextsRef.current = valueTexts;
function triggerTextChange(value: string) {
setInnerText(value);
onTextChange(value);
}
function resetText() {
setInnerText(valueTextsRef.current[0]);
}
React.useEffect(() => {
if (valueTexts.every(valText => valText !== text)) {
resetText();
}
}, [valueTexts.join('||')]);
return [text, triggerTextChange, resetText];
}

View File

@ -0,0 +1,42 @@
import shallowEqual from 'shallowequal';
import useMemo from 'rc-util/lib/hooks/useMemo';
import type { GenerateConfig } from '../generate';
import type { CustomFormat, Locale } from '../interface';
import { formatValue } from '../utils/dateUtil';
export type ValueTextConfig<DateType> = {
formatList: (string | CustomFormat<DateType>)[];
generateConfig: GenerateConfig<DateType>;
locale: Locale;
};
export default function useValueTexts<DateType>(
value: DateType | null,
{ formatList, generateConfig, locale }: ValueTextConfig<DateType>,
) {
return useMemo<[string[], string]>(
() => {
if (!value) {
return [[''], ''];
}
// We will convert data format back to first format
let firstValueText: string = '';
const fullValueTexts: string[] = [];
for (let i = 0; i < formatList.length; i += 1) {
const format = formatList[i];
const formatStr = formatValue(value, { generateConfig, locale, format });
fullValueTexts.push(formatStr);
if (i === 0) {
firstValueText = formatStr;
}
}
return [fullValueTexts, firstValueText];
},
[value, formatList],
(prev, next) => prev[0] !== next[0] || !shallowEqual(prev[1], next[1]),
);
}

View File

@ -0,0 +1,13 @@
import Picker, { PickerProps } from './Picker';
import PickerPanel, { PickerPanelProps } from './PickerPanel';
import RangePicker, { RangePickerProps } from './RangePicker';
export {
PickerPanel,
RangePicker,
PickerProps,
PickerPanelProps,
RangePickerProps,
};
export default Picker;

View File

@ -0,0 +1,109 @@
import type { GenerateConfig } from './generate';
export type Locale = {
locale: string;
// ===================== Date Panel =====================
/** Display month before year in date panel header */
monthBeforeYear?: boolean;
yearFormat: string;
monthFormat?: string;
quarterFormat?: string;
today: string;
now: string;
backToToday: string;
ok: string;
timeSelect: string;
dateSelect: string;
weekSelect?: string;
clear: string;
month: string;
year: string;
previousMonth: string;
nextMonth: string;
monthSelect: string;
yearSelect: string;
decadeSelect: string;
dayFormat: string;
dateFormat: string;
dateTimeFormat: string;
previousYear: string;
nextYear: string;
previousDecade: string;
nextDecade: string;
previousCentury: string;
nextCentury: string;
shortWeekDays?: string[];
shortMonths?: string[];
};
export type PanelMode = 'time' | 'date' | 'week' | 'month' | 'quarter' | 'year' | 'decade';
export type PickerMode = Exclude<PanelMode, 'datetime' | 'decade'>;
export type PanelRefProps = {
onKeyDown?: (e: KeyboardEvent) => boolean;
onBlur?: (e: FocusEvent)=> void;
onClose?: () => void;
};
export type NullableDateType<DateType> = DateType | null | undefined;
export type OnSelect<DateType> = (value: DateType, type: 'key' | 'mouse' | 'submit') => void;
export type PanelSharedProps<DateType> = {
prefixCls: string;
generateConfig: GenerateConfig<DateType>;
value?: NullableDateType<DateType>;
viewDate: DateType;
/** [Legacy] Set default display picker view date */
defaultPickerValue?: DateType;
locale: Locale;
disabledDate?: (date: DateType) => boolean;
// prevIcon?: React.ReactNode;
// nextIcon?: React.ReactNode;
// superPrevIcon?: React.ReactNode;
// superNextIcon?: React.ReactNode;
// /**
// * Typescript can not handle generic type so we can not use `forwardRef` here.
// * Thus, move ref into operationRef.
// * This is little hack which should refactor after typescript support.
// */
// operationRef: React.MutableRefObject<PanelRefProps>;
onSelect: OnSelect<DateType>;
onViewDateChange: (value: DateType) => void;
onPanelChange: (mode: PanelMode | null, viewValue: DateType) => void;
};
export type DisabledTimes = {
disabledHours?: () => number[];
disabledMinutes?: (hour: number) => number[];
disabledSeconds?: (hour: number, minute: number) => number[];
};
export type DisabledTime<DateType> = (date: DateType | null) => DisabledTimes;
export type OnPanelChange<DateType> = (value: DateType, mode: PanelMode) => void;
export type EventValue<DateType> = DateType | null;
export type RangeValue<DateType> = [EventValue<DateType>, EventValue<DateType>] | null;
export type Components = {
button?: any;
rangeItem?: any;
};
export type RangeList = {
label: string;
onClick: () => void;
onMouseEnter: () => void;
onMouseLeave: () => void;
}[];
export type CustomFormat<DateType> = (value: DateType) => string;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ar_EG',
today: 'اليوم',
now: 'الأن',
backToToday: 'العودة إلى اليوم',
ok: 'تأكيد',
clear: 'مسح',
month: 'الشهر',
year: 'السنة',
timeSelect: 'اختيار الوقت',
dateSelect: 'اختيار التاريخ',
monthSelect: 'اختيار الشهر',
yearSelect: 'اختيار السنة',
decadeSelect: 'اختيار العقد',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'الشهر السابق (PageUp)',
nextMonth: 'الشهر التالى(PageDown)',
previousYear: 'العام السابق (Control + left)',
nextYear: 'العام التالى (Control + right)',
previousDecade: 'العقد السابق',
nextDecade: 'العقد التالى',
previousCentury: 'القرن السابق',
nextCentury: 'القرن التالى',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'az_AZ',
today: 'Bugün',
now: 'İndi',
backToToday: 'Bugünə qayıt',
ok: 'Təsdiq',
clear: 'Təmizlə',
month: 'Ay',
year: 'İl',
timeSelect: 'vaxtı seç',
dateSelect: 'tarixi seç',
weekSelect: 'Həftə seç',
monthSelect: 'Ay seç',
yearSelect: 'il seç',
decadeSelect: 'Onillik seçin',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Əvvəlki ay (PageUp)',
nextMonth: 'Növbəti ay (PageDown)',
previousYear: 'Sonuncu il (Control + left)',
nextYear: 'Növbəti il (Control + right)',
previousDecade: 'Sonuncu onillik',
nextDecade: 'Növbəti onillik',
previousCentury: 'Sonuncu əsr',
nextCentury: 'Növbəti əsr',
};
export default locale;

View File

@ -0,0 +1,31 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'bg_BG',
today: 'Днес',
now: 'Сега',
backToToday: 'Към днес',
ok: 'Добре',
clear: 'Изчистване',
month: 'Месец',
year: 'Година',
timeSelect: 'Избор на час',
dateSelect: 'Избор на дата',
monthSelect: 'Избор на месец',
yearSelect: 'Избор на година',
decadeSelect: 'Десетилетие',
yearFormat: 'YYYY',
dateFormat: 'D M YYYY',
dayFormat: 'D',
dateTimeFormat: 'D M YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Предишен месец (PageUp)',
nextMonth: 'Следващ месец (PageDown)',
previousYear: 'Последна година (Control + left)',
nextYear: 'Следваща година (Control + right)',
previousDecade: 'Предишно десетилетие',
nextDecade: 'Следващо десетилетие',
previousCentury: 'Последен век',
nextCentury: 'Следващ век',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'by_BY',
today: 'Сёння',
now: 'Зараз',
backToToday: 'Дадзеная дата',
ok: 'Ok',
clear: 'Ачысціць',
month: 'Месяц',
year: 'Год',
timeSelect: 'Выбраць час',
dateSelect: 'Выбраць дату',
weekSelect: 'Выбраць тыдзень',
monthSelect: 'Выбраць месяц',
yearSelect: 'Выбраць год',
decadeSelect: 'Выбраць дзесяцігоддзе',
yearFormat: 'YYYY',
dateFormat: 'D-M-YYYY',
dayFormat: 'D',
dateTimeFormat: 'D-M-YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Папярэдні месяц (PageUp)',
nextMonth: 'Наступны месяц (PageDown)',
previousYear: 'Папярэдні год (Control + left)',
nextYear: 'Наступны год (Control + right)',
previousDecade: 'Папярэдняе дзесяцігоддзе',
nextDecade: 'Наступнае дзесяцігоддзе',
previousCentury: 'Папярэдні век',
nextCentury: 'Наступны век',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ca_ES',
today: 'Avui',
now: 'Ara',
backToToday: 'Tornar a avui',
ok: 'Acceptar',
clear: 'Netejar',
month: 'Mes',
year: 'Any',
timeSelect: 'Seleccionar hora',
dateSelect: 'Seleccionar data',
monthSelect: 'Escollir un mes',
yearSelect: 'Escollir un any',
decadeSelect: 'Escollir una dècada',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mes anterior (PageUp)',
nextMonth: 'Mes següent (PageDown)',
previousYear: 'Any anterior (Control + left)',
nextYear: 'Mes següent (Control + right)',
previousDecade: 'Dècada anterior',
nextDecade: 'Dècada següent',
previousCentury: 'Segle anterior',
nextCentury: 'Segle següent',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'cs_CZ',
today: 'Dnes',
now: 'Nyní',
backToToday: 'Zpět na dnešek',
ok: 'Ok',
clear: 'Vymazat',
month: 'Měsíc',
year: 'Rok',
timeSelect: 'Vybrat čas',
dateSelect: 'Vybrat datum',
monthSelect: 'Vyberte měsíc',
yearSelect: 'Vyberte rok',
decadeSelect: 'Vyberte dekádu',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Předchozí měsíc (PageUp)',
nextMonth: 'Následující (PageDown)',
previousYear: 'Předchozí rok (Control + left)',
nextYear: 'Následující rok (Control + right)',
previousDecade: 'Předchozí dekáda',
nextDecade: 'Následující dekáda',
previousCentury: 'Předchozí století',
nextCentury: 'Následující století',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'da_DK',
today: 'I dag',
now: 'Nu',
backToToday: 'Gå til i dag',
ok: 'Ok',
clear: 'Ryd',
month: 'Måned',
year: 'År',
timeSelect: 'Vælg tidspunkt',
dateSelect: 'Vælg dato',
monthSelect: 'Vælg måned',
yearSelect: 'Vælg år',
decadeSelect: 'Vælg årti',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Forrige måned (Page Up)',
nextMonth: 'Næste måned (Page Down)',
previousYear: 'Forrige år (Ctrl-venstre pil)',
nextYear: 'Næste år (Ctrl-højre pil)',
previousDecade: 'Forrige årti',
nextDecade: 'Næste årti',
previousCentury: 'Forrige århundrede',
nextCentury: 'Næste århundrede',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'de_DE',
today: 'Heute',
now: 'Jetzt',
backToToday: 'Zurück zu Heute',
ok: 'OK',
clear: 'Zurücksetzen',
month: 'Monat',
year: 'Jahr',
timeSelect: 'Zeit wählen',
dateSelect: 'Datum wählen',
monthSelect: 'Wähle einen Monat',
yearSelect: 'Wähle ein Jahr',
decadeSelect: 'Wähle ein Jahrzehnt',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Vorheriger Monat (PageUp)',
nextMonth: 'Nächster Monat (PageDown)',
previousYear: 'Vorheriges Jahr (Ctrl + left)',
nextYear: 'Nächstes Jahr (Ctrl + right)',
previousDecade: 'Vorheriges Jahrzehnt',
nextDecade: 'Nächstes Jahrzehnt',
previousCentury: 'Vorheriges Jahrhundert',
nextCentury: 'Nächstes Jahrhundert',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'el_GR',
today: 'Σήμερα',
now: 'Τώρα',
backToToday: 'Πίσω στη σημερινή μέρα',
ok: 'Ok',
clear: 'Καθαρισμός',
month: 'Μήνας',
year: 'Έτος',
timeSelect: 'Επιλογή ώρας',
dateSelect: 'Επιλογή ημερομηνίας',
monthSelect: 'Επιλογή μήνα',
yearSelect: 'Επιλογή έτους',
decadeSelect: 'Επιλογή δεκαετίας',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Προηγούμενος μήνας (PageUp)',
nextMonth: 'Επόμενος μήνας (PageDown)',
previousYear: 'Προηγούμενο έτος (Control + αριστερά)',
nextYear: 'Επόμενο έτος (Control + δεξιά)',
previousDecade: 'Προηγούμενη δεκαετία',
nextDecade: 'Επόμενη δεκαετία',
previousCentury: 'Προηγούμενος αιώνας',
nextCentury: 'Επόμενος αιώνας',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'en_GB',
today: 'Today',
now: 'Now',
backToToday: 'Back to today',
ok: 'Ok',
clear: 'Clear',
month: 'Month',
year: 'Year',
timeSelect: 'Select time',
dateSelect: 'Select date',
monthSelect: 'Choose a month',
yearSelect: 'Choose a year',
decadeSelect: 'Choose a decade',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Previous month (PageUp)',
nextMonth: 'Next month (PageDown)',
previousYear: 'Last year (Control + left)',
nextYear: 'Next year (Control + right)',
previousDecade: 'Last decade',
nextDecade: 'Next decade',
previousCentury: 'Last century',
nextCentury: 'Next century',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'en_US',
today: 'Today',
now: 'Now',
backToToday: 'Back to today',
ok: 'Ok',
clear: 'Clear',
month: 'Month',
year: 'Year',
timeSelect: 'select time',
dateSelect: 'select date',
weekSelect: 'Choose a week',
monthSelect: 'Choose a month',
yearSelect: 'Choose a year',
decadeSelect: 'Choose a decade',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Previous month (PageUp)',
nextMonth: 'Next month (PageDown)',
previousYear: 'Last year (Control + left)',
nextYear: 'Next year (Control + right)',
previousDecade: 'Last decade',
nextDecade: 'Next decade',
previousCentury: 'Last century',
nextCentury: 'Next century',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'es_ES',
today: 'Hoy',
now: 'Ahora',
backToToday: 'Volver a hoy',
ok: 'Aceptar',
clear: 'Limpiar',
month: 'Mes',
year: 'Año',
timeSelect: 'Seleccionar hora',
dateSelect: 'Seleccionar fecha',
monthSelect: 'Elegir un mes',
yearSelect: 'Elegir un año',
decadeSelect: 'Elegir una década',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mes anterior (PageUp)',
nextMonth: 'Mes siguiente (PageDown)',
previousYear: 'Año anterior (Control + left)',
nextYear: 'Año siguiente (Control + right)',
previousDecade: 'Década anterior',
nextDecade: 'Década siguiente',
previousCentury: 'Siglo anterior',
nextCentury: 'Siglo siguiente',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'et_EE',
today: 'Täna',
now: 'Praegu',
backToToday: 'Tagasi tänase juurde',
ok: 'Ok',
clear: 'Tühista',
month: 'Kuu',
year: 'Aasta',
timeSelect: 'Vali aeg',
dateSelect: 'Vali kuupäev',
monthSelect: 'Vali kuu',
yearSelect: 'Vali aasta',
decadeSelect: 'Vali dekaad',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Eelmine kuu (PageUp)',
nextMonth: 'Järgmine kuu (PageDown)',
previousYear: 'Eelmine aasta (Control + left)',
nextYear: 'Järgmine aasta (Control + right)',
previousDecade: 'Eelmine dekaad',
nextDecade: 'Järgmine dekaad',
previousCentury: 'Eelmine sajand',
nextCentury: 'Järgmine sajand',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'fa_IR',
today: 'امروز',
now: 'اکنون',
backToToday: 'بازگشت به روز',
ok: 'باشه',
clear: 'پاک کردن',
month: 'ماه',
year: 'سال',
timeSelect: 'انتخاب زمان',
dateSelect: 'انتخاب تاریخ',
monthSelect: 'یک ماه را انتخاب کنید',
yearSelect: 'یک سال را انتخاب کنید',
decadeSelect: 'یک دهه را انتخاب کنید',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'ماه قبل (PageUp)',
nextMonth: 'ماه بعد (PageDown)',
previousYear: 'سال قبل (Control + left)',
nextYear: 'سال بعد (Control + right)',
previousDecade: 'دهه قبل',
nextDecade: 'دهه بعد',
previousCentury: 'قرن قبل',
nextCentury: 'قرن بعد',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'fi_FI',
today: 'Tänään',
now: 'Nyt',
backToToday: 'Tämä päivä',
ok: 'Ok',
clear: 'Tyhjennä',
month: 'Kuukausi',
year: 'Vuosi',
timeSelect: 'Valise aika',
dateSelect: 'Valitse päivä',
monthSelect: 'Valitse kuukausi',
yearSelect: 'Valitse vuosi',
decadeSelect: 'Valitse vuosikymmen',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Edellinen kuukausi (PageUp)',
nextMonth: 'Seuraava kuukausi (PageDown)',
previousYear: 'Edellinen vuosi (Control + left)',
nextYear: 'Seuraava vuosi (Control + right)',
previousDecade: 'Edellinen vuosikymmen',
nextDecade: 'Seuraava vuosikymmen',
previousCentury: 'Edellinen vuosisata',
nextCentury: 'Seuraava vuosisata',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'fr_BE',
today: "Aujourd'hui",
now: 'Maintenant',
backToToday: "Aujourd'hui",
ok: 'Ok',
clear: 'Rétablir',
month: 'Mois',
year: 'Année',
timeSelect: "Sélectionner l'heure",
dateSelect: "Sélectionner l'heure",
monthSelect: 'Choisissez un mois',
yearSelect: 'Choisissez une année',
decadeSelect: 'Choisissez une décennie',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mois précédent (PageUp)',
nextMonth: 'Mois suivant (PageDown)',
previousYear: 'Année précédente (Ctrl + gauche)',
nextYear: 'Année prochaine (Ctrl + droite)',
previousDecade: 'Décennie précédente',
nextDecade: 'Décennie suivante',
previousCentury: 'Siècle précédent',
nextCentury: 'Siècle suivant',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'fr_CA',
today: "Aujourd'hui",
now: 'Maintenant',
backToToday: "Aujourd'hui",
ok: 'Ok',
clear: 'Rétablir',
month: 'Mois',
year: 'Année',
timeSelect: "Sélectionner l'heure",
dateSelect: 'Sélectionner la date',
monthSelect: 'Choisissez un mois',
yearSelect: 'Choisissez une année',
decadeSelect: 'Choisissez une décennie',
yearFormat: 'YYYY',
dateFormat: 'DD/MM/YYYY',
dayFormat: 'DD',
dateTimeFormat: 'DD/MM/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mois précédent (PageUp)',
nextMonth: 'Mois suivant (PageDown)',
previousYear: 'Année précédente (Ctrl + gauche)',
nextYear: 'Année prochaine (Ctrl + droite)',
previousDecade: 'Décennie précédente',
nextDecade: 'Décennie suivante',
previousCentury: 'Siècle précédent',
nextCentury: 'Siècle suivant',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'fr_FR',
today: "Aujourd'hui",
now: 'Maintenant',
backToToday: "Aujourd'hui",
ok: 'Ok',
clear: 'Rétablir',
month: 'Mois',
year: 'Année',
timeSelect: "Sélectionner l'heure",
dateSelect: 'Sélectionner la date',
monthSelect: 'Choisissez un mois',
yearSelect: 'Choisissez une année',
decadeSelect: 'Choisissez une décennie',
yearFormat: 'YYYY',
dateFormat: 'DD/MM/YYYY',
dayFormat: 'DD',
dateTimeFormat: 'DD/MM/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mois précédent (PageUp)',
nextMonth: 'Mois suivant (PageDown)',
previousYear: 'Année précédente (Ctrl + gauche)',
nextYear: 'Année prochaine (Ctrl + droite)',
previousDecade: 'Décennie précédente',
nextDecade: 'Décennie suivante',
previousCentury: 'Siècle précédent',
nextCentury: 'Siècle suivant',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ga_IE',
today: 'inniu',
now: 'anois',
backToToday: 'Ar ais inniu',
ok: 'ceart go leor',
clear: 'soiléir',
month: 'mhí',
year: 'bhliain',
timeSelect: 'roghnaigh am',
dateSelect: 'roghnaigh dáta',
weekSelect: 'Roghnaigh seachtain',
monthSelect: 'Roghnaigh mí',
yearSelect: 'Roghnaigh bliain',
decadeSelect: 'Roghnaigh deich mbliana',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'An mhí roimhe seo (PageUp)',
nextMonth: 'An mhí seo chugainn (PageDown)',
previousYear: 'Anuraidh (Control + left)',
nextYear: 'An bhliain seo chugainn (Control + right)',
previousDecade: 'Le deich mbliana anuas',
nextDecade: 'Deich mbliana amach romhainn',
previousCentury: 'An chéid seo caite',
nextCentury: 'An chéad aois eile',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'gl_ES',
today: 'Hoxe',
now: 'Agora',
backToToday: 'Voltar a hoxe',
ok: 'Aceptar',
clear: 'Limpar',
month: 'Mes',
year: 'Ano',
timeSelect: 'Seleccionar hora',
dateSelect: 'Seleccionar data',
monthSelect: 'Elexir un mes',
yearSelect: 'Elexir un año',
decadeSelect: 'Elexir unha década',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mes anterior (PageUp)',
nextMonth: 'Mes seguinte (PageDown)',
previousYear: 'Ano anterior (Control + left)',
nextYear: 'Ano seguinte (Control + right)',
previousDecade: 'Década anterior',
nextDecade: 'Década seguinte',
previousCentury: 'Século anterior',
nextCentury: 'Século seguinte',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'he_IL',
today: 'היום',
now: 'עכשיו',
backToToday: 'חזור להיום',
ok: 'אישור',
clear: 'איפוס',
month: 'חודש',
year: 'שנה',
timeSelect: 'בחר שעה',
dateSelect: 'בחר תאריך',
weekSelect: 'בחר שבוע',
monthSelect: 'בחר חודש',
yearSelect: 'בחר שנה',
decadeSelect: 'בחר עשור',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'חודש קודם (PageUp)',
nextMonth: 'חודש הבא (PageDown)',
previousYear: 'שנה שעברה (Control + left)',
nextYear: 'שנה הבאה (Control + right)',
previousDecade: 'העשור הקודם',
nextDecade: 'העשור הבא',
previousCentury: 'המאה הקודמת',
nextCentury: 'המאה הבאה',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'hi_IN',
today: 'आज',
now: 'अभी',
backToToday: 'आज तक',
ok: 'ठीक',
clear: 'स्पष्ट',
month: 'महीना',
year: 'साल',
timeSelect: 'समय का चयन करें',
dateSelect: 'तारीख़ चुनें',
weekSelect: 'एक सप्ताह चुनें',
monthSelect: 'एक महीना चुनें',
yearSelect: 'एक वर्ष चुनें',
decadeSelect: 'एक दशक चुनें',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'पिछला महीना (पेजअप)',
nextMonth: 'अगले महीने (पेजडाउन)',
previousYear: 'पिछले साल (Ctrl + बाएं)',
nextYear: 'अगले साल (Ctrl + दाहिना)',
previousDecade: 'पिछला दशक',
nextDecade: 'अगले दशक',
previousCentury: 'पीछ्ली शताब्दी',
nextCentury: 'अगली सदी',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'hr_HR',
today: 'Danas',
now: 'Sad',
backToToday: 'Natrag na danas',
ok: 'Ok',
clear: 'Očisti',
month: 'Mjesec',
year: 'Godina',
timeSelect: 'odaberite vrijeme',
dateSelect: 'odaberite datum',
weekSelect: 'Odaberite tjedan',
monthSelect: 'Odaberite mjesec',
yearSelect: 'Odaberite godinu',
decadeSelect: 'Odaberite desetljeće',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Prošli mjesec (PageUp)',
nextMonth: 'Sljedeći mjesec (PageDown)',
previousYear: 'Prošla godina (Control + left)',
nextYear: 'Sljedeća godina (Control + right)',
previousDecade: 'Prošlo desetljeće',
nextDecade: 'Sljedeće desetljeće',
previousCentury: 'Prošlo stoljeće',
nextCentury: 'Sljedeće stoljeće',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'hu_HU',
today: 'Ma', // 'Today',
now: 'Most', // 'Now',
backToToday: 'Vissza a mai napra', // 'Back to today',
ok: 'Ok',
clear: 'Törlés', // 'Clear',
month: 'Hónap', // 'Month',
year: 'Év', // 'Year',
timeSelect: 'Időpont kiválasztása', // 'Select time',
dateSelect: 'Dátum kiválasztása', // 'Select date',
monthSelect: 'Hónap kiválasztása', // 'Choose a month',
yearSelect: 'Év kiválasztása', // 'Choose a year',
decadeSelect: 'Évtized kiválasztása', // 'Choose a decade',
yearFormat: 'YYYY',
dateFormat: 'YYYY/MM/DD', // 'M/D/YYYY',
dayFormat: 'DD', // 'D',
dateTimeFormat: 'YYYY/MM/DD HH:mm:ss', // 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Előző hónap (PageUp)', // 'Previous month (PageUp)',
nextMonth: 'Következő hónap (PageDown)', // 'Next month (PageDown)',
previousYear: 'Múlt év (Control + left)', // 'Last year (Control + left)',
nextYear: 'Jövő év (Control + right)', // 'Next year (Control + right)',
previousDecade: 'Előző évtized', // 'Last decade',
nextDecade: 'Következő évtized', // 'Next decade',
previousCentury: 'Múlt évszázad', // 'Last century',
nextCentury: 'Jövő évszázad', // 'Next century',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'id_ID',
today: 'Hari ini',
now: 'Sekarang',
backToToday: 'Kembali ke hari ini',
ok: 'Baik',
clear: 'Bersih',
month: 'Bulan',
year: 'Tahun',
timeSelect: 'pilih waktu',
dateSelect: 'pilih tanggal',
weekSelect: 'Pilih satu minggu',
monthSelect: 'Pilih satu bulan',
yearSelect: 'Pilih satu tahun',
decadeSelect: 'Pilih satu dekade',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Bulan sebelumnya (PageUp)',
nextMonth: 'Bulan selanjutnya (PageDown)',
previousYear: 'Tahun lalu (Control + kiri)',
nextYear: 'Tahun selanjutnya (Kontrol + kanan)',
previousDecade: 'Dekade terakhir',
nextDecade: 'Dekade berikutnya',
previousCentury: 'Abad terakhir',
nextCentury: 'Abad berikutnya',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'is_IS',
today: 'Í dag',
now: 'Núna',
backToToday: 'Til baka til dagsins í dag',
ok: 'Í lagi',
clear: 'Hreinsa',
month: 'Mánuður',
year: 'Ár',
timeSelect: 'Velja tíma',
dateSelect: 'Velja dag',
monthSelect: 'Velja mánuð',
yearSelect: 'Velja ár',
decadeSelect: 'Velja áratug',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Fyrri mánuður (PageUp)',
nextMonth: 'Næsti mánuður (PageDown)',
previousYear: 'Fyrra ár (Control + left)',
nextYear: 'Næsta ár (Control + right)',
previousDecade: 'Fyrri áratugur',
nextDecade: 'Næsti áratugur',
previousCentury: 'Fyrri öld',
nextCentury: 'Næsta öld',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'it_IT',
today: 'Oggi',
now: 'Adesso',
backToToday: 'Torna ad oggi',
ok: 'Ok',
clear: 'Cancella',
month: 'Mese',
year: 'Anno',
timeSelect: "Seleziona l'ora",
dateSelect: 'Seleziona la data',
monthSelect: 'Seleziona il mese',
yearSelect: "Seleziona l'anno",
decadeSelect: 'Seleziona il decennio',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Il mese scorso (PageUp)',
nextMonth: 'Il prossimo mese (PageDown)',
previousYear: "L'anno scorso (Control + sinistra)",
nextYear: "L'anno prossimo (Control + destra)",
previousDecade: 'Ultimo decennio',
nextDecade: 'Prossimo decennio',
previousCentury: 'Secolo precedente',
nextCentury: 'Prossimo secolo',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ja_JP',
today: '今日',
now: '現在時刻',
backToToday: '今日に戻る',
ok: '決定',
timeSelect: '時間を選択',
dateSelect: '日時を選択',
weekSelect: '週を選択',
clear: 'クリア',
month: '月',
year: '年',
previousMonth: '前月 (ページアップキー)',
nextMonth: '翌月 (ページダウンキー)',
monthSelect: '月を選択',
yearSelect: '年を選択',
decadeSelect: '年代を選択',
yearFormat: 'YYYY年',
dayFormat: 'D日',
dateFormat: 'YYYY年M月D日',
dateTimeFormat: 'YYYY年M月D日 HH時mm分ss秒',
previousYear: '前年 (Controlを押しながら左キー)',
nextYear: '翌年 (Controlを押しながら右キー)',
previousDecade: '前の年代',
nextDecade: '次の年代',
previousCentury: '前の世紀',
nextCentury: '次の世紀',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'kk_KZ',
today: 'Бүгін',
now: 'Қазір',
backToToday: 'Ағымдағы күн',
ok: 'Таңдау',
clear: 'Таза',
month: 'Ай',
year: 'Жыл',
timeSelect: 'Уақытты таңдау',
dateSelect: 'Күнді таңдау',
monthSelect: 'Айды таңдаңыз',
yearSelect: 'Жылды таңдаңыз',
decadeSelect: 'Онжылды таңдаңыз',
yearFormat: 'YYYY',
dateFormat: 'D-M-YYYY',
dayFormat: 'D',
dateTimeFormat: 'D-M-YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Алдыңғы ай (PageUp)',
nextMonth: 'Келесі ай (PageDown)',
previousYear: 'Алдыңғы жыл (Control + left)',
nextYear: 'Келесі жыл (Control + right)',
previousDecade: 'Алдыңғы онжылдық',
nextDecade: 'Келесі онжылдық',
previousCentury: 'Алдыңғы ғасыр',
nextCentury: 'Келесі ғасыр',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'km',
today: 'ថ្ងៃនេះ',
now: 'ឥឡូវ​នេះ',
backToToday: 'ត្រលប់ទៅថ្ងៃនេះ',
ok: 'កំណត់',
timeSelect: 'រយៈពេលជ្រើសរើស',
dateSelect: 'ជ្រើសរើសកាលបរិច្ឆេទ',
weekSelect: 'ជ្រើសរើសសប្តាហ៍',
clear: 'ច្បាស់',
month: 'ខែ',
year: 'ឆ្នាំ',
previousMonth: 'ខែមុន (ឡើងទំព័រ)',
nextMonth: 'ខែបន្ទាប់ (ប៊ូតុងចុះទំព័រ)',
monthSelect: 'ជ្រើសរើសខែ',
yearSelect: 'ជ្រើសរើសឆ្នាំ',
decadeSelect: 'ជ្រើសរើសអាយុ',
yearFormat: 'YYYY',
dayFormat: 'D',
dateFormat: 'YYYY-M-D',
dateTimeFormat: 'YYYY-M-D HH:mm:ss',
previousYear: 'ឆ្នាំមុន (Controlគ្រាប់ចុចបូកព្រួញខាងឆ្វេង)',
nextYear: 'ឆ្នាំក្រោយ (Control គ្រាប់ចុចបូកព្រួញស្ដាំ)',
previousDecade: 'ជំនាន់ចុងក្រោយ',
nextDecade: 'ជំនាន់​ក្រោយ',
previousCentury: 'សតវត្សចុងក្រោយ',
nextCentury: 'សតវត្សរ៍បន្ទាប់',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ku',
today: 'Îro',
now: 'Niha',
backToToday: 'Vegere îro',
ok: 'Temam',
clear: 'Paqij bike',
month: 'Meh',
year: 'Sal',
timeSelect: 'Demê hilbijêre',
dateSelect: 'Dîrok hilbijêre',
monthSelect: 'Meh hilbijêre',
yearSelect: 'Sal hilbijêre',
decadeSelect: 'Dehsal hilbijêre',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Meha peş (PageUp))',
nextMonth: 'Meha paş (PageDown)',
previousYear: 'Sala peş (Control + şep)',
nextYear: 'Sala paş (Control + rast)',
previousDecade: 'Dehsalen peş',
nextDecade: 'Dehsalen paş',
previousCentury: 'Sedsalen peş',
nextCentury: 'Sedsalen paş',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'kn_IN',
today: 'ಇಂದು',
now: 'ಈಗ',
backToToday: 'ಇಂದು ಹಿಂದಿರುಗಿ',
ok: 'ಸರಿ',
clear: 'ಸ್ಪಷ್ಟ',
month: 'ತಿಂಗಳು',
year: 'ವರ್ಷ',
timeSelect: 'ಸಮಯ ಆಯ್ಕೆಮಾಡಿ',
dateSelect: 'ದಿನಾಂಕವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ',
weekSelect: 'ಒಂದು ವಾರದ ಆರಿಸಿ',
monthSelect: 'ಒಂದು ತಿಂಗಳು ಆಯ್ಕೆಮಾಡಿ',
yearSelect: 'ಒಂದು ವರ್ಷ ಆರಿಸಿ',
decadeSelect: 'ಒಂದು ದಶಕದ ಆಯ್ಕೆಮಾಡಿ',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'ಹಿಂದಿನ ತಿಂಗಳು (ಪೇಜ್ಅಪ್)',
nextMonth: 'ಮುಂದಿನ ತಿಂಗಳು (ಪೇಜ್ಡೌನ್)',
previousYear: 'ಕಳೆದ ವರ್ಷ (Ctrl + ಎಡ)',
nextYear: 'ಮುಂದಿನ ವರ್ಷ (Ctrl + ಬಲ)',
previousDecade: 'ಕಳೆದ ದಶಕ',
nextDecade: 'ಮುಂದಿನ ದಶಕ',
previousCentury: 'ಕಳೆದ ಶತಮಾನ',
nextCentury: 'ಮುಂದಿನ ಶತಮಾನ',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ko_KR',
today: '오늘',
now: '현재 시각',
backToToday: '오늘로 돌아가기',
ok: '확인',
clear: '지우기',
month: '월',
year: '년',
timeSelect: '시간 선택',
dateSelect: '날짜 선택',
monthSelect: '달 선택',
yearSelect: '연 선택',
decadeSelect: '연대 선택',
yearFormat: 'YYYY년',
dateFormat: 'YYYY-MM-DD',
dayFormat: 'Do',
dateTimeFormat: 'YYYY-MM-DD HH:mm:ss',
monthBeforeYear: false,
previousMonth: '이전 달 (PageUp)',
nextMonth: '다음 달 (PageDown)',
previousYear: '이전 해 (Control + left)',
nextYear: '다음 해 (Control + right)',
previousDecade: '이전 연대',
nextDecade: '다음 연대',
previousCentury: '이전 세기',
nextCentury: '다음 세기',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'lt_LT',
today: 'Šiandien',
now: 'Dabar',
backToToday: 'Rodyti šiandien',
ok: 'Gerai',
clear: 'Išvalyti',
month: 'Mėnesis',
year: 'Metai',
timeSelect: 'Pasirinkti laiką',
dateSelect: 'Pasirinkti datą',
monthSelect: 'Pasirinkti mėnesį',
yearSelect: 'Pasirinkti metus',
decadeSelect: 'Pasirinkti dešimtmetį',
yearFormat: 'YYYY',
dateFormat: 'YYYY-MM-DD',
dayFormat: 'DD',
dateTimeFormat: 'YYYY-MM-DD HH:MM:SS',
monthBeforeYear: true,
previousMonth: 'Buvęs mėnesis (PageUp)',
nextMonth: 'Sekantis mėnesis (PageDown)',
previousYear: 'Buvę metai (Control + left)',
nextYear: 'Sekantis metai (Control + right)',
previousDecade: 'Buvęs dešimtmetis',
nextDecade: 'Sekantis dešimtmetis',
previousCentury: 'Buvęs amžius',
nextCentury: 'Sekantis amžius',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'lv_LV',
today: 'Šodien',
now: 'Tagad',
backToToday: 'Atpakaļ pie šodienas',
ok: 'Ok',
clear: 'Skaidrs',
month: 'Mēnesis',
year: 'Gads',
timeSelect: 'Izvēlieties laiku',
dateSelect: 'Izvēlieties datumu',
monthSelect: 'Izvēlieties mēnesi',
yearSelect: 'Izvēlieties gadu',
decadeSelect: 'Izvēlieties desmit gadus',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Iepriekšējais mēnesis (PageUp)',
nextMonth: 'Nākammēnes (PageDown)',
previousYear: 'Pagājušais gads (Control + left)',
nextYear: 'Nākamgad (Control + right)',
previousDecade: 'Pēdējā desmitgadē',
nextDecade: 'Nākamā desmitgade',
previousCentury: 'Pagājušajā gadsimtā',
nextCentury: 'Nākamajā gadsimtā',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'mk_MK',
today: 'Денес',
now: 'Сега',
backToToday: 'Назад до денес',
ok: 'ОК',
clear: 'Избриши',
month: 'Месец',
year: 'Година',
timeSelect: 'Избери време',
dateSelect: 'Избери датум',
monthSelect: 'Избери месец',
yearSelect: 'Избери година',
decadeSelect: 'Избери деценија',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Претходен месец (PageUp)',
nextMonth: 'Нареден месец (PageDown)',
previousYear: 'Претходна година (Control + left)',
nextYear: 'Наредна година (Control + right)',
previousDecade: 'Претходна деценија',
nextDecade: 'Наредна деценија',
previousCentury: 'Претходен век',
nextCentury: 'Нареден век',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ml_IN',
today: 'ഇന്ന്',
now: 'ഇപ്പോൾ',
backToToday: 'ഇന്നത്തെ ദിവസത്തിലേക്ക് തിരിച്ചു പോകുക',
ok: 'ശരിയാണ്',
clear: 'നീക്കം ചെയ്യുക',
month: 'മാസം',
year: 'വർഷം',
timeSelect: 'സമയം തിരഞ്ഞെടുക്കുക',
dateSelect: 'ദിവസം തിരഞ്ഞെടുക്കുക',
weekSelect: 'വാരം തിരഞ്ഞെടുക്കുക',
monthSelect: 'മാസം തിരഞ്ഞെടുക്കുക',
yearSelect: 'വർഷം തിരഞ്ഞെടുക്കുക',
decadeSelect: 'ദശാബ്ദം തിരഞ്ഞെടുക്കുക',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'കഴിഞ്ഞ മാസം (PageUp)',
nextMonth: 'അടുത്ത മാസം (PageDown)',
previousYear: 'കഴിഞ്ഞ വർഷം (Control + left)',
nextYear: 'അടുത്ത വർഷം (Control + right)',
previousDecade: 'കഴിഞ്ഞ ദശാബ്ദം',
nextDecade: 'അടുത്ത ദശാബ്ദം',
previousCentury: 'കഴിഞ്ഞ നൂറ്റാണ്ട്',
nextCentury: 'അടുത്ത നൂറ്റാണ്ട്',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'mm_MM',
today: 'ယနေ့',
now: 'ယခု',
backToToday: 'ယနေ့ မတိုင်ခင် သို့',
ok: 'Ok',
clear: 'ရှင်းမည်',
month: 'လ',
year: 'နှစ်',
timeSelect: 'အချိန်ကို ရွေး',
dateSelect: 'နေ့ကို ရွေး',
weekSelect: 'အပတ်ကို ရွေး',
monthSelect: 'လကို ရွေး',
yearSelect: 'နှစ်ကို ရွေး',
decadeSelect: 'ဆယ်စုနှစ်ကို ရွေး',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'ယခင် လ (PageUp)',
nextMonth: 'နောက် လ (PageDown)',
previousYear: 'ယခင် နှစ် (Control + left)',
nextYear: 'နောက် နှစ် (Control + right)',
previousDecade: 'ယခင် ဆယ်စုနှစ်',
nextDecade: 'နောက် ဆယ်စုနှစ်',
previousCentury: 'ယခင် ရာစုနှစ်',
nextCentury: 'နောက် ရာစုနှစ်',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'mn_MN',
today: 'Өнөөдөр',
now: 'Одоо',
backToToday: 'Өнөөдөрлүү буцах',
ok: 'Ok',
clear: 'Цэвэрлэх',
month: 'Сар',
year: 'Жил',
timeSelect: 'Цаг сонгох',
dateSelect: 'Огноо сонгох',
weekSelect: '7 хоног сонгох',
monthSelect: 'Сар сонгох',
yearSelect: 'Жил сонгох',
decadeSelect: 'Арван сонгох',
yearFormat: 'YYYY',
dateFormat: 'YYYY/MM/DD',
dayFormat: 'DD',
dateTimeFormat: 'YYYY/MM/DD HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Өмнөх сар (PageUp)',
nextMonth: 'Дараа сар (PageDown)',
previousYear: 'Өмнөх жил (Control + left)',
nextYear: 'Дараа жил (Control + right)',
previousDecade: 'Өмнөх арван',
nextDecade: 'Дараа арван',
previousCentury: 'Өмнөх зуун',
nextCentury: 'Дараа зуун',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ms_MY',
today: 'Hari ini',
now: 'Sekarang',
backToToday: 'Kembali ke hari ini',
ok: 'Ok',
timeSelect: 'Pilih masa',
dateSelect: 'Pilih tarikh',
weekSelect: 'Pilih minggu',
clear: 'Padam',
month: 'Bulan',
year: 'Tahun',
previousMonth: 'Bulan lepas',
nextMonth: 'Bulan depan',
monthSelect: 'Pilih bulan',
yearSelect: 'Pilih tahun',
decadeSelect: 'Pilih dekad',
yearFormat: 'YYYY',
dayFormat: 'D',
dateFormat: 'M/D/YYYY',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
previousYear: 'Tahun lepas (Ctrl+left)',
nextYear: 'Tahun depan (Ctrl+right)',
previousDecade: 'Dekad lepas',
nextDecade: 'Dekad depan',
previousCentury: 'Abad lepas',
nextCentury: 'Abad depan',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'nb_NO',
today: 'I dag',
now: 'Nå',
backToToday: 'Gå til i dag',
ok: 'Ok',
clear: 'Annuller',
month: 'Måned',
year: 'År',
timeSelect: 'Velg tidspunkt',
dateSelect: 'Velg dato',
weekSelect: 'Velg uke',
monthSelect: 'Velg måned',
yearSelect: 'Velg år',
decadeSelect: 'Velg tiår',
yearFormat: 'YYYY',
dateFormat: 'DD.MM.YYYY',
dayFormat: 'DD',
dateTimeFormat: 'DD.MM.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Forrige måned (PageUp)',
nextMonth: 'Neste måned (PageDown)',
previousYear: 'Forrige år (Control + venstre)',
nextYear: 'Neste år (Control + høyre)',
previousDecade: 'Forrige tiår',
nextDecade: 'Neste tiår',
previousCentury: 'Forrige århundre',
nextCentury: 'Neste århundre',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'nl_BE',
today: 'Vandaag',
now: 'Nu',
backToToday: 'Terug naar vandaag',
ok: 'Ok',
clear: 'Reset',
month: 'Maand',
year: 'Jaar',
timeSelect: 'Selecteer tijd',
dateSelect: 'Selecteer datum',
monthSelect: 'Kies een maand',
yearSelect: 'Kies een jaar',
decadeSelect: 'Kies een decennium',
yearFormat: 'YYYY',
dateFormat: 'D-M-YYYY',
dayFormat: 'D',
dateTimeFormat: 'D-M-YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Vorige maand (PageUp)',
nextMonth: 'Volgende maand (PageDown)',
previousYear: 'Vorig jaar (Control + left)',
nextYear: 'Volgend jaar (Control + right)',
previousDecade: 'Vorig decennium',
nextDecade: 'Volgend decennium',
previousCentury: 'Vorige eeuw',
nextCentury: 'Volgende eeuw',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'nl_NL',
today: 'Vandaag',
now: 'Nu',
backToToday: 'Terug naar vandaag',
ok: 'Ok',
clear: 'Reset',
month: 'Maand',
year: 'Jaar',
timeSelect: 'Selecteer tijd',
dateSelect: 'Selecteer datum',
monthSelect: 'Kies een maand',
yearSelect: 'Kies een jaar',
decadeSelect: 'Kies een decennium',
yearFormat: 'YYYY',
dateFormat: 'D-M-YYYY',
dayFormat: 'D',
dateTimeFormat: 'D-M-YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Vorige maand (PageUp)',
nextMonth: 'Volgende maand (PageDown)',
previousYear: 'Vorig jaar (Control + left)',
nextYear: 'Volgend jaar (Control + right)',
previousDecade: 'Vorig decennium',
nextDecade: 'Volgend decennium',
previousCentury: 'Vorige eeuw',
nextCentury: 'Volgende eeuw',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'pl_PL',
today: 'Dzisiaj',
now: 'Teraz',
backToToday: 'Ustaw dzisiaj',
ok: 'Ok',
clear: 'Wyczyść',
month: 'Miesiąc',
year: 'Rok',
timeSelect: 'Ustaw czas',
dateSelect: 'Ustaw datę',
monthSelect: 'Wybierz miesiąc',
yearSelect: 'Wybierz rok',
decadeSelect: 'Wybierz dekadę',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Poprzedni miesiąc (PageUp)',
nextMonth: 'Następny miesiąc (PageDown)',
previousYear: 'Ostatni rok (Ctrl + left)',
nextYear: 'Następny rok (Ctrl + right)',
previousDecade: 'Ostatnia dekada',
nextDecade: 'Następna dekada',
previousCentury: 'Ostatni wiek',
nextCentury: 'Następny wiek',
};
export default locale;

View File

@ -0,0 +1,47 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'pt_BR',
today: 'Hoje',
now: 'Agora',
backToToday: 'Voltar para hoje',
ok: 'Ok',
clear: 'Limpar',
month: 'Mês',
year: 'Ano',
timeSelect: 'Selecionar hora',
dateSelect: 'Selecionar data',
monthSelect: 'Escolher mês',
yearSelect: 'Escolher ano',
decadeSelect: 'Escolher década',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: false,
previousMonth: 'Mês anterior (PageUp)',
nextMonth: 'Próximo mês (PageDown)',
previousYear: 'Ano anterior (Control + esquerda)',
nextYear: 'Próximo ano (Control + direita)',
previousDecade: 'Década anterior',
nextDecade: 'Próxima década',
previousCentury: 'Século anterior',
nextCentury: 'Próximo século',
shortWeekDays: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
shortMonths: [
'Jan',
'Fev',
'Mar',
'Abr',
'Mai',
'Jun',
'Jul',
'Ago',
'Set',
'Out',
'Nov',
'Dez',
],
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'pt_PT',
today: 'Hoje',
now: 'Agora',
backToToday: 'Hoje',
ok: 'Ok',
clear: 'Limpar',
month: 'Mês',
year: 'Ano',
timeSelect: 'Selecionar hora',
dateSelect: 'Selecionar data',
monthSelect: 'Selecionar mês',
yearSelect: 'Selecionar ano',
decadeSelect: 'Selecionar década',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Mês anterior (PageUp)',
nextMonth: 'Mês seguinte (PageDown)',
previousYear: 'Ano anterior (Control + left)',
nextYear: 'Ano seguinte (Control + right)',
previousDecade: 'Década anterior',
nextDecade: 'Década seguinte',
previousCentury: 'Século anterior',
nextCentury: 'Século seguinte',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ro_RO',
today: 'Azi',
now: 'Acum',
backToToday: 'Înapoi la azi',
ok: 'Ok',
clear: 'Șterge',
month: 'Lună',
year: 'An',
timeSelect: 'selectează timpul',
dateSelect: 'selectează data',
weekSelect: 'Alege o săptămână',
monthSelect: 'Alege o lună',
yearSelect: 'Alege un an',
decadeSelect: 'Alege un deceniu',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Luna anterioară (PageUp)',
nextMonth: 'Luna următoare (PageDown)',
previousYear: 'Anul anterior (Control + stânga)',
nextYear: 'Anul următor (Control + dreapta)',
previousDecade: 'Deceniul anterior',
nextDecade: 'Deceniul următor',
previousCentury: 'Secolul anterior',
nextCentury: 'Secolul următor',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ru_RU',
today: 'Сегодня',
now: 'Сейчас',
backToToday: 'Текущая дата',
ok: 'ОК',
clear: 'Очистить',
month: 'Месяц',
year: 'Год',
timeSelect: 'Выбрать время',
dateSelect: 'Выбрать дату',
monthSelect: 'Выбрать месяц',
yearSelect: 'Выбрать год',
decadeSelect: 'Выбрать десятилетие',
yearFormat: 'YYYY',
dateFormat: 'D-M-YYYY',
dayFormat: 'D',
dateTimeFormat: 'D-M-YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Предыдущий месяц (PageUp)',
nextMonth: 'Следующий месяц (PageDown)',
previousYear: 'Предыдущий год (Control + left)',
nextYear: 'Следующий год (Control + right)',
previousDecade: 'Предыдущее десятилетие',
nextDecade: 'Следущее десятилетие',
previousCentury: 'Предыдущий век',
nextCentury: 'Следующий век',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'sk_SK',
today: 'Dnes',
now: 'Teraz',
backToToday: 'Späť na dnes',
ok: 'Ok',
clear: 'Vymazať',
month: 'Mesiac',
year: 'Rok',
timeSelect: 'Vybrať čas',
dateSelect: 'Vybrať dátum',
monthSelect: 'Vybrať mesiac',
yearSelect: 'Vybrať rok',
decadeSelect: 'Vybrať dekádu',
yearFormat: 'YYYY',
dateFormat: 'D.M.YYYY',
dayFormat: 'D',
dateTimeFormat: 'D.M.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Predchádzajúci mesiac (PageUp)',
nextMonth: 'Nasledujúci mesiac (PageDown)',
previousYear: 'Predchádzajúci rok (Control + left)',
nextYear: 'Nasledujúci rok (Control + right)',
previousDecade: 'Predchádzajúca dekáda',
nextDecade: 'Nasledujúca dekáda',
previousCentury: 'Predchádzajúce storočie',
nextCentury: 'Nasledujúce storočie',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'sl_SI',
today: 'Danes',
now: 'Trenutno',
backToToday: 'Nazaj na danes',
ok: 'V redu',
clear: 'Počisti',
month: 'Mesec',
year: 'Leto',
timeSelect: 'Izberite čas',
dateSelect: 'Izberite datum',
monthSelect: 'Izberite mesec',
yearSelect: 'Izberite leto',
decadeSelect: 'Izberite desetletje',
yearFormat: 'YYYY',
dateFormat: 'DD.MM.YYYY',
dayFormat: 'D',
dateTimeFormat: 'DD.MM.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Prejšnji mesec (PageUp)',
nextMonth: 'Naslednji mesec (PageDown)',
previousYear: 'Prejšnje leto (Control + left)',
nextYear: 'Naslednje leto (Control + right)',
previousDecade: 'Prejšnje desetletje',
nextDecade: 'Naslednje desetletje',
previousCentury: 'Prejšnje stoletje',
nextCentury: 'Naslednje stoletje',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'sr_RS',
today: 'Danas',
now: 'Sada',
backToToday: 'Vrati se na danas',
ok: 'U redu',
clear: 'Obriši',
month: 'Mesec',
year: 'Godina',
timeSelect: 'Izaberi vreme',
dateSelect: 'Izaberi datum',
monthSelect: 'Izaberi mesec',
yearSelect: 'Izaberi godinu',
decadeSelect: 'Izaberi deceniju',
yearFormat: 'YYYY',
dateFormat: 'DD.MM.YYYY',
dayFormat: 'D',
dateTimeFormat: 'DD.MM.YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Prethodni mesec (PageUp)',
nextMonth: 'Sledeći mesec (PageDown)',
previousYear: 'Prethodna godina (Control + left)',
nextYear: 'Sledeća godina (Control + right)',
previousDecade: 'Prethodna decenija',
nextDecade: 'Sledeća decenija',
previousCentury: 'Prethodni vek',
nextCentury: 'Sledeći vek',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'sv_SE',
today: 'I dag',
now: 'Nu',
backToToday: 'Till idag',
ok: 'Ok',
clear: 'Avbryt',
month: 'Månad',
year: 'År',
timeSelect: 'Välj tidpunkt',
dateSelect: 'Välj datum',
monthSelect: 'Välj månad',
yearSelect: 'Välj år',
decadeSelect: 'Välj årtionde',
yearFormat: 'YYYY',
dateFormat: 'YYYY-MM-DD',
dayFormat: 'D',
dateTimeFormat: 'YYYY-MM-DD H:mm:ss',
monthBeforeYear: true,
previousMonth: 'Förra månaden (PageUp)',
nextMonth: 'Nästa månad (PageDown)',
previousYear: 'Föreg år (Control + left)',
nextYear: 'Nästa år (Control + right)',
previousDecade: 'Föreg årtionde',
nextDecade: 'Nästa årtionde',
previousCentury: 'Föreg århundrade',
nextCentury: 'Nästa århundrade',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ta_IN',
today: 'இன்று',
now: 'இப்போது',
backToToday: 'இன்றுக்கு திரும்பு',
ok: 'சரி',
clear: 'அழி',
month: 'மாதம்',
year: 'வருடம்',
timeSelect: 'நேரத்தைத் தேர்ந்தெடு',
dateSelect: 'தேதியைத் தேர்ந்தெடு',
weekSelect: 'வாரத்தைத் தேர்வுசெய்க',
monthSelect: 'மாதத்தைத் தேர்வுசெய்க',
yearSelect: 'வருடத்தைத் தேர்வுசெய்க',
decadeSelect: 'தசாப்தத்தைத் தேர்வுசெய்க',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'முந்தைய மாதம் (PageUp)',
nextMonth: 'அடுத்த மாதம் (PageDown)',
previousYear: 'முந்தைய வருடம் (Control + left)',
nextYear: 'அடுத்த வருடம் (Control + right)',
previousDecade: 'முந்தைய தசாப்தம்',
nextDecade: 'அடுத்த தசாப்தம்',
previousCentury: 'முந்தைய நூற்றாண்டு',
nextCentury: 'அடுத்த நூற்றாண்டு',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'th_TH',
today: 'วันนี้',
now: 'ตอนนี้',
backToToday: 'กลับไปยังวันนี้',
ok: 'ตกลง',
clear: 'ลบล้าง',
month: 'เดือน',
year: 'ปี',
timeSelect: 'เลือกเวลา',
dateSelect: 'เลือกวัน',
monthSelect: 'เลือกเดือน',
yearSelect: 'เลือกปี',
decadeSelect: 'เลือกทศวรรษ',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'เดือนก่อนหน้า (PageUp)',
nextMonth: 'เดือนถัดไป (PageDown)',
previousYear: 'ปีก่อนหน้า (Control + left)',
nextYear: 'ปีถัดไป (Control + right)',
previousDecade: 'ทศวรรษก่อนหน้า',
nextDecade: 'ทศวรรษถัดไป',
previousCentury: 'ศตวรรษก่อนหน้า',
nextCentury: 'ศตวรรษถัดไป',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'tr_TR',
today: 'Bugün',
now: 'Şimdi',
backToToday: 'Bugüne Geri Dön',
ok: 'tamam',
clear: 'Temizle',
month: 'Ay',
year: 'Yıl',
timeSelect: 'Zaman Seç',
dateSelect: 'Tarih Seç',
monthSelect: 'Ay Seç',
yearSelect: 'Yıl Seç',
decadeSelect: 'On Yıl Seç',
yearFormat: 'YYYY',
dateFormat: 'M/D/YYYY',
dayFormat: 'D',
dateTimeFormat: 'M/D/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Önceki Ay (PageUp)',
nextMonth: 'Sonraki Ay (PageDown)',
previousYear: 'Önceki Yıl (Control + Sol)',
nextYear: 'Sonraki Yıl (Control + Sağ)',
previousDecade: 'Önceki On Yıl',
nextDecade: 'Sonraki On Yıl',
previousCentury: 'Önceki Yüzyıl',
nextCentury: 'Sonraki Yüzyıl',
};
export default locale;

View File

@ -0,0 +1,31 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'ug_CN',
today: 'بۈگۈن',
now: 'ھازىر',
backToToday: 'بۈگۈنگە قايتىش',
ok: 'مۇقىملاشتۇرۇش',
timeSelect: 'ۋاقىت تاللاش',
dateSelect: 'كۈن تاللاش',
clear: 'تازىلاش',
month: 'ئاي',
year: 'يىل',
previousMonth: 'ئالدىنقى ئاي(ئالدىنقى بەت )',
nextMonth: 'كېلەركى ئاي (كېلەركى بەت)',
monthSelect: 'ئاي تاللاش',
yearSelect: 'يىل تاللاش',
decadeSelect: 'يىللارنى تاللاش',
yearFormat: 'YYYY-يىلى',
dayFormat: 'D-كۈنى',
dateFormat: 'YYYY-يىلىM-ئاينىڭD-كۈنى',
dateTimeFormat: 'YYYY-يىلىM—ئاينىڭD-كۈنى، HH:mm:ss',
previousYear: 'ئالدىنقى يىلى (Controlبىلەن يۆنىلىش كونۇپكىسى)',
nextYear: 'كېلەركى يىلى (Controlبىلەن يۆنىلىش كونۇپكىسى)',
previousDecade: 'ئالدىنقى يىللار',
nextDecade: 'كېيىنكى يىللار',
previousCentury: 'ئالدىنقى ئەسىر',
nextCentury: 'كېيىنكى ئەسىر',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'uk_UA',
today: 'Сьогодні',
now: 'Зараз',
backToToday: 'Поточна дата',
ok: 'Ok',
clear: 'Очистити',
month: 'Місяць',
year: 'Рік',
timeSelect: 'Обрати час',
dateSelect: 'Обрати дату',
monthSelect: 'Обрати місяць',
yearSelect: 'Обрати рік',
decadeSelect: 'Обрати десятиріччя',
yearFormat: 'YYYY',
dateFormat: 'D-M-YYYY',
dayFormat: 'D',
dateTimeFormat: 'D-M-YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Попередній місяць (PageUp)',
nextMonth: 'Наступний місяць (PageDown)',
previousYear: 'Попередній рік (Control + left)',
nextYear: 'Наступний рік (Control + right)',
previousDecade: 'Попереднє десятиріччя',
nextDecade: 'Наступне десятиріччя',
previousCentury: 'Попереднє століття',
nextCentury: 'Наступне століття',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'vi_VN',
today: 'Hôm nay',
now: 'Bây giờ',
backToToday: 'Trở về hôm nay',
ok: 'Ok',
clear: 'Xóa',
month: 'Tháng',
year: 'Năm',
timeSelect: 'Chọn thời gian',
dateSelect: 'Chọn ngày',
weekSelect: 'Chọn tuần',
monthSelect: 'Chọn tháng',
yearSelect: 'Chọn năm',
decadeSelect: 'Chọn thập kỷ',
yearFormat: 'YYYY',
dateFormat: 'D/M/YYYY',
dayFormat: 'D',
dateTimeFormat: 'D/M/YYYY HH:mm:ss',
monthBeforeYear: true,
previousMonth: 'Tháng trước (PageUp)',
nextMonth: 'Tháng sau (PageDown)',
previousYear: 'Năm trước (Control + left)',
nextYear: 'Năm sau (Control + right)',
previousDecade: 'Thập kỷ trước',
nextDecade: 'Thập kỷ sau',
previousCentury: 'Thế kỷ trước',
nextCentury: 'Thế kỷ sau',
};
export default locale;

View File

@ -0,0 +1,32 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'zh_CN',
today: '今天',
now: '此刻',
backToToday: '返回今天',
ok: '确定',
timeSelect: '选择时间',
dateSelect: '选择日期',
weekSelect: '选择周',
clear: '清除',
month: '月',
year: '年',
previousMonth: '上个月 (翻页上键)',
nextMonth: '下个月 (翻页下键)',
monthSelect: '选择月份',
yearSelect: '选择年份',
decadeSelect: '选择年代',
yearFormat: 'YYYY年',
dayFormat: 'D日',
dateFormat: 'YYYY年M月D日',
dateTimeFormat: 'YYYY年M月D日 HH时mm分ss秒',
previousYear: '上一年 (Control键加左方向键)',
nextYear: '下一年 (Control键加右方向键)',
previousDecade: '上一年代',
nextDecade: '下一年代',
previousCentury: '上一世纪',
nextCentury: '下一世纪',
};
export default locale;

View File

@ -0,0 +1,33 @@
import type { Locale } from '../interface';
const locale: Locale = {
locale: 'zh_TW',
today: '今天',
now: '此刻',
backToToday: '返回今天',
ok: '確定',
timeSelect: '選擇時間',
dateSelect: '選擇日期',
weekSelect: '選擇周',
clear: '清除',
month: '月',
year: '年',
previousMonth: '上個月 (翻頁上鍵)',
nextMonth: '下個月 (翻頁下鍵)',
monthSelect: '選擇月份',
yearSelect: '選擇年份',
decadeSelect: '選擇年代',
yearFormat: 'YYYY年',
dayFormat: 'D日',
dateFormat: 'YYYY年M月D日',
dateTimeFormat: 'YYYY年M月D日 HH時mm分ss秒',
previousYear: '上一年 (Control鍵加左方向鍵)',
nextYear: '下一年 (Control鍵加右方向鍵)',
previousDecade: '上一年代',
nextDecade: '下一年代',
previousCentury: '上一世紀',
nextCentury: '下一世紀',
};
export default locale;

View File

@ -0,0 +1,111 @@
import type { GenerateConfig } from '../../generate';
import {
WEEK_DAY_COUNT,
getWeekStartDate,
isSameDate,
isSameMonth,
formatValue,
} from '../../utils/dateUtil';
import type { Locale } from '../../interface';
import useCellClassName from '../../hooks/useCellClassName';
import PanelBody from '../PanelBody';
import { VueNode } from '../../../_util/type';
import { useInjectRange } from '../../RangeContext';
export type DateRender<DateType> = (currentDate: DateType, today: DateType) => VueNode;
export type DateBodyPassProps<DateType> = {
dateRender?: DateRender<DateType>;
disabledDate?: (date: DateType) => boolean;
// Used for week panel
prefixColumn?: (date: DateType) => VueNode;
rowClassName?: (date: DateType) => string;
};
export type DateBodyProps<DateType> = {
prefixCls: string;
generateConfig: GenerateConfig<DateType>;
value?: DateType | null;
viewDate: DateType;
locale: Locale;
rowCount: number;
onSelect: (value: DateType) => void;
} & DateBodyPassProps<DateType>;
function DateBody<DateType>(props: DateBodyProps<DateType>) {
const {
prefixCls,
generateConfig,
prefixColumn,
locale,
rowCount,
viewDate,
value,
dateRender,
} = props;
const { rangedValue, hoverRangedValue } =useInjectRange()
const baseDate = getWeekStartDate(locale.locale, generateConfig, viewDate);
const cellPrefixCls = `${prefixCls}-cell`;
const weekFirstDay = generateConfig.locale.getWeekFirstDay(locale.locale);
const today = generateConfig.getNow();
// ============================== Header ==============================
const headerCells: VueNode[] = [];
const weekDaysLocale: string[] =
locale.shortWeekDays ||
(generateConfig.locale.getShortWeekDays
? generateConfig.locale.getShortWeekDays(locale.locale)
: []);
if (prefixColumn) {
headerCells.push(<th key="empty" aria-label="empty cell" />);
}
for (let i = 0; i < WEEK_DAY_COUNT; i += 1) {
headerCells.push(<th key={i}>{weekDaysLocale[(i + weekFirstDay) % WEEK_DAY_COUNT]}</th>);
}
// =============================== Body ===============================
const getCellClassName = useCellClassName({
cellPrefixCls,
today,
value,
generateConfig,
rangedValue: prefixColumn ? null : rangedValue,
hoverRangedValue: prefixColumn ? null : hoverRangedValue,
isSameCell: (current, target) => isSameDate(generateConfig, current, target),
isInView: date => isSameMonth(generateConfig, date, viewDate),
offsetCell: (date, offset) => generateConfig.addDate(date, offset),
});
const getCellNode = dateRender ? (date: DateType) => dateRender(date, today) : undefined;
return (
<PanelBody
{...props}
rowNum={rowCount}
colNum={WEEK_DAY_COUNT}
baseDate={baseDate}
getCellNode={getCellNode}
getCellText={generateConfig.getDate}
getCellClassName={getCellClassName}
getCellDate={generateConfig.addDate}
titleCell={date =>
formatValue(date, {
locale,
format: 'YYYY-MM-DD',
generateConfig,
})
}
headerCells={headerCells}
/>
);
}
DateBody.displayName = 'DateBody'
DateBody.inheritAttrs = false;
export default DateBody;

View File

@ -0,0 +1,105 @@
import Header from '../Header';
import type { Locale } from '../../interface';
import type { GenerateConfig } from '../../generate';
import { useInjectPanel } from '../../PanelContext';
import { formatValue } from '../../utils/dateUtil';
import { VueNode } from '../../../_util/type';
export type DateHeaderProps<DateType> = {
prefixCls: string;
viewDate: DateType;
value?: DateType | null;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
onPrevYear: () => void;
onNextYear: () => void;
onPrevMonth: () => void;
onNextMonth: () => void;
onYearClick: () => void;
onMonthClick: () => void;
};
function DateHeader<DateType>(props: DateHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
locale,
viewDate,
onNextMonth,
onPrevMonth,
onNextYear,
onPrevYear,
onYearClick,
onMonthClick,
} = props;
const { hideHeader } = useInjectPanel()
if (hideHeader) {
return null;
}
const headerPrefixCls = `${prefixCls}-header`;
const monthsLocale: string[] =
locale.shortMonths ||
(generateConfig.locale.getShortMonths
? generateConfig.locale.getShortMonths(locale.locale)
: []);
const month = generateConfig.getMonth(viewDate);
// =================== Month & Year ===================
const yearNode: VueNode = (
<button
type="button"
key="year"
onClick={onYearClick}
tabindex={-1}
class={`${prefixCls}-year-btn`}
>
{formatValue(viewDate, {
locale,
format: locale.yearFormat,
generateConfig,
})}
</button>
);
const monthNode: VueNode = (
<button
type="button"
key="month"
onClick={onMonthClick}
tabindex={-1}
class={`${prefixCls}-month-btn`}
>
{locale.monthFormat
? formatValue(viewDate, {
locale,
format: locale.monthFormat,
generateConfig,
})
: monthsLocale[month]}
</button>
);
const monthYearNodes = locale.monthBeforeYear ? [monthNode, yearNode] : [yearNode, monthNode];
return (
<Header
{...props}
prefixCls={headerPrefixCls}
onSuperPrev={onPrevYear}
onPrev={onPrevMonth}
onNext={onNextMonth}
onSuperNext={onNextYear}
>
{monthYearNodes}
</Header>
);
}
DateHeader.displayName = 'DateHeader'
DateHeader.inheritAttrs = false;
export default DateHeader;

View File

@ -0,0 +1,116 @@
import type { DateBodyPassProps, DateRender } from './DateBody';
import DateBody from './DateBody';
import DateHeader from './DateHeader';
import type { PanelSharedProps } from '../../interface';
import { WEEK_DAY_COUNT } from '../../utils/dateUtil';
import type { KeyboardConfig } from '../../utils/uiUtil';
import { createKeyDownHandler } from '../../utils/uiUtil';
import classNames from '../../../_util/classNames';
import { ref } from '@vue/reactivity';
const DATE_ROW_COUNT = 6;
export type DatePanelProps<DateType> = {
active?: boolean;
dateRender?: DateRender<DateType>;
// Used for week panel
panelName?: string;
keyboardConfig?: KeyboardConfig;
} & PanelSharedProps<DateType> & DateBodyPassProps<DateType>;
function DatePanel<DateType>(props: DatePanelProps<DateType>) {
const {
prefixCls,
panelName = 'date',
keyboardConfig,
active,
generateConfig,
value,
viewDate,
onViewDateChange,
onPanelChange,
onSelect,
} = props;
const panelPrefixCls = `${prefixCls}-${panelName}-panel`;
const operationRef = ref()
// ======================= Keyboard =======================
operationRef.value = {
onKeyDown: event =>
createKeyDownHandler(event, {
onLeftRight: diff => {
onSelect(generateConfig.addDate(value || viewDate, diff), 'key');
},
onCtrlLeftRight: diff => {
onSelect(generateConfig.addYear(value || viewDate, diff), 'key');
},
onUpDown: diff => {
onSelect(generateConfig.addDate(value || viewDate, diff * WEEK_DAY_COUNT), 'key');
},
onPageUpDown: diff => {
onSelect(generateConfig.addMonth(value || viewDate, diff), 'key');
},
...keyboardConfig,
}),
};
// ==================== View Operation ====================
const onYearChange = (diff: number) => {
const newDate = generateConfig.addYear(viewDate, diff);
onViewDateChange(newDate);
onPanelChange(null, newDate);
};
const onMonthChange = (diff: number) => {
const newDate = generateConfig.addMonth(viewDate, diff);
onViewDateChange(newDate);
onPanelChange(null, newDate);
};
return (
<div
class={classNames(panelPrefixCls, {
[`${panelPrefixCls}-active`]: active,
})}
>
<DateHeader
{...props}
prefixCls={prefixCls}
value={value}
viewDate={viewDate}
// View Operation
onPrevYear={() => {
onYearChange(-1);
}}
onNextYear={() => {
onYearChange(1);
}}
onPrevMonth={() => {
onMonthChange(-1);
}}
onNextMonth={() => {
onMonthChange(1);
}}
onMonthClick={() => {
onPanelChange('month', viewDate);
}}
onYearClick={() => {
onPanelChange('year', viewDate);
}}
/>
<DateBody
{...props}
onSelect={date => onSelect(date, 'mouse')}
prefixCls={prefixCls}
value={value}
viewDate={viewDate}
rowCount={DATE_ROW_COUNT}
/>
</div>
);
}
DatePanel.displayName ='DatePanel'
DatePanel.inheritAttrs = false;
export default DatePanel;

View File

@ -0,0 +1,186 @@
import type { DatePanelProps } from '../DatePanel';
import DatePanel from '../DatePanel';
import type { SharedTimeProps } from '../TimePanel';
import TimePanel from '../TimePanel';
import { tuple } from '../../utils/miscUtil';
import { setDateTime as setTime } from '../../utils/timeUtil';
import type { PanelRefProps, DisabledTime } from '../../interface';
import KeyCode from '../../../_util/KeyCode';
import classNames from '../../../_util/classNames';
import { ref } from '@vue/reactivity';
export type DatetimePanelProps<DateType> = {
disabledTime?: DisabledTime<DateType>;
showTime?: boolean | SharedTimeProps<DateType>;
defaultValue?: DateType;
} & Omit<
DatePanelProps<DateType>,
'disabledHours' | 'disabledMinutes' | 'disabledSeconds'
>;
const ACTIVE_PANEL = tuple('date', 'time');
type ActivePanelType = typeof ACTIVE_PANEL[number];
function DatetimePanel<DateType>(props: DatetimePanelProps<DateType>) {
const {
prefixCls,
operationRef,
generateConfig,
value,
defaultValue,
disabledTime,
showTime,
onSelect,
} = props;
const panelPrefixCls = `${prefixCls}-datetime-panel`;
const activePanel = ref<ActivePanelType | null>(
null,
);
const dateOperationRef = ref<PanelRefProps>({});
const timeOperationRef = ref<PanelRefProps>({});
const timeProps = typeof showTime === 'object' ? { ...showTime } : {};
// ======================= Keyboard =======================
function getNextActive(offset: number) {
const activeIndex = ACTIVE_PANEL.indexOf(activePanel.value!) + offset;
const nextActivePanel = ACTIVE_PANEL[activeIndex] || null;
return nextActivePanel;
}
const onBlur = (e?: FocusEvent) => {
if (timeOperationRef.value.onBlur) {
timeOperationRef.value.onBlur(e!);
}
activePanel.value = null;
};
operationRef.current = {
onKeyDown: event => {
// Switch active panel
if (event.which === KeyCode.TAB) {
const nextActivePanel = getNextActive(event.shiftKey ? -1 : 1);
activePanel.value = nextActivePanel
if (nextActivePanel) {
event.preventDefault();
}
return true;
}
// Operate on current active panel
if (activePanel.value) {
const ref =
activePanel.value === 'date' ? dateOperationRef : timeOperationRef;
if (ref.value && ref.value.onKeyDown) {
ref.value.onKeyDown(event);
}
return true;
}
// Switch first active panel if operate without panel
if (
[KeyCode.LEFT, KeyCode.RIGHT, KeyCode.UP, KeyCode.DOWN].includes(
event.which,
)
) {
activePanel.value = 'date'
return true;
}
return false;
},
onBlur,
onClose: onBlur,
};
// ======================== Events ========================
const onInternalSelect = (date: DateType, source: 'date' | 'time') => {
let selectedDate = date;
if (source === 'date' && !value && timeProps.defaultValue) {
// Date with time defaultValue
selectedDate = generateConfig.setHour(
selectedDate,
generateConfig.getHour(timeProps.defaultValue),
);
selectedDate = generateConfig.setMinute(
selectedDate,
generateConfig.getMinute(timeProps.defaultValue),
);
selectedDate = generateConfig.setSecond(
selectedDate,
generateConfig.getSecond(timeProps.defaultValue),
);
} else if (source === 'time' && !value && defaultValue) {
selectedDate = generateConfig.setYear(
selectedDate,
generateConfig.getYear(defaultValue),
);
selectedDate = generateConfig.setMonth(
selectedDate,
generateConfig.getMonth(defaultValue),
);
selectedDate = generateConfig.setDate(
selectedDate,
generateConfig.getDate(defaultValue),
);
}
if (onSelect) {
onSelect(selectedDate, 'mouse');
}
};
// ======================== Render ========================
const disabledTimes = disabledTime ? disabledTime(value || null) : {};
return (
<div
class={classNames(panelPrefixCls, {
[`${panelPrefixCls}-active`]: activePanel.value,
})}
>
<DatePanel
{...props}
operationRef={dateOperationRef}
active={activePanel.value === 'date'}
onSelect={date => {
onInternalSelect(
setTime(
generateConfig,
date,
showTime && typeof showTime === 'object'
? showTime.defaultValue
: null,
),
'date',
);
}}
/>
<TimePanel
{...props}
format={undefined}
{...timeProps}
{...disabledTimes}
defaultValue={undefined}
operationRef={timeOperationRef}
active={activePanel.value === 'time'}
onSelect={date => {
onInternalSelect(date, 'time');
}}
/>
</div>
);
}
DatetimePanel.displayName ='DatetimePanel'
DatetimePanel.inheritAttrs = false;
export default DatetimePanel;

View File

@ -0,0 +1,68 @@
import type { GenerateConfig } from '../../generate';
import { DECADE_DISTANCE_COUNT, DECADE_UNIT_DIFF } from '.';
import PanelBody from '../PanelBody';
export const DECADE_COL_COUNT = 3;
const DECADE_ROW_COUNT = 4;
export type YearBodyProps<DateType> = {
prefixCls: string;
generateConfig: GenerateConfig<DateType>;
viewDate: DateType;
disabledDate?: (date: DateType) => boolean;
onSelect: (value: DateType) => void;
};
function DecadeBody<DateType>(props: YearBodyProps<DateType>) {
const DECADE_UNIT_DIFF_DES = DECADE_UNIT_DIFF - 1;
const { prefixCls, viewDate, generateConfig } = props;
const cellPrefixCls = `${prefixCls}-cell`;
const yearNumber = generateConfig.getYear(viewDate);
const decadeYearNumber = Math.floor(yearNumber / DECADE_UNIT_DIFF) * DECADE_UNIT_DIFF;
const startDecadeYear = Math.floor(yearNumber / DECADE_DISTANCE_COUNT) * DECADE_DISTANCE_COUNT;
const endDecadeYear = startDecadeYear + DECADE_DISTANCE_COUNT - 1;
const baseDecadeYear = generateConfig.setYear(
viewDate,
startDecadeYear -
Math.ceil(
(DECADE_COL_COUNT * DECADE_ROW_COUNT * DECADE_UNIT_DIFF - DECADE_DISTANCE_COUNT) / 2,
),
);
const getCellClassName = (date: DateType) => {
const startDecadeNumber = generateConfig.getYear(date);
const endDecadeNumber = startDecadeNumber + DECADE_UNIT_DIFF_DES;
return {
[`${cellPrefixCls}-in-view`]:
startDecadeYear <= startDecadeNumber && endDecadeNumber <= endDecadeYear,
[`${cellPrefixCls}-selected`]: startDecadeNumber === decadeYearNumber,
};
};
return (
<PanelBody
{...props}
rowNum={DECADE_ROW_COUNT}
colNum={DECADE_COL_COUNT}
baseDate={baseDecadeYear}
getCellText={date => {
const startDecadeNumber = generateConfig.getYear(date);
return `${startDecadeNumber}-${startDecadeNumber + DECADE_UNIT_DIFF_DES}`;
}}
getCellClassName={getCellClassName}
getCellDate={(date, offset) => generateConfig.addYear(date, offset * DECADE_UNIT_DIFF)}
/>
);
}
DecadeBody.displayName ='DecadeBody'
DecadeBody.inheritAttrs = false;
export default DecadeBody;

View File

@ -0,0 +1,51 @@
import Header from '../Header';
import type { GenerateConfig } from '../../generate';
import { DECADE_DISTANCE_COUNT } from '.';
import { useInjectPanel } from '../../PanelContext';
export type YearHeaderProps<DateType> = {
prefixCls: string;
viewDate: DateType;
generateConfig: GenerateConfig<DateType>;
onPrevDecades: () => void;
onNextDecades: () => void;
};
function DecadeHeader<DateType>(props: YearHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
viewDate,
onPrevDecades,
onNextDecades,
} = props;
const { hideHeader } =useInjectPanel()
if (hideHeader) {
return null;
}
const headerPrefixCls = `${prefixCls}-header`;
const yearNumber = generateConfig.getYear(viewDate);
const startYear =
Math.floor(yearNumber / DECADE_DISTANCE_COUNT) * DECADE_DISTANCE_COUNT;
const endYear = startYear + DECADE_DISTANCE_COUNT - 1;
return (
<Header
{...props}
prefixCls={headerPrefixCls}
onSuperPrev={onPrevDecades}
onSuperNext={onNextDecades}
>
{startYear}-{endYear}
</Header>
);
}
DecadeHeader.displayName ='DecadeHeader'
DecadeHeader.inheritAttrs = false;
export default DecadeHeader;

View File

@ -0,0 +1,96 @@
import DecadeHeader from './DecadeHeader';
import DecadeBody, { DECADE_COL_COUNT } from './DecadeBody';
import type { PanelSharedProps } from '../../interface';
import { createKeyDownHandler } from '../../utils/uiUtil';
export type DecadePanelProps<DateType> = PanelSharedProps<DateType>;
export const DECADE_UNIT_DIFF = 10;
export const DECADE_DISTANCE_COUNT = DECADE_UNIT_DIFF * 10;
function DecadePanel<DateType>(props: DecadePanelProps<DateType>) {
const {
prefixCls,
onViewDateChange,
generateConfig,
viewDate,
operationRef,
onSelect,
onPanelChange,
} = props;
const panelPrefixCls = `${prefixCls}-decade-panel`;
// ======================= Keyboard =======================
operationRef.current = {
onKeyDown: event =>
createKeyDownHandler(event, {
onLeftRight: diff => {
onSelect(
generateConfig.addYear(viewDate, diff * DECADE_UNIT_DIFF),
'key',
);
},
onCtrlLeftRight: diff => {
onSelect(
generateConfig.addYear(viewDate, diff * DECADE_DISTANCE_COUNT),
'key',
);
},
onUpDown: diff => {
onSelect(
generateConfig.addYear(
viewDate,
diff * DECADE_UNIT_DIFF * DECADE_COL_COUNT,
),
'key',
);
},
onEnter: () => {
onPanelChange('year', viewDate);
},
}),
};
// ==================== View Operation ====================
const onDecadesChange = (diff: number) => {
const newDate = generateConfig.addYear(
viewDate,
diff * DECADE_DISTANCE_COUNT,
);
onViewDateChange(newDate);
onPanelChange(null, newDate);
};
const onInternalSelect = (date: DateType) => {
onSelect(date, 'mouse');
onPanelChange('year', date);
};
return (
<div class={panelPrefixCls}>
<DecadeHeader
{...props}
prefixCls={prefixCls}
onPrevDecades={() => {
onDecadesChange(-1);
}}
onNextDecades={() => {
onDecadesChange(1);
}}
/>
<DecadeBody
{...props}
prefixCls={prefixCls}
onSelect={onInternalSelect}
/>
</div>
);
}
DecadePanel.displayName ='DecadePanel'
DecadePanel.inheritAttrs = false;
export default DecadePanel;

View File

@ -0,0 +1,100 @@
import { CSSProperties } from '@vue/runtime-dom';
import { VueNode } from '../../_util/type';
import { useInjectPanel } from '../PanelContext';
const HIDDEN_STYLE: CSSProperties = {
visibility: 'hidden',
};
export type HeaderProps = {
prefixCls: string;
// Icons
prevIcon?: VueNode;
nextIcon?: VueNode;
superPrevIcon?: VueNode;
superNextIcon?: VueNode;
/** Last one step */
onPrev?: () => void;
/** Next one step */
onNext?: () => void;
/** Last multiple steps */
onSuperPrev?: () => void;
/** Next multiple steps */
onSuperNext?: () => void;
children?: VueNode;
};
function Header(
{
prefixCls,
prevIcon = '\u2039',
nextIcon = '\u203A',
superPrevIcon = '\u00AB',
superNextIcon = '\u00BB',
onSuperPrev,
onSuperNext,
onPrev,
onNext,
}: HeaderProps,
{ slots },
) {
const { hideNextBtn, hidePrevBtn } = useInjectPanel();
return (
<div class={prefixCls}>
{onSuperPrev && (
<button
type="button"
onClick={onSuperPrev}
tabindex={-1}
class={`${prefixCls}-super-prev-btn`}
style={hidePrevBtn ? HIDDEN_STYLE : {}}
>
{superPrevIcon}
</button>
)}
{onPrev && (
<button
type="button"
onClick={onPrev}
tabindex={-1}
class={`${prefixCls}-prev-btn`}
style={hidePrevBtn ? HIDDEN_STYLE : {}}
>
{prevIcon}
</button>
)}
<div class={`${prefixCls}-view`}>{slots.default?.()}</div>
{onNext && (
<button
type="button"
onClick={onNext}
tabindex={-1}
class={`${prefixCls}-next-btn`}
style={hideNextBtn ? HIDDEN_STYLE : {}}
>
{nextIcon}
</button>
)}
{onSuperNext && (
<button
type="button"
onClick={onSuperNext}
tabindex={-1}
class={`${prefixCls}-super-next-btn`}
style={hideNextBtn ? HIDDEN_STYLE : {}}
>
{superNextIcon}
</button>
)}
</div>
);
}
Header.displayName = 'Header';
Header.inheritAttrs = false;
export default Header;

View File

@ -0,0 +1,89 @@
import type { GenerateConfig } from '../../generate';
import type { Locale } from '../../interface';
import { formatValue, isSameMonth } from '../../utils/dateUtil';
import { useInjectRange } from '../../RangeContext';
import useCellClassName from '../../hooks/useCellClassName';
import PanelBody from '../PanelBody';
import { VueNode } from '../../../_util/type';
export const MONTH_COL_COUNT = 3;
const MONTH_ROW_COUNT = 4;
export type MonthCellRender<DateType> = (currentDate: DateType, locale: Locale) => VueNode;
export type MonthBodyProps<DateType> = {
prefixCls: string;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
value?: DateType | null;
viewDate: DateType;
disabledDate?: (date: DateType) => boolean;
monthCellRender?: MonthCellRender<DateType>;
onSelect: (value: DateType) => void;
};
function MonthBody<DateType>(props: MonthBodyProps<DateType>) {
const { prefixCls, locale, value, viewDate, generateConfig, monthCellRender } = props;
const { rangedValue, hoverRangedValue } = useInjectRange()
const cellPrefixCls = `${prefixCls}-cell`;
const getCellClassName = useCellClassName({
cellPrefixCls,
value,
generateConfig,
rangedValue,
hoverRangedValue,
isSameCell: (current, target) => isSameMonth(generateConfig, current, target),
isInView: () => true,
offsetCell: (date, offset) => generateConfig.addMonth(date, offset),
});
const monthsLocale: string[] =
locale.shortMonths ||
(generateConfig.locale.getShortMonths
? generateConfig.locale.getShortMonths(locale.locale)
: []);
const baseMonth = generateConfig.setMonth(viewDate, 0);
const getCellNode = monthCellRender
? (date: DateType) => monthCellRender(date, locale)
: undefined;
return (
<PanelBody
{...props}
rowNum={MONTH_ROW_COUNT}
colNum={MONTH_COL_COUNT}
baseDate={baseMonth}
getCellNode={getCellNode}
getCellText={date =>
locale.monthFormat
? formatValue(date, {
locale,
format: locale.monthFormat,
generateConfig,
})
: monthsLocale[generateConfig.getMonth(date)]
}
getCellClassName={getCellClassName}
getCellDate={generateConfig.addMonth}
titleCell={date =>
formatValue(date, {
locale,
format: 'YYYY-MM',
generateConfig,
})
}
/>
);
}
MonthBody.displayName ='MonthBody'
MonthBody.inheritAttrs = false;
export default MonthBody;

View File

@ -0,0 +1,58 @@
import Header from '../Header';
import type { Locale } from '../../interface';
import type { GenerateConfig } from '../../generate';
import { useInjectPanel } from '../../PanelContext';
import { formatValue } from '../../utils/dateUtil';
export type MonthHeaderProps<DateType> = {
prefixCls: string;
viewDate: DateType;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
onPrevYear: () => void;
onNextYear: () => void;
onYearClick: () => void;
};
function MonthHeader<DateType>(props: MonthHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
locale,
viewDate,
onNextYear,
onPrevYear,
onYearClick,
} = props;
const { hideHeader } = useInjectPanel()
if (hideHeader) {
return null;
}
const headerPrefixCls = `${prefixCls}-header`;
return (
<Header
{...props}
prefixCls={headerPrefixCls}
onSuperPrev={onPrevYear}
onSuperNext={onNextYear}
>
<button type="button" onClick={onYearClick} class={`${prefixCls}-year-btn`}>
{formatValue(viewDate, {
locale,
format: locale.yearFormat,
generateConfig,
})}
</button>
</Header>
);
}
MonthHeader.displayName ='MonthHeader'
MonthHeader.inheritAttrs = false;
export default MonthHeader;

View File

@ -0,0 +1,86 @@
import MonthHeader from './MonthHeader';
import type { MonthCellRender } from './MonthBody';
import MonthBody, { MONTH_COL_COUNT } from './MonthBody';
import type { PanelSharedProps } from '../../interface';
import { createKeyDownHandler } from '../../utils/uiUtil';
export type MonthPanelProps<DateType> = {
monthCellContentRender?: MonthCellRender<DateType>;
} & PanelSharedProps<DateType>;
function MonthPanel<DateType>(props: MonthPanelProps<DateType>) {
const {
prefixCls,
operationRef,
onViewDateChange,
generateConfig,
value,
viewDate,
onPanelChange,
onSelect,
} = props;
const panelPrefixCls = `${prefixCls}-month-panel`;
// ======================= Keyboard =======================
operationRef.current = {
onKeyDown: event =>
createKeyDownHandler(event, {
onLeftRight: diff => {
onSelect(generateConfig.addMonth(value || viewDate, diff), 'key');
},
onCtrlLeftRight: diff => {
onSelect(generateConfig.addYear(value || viewDate, diff), 'key');
},
onUpDown: diff => {
onSelect(
generateConfig.addMonth(value || viewDate, diff * MONTH_COL_COUNT),
'key',
);
},
onEnter: () => {
onPanelChange('date', value || viewDate);
},
}),
};
// ==================== View Operation ====================
const onYearChange = (diff: number) => {
const newDate = generateConfig.addYear(viewDate, diff);
onViewDateChange(newDate);
onPanelChange(null, newDate);
};
return (
<div class={panelPrefixCls}>
<MonthHeader
{...props}
prefixCls={prefixCls}
onPrevYear={() => {
onYearChange(-1);
}}
onNextYear={() => {
onYearChange(1);
}}
onYearClick={() => {
onPanelChange('year', viewDate);
}}
/>
<MonthBody<DateType>
{...props}
prefixCls={prefixCls}
onSelect={date => {
onSelect(date, 'mouse');
onPanelChange('date', date);
}}
/>
</div>
);
}
MonthPanel.displayName ='MonthPanel'
MonthPanel.inheritAttrs = false;
export default MonthPanel;

View File

@ -0,0 +1,144 @@
import { useInjectPanel } from '../PanelContext';
import type { GenerateConfig } from '../generate';
import { getLastDay } from '../utils/timeUtil';
import type { PanelMode } from '../interface';
import { getCellDateDisabled } from '../utils/dateUtil';
import { VueNode } from '../../_util/type';
import classNames from '../../_util/classNames';
export type PanelBodyProps<DateType> = {
prefixCls: string;
disabledDate?: (date: DateType) => boolean;
onSelect: (value: DateType) => void;
picker?: PanelMode;
// By panel
headerCells?: VueNode;
rowNum: number;
colNum: number;
baseDate: DateType;
getCellClassName: (date: DateType) => Record<string, boolean | undefined>;
getCellDate: (date: DateType, offset: number) => DateType;
getCellText: (date: DateType) => VueNode;
getCellNode?: (date: DateType) => VueNode;
titleCell?: (date: DateType) => string;
generateConfig: GenerateConfig<DateType>;
// Used for week panel
prefixColumn?: (date: DateType) => VueNode;
rowClassName?: (date: DateType) => string;
};
function PanelBody<DateType>({
prefixCls,
disabledDate,
onSelect,
picker,
rowNum,
colNum,
prefixColumn,
rowClassName,
baseDate,
getCellClassName,
getCellText,
getCellNode,
getCellDate,
generateConfig,
titleCell,
headerCells,
}: PanelBodyProps<DateType>) {
const { onDateMouseEnter, onDateMouseLeave, mode } = useInjectPanel()
const cellPrefixCls = `${prefixCls}-cell`;
// =============================== Body ===============================
const rows: VueNode[] = [];
for (let i = 0; i < rowNum; i += 1) {
const row: VueNode[] = [];
let rowStartDate: DateType;
for (let j = 0; j < colNum; j += 1) {
const offset = i * colNum + j;
const currentDate = getCellDate(baseDate, offset);
const disabled = getCellDateDisabled({
cellDate: currentDate,
mode,
disabledDate,
generateConfig,
});
if (j === 0) {
rowStartDate = currentDate;
if (prefixColumn) {
row.push(prefixColumn(rowStartDate));
}
}
const title = titleCell && titleCell(currentDate);
row.push(
<td
key={j}
title={title}
class={classNames(cellPrefixCls, {
[`${cellPrefixCls}-disabled`]: disabled,
[`${cellPrefixCls}-start`]:
getCellText(currentDate) === 1 || (picker === 'year' && Number(title) % 10 === 0),
[`${cellPrefixCls}-end`]:
title === getLastDay(generateConfig, currentDate) ||
(picker === 'year' && Number(title) % 10 === 9),
...getCellClassName(currentDate),
})}
onClick={() => {
if (!disabled) {
onSelect(currentDate);
}
}}
onMouseenter={() => {
if (!disabled && onDateMouseEnter) {
onDateMouseEnter(currentDate);
}
}}
onMouseleave={() => {
if (!disabled && onDateMouseLeave) {
onDateMouseLeave(currentDate);
}
}}
>
{getCellNode ? (
getCellNode(currentDate)
) : (
<div class={`${cellPrefixCls}-inner`}>{getCellText(currentDate)}</div>
)}
</td>,
);
}
rows.push(
<tr key={i} class={rowClassName && rowClassName(rowStartDate!)}>
{row}
</tr>,
);
}
return (
<div class={`${prefixCls}-body`}>
<table class={`${prefixCls}-content`}>
{headerCells && (
<thead>
<tr>{headerCells}</tr>
</thead>
)}
<tbody>{rows}</tbody>
</table>
</div>
);
}
PanelBody.displayName = 'PanelBody';
PanelBody.inheritAttrs = false;
export default PanelBody;

View File

@ -0,0 +1,71 @@
import type { GenerateConfig } from '../../generate';
import type { Locale } from '../../interface';
import { formatValue, isSameQuarter } from '../../utils/dateUtil';
import RangeContext, { useInjectRange } from '../../RangeContext';
import useCellClassName from '../../hooks/useCellClassName';
import PanelBody from '../PanelBody';
export const QUARTER_COL_COUNT = 4;
const QUARTER_ROW_COUNT = 1;
export type QuarterBodyProps<DateType> = {
prefixCls: string;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
value?: DateType | null;
viewDate: DateType;
disabledDate?: (date: DateType) => boolean;
onSelect: (value: DateType) => void;
};
function QuarterBody<DateType>(props: QuarterBodyProps<DateType>) {
const { prefixCls, locale, value, viewDate, generateConfig } = props;
const { rangedValue, hoverRangedValue } = useInjectRange()
const cellPrefixCls = `${prefixCls}-cell`;
const getCellClassName = useCellClassName({
cellPrefixCls,
value,
generateConfig,
rangedValue,
hoverRangedValue,
isSameCell: (current, target) => isSameQuarter(generateConfig, current, target),
isInView: () => true,
offsetCell: (date, offset) => generateConfig.addMonth(date, offset * 3),
});
const baseQuarter = generateConfig.setDate(generateConfig.setMonth(viewDate, 0), 1);
return (
<PanelBody
{...props}
rowNum={QUARTER_ROW_COUNT}
colNum={QUARTER_COL_COUNT}
baseDate={baseQuarter}
getCellText={date =>
formatValue(date, {
locale,
format: locale.quarterFormat || '[Q]Q',
generateConfig,
})
}
getCellClassName={getCellClassName}
getCellDate={(date, offset) => generateConfig.addMonth(date, offset * 3)}
titleCell={date =>
formatValue(date, {
locale,
format: 'YYYY-[Q]Q',
generateConfig,
})
}
/>
);
}
QuarterBody.displayName ='QuarterBody'
QuarterBody.inheritAttrs = false;
export default QuarterBody;

View File

@ -0,0 +1,57 @@
import Header from '../Header';
import type { Locale } from '../../interface';
import type { GenerateConfig } from '../../generate';
import { useInjectPanel } from '../../PanelContext';
import { formatValue } from '../../utils/dateUtil';
export type QuarterHeaderProps<DateType> = {
prefixCls: string;
viewDate: DateType;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
onPrevYear: () => void;
onNextYear: () => void;
onYearClick: () => void;
};
function QuarterHeader<DateType>(props: QuarterHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
locale,
viewDate,
onNextYear,
onPrevYear,
onYearClick,
} = props;
const { hideHeader } =useInjectPanel()
if (hideHeader) {
return null;
}
const headerPrefixCls = `${prefixCls}-header`;
return (
<Header
{...props}
prefixCls={headerPrefixCls}
onSuperPrev={onPrevYear}
onSuperNext={onNextYear}
>
<button type="button" onClick={onYearClick} class={`${prefixCls}-year-btn`}>
{formatValue(viewDate, {
locale,
format: locale.yearFormat,
generateConfig,
})}
</button>
</Header>
);
}
QuarterHeader.displayName ='QuarterHeader'
QuarterHeader.inheritAttrs = false;
export default QuarterHeader;

View File

@ -0,0 +1,76 @@
import QuarterHeader from './QuarterHeader';
import QuarterBody from './QuarterBody';
import type { PanelSharedProps } from '../../interface';
import { createKeyDownHandler } from '../../utils/uiUtil';
export type QuarterPanelProps<DateType> = {} & PanelSharedProps<DateType>;
function QuarterPanel<DateType>(props: QuarterPanelProps<DateType>) {
const {
prefixCls,
operationRef,
onViewDateChange,
generateConfig,
value,
viewDate,
onPanelChange,
onSelect,
} = props;
const panelPrefixCls = `${prefixCls}-quarter-panel`;
// ======================= Keyboard =======================
operationRef.current = {
onKeyDown: event =>
createKeyDownHandler(event, {
onLeftRight: diff => {
onSelect(generateConfig.addMonth(value || viewDate, diff * 3), 'key');
},
onCtrlLeftRight: diff => {
onSelect(generateConfig.addYear(value || viewDate, diff), 'key');
},
onUpDown: diff => {
onSelect(generateConfig.addYear(value || viewDate, diff), 'key');
},
}),
};
// ==================== View Operation ====================
const onYearChange = (diff: number) => {
const newDate = generateConfig.addYear(viewDate, diff);
onViewDateChange(newDate);
onPanelChange(null, newDate);
};
return (
<div class={panelPrefixCls}>
<QuarterHeader
{...props}
prefixCls={prefixCls}
onPrevYear={() => {
onYearChange(-1);
}}
onNextYear={() => {
onYearChange(1);
}}
onYearClick={() => {
onPanelChange('year', viewDate);
}}
/>
<QuarterBody<DateType>
{...props}
prefixCls={prefixCls}
onSelect={date => {
onSelect(date, 'mouse');
}}
/>
</div>
);
}
QuarterPanel.displayName ='QuarterPanel'
QuarterPanel.inheritAttrs = false;
export default QuarterPanel;

View File

@ -0,0 +1,249 @@
import type { GenerateConfig } from '../../generate';
import type { Locale, OnSelect } from '../../interface';
import type { Unit } from './TimeUnitColumn';
import TimeUnitColumn from './TimeUnitColumn';
import { leftPad } from '../../utils/miscUtil';
import type { SharedTimeProps } from '.';
import { setTime as utilSetTime } from '../../utils/timeUtil';
import { cloneElement } from '../../../_util/vnode';
import { VueNode } from '../../../_util/type';
import { Ref } from '@vue/reactivity';
function shouldUnitsUpdate(prevUnits: Unit[], nextUnits: Unit[]) {
if (prevUnits.length !== nextUnits.length) return true;
// if any unit's disabled status is different, the units should be re-evaluted
for (let i = 0; i < prevUnits.length; i += 1) {
if (prevUnits[i].disabled !== nextUnits[i].disabled) return true;
}
return false;
}
function generateUnits(
start: number,
end: number,
step: number,
disabledUnits: number[] | undefined,
) {
const units: Unit[] = [];
for (let i = start; i <= end; i += step) {
units.push({
label: leftPad(i, 2),
value: i,
disabled: (disabledUnits || []).includes(i),
});
}
return units;
}
export type BodyOperationRef = {
onUpDown: (diff: number) => void;
};
export type TimeBodyProps<DateType> = {
prefixCls: string;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
value?: DateType | null;
onSelect: OnSelect<DateType>;
activeColumnIndex: number;
operationRef: Ref<BodyOperationRef | undefined>;
} & SharedTimeProps<DateType>;
function TimeBody<DateType>(props: TimeBodyProps<DateType>) {
const {
generateConfig,
prefixCls,
operationRef,
activeColumnIndex,
value,
showHour,
showMinute,
showSecond,
use12Hours,
hourStep = 1,
minuteStep = 1,
secondStep = 1,
disabledHours,
disabledMinutes,
disabledSeconds,
hideDisabledOptions,
onSelect,
} = props;
const columns: {
node: VueNode;
value: number;
units: Unit[];
onSelect: (diff: number) => void;
}[] = [];
const contentPrefixCls = `${prefixCls}-content`;
const columnPrefixCls = `${prefixCls}-time-panel`;
let isPM: boolean | undefined;
const originHour = value ? generateConfig.getHour(value) : -1;
let hour = originHour;
const minute = value ? generateConfig.getMinute(value) : -1;
const second = value ? generateConfig.getSecond(value) : -1;
const setTime = (
isNewPM: boolean | undefined,
newHour: number,
newMinute: number,
newSecond: number,
) => {
let newDate = value || generateConfig.getNow();
const mergedHour = Math.max(0, newHour);
const mergedMinute = Math.max(0, newMinute);
const mergedSecond = Math.max(0, newSecond);
newDate = utilSetTime(
generateConfig,
newDate,
!use12Hours || !isNewPM ? mergedHour : mergedHour + 12,
mergedMinute,
mergedSecond,
);
return newDate;
};
// ========================= Unit =========================
const rawHours = generateUnits(0, 23, hourStep, disabledHours && disabledHours());
const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate);
// Should additional logic to handle 12 hours
if (use12Hours) {
isPM = hour >= 12; // -1 means should display AM
hour %= 12;
}
const [AMDisabled, PMDisabled] = React.useMemo(() => {
if (!use12Hours) {
return [false, false];
}
const AMPMDisabled = [true, true];
memorizedRawHours.forEach(({ disabled, value: hourValue }) => {
if (disabled) return;
if (hourValue >= 12) {
AMPMDisabled[1] = false;
} else {
AMPMDisabled[0] = false;
}
});
return AMPMDisabled;
}, [use12Hours, memorizedRawHours]);
const hours = React.useMemo(() => {
if (!use12Hours) return memorizedRawHours;
return memorizedRawHours
.filter(isPM ? hourMeta => hourMeta.value >= 12 : hourMeta => hourMeta.value < 12)
.map(hourMeta => {
const hourValue = hourMeta.value % 12;
const hourLabel = hourValue === 0 ? '12' : leftPad(hourValue, 2);
return {
...hourMeta,
label: hourLabel,
value: hourValue,
};
});
}, [use12Hours, isPM, memorizedRawHours]);
const minutes = generateUnits(0, 59, minuteStep, disabledMinutes && disabledMinutes(originHour));
const seconds = generateUnits(
0,
59,
secondStep,
disabledSeconds && disabledSeconds(originHour, minute),
);
// ====================== Operations ======================
operationRef.value = {
onUpDown: diff => {
const column = columns[activeColumnIndex];
if (column) {
const valueIndex = column.units.findIndex(unit => unit.value === column.value);
const unitLen = column.units.length;
for (let i = 1; i < unitLen; i += 1) {
const nextUnit = column.units[(valueIndex + diff * i + unitLen) % unitLen];
if (nextUnit.disabled !== true) {
column.onSelect(nextUnit.value);
break;
}
}
}
},
};
// ======================== Render ========================
function addColumnNode(
condition: boolean | undefined,
node: VueNode,
columnValue: number,
units: Unit[],
onColumnSelect: (diff: number) => void,
) {
if (condition !== false) {
columns.push({
node: cloneElement(node, {
prefixCls: columnPrefixCls,
value: columnValue,
active: activeColumnIndex === columns.length,
onSelect: onColumnSelect,
units,
hideDisabledOptions,
}),
onSelect: onColumnSelect,
value: columnValue,
units,
});
}
}
// Hour
addColumnNode(showHour, <TimeUnitColumn key="hour" />, hour, hours, num => {
onSelect(setTime(isPM, num, minute, second), 'mouse');
});
// Minute
addColumnNode(showMinute, <TimeUnitColumn key="minute" />, minute, minutes, num => {
onSelect(setTime(isPM, hour, num, second), 'mouse');
});
// Second
addColumnNode(showSecond, <TimeUnitColumn key="second" />, second, seconds, num => {
onSelect(setTime(isPM, hour, minute, num), 'mouse');
});
// 12 Hours
let PMIndex = -1;
if (typeof isPM === 'boolean') {
PMIndex = isPM ? 1 : 0;
}
addColumnNode(
use12Hours === true,
<TimeUnitColumn key="12hours" />,
PMIndex,
[
{ label: 'AM', value: 0, disabled: AMDisabled },
{ label: 'PM', value: 1, disabled: PMDisabled },
],
num => {
onSelect(setTime(!!num, hour, minute, second), 'mouse');
},
);
return <div class={contentPrefixCls}>{columns.map(({ node }) => node)}</div>;
}
TimeBody.displayName ='TimeBody'
TimeBody.inheritAttrs = false;
export default TimeBody;

View File

@ -0,0 +1,42 @@
import Header from '../Header';
import type { Locale } from '../../interface';
import type { GenerateConfig } from '../../generate';
import { useInjectPanel } from '../../PanelContext';
import { formatValue } from '../../utils/dateUtil';
export type TimeHeaderProps<DateType> = {
prefixCls: string;
value?: DateType | null;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
format: string;
};
function TimeHeader<DateType>(props: TimeHeaderProps<DateType>) {
const { hideHeader } = useInjectPanel()
if (hideHeader) {
return null;
}
const { prefixCls, generateConfig, locale, value, format } = props;
const headerPrefixCls = `${prefixCls}-header`;
return (
<Header prefixCls={headerPrefixCls}>
{value
? formatValue(value, {
locale,
format,
generateConfig,
})
: '\u00A0'}
</Header>
);
}
TimeHeader.displayName ='TimeHeader'
TimeHeader.inheritAttrs = false;
export default TimeHeader;

View File

@ -0,0 +1,97 @@
import { scrollTo, waitElementReady } from '../../utils/uiUtil';
import { useInjectPanel } from '../../PanelContext';
import classNames from '../../../_util/classNames';
import { ref } from '@vue/reactivity';
import { onBeforeUnmount, watch } from '@vue/runtime-core';
export type Unit = {
label: any;
value: number;
disabled: boolean;
};
export type TimeUnitColumnProps = {
prefixCls?: string;
units?: Unit[];
value?: number;
active?: boolean;
hideDisabledOptions?: boolean;
onSelect?: (value: number) => void;
};
function TimeUnitColumn(props: TimeUnitColumnProps) {
const { prefixCls, units, onSelect, value, active, hideDisabledOptions } = props;
const cellPrefixCls = `${prefixCls}-cell`;
const { open } = useInjectPanel();
const ulRef = ref<HTMLUListElement>(null);
const liRefs = ref<Map<number, HTMLElement | null>>(new Map());
const scrollRef = ref<Function>();
watch(
() => props.value,
() => {
const li = liRefs.value.get(value!);
if (li && open !== false) {
scrollTo(ulRef.value!, li.offsetTop, 120);
}
},
);
onBeforeUnmount(() => {
scrollRef.value?.();
});
watch(open, () => {
scrollRef.value?.();
if (open) {
const li = liRefs.value.get(value!);
if (li) {
scrollRef.value = waitElementReady(li, () => {
scrollTo(ulRef.value!, li.offsetTop, 0);
});
}
}
});
return (
<ul
class={classNames(`${prefixCls}-column`, {
[`${prefixCls}-column-active`]: active,
})}
ref={ulRef}
style={{ position: 'relative' }}
>
{units!.map(unit => {
if (hideDisabledOptions && unit.disabled) {
return null;
}
return (
<li
key={unit.value}
ref={element => {
liRefs.value.set(unit.value, element as HTMLElement);
}}
class={classNames(cellPrefixCls, {
[`${cellPrefixCls}-disabled`]: unit.disabled,
[`${cellPrefixCls}-selected`]: value === unit.value,
})}
onClick={() => {
if (unit.disabled) {
return;
}
onSelect!(unit.value);
}}
>
<div class={`${cellPrefixCls}-inner`}>{unit.label}</div>
</li>
);
})}
</ul>
);
}
TimeUnitColumn.displayName = 'TimeUnitColumn';
TimeUnitColumn.inheritAttrs = false;
export default TimeUnitColumn;

View File

@ -0,0 +1,98 @@
import TimeHeader from './TimeHeader';
import type { BodyOperationRef } from './TimeBody';
import TimeBody from './TimeBody';
import type { PanelSharedProps, DisabledTimes } from '../../interface';
import { createKeyDownHandler } from '../../utils/uiUtil';
import classNames from '../../../_util/classNames';
import { ref } from '@vue/reactivity';
export type SharedTimeProps<DateType> = {
format?: string;
showNow?: boolean;
showHour?: boolean;
showMinute?: boolean;
showSecond?: boolean;
use12Hours?: boolean;
hourStep?: number;
minuteStep?: number;
secondStep?: number;
hideDisabledOptions?: boolean;
defaultValue?: DateType;
} & DisabledTimes;
export type TimePanelProps<DateType> = {
format?: string;
active?: boolean;
} & PanelSharedProps<DateType> & SharedTimeProps<DateType>;
const countBoolean = (boolList: (boolean | undefined)[]) =>
boolList.filter(bool => bool !== false).length;
function TimePanel<DateType>(props: TimePanelProps<DateType>) {
const {
generateConfig,
format = 'HH:mm:ss',
prefixCls,
active,
operationRef,
showHour,
showMinute,
showSecond,
use12Hours = false,
onSelect,
value,
} = props;
const panelPrefixCls = `${prefixCls}-time-panel`;
const bodyOperationRef = ref<BodyOperationRef>();
// ======================= Keyboard =======================
const activeColumnIndex = ref(-1);
const columnsCount = countBoolean([showHour, showMinute, showSecond, use12Hours]);
operationRef.current = {
onKeyDown: event =>
createKeyDownHandler(event, {
onLeftRight: diff => {
activeColumnIndex.value = (activeColumnIndex.value + diff + columnsCount) % columnsCount;
},
onUpDown: diff => {
if (activeColumnIndex.value === -1) {
activeColumnIndex.value = 0
} else if (bodyOperationRef.value) {
bodyOperationRef.value.onUpDown(diff);
}
},
onEnter: () => {
onSelect(value || generateConfig.getNow(), 'key');
activeColumnIndex.value = -1
},
}),
onBlur: () => {
activeColumnIndex.value = -1
},
};
return (
<div
class={classNames(panelPrefixCls, {
[`${panelPrefixCls}-active`]: active,
})}
>
<TimeHeader {...props} format={format} prefixCls={prefixCls} />
<TimeBody
{...props}
prefixCls={prefixCls}
activeColumnIndex={activeColumnIndex.value}
operationRef={bodyOperationRef}
/>
</div>
);
}
TimePanel.displayName ='TimePanel'
TimePanel.inheritAttrs = false;
export default TimePanel;

View File

@ -0,0 +1,52 @@
import DatePanel from '../DatePanel';
import type { PanelSharedProps } from '../../interface';
import { isSameWeek } from '../../utils/dateUtil';
import classNames from '../../../_util/classNames';
export type WeekPanelProps<DateType> = PanelSharedProps<DateType>;
function WeekPanel<DateType>(props: WeekPanelProps<DateType>) {
const { prefixCls, generateConfig, locale, value } = props;
// Render additional column
const cellPrefixCls = `${prefixCls}-cell`;
const prefixColumn = (date: DateType) => (
<td
key="week"
class={classNames(cellPrefixCls, `${cellPrefixCls}-week`)}
>
{generateConfig.locale.getWeek(locale.locale, date)}
</td>
);
// Add row className
const rowPrefixCls = `${prefixCls}-week-panel-row`;
const rowClassName = (date: DateType) =>
classNames(rowPrefixCls, {
[`${rowPrefixCls}-selected`]: isSameWeek(
generateConfig,
locale.locale,
value,
date,
),
});
return (
<DatePanel
{...props}
panelName="week"
prefixColumn={prefixColumn}
rowClassName={rowClassName}
keyboardConfig={{
onLeftRight: null,
}}
/>
);
}
WeekPanel.displayName = 'WeekPanel';
WeekPanel.inheritAttrs = false;
export default WeekPanel;

View File

@ -0,0 +1,79 @@
import type { GenerateConfig } from '../../generate';
import { YEAR_DECADE_COUNT } from '.';
import type { Locale, NullableDateType } from '../../interface';
import useCellClassName from '../../hooks/useCellClassName';
import { formatValue, isSameYear } from '../../utils/dateUtil';
import RangeContext, { useInjectRange } from '../../RangeContext';
import PanelBody from '../PanelBody';
export const YEAR_COL_COUNT = 3;
const YEAR_ROW_COUNT = 4;
export type YearBodyProps<DateType> = {
prefixCls: string;
locale: Locale;
generateConfig: GenerateConfig<DateType>;
value?: NullableDateType<DateType>;
viewDate: DateType;
disabledDate?: (date: DateType) => boolean;
onSelect: (value: DateType) => void;
};
function YearBody<DateType>(props: YearBodyProps<DateType>) {
const { prefixCls, value, viewDate, locale, generateConfig } = props;
const { rangedValue, hoverRangedValue } = useInjectRange()
const yearPrefixCls = `${prefixCls}-cell`;
// =============================== Year ===============================
const yearNumber = generateConfig.getYear(viewDate);
const startYear = Math.floor(yearNumber / YEAR_DECADE_COUNT) * YEAR_DECADE_COUNT;
const endYear = startYear + YEAR_DECADE_COUNT - 1;
const baseYear = generateConfig.setYear(
viewDate,
startYear - Math.ceil((YEAR_COL_COUNT * YEAR_ROW_COUNT - YEAR_DECADE_COUNT) / 2),
);
const isInView = (date: DateType) => {
const currentYearNumber = generateConfig.getYear(date);
return startYear <= currentYearNumber && currentYearNumber <= endYear;
};
const getCellClassName = useCellClassName<DateType>({
cellPrefixCls: yearPrefixCls,
value,
generateConfig,
rangedValue,
hoverRangedValue,
isSameCell: (current, target) => isSameYear(generateConfig, current, target),
isInView,
offsetCell: (date, offset) => generateConfig.addYear(date, offset),
});
return (
<PanelBody
{...props}
rowNum={YEAR_ROW_COUNT}
colNum={YEAR_COL_COUNT}
baseDate={baseYear}
getCellText={generateConfig.getYear}
getCellClassName={getCellClassName}
getCellDate={generateConfig.addYear}
titleCell={date =>
formatValue(date, {
locale,
format: 'YYYY',
generateConfig,
})
}
/>
);
}
YearBody.displayName = 'YearBody';
YearBody.inheritAttrs = false;
export default YearBody;

View File

@ -0,0 +1,50 @@
import Header from '../Header';
import type { GenerateConfig } from '../../generate';
import { YEAR_DECADE_COUNT } from '.';
import { useInjectPanel } from '../../PanelContext';
export type YearHeaderProps<DateType> = {
prefixCls: string;
viewDate: DateType;
value?: DateType | null;
generateConfig: GenerateConfig<DateType>;
onPrevDecade: () => void;
onNextDecade: () => void;
onDecadeClick: () => void;
};
function YearHeader<DateType>(props: YearHeaderProps<DateType>) {
const { prefixCls, generateConfig, viewDate, onPrevDecade, onNextDecade, onDecadeClick } = props;
const { hideHeader } = useInjectPanel()
if (hideHeader) {
return null;
}
const headerPrefixCls = `${prefixCls}-header`;
const yearNumber = generateConfig.getYear(viewDate);
const startYear = Math.floor(yearNumber / YEAR_DECADE_COUNT) * YEAR_DECADE_COUNT;
const endYear = startYear + YEAR_DECADE_COUNT - 1;
return (
<Header
{...props}
prefixCls={headerPrefixCls}
onSuperPrev={onPrevDecade}
onSuperNext={onNextDecade}
>
<button type="button" onClick={onDecadeClick} class={`${prefixCls}-decade-btn`}>
{startYear}-{endYear}
</button>
</Header>
);
}
YearHeader.displayName = 'YearHeader';
YearHeader.inheritAttrs = false;
export default YearHeader;

View File

@ -0,0 +1,95 @@
import YearHeader from './YearHeader';
import YearBody, { YEAR_COL_COUNT } from './YearBody';
import type { PanelSharedProps, PanelMode } from '../../interface';
import { createKeyDownHandler } from '../../utils/uiUtil';
export type YearPanelProps<DateType> = {
sourceMode: PanelMode;
} & PanelSharedProps<DateType>;
export const YEAR_DECADE_COUNT = 10;
function YearPanel<DateType>(props: YearPanelProps<DateType>) {
const {
prefixCls,
operationRef,
onViewDateChange,
generateConfig,
value,
viewDate,
sourceMode,
onSelect,
onPanelChange,
} = props;
const panelPrefixCls = `${prefixCls}-year-panel`;
// ======================= Keyboard =======================
operationRef.current = {
onKeyDown: event =>
createKeyDownHandler(event, {
onLeftRight: diff => {
onSelect(generateConfig.addYear(value || viewDate, diff), 'key');
},
onCtrlLeftRight: diff => {
onSelect(
generateConfig.addYear(value || viewDate, diff * YEAR_DECADE_COUNT),
'key',
);
},
onUpDown: diff => {
onSelect(
generateConfig.addYear(value || viewDate, diff * YEAR_COL_COUNT),
'key',
);
},
onEnter: () => {
onPanelChange(
sourceMode === 'date' ? 'date' : 'month',
value || viewDate,
);
},
}),
};
// ==================== View Operation ====================
const onDecadeChange = (diff: number) => {
const newDate = generateConfig.addYear(viewDate, diff * 10);
onViewDateChange(newDate);
onPanelChange(null, newDate);
};
return (
<div class={panelPrefixCls}>
<YearHeader
{...props}
prefixCls={prefixCls}
onPrevDecade={() => {
onDecadeChange(-1);
}}
onNextDecade={() => {
onDecadeChange(1);
}}
onDecadeClick={() => {
onPanelChange('decade', viewDate);
}}
/>
<YearBody
{...props}
prefixCls={prefixCls}
onSelect={date => {
onPanelChange(sourceMode === 'date' ? 'date' : 'month', date);
onSelect(date, 'mouse');
}}
/>
</div>
);
}
YearPanel.displayName = 'YearPanel';
YearPanel.inheritAttrs = false;
export default YearPanel;

Some files were not shown because too many files have changed in this diff Show More