foundation.ts 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201
  1. /* eslint-disable no-nested-ternary */
  2. /* eslint-disable max-len, max-depth, */
  3. import { format, isValid, isSameSecond, isEqual as isDateEqual, isDate } from 'date-fns';
  4. import { get, isObject, isString, isEqual } from 'lodash';
  5. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  6. import { isValidDate, isTimestamp } from './_utils/index';
  7. import isNullOrUndefined from '../utils/isNullOrUndefined';
  8. import { utcToZonedTime, zonedTimeToUtc } from '../utils/date-fns-extra';
  9. import { compatibleParse } from './_utils/parser';
  10. import { getDefaultFormatTokenByType } from './_utils/getDefaultFormatToken';
  11. import { strings } from './constants';
  12. import { strings as inputStrings } from '../input/constants';
  13. import { Type, DateInputFoundationProps, InsetInputValue } from './inputFoundation';
  14. import { MonthsGridFoundationProps } from './monthsGridFoundation';
  15. import { WeekStartNumber } from './_utils/getMonthTable';
  16. import { ArrayElement, Motion } from '../utils/type';
  17. import getInsetInputFormatToken from './_utils/getInsetInputFormatToken';
  18. import getInsetInputValueFromInsetInputStr from './_utils/getInsetInputValueFromInsetInputStr';
  19. export type ValidateStatus = ArrayElement<typeof strings.STATUS>;
  20. export type InputSize = ArrayElement<typeof strings.SIZE_SET>;
  21. export type Position = ArrayElement<typeof strings.POSITION_SET>;
  22. export type BaseValueType = string | number | Date;
  23. export type DayStatusType = {
  24. isToday?: boolean; // Current day
  25. isSelected?: boolean; // Selected
  26. isDisabled?: boolean; // Disabled
  27. isSelectedStart?: boolean; // Select Start
  28. isSelectedEnd?: boolean; // End of selection
  29. isInRange?: boolean; // Range within the selected date
  30. isHover?: boolean; // Date between selection and hover date
  31. isOffsetRangeStart?: boolean; // Week selection start
  32. isOffsetRangeEnd?: boolean; // End of week selection
  33. isHoverInOffsetRange?: boolean; // Hover in the week selection
  34. };
  35. export type DisabledDateOptions = {
  36. rangeStart?: string;
  37. rangeEnd?: string;
  38. };
  39. export type PresetType = {
  40. start?: string | Date | number;
  41. end?: string | Date | number;
  42. text?: string;
  43. };
  44. export type TriggerRenderProps = {
  45. [x: string]: any;
  46. value?: ValueType;
  47. inputValue?: string;
  48. placeholder?: string | string[];
  49. autoFocus?: boolean;
  50. size?: InputSize;
  51. disabled?: boolean;
  52. inputReadOnly?: boolean;
  53. componentProps?: DatePickerFoundationProps;
  54. };
  55. export type DateOffsetType = (selectedDate?: Date) => Date;
  56. export type DensityType = 'default' | 'compact';
  57. export type DisabledDateType = (date?: Date, options?: DisabledDateOptions) => boolean;
  58. export type DisabledTimeType = (date?: Date | Date[], panelType?: string) => ({
  59. disabledHours?: () => number[];
  60. disabledMinutes?: (hour: number) => number[];
  61. disabledSeconds?: (hour: number, minute: number) => number[];
  62. });
  63. export type OnCancelType = (date: Date | Date[], dateStr: string | string[]) => void;
  64. export type OnPanelChangeType = (date: Date | Date[], dateStr: string | string[]) => void;
  65. export type OnChangeType = (date?: Date | Date[] | string | string[], dateStr?: string | string[] | Date | Date[]) => void;
  66. export type OnConfirmType = (date: Date | Date[], dateStr: string | string[]) => void;
  67. // type OnPresetClickType = (item: PresetType, e: React.MouseEvent<HTMLDivElement>) => void;
  68. export type OnPresetClickType = (item: PresetType, e: any) => void;
  69. export type PresetsType = Array<PresetType | (() => PresetType)>;
  70. // type RenderDateType = (dayNumber?: number, fullDate?: string) => React.ReactNode;
  71. export type RenderDateType = (dayNumber?: number, fullDate?: string) => any;
  72. // type RenderFullDateType = (dayNumber?: number, fullDate?: string, dayStatus?: DayStatusType) => React.ReactNode;
  73. export type RenderFullDateType = (dayNumber?: number, fullDate?: string, dayStatus?: DayStatusType) => any;
  74. // type TriggerRenderType = (props: TriggerRenderProps) => React.ReactNode;
  75. export type TriggerRenderType = (props: TriggerRenderProps) => any;
  76. export type ValueType = BaseValueType | BaseValueType[];
  77. export interface ElementProps {
  78. bottomSlot?: any;
  79. insetLabel?: any;
  80. prefix?: any;
  81. topSlot?: any;
  82. }
  83. export interface RenderProps {
  84. renderDate?: RenderDateType;
  85. renderFullDate?: RenderFullDateType;
  86. triggerRender?: TriggerRenderType;
  87. }
  88. export type RangeType = 'rangeStart' | 'rangeEnd' | false;
  89. export interface EventHandlerProps {
  90. onCancel?: OnCancelType;
  91. onChange?: OnChangeType;
  92. onOpenChange?: (status: boolean) => void;
  93. onPanelChange?: OnPanelChangeType;
  94. onConfirm?: OnConfirmType;
  95. // properties below need overwrite
  96. // onBlur?: React.MouseEventHandler<HTMLInputElement>;
  97. onBlur?: (e: any) => void;
  98. // onClear?: React.MouseEventHandler<HTMLDivElement>;
  99. onClear?: (e: any) => void;
  100. // onFocus?: React.MouseEventHandler<HTMLInputElement>;
  101. onFocus?: (e: any, rangType: RangeType) => void;
  102. onPresetClick?: OnPresetClickType;
  103. }
  104. export interface DatePickerFoundationProps extends ElementProps, RenderProps, EventHandlerProps {
  105. autoAdjustOverflow?: boolean;
  106. autoFocus?: boolean;
  107. autoSwitchDate?: boolean;
  108. className?: string;
  109. defaultOpen?: boolean;
  110. defaultPickerValue?: ValueType;
  111. defaultValue?: ValueType;
  112. density?: DensityType;
  113. disabled?: boolean;
  114. disabledDate?: DisabledDateType;
  115. disabledTime?: DisabledTimeType;
  116. dropdownClassName?: string;
  117. dropdownStyle?: React.CSSProperties;
  118. endDateOffset?: DateOffsetType;
  119. format?: string;
  120. getPopupContainer?: () => HTMLElement;
  121. inputReadOnly?: boolean;
  122. inputStyle?: React.CSSProperties;
  123. max?: number;
  124. motion?: Motion;
  125. multiple?: boolean;
  126. needConfirm?: boolean;
  127. onChangeWithDateFirst?: boolean;
  128. open?: boolean;
  129. placeholder?: string | string[];
  130. position?: Position;
  131. prefixCls?: string;
  132. presets?: PresetsType;
  133. showClear?: boolean;
  134. size?: InputSize;
  135. spacing?: number;
  136. startDateOffset?: DateOffsetType;
  137. stopPropagation?: boolean | string;
  138. style?: React.CSSProperties;
  139. timePickerOpts?: any; // TODO import timePicker props
  140. timeZone?: string | number;
  141. type?: Type;
  142. validateStatus?: ValidateStatus;
  143. value?: ValueType;
  144. weekStartsOn?: WeekStartNumber;
  145. zIndex?: number;
  146. syncSwitchMonth?: boolean;
  147. hideDisabledOptions?: MonthsGridFoundationProps['hideDisabledOptions'];
  148. disabledTimePicker?: MonthsGridFoundationProps['disabledTimePicker'];
  149. locale?: any;
  150. dateFnsLocale?: any;
  151. localeCode?: string;
  152. rangeSeparator?: string;
  153. insetInput?: boolean;
  154. }
  155. export interface DatePickerFoundationState {
  156. panelShow: boolean;
  157. isRange: boolean;
  158. inputValue: string;
  159. value: Date[];
  160. cachedSelectedValue: Date[];
  161. prevTimeZone: string | number;
  162. motionEnd: boolean;
  163. rangeInputFocus: RangeType;
  164. autofocus: boolean;
  165. insetInputValue: InsetInputValue;
  166. triggerDisabled: boolean;
  167. }
  168. export { Type, DateInputFoundationProps };
  169. export interface DatePickerAdapter extends DefaultAdapter<DatePickerFoundationProps, DatePickerFoundationState> {
  170. togglePanel: (panelShow: boolean) => void;
  171. registerClickOutSide: () => void;
  172. unregisterClickOutSide: () => void;
  173. notifyBlur: DatePickerFoundationProps['onBlur'];
  174. notifyFocus: DatePickerFoundationProps['onFocus'];
  175. notifyClear: DatePickerFoundationProps['onClear'];
  176. notifyChange: DatePickerFoundationProps['onChange'];
  177. notifyCancel: DatePickerFoundationProps['onCancel'];
  178. notifyConfirm: DatePickerFoundationProps['onConfirm'];
  179. notifyOpenChange: DatePickerFoundationProps['onOpenChange'];
  180. notifyPresetsClick: DatePickerFoundationProps['onPresetClick'];
  181. updateValue: (value: Date[]) => void;
  182. updatePrevTimezone: (prevTimeZone: string | number) => void;
  183. updateCachedSelectedValue: (cachedSelectedValue: Date[]) => void;
  184. updateInputValue: (inputValue: string) => void;
  185. needConfirm: () => boolean;
  186. typeIsYearOrMonth: () => boolean;
  187. setMotionEnd: (motionEnd: boolean) => void;
  188. setRangeInputFocus: (rangeInputFocus: DatePickerFoundationState['rangeInputFocus']) => void;
  189. couldPanelClosed: () => boolean;
  190. isEventTarget: (e: any) => boolean;
  191. updateInsetInputValue: (insetInputValue: InsetInputValue) => void;
  192. setInsetInputFocus: () => void;
  193. setTriggerDisabled: (disabled: boolean) => void;
  194. }
  195. /**
  196. * The datePicker foundation.js is responsible for maintaining the date value and the input box value, as well as the callback of both
  197. * task 1. Accept the selected date change, update the date value, and update the input box value according to the date = > Notify the change
  198. * task 2. When the input box changes, update the date value = > Notify the change
  199. */
  200. export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapter> {
  201. constructor(adapter: DatePickerAdapter) {
  202. super({ ...adapter });
  203. }
  204. init() {
  205. const timeZone = this.getProp('timeZone');
  206. if (this._isControlledComponent()) {
  207. this.initFromProps({ timeZone, value: this.getProp('value') });
  208. } else if (this._isInProps('defaultValue')) {
  209. this.initFromProps({ timeZone, value: this.getProp('defaultValue') });
  210. }
  211. this.initPanelOpenStatus(this.getProp('defaultOpen'));
  212. }
  213. isValidTimeZone(timeZone?: string | number) {
  214. const propTimeZone = this.getProp('timeZone');
  215. const _timeZone = isNullOrUndefined(timeZone) ? propTimeZone : timeZone;
  216. return ['string', 'number'].includes(typeof _timeZone) && _timeZone !== '';
  217. }
  218. initFromProps({ value, timeZone, prevTimeZone }: Pick<DatePickerFoundationProps, 'value' | 'timeZone'> & { prevTimeZone?: string | number }) {
  219. const _value = (Array.isArray(value) ? [...value] : (value || value === 0) && [value]) || [];
  220. const result = this.parseWithTimezone(_value, timeZone, prevTimeZone);
  221. this._adapter.updatePrevTimezone(prevTimeZone);
  222. this._adapter.updateInputValue(null);
  223. this._adapter.updateValue(result);
  224. if (this._adapter.needConfirm()) {
  225. this._adapter.updateCachedSelectedValue(result);
  226. }
  227. }
  228. parseWithTimezone(value: ValueType, timeZone: string | number, prevTimeZone: string | number) {
  229. const result: Date[] = [];
  230. if (Array.isArray(value) && value.length) {
  231. for (const v of value) {
  232. let parsedV = (v || v === 0) && this._parseValue(v);
  233. if (parsedV) {
  234. if (this.isValidTimeZone(prevTimeZone)) {
  235. parsedV = zonedTimeToUtc(parsedV, prevTimeZone as string);
  236. }
  237. result.push(this.isValidTimeZone(timeZone) ? utcToZonedTime(parsedV, timeZone as string) : parsedV);
  238. }
  239. }
  240. }
  241. return result;
  242. }
  243. _isMultiple() {
  244. return Boolean(this.getProp('multiple'));
  245. }
  246. /**
  247. *
  248. * Verify and parse the following three format inputs
  249. *
  250. 1. Date object
  251. 2. ISO 9601-compliant string
  252. 3. ts timestamp
  253. Unified here to format the incoming value and output it as a Date object
  254. *
  255. */
  256. _parseValue(value: BaseValueType): Date {
  257. const dateFnsLocale = this._adapter.getProp('dateFnsLocale');
  258. let dateObj: Date;
  259. if (!value && value !== 0) {
  260. return new Date();
  261. }
  262. if (isValidDate(value)) {
  263. dateObj = value as Date;
  264. } else if (isString(value)) {
  265. dateObj = compatibleParse(value as string, this.getProp('format'), undefined, dateFnsLocale);
  266. } else if (isTimestamp(value)) {
  267. dateObj = new Date(value);
  268. } else {
  269. throw new TypeError('defaultValue should be valid Date object/timestamp or string');
  270. }
  271. return dateObj;
  272. }
  273. destroy() {
  274. // Ensure that event listeners will be uninstalled and users may not trigger closePanel
  275. // this._adapter.togglePanel(false);
  276. this._adapter.unregisterClickOutSide();
  277. }
  278. initPanelOpenStatus(defaultOpen?: boolean) {
  279. if ((this.getProp('open') || defaultOpen) && !this.getProp('disabled')) {
  280. this._adapter.togglePanel(true);
  281. this._adapter.registerClickOutSide();
  282. } else {
  283. this._adapter.togglePanel(false);
  284. this._adapter.unregisterClickOutSide();
  285. }
  286. }
  287. openPanel() {
  288. if (!this.getProp('disabled')) {
  289. if (!this._isControlledComponent('open')) {
  290. this._adapter.togglePanel(true);
  291. this._adapter.registerClickOutSide();
  292. }
  293. this._adapter.notifyOpenChange(true);
  294. }
  295. }
  296. /**
  297. * do these side effects when type is dateRange or dateTimeRange
  298. * 1. trigger input blur, if input value is invalid, set input value and state value to previous status
  299. * 2. set cachedSelectedValue using given dates(in needConfirm mode)
  300. * - directly closePanel without click confirm will set cachedSelectedValue to state value
  301. * - select one date(which means that the selection value is incomplete) and click confirm also set cachedSelectedValue to state value
  302. */
  303. rangeTypeSideEffectsWhenClosePanel(inputValue: string, willUpdateDates: Date[]) {
  304. if (this._isRangeType()) {
  305. this._adapter.setRangeInputFocus(false);
  306. /**
  307. * inputValue is string when it is not disabled or can't parsed
  308. * when inputValue is null, picker value will back to last selected value
  309. */
  310. this.handleInputBlur(inputValue);
  311. this.resetCachedSelectedValue(willUpdateDates);
  312. }
  313. }
  314. /**
  315. * clear input value when selected date is not confirmed
  316. */
  317. needConfirmSideEffectsWhenClosePanel(willUpdateDates: Date[] | null | undefined) {
  318. if (this._adapter.needConfirm() && !this._isRangeType()) {
  319. /**
  320. * if `null` input element will show `cachedSelectedValue` formatted value(format in DateInput render)
  321. * if `` input element will show `` directly
  322. */
  323. this._adapter.updateInputValue(null);
  324. this.resetCachedSelectedValue(willUpdateDates);
  325. }
  326. }
  327. /**
  328. * clear inset input value when close panel
  329. */
  330. clearInsetInputValue() {
  331. const { insetInput } = this._adapter.getProps();
  332. if (insetInput) {
  333. this._adapter.updateInsetInputValue(null);
  334. }
  335. }
  336. resetCachedSelectedValue(willUpdateDates?: Date[]) {
  337. const { value, cachedSelectedValue } = this._adapter.getStates();
  338. const newCachedSelectedValue = Array.isArray(willUpdateDates) ? willUpdateDates : value;
  339. if (!isEqual(newCachedSelectedValue, cachedSelectedValue)) {
  340. this._adapter.updateCachedSelectedValue(newCachedSelectedValue);
  341. }
  342. }
  343. /**
  344. * timing to call closePanel
  345. * 1. click confirm button
  346. * 2. click cancel button
  347. * 3. select date, time, year, month
  348. * - date type and not multiple, close panel after select date
  349. * - dateRange type, close panel after select rangeStart and rangeEnd
  350. * 4. click outside
  351. * @param {Event} e
  352. * @param {String} inputValue
  353. * @param {Date[]} dates
  354. */
  355. closePanel(e?: any, inputValue: string = null, dates?: Date[]) {
  356. const { value, cachedSelectedValue } = this._adapter.getStates();
  357. const willUpdateDates = isNullOrUndefined(dates) ? this._adapter.needConfirm() ? value : cachedSelectedValue : dates;
  358. if (!this._isControlledComponent('open')) {
  359. this._adapter.togglePanel(false);
  360. this._adapter.unregisterClickOutSide();
  361. }
  362. // range type picker, closing panel requires the following side effects
  363. this.rangeTypeSideEffectsWhenClosePanel(inputValue, willUpdateDates as Date[]);
  364. this.needConfirmSideEffectsWhenClosePanel(willUpdateDates as Date[]);
  365. this.clearInsetInputValue();
  366. this._adapter.notifyOpenChange(false);
  367. this._adapter.notifyBlur(e);
  368. }
  369. /**
  370. * clear range input focus when open is controlled
  371. * fixed github 1375
  372. */
  373. clearRangeInputFocus = () => {
  374. const { type } = this._adapter.getProps();
  375. const { rangeInputFocus } = this._adapter.getStates();
  376. if (type === 'dateTimeRange' && rangeInputFocus) {
  377. this._adapter.setRangeInputFocus(false);
  378. }
  379. }
  380. /**
  381. * Callback when the content of the input box changes
  382. * Update the date panel if the changed value is a legal date, otherwise only update the input box
  383. * @param {String} input The value of the input box after the change
  384. * @param {Event} e
  385. */
  386. handleInputChange(input: string, e: any) {
  387. const result = this._isMultiple() ? this.parseMultipleInput(input) : this.parseInput(input);
  388. const { value: stateValue } = this.getStates();
  389. // Enter a valid date or empty
  390. if ((result && result.length) || input === '') {
  391. // If you click the clear button
  392. if (get(e, inputStrings.CLEARBTN_CLICKED_EVENT_FLAG) && this._isControlledComponent('value')) {
  393. this._notifyChange(result);
  394. return;
  395. }
  396. this._updateValueAndInput(result, input === '', input);
  397. // Updates the selected value when entering a valid date
  398. const changedDates = this._getChangedDates(result);
  399. if (!this._someDateDisabled(changedDates)) {
  400. if (this._adapter.needConfirm()) {
  401. this._adapter.updateCachedSelectedValue(result);
  402. }
  403. if (!isEqual(result, stateValue)) {
  404. this._notifyChange(result);
  405. }
  406. }
  407. } else {
  408. this._adapter.updateInputValue(input);
  409. }
  410. }
  411. /**
  412. * inset input 变化时需要更新以下 state 状态
  413. * - insetInputValue(总是)
  414. * - inputValue(可以解析为合法日期时)
  415. * - value(可以解析为合法日期时)
  416. */
  417. handleInsetInputChange(options: { insetInputStr: string, format: string, insetInputValue: InsetInputValue }) {
  418. const { insetInputStr, format, insetInputValue } = options;
  419. const _isMultiple = this._isMultiple();
  420. const result = _isMultiple ? this.parseMultipleInput(insetInputStr, format) : this.parseInput(insetInputStr, format);
  421. const { value: stateValue } = this.getStates();
  422. if ((result && result.length)) {
  423. const changedDates = this._getChangedDates(result);
  424. if (!this._someDateDisabled(changedDates)) {
  425. if (this._adapter.needConfirm()) {
  426. this._adapter.updateCachedSelectedValue(result);
  427. }
  428. if (!isEqual(result, stateValue)) {
  429. if (!this._isControlledComponent()) {
  430. this._adapter.updateValue(result);
  431. }
  432. this._notifyChange(result);
  433. }
  434. const triggerInput = _isMultiple ? this.formatMultipleDates(result) : this.formatDates(result);
  435. this._adapter.updateInputValue(triggerInput);
  436. }
  437. }
  438. this._adapter.updateInsetInputValue(insetInputValue);
  439. }
  440. /**
  441. * Input box blur
  442. * @param {String} input
  443. * @param {Event} e
  444. */
  445. handleInputBlur(input = '', e?: any) {
  446. const parsedResult = input ?
  447. this._isMultiple() ?
  448. this.parseMultipleInput(input, ',', true) :
  449. this.parseInput(input) :
  450. [];
  451. const stateValue = this.getState('value');
  452. // console.log(input, parsedResult);
  453. if (parsedResult && parsedResult.length) {
  454. this._updateValueAndInput(parsedResult, input === '');
  455. } else if (input === '') {
  456. // if clear input, set input to `''`
  457. this._updateValueAndInput('' as any, true, '');
  458. } else {
  459. this._updateValueAndInput(stateValue);
  460. }
  461. }
  462. /**
  463. * called when range type rangeEnd input tab press
  464. * @param {Event} e
  465. */
  466. handleRangeEndTabPress(e: any) {
  467. this._adapter.setRangeInputFocus(false);
  468. }
  469. /**
  470. * called when the input box is focused
  471. * @param {Event} e input focus event
  472. * @param {String} range 'rangeStart' or 'rangeEnd', use when type is range
  473. */
  474. handleInputFocus(e: any, range: 'rangeStart' | 'rangeEnd') {
  475. const rangeInputFocus = this._adapter.getState('rangeInputFocus');
  476. range && this._adapter.setRangeInputFocus(range);
  477. /**
  478. * rangeType: only notify when range is false
  479. * not rangeType: notify when focus
  480. */
  481. if (!range || !['rangeStart', 'rangeEnd'].includes(rangeInputFocus)) {
  482. this._adapter.notifyFocus(e, range);
  483. }
  484. }
  485. handleSetRangeFocus(rangeInputFocus: RangeType) {
  486. this._adapter.setRangeInputFocus(rangeInputFocus);
  487. }
  488. handleInputClear(e: any) {
  489. this._adapter.notifyClear(e);
  490. }
  491. /**
  492. * 范围选择清除按钮回调
  493. * 因为清除按钮没有集成在Input内,因此需要手动清除 value、inputValue、cachedValue
  494. *
  495. * callback of range input clear button
  496. * Since the clear button is not integrated in Input, you need to manually clear value, inputValue, cachedValue
  497. */
  498. handleRangeInputClear(e: any) {
  499. const value: Date[] = [];
  500. const inputValue = '';
  501. if (!this._isControlledComponent('value')) {
  502. this._updateValueAndInput(value, true, inputValue);
  503. if (this._adapter.needConfirm()) {
  504. this._adapter.updateCachedSelectedValue(value);
  505. }
  506. }
  507. this._notifyChange(value);
  508. this._adapter.notifyClear(e);
  509. }
  510. // eslint-disable-next-line @typescript-eslint/no-empty-function
  511. handleRangeInputBlur(value: any, e: any) {
  512. }
  513. // Parses input only after user returns
  514. handleInputComplete(input: any = '') {
  515. // console.log(input);
  516. let parsedResult = input ?
  517. this._isMultiple() ?
  518. this.parseMultipleInput(input, ',', true) :
  519. this.parseInput(input) :
  520. [];
  521. parsedResult = parsedResult && parsedResult.length ? parsedResult : this.getState('value');
  522. // Use the current date as the value when the current input is empty and the last input is also empty
  523. if (!parsedResult || !parsedResult.length) {
  524. const nowDate = new Date();
  525. if (this._isRangeType()) {
  526. parsedResult = [nowDate, nowDate];
  527. } else {
  528. parsedResult = [nowDate];
  529. }
  530. }
  531. this._updateValueAndInput(parsedResult);
  532. const { value: stateValue } = this.getStates();
  533. const changedDates = this._getChangedDates(parsedResult);
  534. if (!this._someDateDisabled(changedDates) && !isEqual(parsedResult, stateValue)) {
  535. this._notifyChange(parsedResult);
  536. }
  537. }
  538. /**
  539. * Parse the input, return the time object if it is valid,
  540. * otherwise return "
  541. *
  542. * @param {string} input
  543. * @returns {Date [] | '}
  544. */
  545. parseInput(input = '', format?: string) {
  546. let result: Date[] = [];
  547. // console.log(input);
  548. const { dateFnsLocale, rangeSeparator } = this.getProps();
  549. if (input && input.length) {
  550. const type = this.getProp('type');
  551. const formatToken = format || this.getProp('format') || getDefaultFormatTokenByType(type);
  552. let parsedResult,
  553. formatedInput;
  554. const nowDate = new Date();
  555. switch (type) {
  556. case 'date':
  557. case 'dateTime':
  558. case 'month':
  559. parsedResult = input ? compatibleParse(input, formatToken, nowDate, dateFnsLocale) : '';
  560. formatedInput = parsedResult && isValid(parsedResult) && this.localeFormat(parsedResult as Date, formatToken);
  561. if (parsedResult && formatedInput === input) {
  562. result = [parsedResult as Date];
  563. }
  564. break;
  565. case 'dateRange':
  566. case 'dateTimeRange':
  567. const separator = rangeSeparator;
  568. const values = input.split(separator);
  569. parsedResult =
  570. values &&
  571. values.reduce((arr, cur) => {
  572. const parsedVal = cur && compatibleParse(cur, formatToken, nowDate, dateFnsLocale);
  573. parsedVal && arr.push(parsedVal);
  574. return arr;
  575. }, []);
  576. formatedInput =
  577. parsedResult &&
  578. parsedResult.map(v => v && isValid(v) && this.localeFormat(v, formatToken)).join(separator);
  579. if (parsedResult && formatedInput === input) {
  580. parsedResult.sort((d1, d2) => d1.getTime() - d2.getTime());
  581. result = parsedResult;
  582. }
  583. break;
  584. default:
  585. break;
  586. }
  587. }
  588. return result;
  589. }
  590. /**
  591. * Parses the input when multiple is true, if valid,
  592. * returns a list of time objects, otherwise returns an array
  593. *
  594. * @param {string} [input='']
  595. * @param {string} [separator=',']
  596. * @param {boolean} [needDedupe=false]
  597. * @returns {Date[]}
  598. */
  599. parseMultipleInput(input = '', separator: string = strings.DEFAULT_SEPARATOR_MULTIPLE, needDedupe = false) {
  600. const max = this.getProp('max');
  601. const inputArr = input.split(separator);
  602. const result: Date[] = [];
  603. for (const curInput of inputArr) {
  604. let tmpParsed = curInput && this.parseInput(curInput);
  605. tmpParsed = Array.isArray(tmpParsed) ? tmpParsed : tmpParsed && [tmpParsed];
  606. if (tmpParsed && tmpParsed.length) {
  607. if (needDedupe) {
  608. // 20190519 TODO: needs to determine the case where multiple is true and range
  609. !result.filter(r => Boolean(tmpParsed.find(tp => isSameSecond(r, tp)))) && result.push(...tmpParsed);
  610. } else {
  611. result.push(...tmpParsed);
  612. }
  613. } else {
  614. return [];
  615. }
  616. if (max && max > 0 && result.length > max) {
  617. return [];
  618. }
  619. }
  620. return result;
  621. }
  622. /**
  623. * dates[] => string
  624. *
  625. * @param {Date[]} dates
  626. * @returns {string}
  627. */
  628. formatDates(dates: Date[] = [], customFormat?: string) {
  629. let str = '';
  630. const rangeSeparator = this.getProp('rangeSeparator');
  631. if (Array.isArray(dates) && dates.length) {
  632. const type = this.getProp('type');
  633. const formatToken = customFormat || this.getProp('format') || getDefaultFormatTokenByType(type);
  634. switch (type) {
  635. case 'date':
  636. case 'dateTime':
  637. case 'month':
  638. str = this.localeFormat(dates[0], formatToken);
  639. break;
  640. case 'dateRange':
  641. case 'dateTimeRange':
  642. const startIsTruthy = !isNullOrUndefined(dates[0]);
  643. const endIsTruthy = !isNullOrUndefined(dates[1]);
  644. if (startIsTruthy && endIsTruthy) {
  645. str = `${this.localeFormat(dates[0], formatToken)}${rangeSeparator}${this.localeFormat(dates[1], formatToken)}`;
  646. } else {
  647. if (startIsTruthy) {
  648. str = `${this.localeFormat(dates[0], formatToken)}${rangeSeparator}`;
  649. } else if (endIsTruthy) {
  650. str = `${rangeSeparator}${this.localeFormat(dates[1], formatToken)}`;
  651. }
  652. }
  653. break;
  654. default:
  655. break;
  656. }
  657. }
  658. return str;
  659. }
  660. /**
  661. * dates[] => string
  662. *
  663. * @param {Date[]} dates
  664. * @returns {string}
  665. */
  666. formatMultipleDates(dates: Date[] = [], separator: string = strings.DEFAULT_SEPARATOR_MULTIPLE, customFormat?: string) {
  667. const strs = [];
  668. if (Array.isArray(dates) && dates.length) {
  669. const type = this.getProp('type');
  670. switch (type) {
  671. case 'date':
  672. case 'dateTime':
  673. case 'month':
  674. dates.forEach(date => strs.push(this.formatDates([date], customFormat)));
  675. break;
  676. case 'dateRange':
  677. case 'dateTimeRange':
  678. for (let i = 0; i < dates.length; i += 2) {
  679. strs.push(this.formatDates(dates.slice(i, i + 2), customFormat));
  680. }
  681. break;
  682. default:
  683. break;
  684. }
  685. }
  686. return strs.join(separator);
  687. }
  688. /**
  689. * Update date value and the value of the input box
  690. * 1. Select Update
  691. * 2. Input Update
  692. * @param {Date|''} value
  693. * @param {Boolean} forceUpdateValue
  694. * @param {String} input
  695. */
  696. _updateValueAndInput(value: Date | Array<Date>, forceUpdateValue?: boolean, input?: string) {
  697. let _value: Array<Date>;
  698. if (forceUpdateValue || value) {
  699. if (!Array.isArray(value)) {
  700. _value = value ? [value] : [];
  701. } else {
  702. _value = value;
  703. }
  704. const changedDates = this._getChangedDates(_value);
  705. // You cannot update the value directly when needConfirm, you can only change the value through handleConfirm
  706. if (!this._isControlledComponent() && !this._someDateDisabled(changedDates) && !this._adapter.needConfirm()) {
  707. this._adapter.updateValue(_value);
  708. }
  709. }
  710. this._adapter.updateInputValue(input);
  711. }
  712. /**
  713. * when changing the selected value through the date panel
  714. * @param {*} value
  715. * @param {*} options
  716. */
  717. handleSelectedChange(value: Date[], options?: { fromPreset?: boolean; needCheckFocusRecord?: boolean }) {
  718. const { type, format, rangeSeparator, insetInput } = this._adapter.getProps();
  719. const { value: stateValue } = this.getStates();
  720. const controlled = this._isControlledComponent();
  721. const fromPreset = isObject(options) ? options.fromPreset : options;
  722. const closePanel = get(options, 'closePanel', true);
  723. /**
  724. * It is used to determine whether the panel can be stowed. In a Range type component, it is necessary to select both starting Time and endTime before stowing.
  725. * To determine whether both starting Time and endTime have been selected, it is used to judge whether the two inputs have been Focused.
  726. * This variable is used to indicate whether such a judgment is required. In the scene with shortcut operations, it is not required.
  727. */
  728. const needCheckFocusRecord = get(options, 'needCheckFocusRecord', true);
  729. if (this._adapter.needConfirm()) {
  730. this._adapter.updateCachedSelectedValue(value);
  731. }
  732. const dates = Array.isArray(value) ? [...value] : value ? [value] : [];
  733. const changedDates = this._getChangedDates(dates);
  734. let inputValue, insetInputValue;
  735. if (!this._someDateDisabled(changedDates)) {
  736. inputValue = this._isMultiple() ? this.formatMultipleDates(dates) : this.formatDates(dates);
  737. if (insetInput) {
  738. const insetInputFormatToken = getInsetInputFormatToken({ format, type });
  739. const insetInputStr = this._isMultiple() ? this.formatMultipleDates(dates, undefined, insetInputFormatToken) : this.formatDates(dates, insetInputFormatToken);
  740. insetInputValue = getInsetInputValueFromInsetInputStr({ inputValue: insetInputStr, type, rangeSeparator });
  741. }
  742. const isRangeTypeAndInputIncomplete = this._isRangeType() && !this._isRangeValueComplete(dates);
  743. /**
  744. * If the input is incomplete when under control, the notifyChange is not triggered because
  745. * You need to update the value of the input box, otherwise there will be a problem that a date is selected but the input box does not show the date #1357
  746. *
  747. * 受控时如果输入不完整,由于没有触发 notifyChange
  748. * 需要组件内更新一下输入框的值,否则会出现选了一个日期但是输入框没有回显日期的问题 #1357
  749. */
  750. if (!this._adapter.needConfirm() || fromPreset) {
  751. if (isRangeTypeAndInputIncomplete) {
  752. // do not change value when selected value is incomplete
  753. this._adapter.updateInputValue(inputValue);
  754. this._adapter.updateInsetInputValue(insetInputValue);
  755. return;
  756. } else {
  757. if (!controlled || fromPreset) {
  758. this._updateValueAndInput(dates, true, inputValue);
  759. this._adapter.updateInsetInputValue(insetInputValue);
  760. }
  761. }
  762. }
  763. if (!controlled && this._adapter.needConfirm()) {
  764. // select date only change inputValue when needConfirm is true
  765. this._adapter.updateInputValue(inputValue);
  766. this._adapter.updateInsetInputValue(insetInputValue);
  767. // if inputValue is not complete, don't notifyChange
  768. if (isRangeTypeAndInputIncomplete) {
  769. return;
  770. }
  771. }
  772. if (!isEqual(value, stateValue)) {
  773. this._notifyChange(value);
  774. }
  775. }
  776. const focusRecordChecked = !needCheckFocusRecord || (needCheckFocusRecord && this._adapter.couldPanelClosed());
  777. if ((type === 'date' && !this._isMultiple() && closePanel) || (type === 'dateRange' && this._isRangeValueComplete(dates) && closePanel && focusRecordChecked)) {
  778. this.closePanel(undefined, inputValue, dates);
  779. }
  780. }
  781. /**
  782. * when changing the year and month through the panel when the type is year or month
  783. * @param {*} item
  784. */
  785. handleYMSelectedChange(item: { currentMonth?: number; currentYear?: number } = {}) {
  786. // console.log(item);
  787. const { currentMonth, currentYear } = item;
  788. if (typeof currentMonth === 'number' && typeof currentYear === 'number') {
  789. // Strings with only dates (e.g. "1970-01-01") will be treated as UTC instead of local time #1460
  790. const date = new Date(currentYear, currentMonth - 1);
  791. this.handleSelectedChange([date]);
  792. }
  793. }
  794. handleConfirm() {
  795. const { cachedSelectedValue, value } = this._adapter.getStates();
  796. const isRangeValueComplete = this._isRangeValueComplete(cachedSelectedValue);
  797. const newValue = isRangeValueComplete ? cachedSelectedValue : value;
  798. if (this._adapter.needConfirm() && !this._isControlledComponent()) {
  799. this._adapter.updateValue(newValue);
  800. }
  801. // If the input is incomplete, the legal date of the last input is used
  802. this.closePanel(undefined, undefined, newValue);
  803. if (isRangeValueComplete) {
  804. const { notifyValue, notifyDate } = this.disposeCallbackArgs(cachedSelectedValue);
  805. this._adapter.notifyConfirm(notifyDate, notifyValue);
  806. }
  807. }
  808. handleCancel() {
  809. this.closePanel();
  810. const value = this.getState('value');
  811. const { notifyValue, notifyDate } = this.disposeCallbackArgs(value);
  812. this._adapter.notifyCancel(notifyDate, notifyValue);
  813. }
  814. handlePresetClick(item: PresetType, e: any) {
  815. const { type, timeZone } = this.getProps();
  816. const prevTimeZone = this.getState('prevTimezone');
  817. let value;
  818. switch (type) {
  819. case 'month':
  820. case 'dateTime':
  821. case 'date':
  822. value = this.parseWithTimezone([item.start], timeZone, prevTimeZone);
  823. this.handleSelectedChange(value);
  824. break;
  825. case 'dateTimeRange':
  826. case 'dateRange':
  827. value = this.parseWithTimezone([item.start, item.end], timeZone, prevTimeZone);
  828. this.handleSelectedChange(value, { needCheckFocusRecord: false });
  829. break;
  830. default:
  831. break;
  832. }
  833. this._adapter.notifyPresetsClick(item, e);
  834. }
  835. /**
  836. * 根据 type 处理 onChange 返回的参数
  837. *
  838. * - 返回的日期需要把用户时间转换为设置的时区时间
  839. * - 用户时间:用户计算机系统时间
  840. * - 时区时间:通过 ConfigProvider 设置的 timeZone
  841. * - 例子:用户设置时区为+9,计算机所在时区为+8区,然后用户选择了22:00
  842. * - DatePicker 内部保存日期 state 为 +8 的 22:00 => a = new Date("2021-05-25 22:00:00")
  843. * - 传出去时,需要把 +8 的 22:00 => +9 的 22:00 => b = zonedTimeToUtc(a, "+09:00");
  844. *
  845. * According to the type processing onChange returned parameters
  846. *
  847. * - the returned date needs to convert the user time to the set time zone time
  848. * - user time: user computer system time
  849. * - time zone time: timeZone set by ConfigProvider
  850. * - example: the user sets the time zone to + 9, the computer's time zone is + 8 zone, and then the user selects 22:00
  851. * - DatePicker internal save date state is + 8 22:00 = > a = new Date ("2021-05-25 22:00:00")
  852. * - when passed out, you need to + 8 22:00 = > + 9 22:00 = > b = zonedTimeToUtc (a, "+ 09:00");
  853. *
  854. * e.g.
  855. * let a = new Date ("2021-05-25 22:00:00");
  856. * = > Tue May 25 2021 22:00:00 GMT + 0800 (China Standard Time)
  857. * let b = zonedTimeToUtc (a, "+ 09:00");
  858. * = > Tue May 25 2021 21:00:00 GMT + 0800 (China Standard Time)
  859. *
  860. * @param {Date|Date[]} value
  861. * @return {{ notifyDate: Date|Date[], notifyValue: string|string[]}}
  862. */
  863. disposeCallbackArgs(value: Date | Date[]) {
  864. let _value = Array.isArray(value) ? value : (value && [value]) || [];
  865. if (this.isValidTimeZone()) {
  866. const timeZone = this.getProp('timeZone');
  867. _value = _value.map(date => zonedTimeToUtc(date, timeZone));
  868. }
  869. const type = this.getProp('type');
  870. const formatToken = this.getProp('format') || getDefaultFormatTokenByType(type);
  871. let notifyValue,
  872. notifyDate;
  873. switch (type) {
  874. case 'date':
  875. case 'dateTime':
  876. case 'month':
  877. if (!this._isMultiple()) {
  878. notifyValue = _value[0] && this.localeFormat(_value[0], formatToken);
  879. [notifyDate] = _value;
  880. } else {
  881. notifyValue = _value.map(v => v && this.localeFormat(v, formatToken));
  882. notifyDate = [..._value];
  883. }
  884. break;
  885. case 'dateRange':
  886. case 'dateTimeRange':
  887. notifyValue = _value.map(v => v && this.localeFormat(v, formatToken));
  888. notifyDate = [..._value];
  889. break;
  890. default:
  891. break;
  892. }
  893. return {
  894. notifyValue,
  895. notifyDate,
  896. };
  897. }
  898. /**
  899. * Notice: Check whether the date is the same as the state value before calling
  900. * @param {Date[]} value
  901. */
  902. _notifyChange(value: Date[]) {
  903. if (this._isRangeType() && !this._isRangeValueComplete(value)) {
  904. return;
  905. }
  906. const { onChangeWithDateFirst } = this.getProps();
  907. const { notifyValue, notifyDate } = this.disposeCallbackArgs(value);
  908. if (onChangeWithDateFirst) {
  909. this._adapter.notifyChange(notifyDate, notifyValue);
  910. } else {
  911. this._adapter.notifyChange(notifyValue, notifyDate);
  912. }
  913. }
  914. /**
  915. * Get the date changed through the date panel or enter
  916. * @param {Date[]} dates
  917. * @returns {Date[]}
  918. */
  919. _getChangedDates(dates: Date[]) {
  920. const type = this._adapter.getProp('type');
  921. const stateValue: Date[] = this._adapter.getState('value');
  922. const changedDates = [];
  923. switch (type) {
  924. case 'dateRange':
  925. case 'dateTimeRange':
  926. const [stateStart, stateEnd] = stateValue;
  927. const [start, end] = dates;
  928. if (!isDateEqual(start, stateStart)) {
  929. changedDates.push(start);
  930. }
  931. if (!isDateEqual(end, stateEnd)) {
  932. changedDates.push(end);
  933. }
  934. break;
  935. default:
  936. const stateValueSet = new Set<number>();
  937. stateValue.forEach(value => stateValueSet.add(isDate(value) && value.valueOf()));
  938. for (const date of dates) {
  939. if (!stateValueSet.has(isDate(date) && date.valueOf())) {
  940. changedDates.push(date);
  941. }
  942. }
  943. }
  944. return changedDates;
  945. }
  946. /**
  947. * Whether a date is disabled
  948. * @param {Array} value
  949. */
  950. _someDateDisabled(value: Date[]) {
  951. const stateValue = this.getState('value');
  952. const disabledOptions = { rangeStart: '', rangeEnd: '' };
  953. // DisabledDate needs to pass the second parameter
  954. if (this._isRangeType() && Array.isArray(stateValue)) {
  955. if (isValid(stateValue[0])) {
  956. const rangeStart = format(stateValue[0], 'yyyy-MM-dd');
  957. disabledOptions.rangeStart = rangeStart;
  958. }
  959. if (isValid(stateValue[1])) {
  960. const rangeEnd = format(stateValue[1], 'yyyy-MM-dd');
  961. disabledOptions.rangeEnd = rangeEnd;
  962. }
  963. }
  964. let isSomeDateDisabled = false;
  965. for (const date of value) {
  966. // skip check if date is null
  967. if (!isNullOrUndefined(date) && this.disabledDisposeDate(date, disabledOptions)) {
  968. isSomeDateDisabled = true;
  969. break;
  970. }
  971. }
  972. return isSomeDateDisabled;
  973. }
  974. getMergedMotion = (motion: any) => {
  975. const mergedMotion = typeof motion === 'undefined' || motion ? {
  976. ...motion,
  977. didEnter: () => {
  978. this._adapter.setMotionEnd(true);
  979. },
  980. didLeave: () => {
  981. this._adapter.setMotionEnd(false);
  982. }
  983. } : false;
  984. return mergedMotion;
  985. };
  986. /**
  987. * Format locale date
  988. * locale get from LocaleProvider
  989. * @param {Date} date
  990. * @param {String} token
  991. */
  992. localeFormat(date: Date, token: string) {
  993. const dateFnsLocale = this._adapter.getProp('dateFnsLocale');
  994. return format(date, token, { locale: dateFnsLocale });
  995. }
  996. _isRangeType = () => {
  997. const type = this._adapter.getProp('type');
  998. return /range/i.test(type);
  999. };
  1000. _isRangeValueComplete = (value: Date[] | Date) => {
  1001. let result = false;
  1002. if (Array.isArray(value)) {
  1003. result = !value.some(date => isNullOrUndefined(date));
  1004. }
  1005. return result;
  1006. };
  1007. /**
  1008. * Convert computer date to UTC date
  1009. * Before passing the date to the user, you need to convert the date to UTC time
  1010. * dispose date from computer date to utc date
  1011. * When given timeZone prop, you should convert computer date to utc date before passing to user
  1012. * @param {(date: Date) => Boolean} fn
  1013. * @param {Date|Date[]} date
  1014. * @returns {Boolean}
  1015. */
  1016. disposeDateFn(fn: (date: Date, ...rest: any) => boolean, date: Date | Date[], ...rest: any[]) {
  1017. const { notifyDate } = this.disposeCallbackArgs(date);
  1018. const dateIsArray = Array.isArray(date);
  1019. const notifyDateIsArray = Array.isArray(notifyDate);
  1020. let disposeDate;
  1021. if (dateIsArray === notifyDateIsArray) {
  1022. disposeDate = notifyDate;
  1023. } else {
  1024. disposeDate = dateIsArray ? [notifyDate] : notifyDate[0];
  1025. }
  1026. return fn(disposeDate, ...rest);
  1027. }
  1028. /**
  1029. * Determine whether the date is disabled
  1030. * Whether the date is disabled
  1031. * @param {Date} date
  1032. * @returns {Boolean}
  1033. */
  1034. disabledDisposeDate(date: Date, ...rest: any[]) {
  1035. const { disabledDate } = this.getProps();
  1036. return this.disposeDateFn(disabledDate, date, ...rest);
  1037. }
  1038. /**
  1039. * Determine whether the date is disabled
  1040. * Whether the date time is disabled
  1041. * @param {Date|Date[]} date
  1042. * @returns {Object}
  1043. */
  1044. disabledDisposeTime(date: Date | Date[], ...rest: any[]) {
  1045. const { disabledTime } = this.getProps();
  1046. return this.disposeDateFn(disabledTime, date, ...rest);
  1047. }
  1048. /**
  1049. * Trigger wrapper needs to do two things:
  1050. * 1. Open Panel when clicking trigger;
  1051. * 2. When clicking on a child but the child does not listen to the focus event, manually trigger focus
  1052. *
  1053. * @param {Event} e
  1054. * @returns
  1055. */
  1056. handleTriggerWrapperClick(e: any) {
  1057. const { disabled } = this._adapter.getProps();
  1058. const { rangeInputFocus } = this._adapter.getStates();
  1059. if (disabled) {
  1060. return;
  1061. }
  1062. /**
  1063. * - 非范围选择时,trigger 为原生输入框,已在组件内处理了 focus 逻辑
  1064. * - isEventTarget 函数用于判断触发事件的是否为 input wrapper。如果是冒泡上来的不用处理,因为在子级已经处理了 focus 逻辑。
  1065. *
  1066. * - When type is not range type, Input component will automatically focus in the same case
  1067. * - isEventTarget is used to judge whether the event is a bubbling event
  1068. */
  1069. if (this._isRangeType() && !rangeInputFocus && this._adapter.isEventTarget(e)) {
  1070. setTimeout(() => {
  1071. // using setTimeout get correct state value 'rangeInputFocus'
  1072. this.handleInputFocus(e, 'rangeStart');
  1073. this.openPanel();
  1074. }, 0);
  1075. } else {
  1076. this.openPanel();
  1077. }
  1078. }
  1079. handlePanelVisibleChange(visible: boolean) {
  1080. if (visible) {
  1081. this._adapter.setInsetInputFocus();
  1082. /**
  1083. * After the panel is closed, the trigger input is disabled
  1084. * 面板关闭后,trigger input 禁用
  1085. */
  1086. setTimeout(() => {
  1087. this._adapter.setTriggerDisabled(true);
  1088. }, 0);
  1089. } else {
  1090. this._adapter.setTriggerDisabled(false);
  1091. }
  1092. }
  1093. }