getPropGetters.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. var _excluded = ["props", "refresh", "store"],
  2. _excluded2 = ["inputElement", "formElement", "panelElement"],
  3. _excluded3 = ["inputElement"],
  4. _excluded4 = ["inputElement", "maxLength"],
  5. _excluded5 = ["item", "source"];
  6. function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
  7. function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
  8. function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  9. function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
  10. function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
  11. import { onInput } from './onInput';
  12. import { onKeyDown as _onKeyDown } from './onKeyDown';
  13. import { getActiveItem, isOrContainsNode } from './utils';
  14. export function getPropGetters(_ref) {
  15. var props = _ref.props,
  16. refresh = _ref.refresh,
  17. store = _ref.store,
  18. setters = _objectWithoutProperties(_ref, _excluded);
  19. var getEnvironmentProps = function getEnvironmentProps(providedProps) {
  20. var inputElement = providedProps.inputElement,
  21. formElement = providedProps.formElement,
  22. panelElement = providedProps.panelElement,
  23. rest = _objectWithoutProperties(providedProps, _excluded2);
  24. return _objectSpread({
  25. // On touch devices, we do not rely on the native `blur` event of the
  26. // input to close the panel, but rather on a custom `touchstart` event
  27. // outside of the autocomplete elements.
  28. // This ensures a working experience on mobile because we blur the input
  29. // on touch devices when the user starts scrolling (`touchmove`).
  30. // @TODO: support cases where there are multiple Autocomplete instances.
  31. // Right now, a second instance makes this computation return false.
  32. onTouchStart: function onTouchStart(event) {
  33. // The `onTouchStart` event shouldn't trigger the `blur` handler when
  34. // it's not an interaction with Autocomplete. We detect it with the
  35. // following heuristics:
  36. // - the panel is closed AND there are no pending requests
  37. // (no interaction with the autocomplete, no future state updates)
  38. // - OR the touched target is the input element (should open the panel)
  39. var isAutocompleteInteraction = store.getState().isOpen || !store.pendingRequests.isEmpty();
  40. if (!isAutocompleteInteraction || event.target === inputElement) {
  41. return;
  42. }
  43. var isTargetWithinAutocomplete = [formElement, panelElement].some(function (contextNode) {
  44. return isOrContainsNode(contextNode, event.target);
  45. });
  46. if (isTargetWithinAutocomplete === false) {
  47. store.dispatch('blur', null); // If requests are still pending when the user closes the panel, they
  48. // could reopen the panel once they resolve.
  49. // We want to prevent any subsequent query from reopening the panel
  50. // because it would result in an unsolicited UI behavior.
  51. if (!props.debug) {
  52. store.pendingRequests.cancelAll();
  53. }
  54. }
  55. },
  56. // When scrolling on touch devices (mobiles, tablets, etc.), we want to
  57. // mimic the native platform behavior where the input is blurred to
  58. // hide the virtual keyboard. This gives more vertical space to
  59. // discover all the suggestions showing up in the panel.
  60. onTouchMove: function onTouchMove(event) {
  61. if (store.getState().isOpen === false || inputElement !== props.environment.document.activeElement || event.target === inputElement) {
  62. return;
  63. }
  64. inputElement.blur();
  65. }
  66. }, rest);
  67. };
  68. var getRootProps = function getRootProps(rest) {
  69. return _objectSpread({
  70. role: 'combobox',
  71. 'aria-expanded': store.getState().isOpen,
  72. 'aria-haspopup': 'listbox',
  73. 'aria-owns': store.getState().isOpen ? "".concat(props.id, "-list") : undefined,
  74. 'aria-labelledby': "".concat(props.id, "-label")
  75. }, rest);
  76. };
  77. var getFormProps = function getFormProps(providedProps) {
  78. var inputElement = providedProps.inputElement,
  79. rest = _objectWithoutProperties(providedProps, _excluded3);
  80. return _objectSpread({
  81. action: '',
  82. noValidate: true,
  83. role: 'search',
  84. onSubmit: function onSubmit(event) {
  85. var _providedProps$inputE;
  86. event.preventDefault();
  87. props.onSubmit(_objectSpread({
  88. event: event,
  89. refresh: refresh,
  90. state: store.getState()
  91. }, setters));
  92. store.dispatch('submit', null);
  93. (_providedProps$inputE = providedProps.inputElement) === null || _providedProps$inputE === void 0 ? void 0 : _providedProps$inputE.blur();
  94. },
  95. onReset: function onReset(event) {
  96. var _providedProps$inputE2;
  97. event.preventDefault();
  98. props.onReset(_objectSpread({
  99. event: event,
  100. refresh: refresh,
  101. state: store.getState()
  102. }, setters));
  103. store.dispatch('reset', null);
  104. (_providedProps$inputE2 = providedProps.inputElement) === null || _providedProps$inputE2 === void 0 ? void 0 : _providedProps$inputE2.focus();
  105. }
  106. }, rest);
  107. };
  108. var getInputProps = function getInputProps(providedProps) {
  109. function onFocus(event) {
  110. // We want to trigger a query when `openOnFocus` is true
  111. // because the panel should open with the current query.
  112. if (props.openOnFocus || Boolean(store.getState().query)) {
  113. onInput(_objectSpread({
  114. event: event,
  115. props: props,
  116. query: store.getState().completion || store.getState().query,
  117. refresh: refresh,
  118. store: store
  119. }, setters));
  120. }
  121. store.dispatch('focus', null);
  122. }
  123. var isTouchDevice = ('ontouchstart' in props.environment);
  124. var _ref2 = providedProps || {},
  125. inputElement = _ref2.inputElement,
  126. _ref2$maxLength = _ref2.maxLength,
  127. maxLength = _ref2$maxLength === void 0 ? 512 : _ref2$maxLength,
  128. rest = _objectWithoutProperties(_ref2, _excluded4);
  129. var activeItem = getActiveItem(store.getState());
  130. return _objectSpread({
  131. 'aria-autocomplete': 'both',
  132. 'aria-activedescendant': store.getState().isOpen && store.getState().activeItemId !== null ? "".concat(props.id, "-item-").concat(store.getState().activeItemId) : undefined,
  133. 'aria-controls': store.getState().isOpen ? "".concat(props.id, "-list") : undefined,
  134. 'aria-labelledby': "".concat(props.id, "-label"),
  135. value: store.getState().completion || store.getState().query,
  136. id: "".concat(props.id, "-input"),
  137. autoComplete: 'off',
  138. autoCorrect: 'off',
  139. autoCapitalize: 'off',
  140. enterKeyHint: activeItem !== null && activeItem !== void 0 && activeItem.itemUrl ? 'go' : 'search',
  141. spellCheck: 'false',
  142. autoFocus: props.autoFocus,
  143. placeholder: props.placeholder,
  144. maxLength: maxLength,
  145. type: 'search',
  146. onChange: function onChange(event) {
  147. onInput(_objectSpread({
  148. event: event,
  149. props: props,
  150. query: event.currentTarget.value.slice(0, maxLength),
  151. refresh: refresh,
  152. store: store
  153. }, setters));
  154. },
  155. onKeyDown: function onKeyDown(event) {
  156. _onKeyDown(_objectSpread({
  157. event: event,
  158. props: props,
  159. refresh: refresh,
  160. store: store
  161. }, setters));
  162. },
  163. onFocus: onFocus,
  164. onBlur: function onBlur() {
  165. // We do rely on the `blur` event on touch devices.
  166. // See explanation in `onTouchStart`.
  167. if (!isTouchDevice) {
  168. store.dispatch('blur', null); // If requests are still pending when the user closes the panel, they
  169. // could reopen the panel once they resolve.
  170. // We want to prevent any subsequent query from reopening the panel
  171. // because it would result in an unsolicited UI behavior.
  172. if (!props.debug) {
  173. store.pendingRequests.cancelAll();
  174. }
  175. }
  176. },
  177. onClick: function onClick(event) {
  178. // When the panel is closed and you click on the input while
  179. // the input is focused, the `onFocus` event is not triggered
  180. // (default browser behavior).
  181. // In an autocomplete context, it makes sense to open the panel in this
  182. // case.
  183. // We mimic this event by catching the `onClick` event which
  184. // triggers the `onFocus` for the panel to open.
  185. if (providedProps.inputElement === props.environment.document.activeElement && !store.getState().isOpen) {
  186. onFocus(event);
  187. }
  188. }
  189. }, rest);
  190. };
  191. var getLabelProps = function getLabelProps(rest) {
  192. return _objectSpread({
  193. htmlFor: "".concat(props.id, "-input"),
  194. id: "".concat(props.id, "-label")
  195. }, rest);
  196. };
  197. var getListProps = function getListProps(rest) {
  198. return _objectSpread({
  199. role: 'listbox',
  200. 'aria-labelledby': "".concat(props.id, "-label"),
  201. id: "".concat(props.id, "-list")
  202. }, rest);
  203. };
  204. var getPanelProps = function getPanelProps(rest) {
  205. return _objectSpread({
  206. onMouseDown: function onMouseDown(event) {
  207. // Prevents the `activeElement` from being changed to the panel so
  208. // that the blur event is not triggered, otherwise it closes the
  209. // panel.
  210. event.preventDefault();
  211. },
  212. onMouseLeave: function onMouseLeave() {
  213. store.dispatch('mouseleave', null);
  214. }
  215. }, rest);
  216. };
  217. var getItemProps = function getItemProps(providedProps) {
  218. var item = providedProps.item,
  219. source = providedProps.source,
  220. rest = _objectWithoutProperties(providedProps, _excluded5);
  221. return _objectSpread({
  222. id: "".concat(props.id, "-item-").concat(item.__autocomplete_id),
  223. role: 'option',
  224. 'aria-selected': store.getState().activeItemId === item.__autocomplete_id,
  225. onMouseMove: function onMouseMove(event) {
  226. if (item.__autocomplete_id === store.getState().activeItemId) {
  227. return;
  228. }
  229. store.dispatch('mousemove', item.__autocomplete_id);
  230. var activeItem = getActiveItem(store.getState());
  231. if (store.getState().activeItemId !== null && activeItem) {
  232. var _item = activeItem.item,
  233. itemInputValue = activeItem.itemInputValue,
  234. itemUrl = activeItem.itemUrl,
  235. _source = activeItem.source;
  236. _source.onActive(_objectSpread({
  237. event: event,
  238. item: _item,
  239. itemInputValue: itemInputValue,
  240. itemUrl: itemUrl,
  241. refresh: refresh,
  242. source: _source,
  243. state: store.getState()
  244. }, setters));
  245. }
  246. },
  247. onMouseDown: function onMouseDown(event) {
  248. // Prevents the `activeElement` from being changed to the item so it
  249. // can remain with the current `activeElement`.
  250. event.preventDefault();
  251. },
  252. onClick: function onClick(event) {
  253. var itemInputValue = source.getItemInputValue({
  254. item: item,
  255. state: store.getState()
  256. });
  257. var itemUrl = source.getItemUrl({
  258. item: item,
  259. state: store.getState()
  260. }); // If `getItemUrl` is provided, it means that the suggestion
  261. // is a link, not plain text that aims at updating the query.
  262. // We can therefore skip the state change because it will update
  263. // the `activeItemId`, resulting in a UI flash, especially
  264. // noticeable on mobile.
  265. var runPreCommand = itemUrl ? Promise.resolve() : onInput(_objectSpread({
  266. event: event,
  267. nextState: {
  268. isOpen: false
  269. },
  270. props: props,
  271. query: itemInputValue,
  272. refresh: refresh,
  273. store: store
  274. }, setters));
  275. runPreCommand.then(function () {
  276. source.onSelect(_objectSpread({
  277. event: event,
  278. item: item,
  279. itemInputValue: itemInputValue,
  280. itemUrl: itemUrl,
  281. refresh: refresh,
  282. source: source,
  283. state: store.getState()
  284. }, setters));
  285. });
  286. }
  287. }, rest);
  288. };
  289. return {
  290. getEnvironmentProps: getEnvironmentProps,
  291. getRootProps: getRootProps,
  292. getFormProps: getFormProps,
  293. getLabelProps: getLabelProps,
  294. getInputProps: getInputProps,
  295. getPanelProps: getPanelProps,
  296. getListProps: getListProps,
  297. getItemProps: getItemProps
  298. };
  299. }