@atlaskit/link-datasource 1.13.0 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/analytics/constants.js +1 -1
  3. package/dist/cjs/ui/jira-issues-modal/basic-filters/hooks/useFilterOptions.js +34 -12
  4. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/dropdownIndicator.js +39 -0
  5. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +51 -20
  6. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.js +2 -0
  7. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/index.js +1 -0
  8. package/dist/es2019/analytics/constants.js +1 -1
  9. package/dist/es2019/ui/jira-issues-modal/basic-filters/hooks/useFilterOptions.js +14 -2
  10. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/dropdownIndicator.js +34 -0
  11. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +25 -10
  12. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.js +2 -0
  13. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/index.js +1 -0
  14. package/dist/esm/analytics/constants.js +1 -1
  15. package/dist/esm/ui/jira-issues-modal/basic-filters/hooks/useFilterOptions.js +35 -13
  16. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/dropdownIndicator.js +32 -0
  17. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +52 -21
  18. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.js +2 -0
  19. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/index.js +1 -0
  20. package/dist/types/ui/jira-issues-modal/basic-filters/ui/async-popup-select/dropdownIndicator.d.ts +5 -0
  21. package/dist/types/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.d.ts +2 -1
  22. package/dist/types/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.d.ts +1 -0
  23. package/dist/types-ts4.5/ui/jira-issues-modal/basic-filters/ui/async-popup-select/dropdownIndicator.d.ts +5 -0
  24. package/dist/types-ts4.5/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.d.ts +2 -1
  25. package/dist/types-ts4.5/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.d.ts +1 -0
  26. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/link-datasource
2
2
 
3
+ ## 1.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#42684](https://bitbucket.org/atlassian/atlassian-frontend/pull-requests/42684) [`3752d98e7fd`](https://bitbucket.org/atlassian/atlassian-frontend/commits/3752d98e7fd) - Add search functionality to basic filter dropdowns.
8
+
3
9
  ## 1.13.0
4
10
 
5
11
  ### Minor Changes
@@ -7,5 +7,5 @@ exports.packageMetaData = exports.EVENT_CHANNEL = void 0;
7
7
  var EVENT_CHANNEL = exports.EVENT_CHANNEL = 'media';
8
8
  var packageMetaData = exports.packageMetaData = {
9
9
  packageName: "@atlaskit/link-datasource",
10
- packageVersion: "1.13.0"
10
+ packageVersion: "1.13.1"
11
11
  };
@@ -31,12 +31,15 @@ var useFilterOptions = exports.useFilterOptions = function useFilterOptions(_ref
31
31
  _useState8 = (0, _slicedToArray2.default)(_useState7, 2),
32
32
  nextPageCursor = _useState8[0],
33
33
  setNextPageCursor = _useState8[1];
34
+ var initialData = (0, _react.useRef)();
34
35
  var _useBasicFilterAGG = (0, _useBasicFilterAGG2.useBasicFilterAGG)(),
35
36
  getFieldValues = _useBasicFilterAGG.getFieldValues;
36
37
  var fetchFilterOptions = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
37
38
  var _ref3,
38
39
  pageCursor,
39
40
  searchString,
41
+ isRequestLikeInitialSearch,
42
+ initialResponseData,
40
43
  response,
41
44
  isNewSearch,
42
45
  _args = arguments;
@@ -45,8 +48,18 @@ var useFilterOptions = exports.useFilterOptions = function useFilterOptions(_ref
45
48
  case 0:
46
49
  _ref3 = _args.length > 0 && _args[0] !== undefined ? _args[0] : {}, pageCursor = _ref3.pageCursor, searchString = _ref3.searchString;
47
50
  setStatus('loading');
48
- _context.prev = 2;
49
- _context.next = 5;
51
+ isRequestLikeInitialSearch = !pageCursor && !searchString;
52
+ initialResponseData = initialData.current;
53
+ _context.prev = 4;
54
+ if (!(isRequestLikeInitialSearch && initialResponseData)) {
55
+ _context.next = 9;
56
+ break;
57
+ }
58
+ _context.t0 = initialResponseData;
59
+ _context.next = 12;
60
+ break;
61
+ case 9:
62
+ _context.next = 11;
50
63
  return getFieldValues({
51
64
  cloudId: cloudId,
52
65
  jql: '',
@@ -54,35 +67,44 @@ var useFilterOptions = exports.useFilterOptions = function useFilterOptions(_ref
54
67
  searchString: searchString,
55
68
  pageCursor: pageCursor
56
69
  });
57
- case 5:
58
- response = _context.sent;
70
+ case 11:
71
+ _context.t0 = _context.sent;
72
+ case 12:
73
+ response = _context.t0;
59
74
  if (!(response.errors && response.errors.length > 0)) {
60
- _context.next = 9;
75
+ _context.next = 16;
61
76
  break;
62
77
  }
63
78
  setStatus('rejected');
64
79
  return _context.abrupt("return");
65
- case 9:
80
+ case 16:
66
81
  isNewSearch = !pageCursor;
67
82
  if (isNewSearch) {
68
83
  setFilterOptions((0, _transformers.mapFieldValuesToFilterOptions)(response));
84
+ if (isRequestLikeInitialSearch) {
85
+ /**
86
+ * The initial dataset is used in couple of paths, eg: when a user searches and clears the search text.
87
+ * During these times, we dont want to fetch data again and again, hence a mini cache setup to store and provide the initial dataset
88
+ */
89
+ initialData.current = response;
90
+ }
69
91
  } else {
70
92
  setFilterOptions([].concat((0, _toConsumableArray2.default)(filterOptions), (0, _toConsumableArray2.default)((0, _transformers.mapFieldValuesToFilterOptions)(response))));
71
93
  }
72
94
  setTotalCount((0, _transformers.mapFieldValuesToTotalCount)(response));
73
95
  setNextPageCursor((0, _transformers.mapFieldValuesToPageCursor)(response));
74
96
  setStatus('resolved');
75
- _context.next = 19;
97
+ _context.next = 26;
76
98
  break;
77
- case 16:
78
- _context.prev = 16;
79
- _context.t0 = _context["catch"](2);
99
+ case 23:
100
+ _context.prev = 23;
101
+ _context.t1 = _context["catch"](4);
80
102
  setStatus('rejected');
81
- case 19:
103
+ case 26:
82
104
  case "end":
83
105
  return _context.stop();
84
106
  }
85
- }, _callee, null, [[2, 16]]);
107
+ }, _callee, null, [[4, 23]]);
86
108
  })), [cloudId, filterOptions, filterType, getFieldValues]);
87
109
  return {
88
110
  filterOptions: filterOptions,
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _crossCircle = _interopRequireDefault(require("@atlaskit/icon/glyph/cross-circle"));
10
+ var _search = _interopRequireDefault(require("@atlaskit/icon/glyph/search"));
11
+ var _primitives = require("@atlaskit/primitives");
12
+ var _select = require("@atlaskit/select");
13
+ var customDropdownIndicatorStyles = (0, _primitives.xcss)({
14
+ display: 'flex',
15
+ cursor: 'pointer',
16
+ justifyContent: 'center',
17
+ width: "var(--ds-space-400, 32px)"
18
+ });
19
+ var CustomDropdownIndicator = function CustomDropdownIndicator(props) {
20
+ var selectProps = props.selectProps;
21
+ return /*#__PURE__*/_react.default.createElement(_select.components.DropdownIndicator, props, /*#__PURE__*/_react.default.createElement(_primitives.Box, {
22
+ xcss: customDropdownIndicatorStyles,
23
+ onClick: function onClick() {
24
+ if (selectProps.inputValue) {
25
+ selectProps.onInputChange && selectProps.onInputChange('', {
26
+ action: 'input-change',
27
+ prevInputValue: selectProps.inputValue
28
+ });
29
+ }
30
+ }
31
+ }, selectProps.inputValue ? /*#__PURE__*/_react.default.createElement(_crossCircle.default, {
32
+ size: "small",
33
+ label: ""
34
+ }) : /*#__PURE__*/_react.default.createElement(_search.default, {
35
+ size: "small",
36
+ label: ""
37
+ })));
38
+ };
39
+ var _default = exports.default = CustomDropdownIndicator;
@@ -12,10 +12,12 @@ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"))
12
12
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
13
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
14
14
  var _react = _interopRequireWildcard(require("react"));
15
+ var _debounce = _interopRequireDefault(require("lodash/debounce"));
15
16
  var _reactIntlNext = require("react-intl-next");
16
17
  var _select = require("@atlaskit/select");
17
18
  var _useFilterOptions2 = require("../../hooks/useFilterOptions");
18
19
  var _control = _interopRequireDefault(require("./control"));
20
+ var _dropdownIndicator = _interopRequireDefault(require("./dropdownIndicator"));
19
21
  var _footer = _interopRequireDefault(require("./footer"));
20
22
  var _formatOptionLabel = _interopRequireDefault(require("./formatOptionLabel"));
21
23
  var _messages = require("./messages");
@@ -27,12 +29,15 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
27
29
  var noFilterOptions = function noFilterOptions() {
28
30
  return true;
29
31
  };
32
+ var SEARCH_DEBOUNCE_MS = 350;
30
33
  var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
31
34
  var filterType = _ref.filterType,
32
35
  cloudId = _ref.cloudId,
33
36
  selection = _ref.selection,
34
37
  _ref$onSelectionChang = _ref.onSelectionChange,
35
- onSelectionChange = _ref$onSelectionChang === void 0 ? function () {} : _ref$onSelectionChang;
38
+ onSelectionChange = _ref$onSelectionChang === void 0 ? function () {} : _ref$onSelectionChang,
39
+ _ref$isDisabled = _ref.isDisabled,
40
+ isDisabled = _ref$isDisabled === void 0 ? false : _ref$isDisabled;
36
41
  var _useIntl = (0, _reactIntlNext.useIntl)(),
37
42
  formatMessage = _useIntl.formatMessage;
38
43
  var pickerRef = (0, _react.useRef)(null);
@@ -52,30 +57,49 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
52
57
  fetchFilterOptions = _useFilterOptions.fetchFilterOptions,
53
58
  totalCount = _useFilterOptions.totalCount,
54
59
  status = _useFilterOptions.status;
55
- var handleInputChange = (0, _react.useCallback)(function (searchString, actionMeta) {
56
- if (actionMeta.action === 'input-change' && searchString !== searchTerm) {
57
- setSearchTerm(searchString);
58
- }
59
- }, [searchTerm]);
60
+ var handleDebouncedFetchFilterOptions = (0, _react.useMemo)(function () {
61
+ return (0, _debounce.default)(fetchFilterOptions, SEARCH_DEBOUNCE_MS);
62
+ }, [fetchFilterOptions]);
63
+ var handleInputChange = (0, _react.useCallback)( /*#__PURE__*/function () {
64
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(newSearchTerm, actionMeta) {
65
+ return _regenerator.default.wrap(function _callee$(_context) {
66
+ while (1) switch (_context.prev = _context.next) {
67
+ case 0:
68
+ if (actionMeta.action === 'input-change' && newSearchTerm !== searchTerm) {
69
+ setSearchTerm(newSearchTerm);
70
+ handleDebouncedFetchFilterOptions({
71
+ searchString: newSearchTerm
72
+ });
73
+ }
74
+ case 1:
75
+ case "end":
76
+ return _context.stop();
77
+ }
78
+ }, _callee);
79
+ }));
80
+ return function (_x, _x2) {
81
+ return _ref2.apply(this, arguments);
82
+ };
83
+ }(), [handleDebouncedFetchFilterOptions, searchTerm]);
60
84
  var handleOptionSelection = function handleOptionSelection(newValue) {
61
85
  setSelectedOptions(newValue);
62
86
  onSelectionChange(newValue);
63
87
  };
64
- var handleOpenPopup = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
65
- return _regenerator.default.wrap(function _callee$(_context) {
66
- while (1) switch (_context.prev = _context.next) {
88
+ var handleOpenPopup = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2() {
89
+ return _regenerator.default.wrap(function _callee2$(_context2) {
90
+ while (1) switch (_context2.prev = _context2.next) {
67
91
  case 0:
68
92
  if (!(status === 'empty')) {
69
- _context.next = 3;
93
+ _context2.next = 3;
70
94
  break;
71
95
  }
72
- _context.next = 3;
96
+ _context2.next = 3;
73
97
  return fetchFilterOptions();
74
98
  case 3:
75
99
  case "end":
76
- return _context.stop();
100
+ return _context2.stop();
77
101
  }
78
- }, _callee);
102
+ }, _callee2);
79
103
  })), [fetchFilterOptions, status]);
80
104
  (0, _react.useEffect)(function () {
81
105
  if (status === 'resolved') {
@@ -85,6 +109,9 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
85
109
  }
86
110
  }, [status]);
87
111
  var isLoading = status === 'loading' || status === 'empty';
112
+ var shouldShowFooter = status === 'resolved' && filterOptions.length > 0;
113
+ var options = isLoading ? [] : filterOptions; // if not set to [], then on loading, no loading UI will be shown
114
+
88
115
  return /*#__PURE__*/_react.default.createElement(_select.PopupSelect, {
89
116
  isMulti: true,
90
117
  maxMenuWidth: 300,
@@ -106,24 +133,28 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
106
133
  components: {
107
134
  /* @ts-expect-error - This component has stricter OptionType, hence a temp setup untill its made generic */
108
135
  Option: _select.CheckboxOption,
109
- Control: _control.default
136
+ Control: _control.default,
137
+ LoadingIndicator: undefined,
138
+ // disables the three ... indicator in the searchbox when picker is loading
139
+ DropdownIndicator: _dropdownIndicator.default
110
140
  },
111
- options: filterOptions,
141
+ options: options,
112
142
  value: selectedOptions,
113
143
  filterOption: noFilterOptions,
114
144
  formatOptionLabel: _formatOptionLabel.default,
115
145
  onChange: handleOptionSelection,
116
146
  onInputChange: handleInputChange,
117
- target: function target(_ref3) {
118
- var isOpen = _ref3.isOpen,
119
- triggerProps = (0, _objectWithoutProperties2.default)(_ref3, _excluded);
147
+ target: function target(_ref4) {
148
+ var isOpen = _ref4.isOpen,
149
+ triggerProps = (0, _objectWithoutProperties2.default)(_ref4, _excluded);
120
150
  return /*#__PURE__*/_react.default.createElement(_trigger.default, (0, _extends2.default)({}, triggerProps, {
121
151
  filterType: filterType,
122
152
  isSelected: isOpen,
123
- onClick: handleOpenPopup
153
+ onClick: handleOpenPopup,
154
+ isDisabled: isDisabled
124
155
  }));
125
156
  },
126
- footer: /*#__PURE__*/_react.default.createElement(_footer.default, {
157
+ footer: shouldShowFooter && /*#__PURE__*/_react.default.createElement(_footer.default, {
127
158
  currentDisplayCount: filterOptions.length,
128
159
  totalCount: totalCount
129
160
  })
@@ -16,11 +16,13 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
16
16
  var PopupTrigger = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) {
17
17
  var filterType = _ref.filterType,
18
18
  isSelected = _ref.isSelected,
19
+ isDisabled = _ref.isDisabled,
19
20
  onClick = _ref.onClick;
20
21
  return /*#__PURE__*/_react.default.createElement(_standardButton.default, {
21
22
  ref: ref,
22
23
  appearance: "default",
23
24
  isSelected: isSelected,
25
+ isDisabled: isDisabled,
24
26
  onClick: onClick,
25
27
  testId: "jlol-basic-filter-".concat(filterType, "-trigger"),
26
28
  iconAfter: /*#__PURE__*/_react.default.createElement(_chevronDown.default, {
@@ -35,6 +35,7 @@ var BasicFilterContainer = function BasicFilterContainer(_ref) {
35
35
  filterType: filter,
36
36
  key: filter,
37
37
  selection: selection,
38
+ isDisabled: !cloudId,
38
39
  onSelectionChange: handleSelectionChange
39
40
  });
40
41
  }));
@@ -1,5 +1,5 @@
1
1
  export const EVENT_CHANNEL = 'media';
2
2
  export const packageMetaData = {
3
3
  packageName: "@atlaskit/link-datasource",
4
- packageVersion: "1.13.0"
4
+ packageVersion: "1.13.1"
5
5
  };
@@ -1,4 +1,4 @@
1
- import { useCallback, useState } from 'react';
1
+ import { useCallback, useRef, useState } from 'react';
2
2
  import { useBasicFilterAGG } from '../../../../services/useBasicFilterAGG';
3
3
  import { mapFieldValuesToFilterOptions, mapFieldValuesToPageCursor, mapFieldValuesToTotalCount } from '../utils/transformers';
4
4
  export const useFilterOptions = ({
@@ -9,6 +9,7 @@ export const useFilterOptions = ({
9
9
  const [totalCount, setTotalCount] = useState(0);
10
10
  const [status, setStatus] = useState('empty');
11
11
  const [nextPageCursor, setNextPageCursor] = useState(undefined);
12
+ const initialData = useRef();
12
13
  const {
13
14
  getFieldValues
14
15
  } = useBasicFilterAGG();
@@ -17,8 +18,12 @@ export const useFilterOptions = ({
17
18
  searchString
18
19
  } = {}) => {
19
20
  setStatus('loading');
21
+ const isRequestLikeInitialSearch = !pageCursor && !searchString;
22
+ const {
23
+ current: initialResponseData
24
+ } = initialData;
20
25
  try {
21
- const response = await getFieldValues({
26
+ const response = isRequestLikeInitialSearch && initialResponseData ? initialResponseData : await getFieldValues({
22
27
  cloudId,
23
28
  jql: '',
24
29
  jqlTerm: filterType,
@@ -32,6 +37,13 @@ export const useFilterOptions = ({
32
37
  const isNewSearch = !pageCursor;
33
38
  if (isNewSearch) {
34
39
  setFilterOptions(mapFieldValuesToFilterOptions(response));
40
+ if (isRequestLikeInitialSearch) {
41
+ /**
42
+ * The initial dataset is used in couple of paths, eg: when a user searches and clears the search text.
43
+ * During these times, we dont want to fetch data again and again, hence a mini cache setup to store and provide the initial dataset
44
+ */
45
+ initialData.current = response;
46
+ }
35
47
  } else {
36
48
  setFilterOptions([...filterOptions, ...mapFieldValuesToFilterOptions(response)]);
37
49
  }
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import CloseIcon from '@atlaskit/icon/glyph/cross-circle';
3
+ import SearchIcon from '@atlaskit/icon/glyph/search';
4
+ import { Box, xcss } from '@atlaskit/primitives';
5
+ import { components } from '@atlaskit/select';
6
+ const customDropdownIndicatorStyles = xcss({
7
+ display: 'flex',
8
+ cursor: 'pointer',
9
+ justifyContent: 'center',
10
+ width: "var(--ds-space-400, 32px)"
11
+ });
12
+ const CustomDropdownIndicator = props => {
13
+ const {
14
+ selectProps
15
+ } = props;
16
+ return /*#__PURE__*/React.createElement(components.DropdownIndicator, props, /*#__PURE__*/React.createElement(Box, {
17
+ xcss: customDropdownIndicatorStyles,
18
+ onClick: () => {
19
+ if (selectProps.inputValue) {
20
+ selectProps.onInputChange && selectProps.onInputChange('', {
21
+ action: 'input-change',
22
+ prevInputValue: selectProps.inputValue
23
+ });
24
+ }
25
+ }
26
+ }, selectProps.inputValue ? /*#__PURE__*/React.createElement(CloseIcon, {
27
+ size: "small",
28
+ label: ""
29
+ }) : /*#__PURE__*/React.createElement(SearchIcon, {
30
+ size: "small",
31
+ label: ""
32
+ })));
33
+ };
34
+ export default CustomDropdownIndicator;
@@ -1,20 +1,24 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import debounce from 'lodash/debounce';
3
4
  import { useIntl } from 'react-intl-next';
4
5
  import { CheckboxOption, PopupSelect } from '@atlaskit/select';
5
6
  import { useFilterOptions } from '../../hooks/useFilterOptions';
6
7
  import CustomControl from './control';
8
+ import CustomDropdownIndicator from './dropdownIndicator';
7
9
  import PopupFooter from './footer';
8
10
  import formatOptionLabel from './formatOptionLabel';
9
11
  import { asyncPopupSelectMessages } from './messages';
10
12
  import PopupTrigger from './trigger';
11
13
  // Needed to disable filtering from react-select
12
14
  const noFilterOptions = () => true;
15
+ const SEARCH_DEBOUNCE_MS = 350;
13
16
  const AsyncPopupSelect = ({
14
17
  filterType,
15
18
  cloudId,
16
19
  selection,
17
- onSelectionChange = () => {}
20
+ onSelectionChange = () => {},
21
+ isDisabled = false
18
22
  }) => {
19
23
  const {
20
24
  formatMessage
@@ -31,11 +35,15 @@ const AsyncPopupSelect = ({
31
35
  filterType,
32
36
  cloudId
33
37
  });
34
- const handleInputChange = useCallback((searchString, actionMeta) => {
35
- if (actionMeta.action === 'input-change' && searchString !== searchTerm) {
36
- setSearchTerm(searchString);
38
+ const handleDebouncedFetchFilterOptions = useMemo(() => debounce(fetchFilterOptions, SEARCH_DEBOUNCE_MS), [fetchFilterOptions]);
39
+ const handleInputChange = useCallback(async (newSearchTerm, actionMeta) => {
40
+ if (actionMeta.action === 'input-change' && newSearchTerm !== searchTerm) {
41
+ setSearchTerm(newSearchTerm);
42
+ handleDebouncedFetchFilterOptions({
43
+ searchString: newSearchTerm
44
+ });
37
45
  }
38
- }, [searchTerm]);
46
+ }, [handleDebouncedFetchFilterOptions, searchTerm]);
39
47
  const handleOptionSelection = newValue => {
40
48
  setSelectedOptions(newValue);
41
49
  onSelectionChange(newValue);
@@ -53,6 +61,9 @@ const AsyncPopupSelect = ({
53
61
  }
54
62
  }, [status]);
55
63
  const isLoading = status === 'loading' || status === 'empty';
64
+ const shouldShowFooter = status === 'resolved' && filterOptions.length > 0;
65
+ const options = isLoading ? [] : filterOptions; // if not set to [], then on loading, no loading UI will be shown
66
+
56
67
  return /*#__PURE__*/React.createElement(PopupSelect, {
57
68
  isMulti: true,
58
69
  maxMenuWidth: 300,
@@ -74,9 +85,12 @@ const AsyncPopupSelect = ({
74
85
  components: {
75
86
  /* @ts-expect-error - This component has stricter OptionType, hence a temp setup untill its made generic */
76
87
  Option: CheckboxOption,
77
- Control: CustomControl
88
+ Control: CustomControl,
89
+ LoadingIndicator: undefined,
90
+ // disables the three ... indicator in the searchbox when picker is loading
91
+ DropdownIndicator: CustomDropdownIndicator
78
92
  },
79
- options: filterOptions,
93
+ options: options,
80
94
  value: selectedOptions,
81
95
  filterOption: noFilterOptions,
82
96
  formatOptionLabel: formatOptionLabel,
@@ -88,9 +102,10 @@ const AsyncPopupSelect = ({
88
102
  }) => /*#__PURE__*/React.createElement(PopupTrigger, _extends({}, triggerProps, {
89
103
  filterType: filterType,
90
104
  isSelected: isOpen,
91
- onClick: handleOpenPopup
105
+ onClick: handleOpenPopup,
106
+ isDisabled: isDisabled
92
107
  })),
93
- footer: /*#__PURE__*/React.createElement(PopupFooter, {
108
+ footer: shouldShowFooter && /*#__PURE__*/React.createElement(PopupFooter, {
94
109
  currentDisplayCount: filterOptions.length,
95
110
  totalCount: totalCount
96
111
  })
@@ -6,12 +6,14 @@ import { asyncPopupSelectMessages } from './messages';
6
6
  const PopupTrigger = /*#__PURE__*/forwardRef(({
7
7
  filterType,
8
8
  isSelected,
9
+ isDisabled,
9
10
  onClick
10
11
  }, ref) => {
11
12
  return /*#__PURE__*/React.createElement(Button, {
12
13
  ref: ref,
13
14
  appearance: "default",
14
15
  isSelected: isSelected,
16
+ isDisabled: isDisabled,
15
17
  onClick: onClick,
16
18
  testId: `jlol-basic-filter-${filterType}-trigger`,
17
19
  iconAfter: /*#__PURE__*/React.createElement(ChevronDownIcon, {
@@ -22,6 +22,7 @@ const BasicFilterContainer = ({
22
22
  filterType: filter,
23
23
  key: filter,
24
24
  selection: selection,
25
+ isDisabled: !cloudId,
25
26
  onSelectionChange: handleSelectionChange
26
27
  })));
27
28
  };
@@ -1,5 +1,5 @@
1
1
  export var EVENT_CHANNEL = 'media';
2
2
  export var packageMetaData = {
3
3
  packageName: "@atlaskit/link-datasource",
4
- packageVersion: "1.13.0"
4
+ packageVersion: "1.13.1"
5
5
  };
@@ -2,7 +2,7 @@ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
3
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
4
  import _regeneratorRuntime from "@babel/runtime/regenerator";
5
- import { useCallback, useState } from 'react';
5
+ import { useCallback, useRef, useState } from 'react';
6
6
  import { useBasicFilterAGG } from '../../../../services/useBasicFilterAGG';
7
7
  import { mapFieldValuesToFilterOptions, mapFieldValuesToPageCursor, mapFieldValuesToTotalCount } from '../utils/transformers';
8
8
  export var useFilterOptions = function useFilterOptions(_ref) {
@@ -24,12 +24,15 @@ export var useFilterOptions = function useFilterOptions(_ref) {
24
24
  _useState8 = _slicedToArray(_useState7, 2),
25
25
  nextPageCursor = _useState8[0],
26
26
  setNextPageCursor = _useState8[1];
27
+ var initialData = useRef();
27
28
  var _useBasicFilterAGG = useBasicFilterAGG(),
28
29
  getFieldValues = _useBasicFilterAGG.getFieldValues;
29
30
  var fetchFilterOptions = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
30
31
  var _ref3,
31
32
  pageCursor,
32
33
  searchString,
34
+ isRequestLikeInitialSearch,
35
+ initialResponseData,
33
36
  response,
34
37
  isNewSearch,
35
38
  _args = arguments;
@@ -38,8 +41,18 @@ export var useFilterOptions = function useFilterOptions(_ref) {
38
41
  case 0:
39
42
  _ref3 = _args.length > 0 && _args[0] !== undefined ? _args[0] : {}, pageCursor = _ref3.pageCursor, searchString = _ref3.searchString;
40
43
  setStatus('loading');
41
- _context.prev = 2;
42
- _context.next = 5;
44
+ isRequestLikeInitialSearch = !pageCursor && !searchString;
45
+ initialResponseData = initialData.current;
46
+ _context.prev = 4;
47
+ if (!(isRequestLikeInitialSearch && initialResponseData)) {
48
+ _context.next = 9;
49
+ break;
50
+ }
51
+ _context.t0 = initialResponseData;
52
+ _context.next = 12;
53
+ break;
54
+ case 9:
55
+ _context.next = 11;
43
56
  return getFieldValues({
44
57
  cloudId: cloudId,
45
58
  jql: '',
@@ -47,35 +60,44 @@ export var useFilterOptions = function useFilterOptions(_ref) {
47
60
  searchString: searchString,
48
61
  pageCursor: pageCursor
49
62
  });
50
- case 5:
51
- response = _context.sent;
63
+ case 11:
64
+ _context.t0 = _context.sent;
65
+ case 12:
66
+ response = _context.t0;
52
67
  if (!(response.errors && response.errors.length > 0)) {
53
- _context.next = 9;
68
+ _context.next = 16;
54
69
  break;
55
70
  }
56
71
  setStatus('rejected');
57
72
  return _context.abrupt("return");
58
- case 9:
73
+ case 16:
59
74
  isNewSearch = !pageCursor;
60
75
  if (isNewSearch) {
61
76
  setFilterOptions(mapFieldValuesToFilterOptions(response));
77
+ if (isRequestLikeInitialSearch) {
78
+ /**
79
+ * The initial dataset is used in couple of paths, eg: when a user searches and clears the search text.
80
+ * During these times, we dont want to fetch data again and again, hence a mini cache setup to store and provide the initial dataset
81
+ */
82
+ initialData.current = response;
83
+ }
62
84
  } else {
63
85
  setFilterOptions([].concat(_toConsumableArray(filterOptions), _toConsumableArray(mapFieldValuesToFilterOptions(response))));
64
86
  }
65
87
  setTotalCount(mapFieldValuesToTotalCount(response));
66
88
  setNextPageCursor(mapFieldValuesToPageCursor(response));
67
89
  setStatus('resolved');
68
- _context.next = 19;
90
+ _context.next = 26;
69
91
  break;
70
- case 16:
71
- _context.prev = 16;
72
- _context.t0 = _context["catch"](2);
92
+ case 23:
93
+ _context.prev = 23;
94
+ _context.t1 = _context["catch"](4);
73
95
  setStatus('rejected');
74
- case 19:
96
+ case 26:
75
97
  case "end":
76
98
  return _context.stop();
77
99
  }
78
- }, _callee, null, [[2, 16]]);
100
+ }, _callee, null, [[4, 23]]);
79
101
  })), [cloudId, filterOptions, filterType, getFieldValues]);
80
102
  return {
81
103
  filterOptions: filterOptions,
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import CloseIcon from '@atlaskit/icon/glyph/cross-circle';
3
+ import SearchIcon from '@atlaskit/icon/glyph/search';
4
+ import { Box, xcss } from '@atlaskit/primitives';
5
+ import { components } from '@atlaskit/select';
6
+ var customDropdownIndicatorStyles = xcss({
7
+ display: 'flex',
8
+ cursor: 'pointer',
9
+ justifyContent: 'center',
10
+ width: "var(--ds-space-400, 32px)"
11
+ });
12
+ var CustomDropdownIndicator = function CustomDropdownIndicator(props) {
13
+ var selectProps = props.selectProps;
14
+ return /*#__PURE__*/React.createElement(components.DropdownIndicator, props, /*#__PURE__*/React.createElement(Box, {
15
+ xcss: customDropdownIndicatorStyles,
16
+ onClick: function onClick() {
17
+ if (selectProps.inputValue) {
18
+ selectProps.onInputChange && selectProps.onInputChange('', {
19
+ action: 'input-change',
20
+ prevInputValue: selectProps.inputValue
21
+ });
22
+ }
23
+ }
24
+ }, selectProps.inputValue ? /*#__PURE__*/React.createElement(CloseIcon, {
25
+ size: "small",
26
+ label: ""
27
+ }) : /*#__PURE__*/React.createElement(SearchIcon, {
28
+ size: "small",
29
+ label: ""
30
+ })));
31
+ };
32
+ export default CustomDropdownIndicator;
@@ -4,11 +4,13 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
4
4
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
5
  var _excluded = ["isOpen"];
6
6
  import _regeneratorRuntime from "@babel/runtime/regenerator";
7
- import React, { useCallback, useEffect, useRef, useState } from 'react';
7
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
+ import debounce from 'lodash/debounce';
8
9
  import { useIntl } from 'react-intl-next';
9
10
  import { CheckboxOption, PopupSelect } from '@atlaskit/select';
10
11
  import { useFilterOptions } from '../../hooks/useFilterOptions';
11
12
  import CustomControl from './control';
13
+ import CustomDropdownIndicator from './dropdownIndicator';
12
14
  import PopupFooter from './footer';
13
15
  import formatOptionLabel from './formatOptionLabel';
14
16
  import { asyncPopupSelectMessages } from './messages';
@@ -17,12 +19,15 @@ import PopupTrigger from './trigger';
17
19
  var noFilterOptions = function noFilterOptions() {
18
20
  return true;
19
21
  };
22
+ var SEARCH_DEBOUNCE_MS = 350;
20
23
  var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
21
24
  var filterType = _ref.filterType,
22
25
  cloudId = _ref.cloudId,
23
26
  selection = _ref.selection,
24
27
  _ref$onSelectionChang = _ref.onSelectionChange,
25
- onSelectionChange = _ref$onSelectionChang === void 0 ? function () {} : _ref$onSelectionChang;
28
+ onSelectionChange = _ref$onSelectionChang === void 0 ? function () {} : _ref$onSelectionChang,
29
+ _ref$isDisabled = _ref.isDisabled,
30
+ isDisabled = _ref$isDisabled === void 0 ? false : _ref$isDisabled;
26
31
  var _useIntl = useIntl(),
27
32
  formatMessage = _useIntl.formatMessage;
28
33
  var pickerRef = useRef(null);
@@ -42,30 +47,49 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
42
47
  fetchFilterOptions = _useFilterOptions.fetchFilterOptions,
43
48
  totalCount = _useFilterOptions.totalCount,
44
49
  status = _useFilterOptions.status;
45
- var handleInputChange = useCallback(function (searchString, actionMeta) {
46
- if (actionMeta.action === 'input-change' && searchString !== searchTerm) {
47
- setSearchTerm(searchString);
48
- }
49
- }, [searchTerm]);
50
+ var handleDebouncedFetchFilterOptions = useMemo(function () {
51
+ return debounce(fetchFilterOptions, SEARCH_DEBOUNCE_MS);
52
+ }, [fetchFilterOptions]);
53
+ var handleInputChange = useCallback( /*#__PURE__*/function () {
54
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(newSearchTerm, actionMeta) {
55
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
56
+ while (1) switch (_context.prev = _context.next) {
57
+ case 0:
58
+ if (actionMeta.action === 'input-change' && newSearchTerm !== searchTerm) {
59
+ setSearchTerm(newSearchTerm);
60
+ handleDebouncedFetchFilterOptions({
61
+ searchString: newSearchTerm
62
+ });
63
+ }
64
+ case 1:
65
+ case "end":
66
+ return _context.stop();
67
+ }
68
+ }, _callee);
69
+ }));
70
+ return function (_x, _x2) {
71
+ return _ref2.apply(this, arguments);
72
+ };
73
+ }(), [handleDebouncedFetchFilterOptions, searchTerm]);
50
74
  var handleOptionSelection = function handleOptionSelection(newValue) {
51
75
  setSelectedOptions(newValue);
52
76
  onSelectionChange(newValue);
53
77
  };
54
- var handleOpenPopup = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
55
- return _regeneratorRuntime.wrap(function _callee$(_context) {
56
- while (1) switch (_context.prev = _context.next) {
78
+ var handleOpenPopup = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
79
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
80
+ while (1) switch (_context2.prev = _context2.next) {
57
81
  case 0:
58
82
  if (!(status === 'empty')) {
59
- _context.next = 3;
83
+ _context2.next = 3;
60
84
  break;
61
85
  }
62
- _context.next = 3;
86
+ _context2.next = 3;
63
87
  return fetchFilterOptions();
64
88
  case 3:
65
89
  case "end":
66
- return _context.stop();
90
+ return _context2.stop();
67
91
  }
68
- }, _callee);
92
+ }, _callee2);
69
93
  })), [fetchFilterOptions, status]);
70
94
  useEffect(function () {
71
95
  if (status === 'resolved') {
@@ -75,6 +99,9 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
75
99
  }
76
100
  }, [status]);
77
101
  var isLoading = status === 'loading' || status === 'empty';
102
+ var shouldShowFooter = status === 'resolved' && filterOptions.length > 0;
103
+ var options = isLoading ? [] : filterOptions; // if not set to [], then on loading, no loading UI will be shown
104
+
78
105
  return /*#__PURE__*/React.createElement(PopupSelect, {
79
106
  isMulti: true,
80
107
  maxMenuWidth: 300,
@@ -96,24 +123,28 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
96
123
  components: {
97
124
  /* @ts-expect-error - This component has stricter OptionType, hence a temp setup untill its made generic */
98
125
  Option: CheckboxOption,
99
- Control: CustomControl
126
+ Control: CustomControl,
127
+ LoadingIndicator: undefined,
128
+ // disables the three ... indicator in the searchbox when picker is loading
129
+ DropdownIndicator: CustomDropdownIndicator
100
130
  },
101
- options: filterOptions,
131
+ options: options,
102
132
  value: selectedOptions,
103
133
  filterOption: noFilterOptions,
104
134
  formatOptionLabel: formatOptionLabel,
105
135
  onChange: handleOptionSelection,
106
136
  onInputChange: handleInputChange,
107
- target: function target(_ref3) {
108
- var isOpen = _ref3.isOpen,
109
- triggerProps = _objectWithoutProperties(_ref3, _excluded);
137
+ target: function target(_ref4) {
138
+ var isOpen = _ref4.isOpen,
139
+ triggerProps = _objectWithoutProperties(_ref4, _excluded);
110
140
  return /*#__PURE__*/React.createElement(PopupTrigger, _extends({}, triggerProps, {
111
141
  filterType: filterType,
112
142
  isSelected: isOpen,
113
- onClick: handleOpenPopup
143
+ onClick: handleOpenPopup,
144
+ isDisabled: isDisabled
114
145
  }));
115
146
  },
116
- footer: /*#__PURE__*/React.createElement(PopupFooter, {
147
+ footer: shouldShowFooter && /*#__PURE__*/React.createElement(PopupFooter, {
117
148
  currentDisplayCount: filterOptions.length,
118
149
  totalCount: totalCount
119
150
  })
@@ -6,11 +6,13 @@ import { asyncPopupSelectMessages } from './messages';
6
6
  var PopupTrigger = /*#__PURE__*/forwardRef(function (_ref, ref) {
7
7
  var filterType = _ref.filterType,
8
8
  isSelected = _ref.isSelected,
9
+ isDisabled = _ref.isDisabled,
9
10
  onClick = _ref.onClick;
10
11
  return /*#__PURE__*/React.createElement(Button, {
11
12
  ref: ref,
12
13
  appearance: "default",
13
14
  isSelected: isSelected,
15
+ isDisabled: isDisabled,
14
16
  onClick: onClick,
15
17
  testId: "jlol-basic-filter-".concat(filterType, "-trigger"),
16
18
  iconAfter: /*#__PURE__*/React.createElement(ChevronDownIcon, {
@@ -25,6 +25,7 @@ var BasicFilterContainer = function BasicFilterContainer(_ref) {
25
25
  filterType: filter,
26
26
  key: filter,
27
27
  selection: selection,
28
+ isDisabled: !cloudId,
28
29
  onSelectionChange: handleSelectionChange
29
30
  });
30
31
  }));
@@ -0,0 +1,5 @@
1
+ /// <reference types="react" />
2
+ import { DropdownIndicatorProps } from '@atlaskit/select';
3
+ import { SelectOption } from '../../types';
4
+ declare const CustomDropdownIndicator: (props: DropdownIndicatorProps<SelectOption, true>) => JSX.Element;
5
+ export default CustomDropdownIndicator;
@@ -5,6 +5,7 @@ export interface AsyncPopupSelectProps {
5
5
  cloudId: string;
6
6
  selection: SelectOption[];
7
7
  onSelectionChange?: (selection: SelectOption[]) => void;
8
+ isDisabled?: boolean;
8
9
  }
9
- declare const AsyncPopupSelect: ({ filterType, cloudId, selection, onSelectionChange, }: AsyncPopupSelectProps) => JSX.Element;
10
+ declare const AsyncPopupSelect: ({ filterType, cloudId, selection, onSelectionChange, isDisabled, }: AsyncPopupSelectProps) => JSX.Element;
10
11
  export default AsyncPopupSelect;
@@ -3,6 +3,7 @@ import { BasicFilterFieldType } from '../../types';
3
3
  export interface PopupTriggerProps {
4
4
  filterType: BasicFilterFieldType;
5
5
  isSelected?: boolean;
6
+ isDisabled?: boolean;
6
7
  onClick?: () => void;
7
8
  }
8
9
  declare const PopupTrigger: React.ForwardRefExoticComponent<PopupTriggerProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,5 @@
1
+ /// <reference types="react" />
2
+ import { DropdownIndicatorProps } from '@atlaskit/select';
3
+ import { SelectOption } from '../../types';
4
+ declare const CustomDropdownIndicator: (props: DropdownIndicatorProps<SelectOption, true>) => JSX.Element;
5
+ export default CustomDropdownIndicator;
@@ -5,6 +5,7 @@ export interface AsyncPopupSelectProps {
5
5
  cloudId: string;
6
6
  selection: SelectOption[];
7
7
  onSelectionChange?: (selection: SelectOption[]) => void;
8
+ isDisabled?: boolean;
8
9
  }
9
- declare const AsyncPopupSelect: ({ filterType, cloudId, selection, onSelectionChange, }: AsyncPopupSelectProps) => JSX.Element;
10
+ declare const AsyncPopupSelect: ({ filterType, cloudId, selection, onSelectionChange, isDisabled, }: AsyncPopupSelectProps) => JSX.Element;
10
11
  export default AsyncPopupSelect;
@@ -3,6 +3,7 @@ import { BasicFilterFieldType } from '../../types';
3
3
  export interface PopupTriggerProps {
4
4
  filterType: BasicFilterFieldType;
5
5
  isSelected?: boolean;
6
+ isDisabled?: boolean;
6
7
  onClick?: () => void;
7
8
  }
8
9
  declare const PopupTrigger: React.ForwardRefExoticComponent<PopupTriggerProps & React.RefAttributes<HTMLElement>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/link-datasource",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "UI Components to support linking platform dataset feature",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"