index.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import { createVNode as _createVNode } from "vue";
  3. /**
  4. * Cursor rule:
  5. * 1. Only `showSearch` enabled
  6. * 2. Only `open` is `true`
  7. * 3. When typing, set `open` to `true` which hit rule of 2
  8. *
  9. * Accessibility:
  10. * - https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
  11. */
  12. import KeyCode from '../../_util/KeyCode';
  13. import MultipleSelector from './MultipleSelector';
  14. import SingleSelector from './SingleSelector';
  15. import { isValidateOpenKey } from '../utils/keyUtil';
  16. import useLock from '../hooks/useLock';
  17. import { defineComponent, ref } from 'vue';
  18. import createRef from '../../_util/createRef';
  19. import PropTypes from '../../_util/vue-types';
  20. const Selector = defineComponent({
  21. name: 'Selector',
  22. inheritAttrs: false,
  23. props: {
  24. id: String,
  25. prefixCls: String,
  26. showSearch: {
  27. type: Boolean,
  28. default: undefined
  29. },
  30. open: {
  31. type: Boolean,
  32. default: undefined
  33. },
  34. /** Display in the Selector value, it's not same as `value` prop */
  35. values: PropTypes.array,
  36. multiple: {
  37. type: Boolean,
  38. default: undefined
  39. },
  40. mode: String,
  41. searchValue: String,
  42. activeValue: String,
  43. inputElement: PropTypes.any,
  44. autofocus: {
  45. type: Boolean,
  46. default: undefined
  47. },
  48. activeDescendantId: String,
  49. tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  50. disabled: {
  51. type: Boolean,
  52. default: undefined
  53. },
  54. placeholder: PropTypes.any,
  55. removeIcon: PropTypes.any,
  56. // Tags
  57. maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  58. maxTagTextLength: Number,
  59. maxTagPlaceholder: PropTypes.any,
  60. tagRender: Function,
  61. optionLabelRender: Function,
  62. /** Check if `tokenSeparators` contains `\n` or `\r\n` */
  63. tokenWithEnter: {
  64. type: Boolean,
  65. default: undefined
  66. },
  67. // Motion
  68. choiceTransitionName: String,
  69. onToggleOpen: {
  70. type: Function
  71. },
  72. /** `onSearch` returns go next step boolean to check if need do toggle open */
  73. onSearch: Function,
  74. onSearchSubmit: Function,
  75. onRemove: Function,
  76. onInputKeyDown: {
  77. type: Function
  78. },
  79. /**
  80. * @private get real dom for trigger align.
  81. * This may be removed after React provides replacement of `findDOMNode`
  82. */
  83. domRef: Function
  84. },
  85. setup(props, _ref) {
  86. let {
  87. expose
  88. } = _ref;
  89. const inputRef = createRef();
  90. const compositionStatus = ref(false);
  91. // ====================== Input ======================
  92. const [getInputMouseDown, setInputMouseDown] = useLock(0);
  93. const onInternalInputKeyDown = event => {
  94. const {
  95. which
  96. } = event;
  97. if (which === KeyCode.UP || which === KeyCode.DOWN) {
  98. event.preventDefault();
  99. }
  100. if (props.onInputKeyDown) {
  101. props.onInputKeyDown(event);
  102. }
  103. if (which === KeyCode.ENTER && props.mode === 'tags' && !compositionStatus.value && !props.open) {
  104. // When menu isn't open, OptionList won't trigger a value change
  105. // So when enter is pressed, the tag's input value should be emitted here to let selector know
  106. props.onSearchSubmit(event.target.value);
  107. }
  108. if (isValidateOpenKey(which)) {
  109. props.onToggleOpen(true);
  110. }
  111. };
  112. /**
  113. * We can not use `findDOMNode` sine it will get warning,
  114. * have to use timer to check if is input element.
  115. */
  116. const onInternalInputMouseDown = () => {
  117. setInputMouseDown(true);
  118. };
  119. // When paste come, ignore next onChange
  120. let pastedText = null;
  121. const triggerOnSearch = value => {
  122. if (props.onSearch(value, true, compositionStatus.value) !== false) {
  123. props.onToggleOpen(true);
  124. }
  125. };
  126. const onInputCompositionStart = () => {
  127. compositionStatus.value = true;
  128. };
  129. const onInputCompositionEnd = e => {
  130. compositionStatus.value = false;
  131. // Trigger search again to support `tokenSeparators` with typewriting
  132. if (props.mode !== 'combobox') {
  133. triggerOnSearch(e.target.value);
  134. }
  135. };
  136. const onInputChange = event => {
  137. let {
  138. target: {
  139. value
  140. }
  141. } = event;
  142. // Pasted text should replace back to origin content
  143. if (props.tokenWithEnter && pastedText && /[\r\n]/.test(pastedText)) {
  144. // CRLF will be treated as a single space for input element
  145. const replacedText = pastedText.replace(/[\r\n]+$/, '').replace(/\r\n/g, ' ').replace(/[\r\n]/g, ' ');
  146. value = value.replace(replacedText, pastedText);
  147. }
  148. pastedText = null;
  149. triggerOnSearch(value);
  150. };
  151. const onInputPaste = e => {
  152. const {
  153. clipboardData
  154. } = e;
  155. const value = clipboardData.getData('text');
  156. pastedText = value;
  157. };
  158. const onClick = _ref2 => {
  159. let {
  160. target
  161. } = _ref2;
  162. if (target !== inputRef.current) {
  163. // Should focus input if click the selector
  164. const isIE = document.body.style.msTouchAction !== undefined;
  165. if (isIE) {
  166. setTimeout(() => {
  167. inputRef.current.focus();
  168. });
  169. } else {
  170. inputRef.current.focus();
  171. }
  172. }
  173. };
  174. const onMousedown = event => {
  175. const inputMouseDown = getInputMouseDown();
  176. if (event.target !== inputRef.current && !inputMouseDown) {
  177. event.preventDefault();
  178. }
  179. if (props.mode !== 'combobox' && (!props.showSearch || !inputMouseDown) || !props.open) {
  180. if (props.open) {
  181. props.onSearch('', true, false);
  182. }
  183. props.onToggleOpen();
  184. }
  185. };
  186. expose({
  187. focus: () => {
  188. inputRef.current.focus();
  189. },
  190. blur: () => {
  191. inputRef.current.blur();
  192. }
  193. });
  194. return () => {
  195. const {
  196. prefixCls,
  197. domRef,
  198. mode
  199. } = props;
  200. const sharedProps = {
  201. inputRef,
  202. onInputKeyDown: onInternalInputKeyDown,
  203. onInputMouseDown: onInternalInputMouseDown,
  204. onInputChange,
  205. onInputPaste,
  206. compositionStatus: compositionStatus.value,
  207. onInputCompositionStart,
  208. onInputCompositionEnd
  209. };
  210. const selectNode = mode === 'multiple' || mode === 'tags' ? _createVNode(MultipleSelector, _objectSpread(_objectSpread({}, props), sharedProps), null) : _createVNode(SingleSelector, _objectSpread(_objectSpread({}, props), sharedProps), null);
  211. return _createVNode("div", {
  212. "ref": domRef,
  213. "class": `${prefixCls}-selector`,
  214. "onClick": onClick,
  215. "onMousedown": onMousedown
  216. }, [selectNode]);
  217. };
  218. }
  219. });
  220. export default Selector;