useRangeValue.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  2. import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
  3. import { useEvent, useMergedState } from 'rc-util';
  4. import * as React from 'react';
  5. import useSyncState from "../../hooks/useSyncState";
  6. import { formatValue, isSame, isSameTimestamp } from "../../utils/dateUtil";
  7. import { fillIndex } from "../../utils/miscUtil";
  8. import useLockEffect from "./useLockEffect";
  9. var EMPTY_VALUE = [];
  10. // Submit Logic:
  11. // * ✅ Value:
  12. // * merged value using controlled value, if not, use stateValue
  13. // * When merged value change, [1] resync calendar value and submit value
  14. // * ✅ Calender Value:
  15. // * 💻 When user typing is validate, change the calendar value
  16. // * 🌅 When user click on the panel, change the calendar value
  17. // * Submit Value:
  18. // * 💻 When user blur the input, flush calendar value to submit value
  19. // * 🌅 When user click on the panel is no needConfirm, flush calendar value to submit value
  20. // * 🌅 When user click on the panel is needConfirm and click OK, flush calendar value to submit value
  21. // * Blur logic & close logic:
  22. // * ✅ For value, always try flush submit
  23. // * ✅ If `needConfirm`, reset as [1]
  24. // * Else (`!needConfirm`)
  25. // * If has another index field, active another index
  26. // * ✅ Flush submit:
  27. // * If all the start & end field is confirmed or all blur or panel closed
  28. // * Update `needSubmit` mark to true
  29. // * trigger onChange by `needSubmit` and update stateValue
  30. function useUtil(generateConfig, locale, formatList) {
  31. var getDateTexts = function getDateTexts(dates) {
  32. return dates.map(function (date) {
  33. return formatValue(date, {
  34. generateConfig: generateConfig,
  35. locale: locale,
  36. format: formatList[0]
  37. });
  38. });
  39. };
  40. var isSameDates = function isSameDates(source, target) {
  41. var maxLen = Math.max(source.length, target.length);
  42. var diffIndex = -1;
  43. for (var i = 0; i < maxLen; i += 1) {
  44. var prev = source[i] || null;
  45. var next = target[i] || null;
  46. if (prev !== next && !isSameTimestamp(generateConfig, prev, next)) {
  47. diffIndex = i;
  48. break;
  49. }
  50. }
  51. return [diffIndex < 0, diffIndex !== 0];
  52. };
  53. return [getDateTexts, isSameDates];
  54. }
  55. function orderDates(dates, generateConfig) {
  56. return _toConsumableArray(dates).sort(function (a, b) {
  57. return generateConfig.isAfter(a, b) ? 1 : -1;
  58. });
  59. }
  60. /**
  61. * Used for internal value management.
  62. * It should always use `mergedValue` in render logic
  63. */
  64. function useCalendarValue(mergedValue) {
  65. var _useSyncState = useSyncState(mergedValue),
  66. _useSyncState2 = _slicedToArray(_useSyncState, 2),
  67. calendarValue = _useSyncState2[0],
  68. setCalendarValue = _useSyncState2[1];
  69. /** Sync calendarValue & submitValue back with value */
  70. var syncWithValue = useEvent(function () {
  71. setCalendarValue(mergedValue);
  72. });
  73. React.useEffect(function () {
  74. syncWithValue();
  75. }, [mergedValue]);
  76. return [calendarValue, setCalendarValue];
  77. }
  78. /**
  79. * Control the internal `value` align with prop `value` and provide a temp `calendarValue` for ui.
  80. * `calendarValue` will be reset when blur & focus & open.
  81. */
  82. export function useInnerValue(generateConfig, locale, formatList, /** Used for RangePicker. `true` means [DateType, DateType] or will be DateType[] */
  83. rangeValue,
  84. /**
  85. * Trigger order when trigger calendar value change.
  86. * This should only used in SinglePicker with `multiple` mode.
  87. * So when `rangeValue` is `true`, order will be ignored.
  88. */
  89. order, defaultValue, value, onCalendarChange, onOk) {
  90. // This is the root value which will sync with controlled or uncontrolled value
  91. var _useMergedState = useMergedState(defaultValue, {
  92. value: value
  93. }),
  94. _useMergedState2 = _slicedToArray(_useMergedState, 2),
  95. innerValue = _useMergedState2[0],
  96. setInnerValue = _useMergedState2[1];
  97. var mergedValue = innerValue || EMPTY_VALUE;
  98. // ========================= Inner Values =========================
  99. var _useCalendarValue = useCalendarValue(mergedValue),
  100. _useCalendarValue2 = _slicedToArray(_useCalendarValue, 2),
  101. calendarValue = _useCalendarValue2[0],
  102. setCalendarValue = _useCalendarValue2[1];
  103. // ============================ Change ============================
  104. var _useUtil = useUtil(generateConfig, locale, formatList),
  105. _useUtil2 = _slicedToArray(_useUtil, 2),
  106. getDateTexts = _useUtil2[0],
  107. isSameDates = _useUtil2[1];
  108. var triggerCalendarChange = useEvent(function (nextCalendarValues) {
  109. var clone = _toConsumableArray(nextCalendarValues);
  110. if (rangeValue) {
  111. for (var i = 0; i < 2; i += 1) {
  112. clone[i] = clone[i] || null;
  113. }
  114. } else if (order) {
  115. clone = orderDates(clone.filter(function (date) {
  116. return date;
  117. }), generateConfig);
  118. }
  119. // Update merged value
  120. var _isSameDates = isSameDates(calendarValue(), clone),
  121. _isSameDates2 = _slicedToArray(_isSameDates, 2),
  122. isSameMergedDates = _isSameDates2[0],
  123. isSameStart = _isSameDates2[1];
  124. if (!isSameMergedDates) {
  125. setCalendarValue(clone);
  126. // Trigger calendar change event
  127. if (onCalendarChange) {
  128. var cellTexts = getDateTexts(clone);
  129. onCalendarChange(clone, cellTexts, {
  130. range: isSameStart ? 'end' : 'start'
  131. });
  132. }
  133. }
  134. });
  135. var triggerOk = function triggerOk() {
  136. if (onOk) {
  137. onOk(calendarValue());
  138. }
  139. };
  140. return [mergedValue, setInnerValue, calendarValue, triggerCalendarChange, triggerOk];
  141. }
  142. export default function useRangeValue(info, mergedValue, setInnerValue, getCalendarValue, triggerCalendarChange, disabled, formatList, focused, open, isInvalidateDate) {
  143. var generateConfig = info.generateConfig,
  144. locale = info.locale,
  145. picker = info.picker,
  146. onChange = info.onChange,
  147. allowEmpty = info.allowEmpty,
  148. order = info.order;
  149. var orderOnChange = disabled.some(function (d) {
  150. return d;
  151. }) ? false : order;
  152. // ============================= Util =============================
  153. var _useUtil3 = useUtil(generateConfig, locale, formatList),
  154. _useUtil4 = _slicedToArray(_useUtil3, 2),
  155. getDateTexts = _useUtil4[0],
  156. isSameDates = _useUtil4[1];
  157. // ============================ Values ============================
  158. // Used for trigger `onChange` event.
  159. // Record current value which is wait for submit.
  160. var _useSyncState3 = useSyncState(mergedValue),
  161. _useSyncState4 = _slicedToArray(_useSyncState3, 2),
  162. submitValue = _useSyncState4[0],
  163. setSubmitValue = _useSyncState4[1];
  164. /** Sync calendarValue & submitValue back with value */
  165. var syncWithValue = useEvent(function () {
  166. setSubmitValue(mergedValue);
  167. });
  168. React.useEffect(function () {
  169. syncWithValue();
  170. }, [mergedValue]);
  171. // ============================ Submit ============================
  172. var triggerSubmit = useEvent(function (nextValue) {
  173. var isNullValue = nextValue === null;
  174. var clone = _toConsumableArray(nextValue || submitValue());
  175. // Fill null value
  176. if (isNullValue) {
  177. var maxLen = Math.max(disabled.length, clone.length);
  178. for (var i = 0; i < maxLen; i += 1) {
  179. if (!disabled[i]) {
  180. clone[i] = null;
  181. }
  182. }
  183. }
  184. // Only when exist value to sort
  185. if (orderOnChange && clone[0] && clone[1]) {
  186. clone = orderDates(clone, generateConfig);
  187. }
  188. // Sync `calendarValue`
  189. triggerCalendarChange(clone);
  190. // ========= Validate check =========
  191. var _clone = clone,
  192. _clone2 = _slicedToArray(_clone, 2),
  193. start = _clone2[0],
  194. end = _clone2[1];
  195. // >>> Empty
  196. var startEmpty = !start;
  197. var endEmpty = !end;
  198. var validateEmptyDateRange = allowEmpty ?
  199. // Validate empty start
  200. (!startEmpty || allowEmpty[0]) && (
  201. // Validate empty end
  202. !endEmpty || allowEmpty[1]) : true;
  203. // >>> Order
  204. var validateOrder = !order || startEmpty || endEmpty || isSame(generateConfig, locale, start, end, picker) || generateConfig.isAfter(end, start);
  205. // >>> Invalid
  206. var validateDates =
  207. // Validate start
  208. (disabled[0] || !start || !isInvalidateDate(start, {
  209. activeIndex: 0
  210. })) && (
  211. // Validate end
  212. disabled[1] || !end || !isInvalidateDate(end, {
  213. from: start,
  214. activeIndex: 1
  215. }));
  216. // >>> Result
  217. var allPassed =
  218. // Null value is from clear button
  219. isNullValue ||
  220. // Normal check
  221. validateEmptyDateRange && validateOrder && validateDates;
  222. if (allPassed) {
  223. // Sync value with submit value
  224. setInnerValue(clone);
  225. var _isSameDates3 = isSameDates(clone, mergedValue),
  226. _isSameDates4 = _slicedToArray(_isSameDates3, 1),
  227. isSameMergedDates = _isSameDates4[0];
  228. // Trigger `onChange` if needed
  229. if (onChange && !isSameMergedDates) {
  230. onChange(
  231. // Return null directly if all date are empty
  232. isNullValue && clone.every(function (val) {
  233. return !val;
  234. }) ? null : clone, getDateTexts(clone));
  235. }
  236. }
  237. return allPassed;
  238. });
  239. // ========================= Flush Submit =========================
  240. var flushSubmit = useEvent(function (index, needTriggerChange) {
  241. var nextSubmitValue = fillIndex(submitValue(), index, getCalendarValue()[index]);
  242. setSubmitValue(nextSubmitValue);
  243. if (needTriggerChange) {
  244. triggerSubmit();
  245. }
  246. });
  247. // ============================ Effect ============================
  248. // All finished action trigger after 2 frames
  249. var interactiveFinished = !focused && !open;
  250. useLockEffect(!interactiveFinished, function () {
  251. if (interactiveFinished) {
  252. // Always try to trigger submit first
  253. triggerSubmit();
  254. // Trigger calendar change since this is a effect reset
  255. // https://github.com/ant-design/ant-design/issues/22351
  256. triggerCalendarChange(mergedValue);
  257. // Sync with value anyway
  258. syncWithValue();
  259. }
  260. }, 2);
  261. // ============================ Return ============================
  262. return [flushSubmit, triggerSubmit];
  263. }