@dhis2-ui/transfer 10.11.0 → 10.12.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.
@@ -3,12 +3,13 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports.PartialSourceList = exports.PartialPickedList = exports.FullSourceList = exports.FullPickedList = void 0;
7
- var _react = _interopRequireDefault(require("react"));
6
+ exports.default = exports.PartialSourceList = exports.PartialPickedList = exports.OptionChangesForShortList = exports.FullSourceList = exports.FullPickedList = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
8
  var _transfer = require("../transfer.js");
9
9
  var _options = require("./common/options.js");
10
10
  var _statefulDecorator = require("./common/stateful-decorator.js");
11
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
12
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
12
13
  var _default = exports.default = {
13
14
  title: 'Transfer End Of List',
14
15
  decorators: [(0, _statefulDecorator.statefulDecorator)()]
@@ -86,4 +87,46 @@ PartialPickedList.story = {
86
87
  return value;
87
88
  })
88
89
  })]
90
+ };
91
+ const selectedOptionsLookup = {
92
+ 'val-9': {
93
+ value: 'val-9',
94
+ label: 'Option nr. 9'
95
+ }
96
+ };
97
+ const OptionChangesForShortList = (_, _ref7) => {
98
+ let {
99
+ onChange,
100
+ selected
101
+ } = _ref7;
102
+ const [optionsCount, setOptionsCount] = (0, _react.useState)(7);
103
+ const myOptions = (0, _react.useMemo)(() => {
104
+ let counter = 1;
105
+ const newOptions = [];
106
+ while (counter <= optionsCount) {
107
+ newOptions.push({
108
+ value: `val-${counter}`,
109
+ label: `Option nr. ${counter}`
110
+ });
111
+ counter++;
112
+ }
113
+ return newOptions;
114
+ }, [optionsCount]);
115
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("button", {
116
+ onClick: () => setOptionsCount(curr => curr + 1)
117
+ }, "Increment options lists"), /*#__PURE__*/_react.default.createElement(_transfer.Transfer, {
118
+ filterable: true,
119
+ selected: selected,
120
+ selectedOptionsLookup: selectedOptionsLookup,
121
+ onChange: onChange,
122
+ options: myOptions,
123
+ onEndReached: window.onEndReached,
124
+ onEndReachedPicked: window.onEndReachedPicked
125
+ }));
126
+ };
127
+ exports.OptionChangesForShortList = OptionChangesForShortList;
128
+ OptionChangesForShortList.story = {
129
+ decorators: [(0, _statefulDecorator.statefulDecorator)({
130
+ initialState: ['val-9']
131
+ })]
89
132
  };
@@ -3,12 +3,13 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.EndIntersectionDetector = void 0;
6
+ exports.INTERSECTION_DETECTOR_HEIGHT = exports.EndIntersectionDetector = void 0;
7
7
  var _style = _interopRequireDefault(require("styled-jsx/style"));
8
8
  var _intersectionDetector = require("@dhis2-ui/intersection-detector");
9
9
  var _propTypes = _interopRequireDefault(require("prop-types"));
10
10
  var _react = _interopRequireDefault(require("react"));
11
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ const INTERSECTION_DETECTOR_HEIGHT = exports.INTERSECTION_DETECTOR_HEIGHT = 50;
12
13
  const EndIntersectionDetector = _ref => {
13
14
  let {
14
15
  rootRef,
@@ -17,7 +18,7 @@ const EndIntersectionDetector = _ref => {
17
18
  } = _ref;
18
19
  return /*#__PURE__*/_react.default.createElement("div", {
19
20
  "data-test": dataTest,
20
- className: "jsx-2799340139"
21
+ className: "jsx-3586508456"
21
22
  }, /*#__PURE__*/_react.default.createElement(_intersectionDetector.IntersectionDetector, {
22
23
  rootRef: rootRef,
23
24
  onChange: _ref2 => {
@@ -27,8 +28,8 @@ const EndIntersectionDetector = _ref => {
27
28
  return isIntersecting && onEndReached();
28
29
  }
29
30
  }), /*#__PURE__*/_react.default.createElement(_style.default, {
30
- id: "2799340139"
31
- }, ["div.jsx-2799340139{width:100%;height:50px;position:absolute;z-index:-1;bottom:0;inset-inline-start:0;}"]));
31
+ id: "3586508456"
32
+ }, [`div.jsx-3586508456{width:100%;height:${INTERSECTION_DETECTOR_HEIGHT}px;position:absolute;z-index:-1;bottom:0;inset-inline-start:0;}`]));
32
33
  };
33
34
  exports.EndIntersectionDetector = EndIntersectionDetector;
34
35
  EndIntersectionDetector.propTypes = {
@@ -13,6 +13,10 @@ var _cypressCucumberPreprocessor = require("@badeball/cypress-cucumber-preproces
13
13
  cy.visitStory('Transfer End Of List', 'Partial Source List');
14
14
  cy.wrap('source').as('listType');
15
15
  });
16
+ (0, _cypressCucumberPreprocessor.Given)('the Transfer source options list does not fill the list completely', () => {
17
+ cy.visitStory('Transfer End Of List', 'Option Changes For Short List');
18
+ cy.wrap('source').as('listType');
19
+ });
16
20
  (0, _cypressCucumberPreprocessor.Given)('the Transfer does not have enough items to fill the picked list completely', () => {
17
21
  cy.visitStory('Transfer End Of List', 'Partial Picked List');
18
22
  cy.wrap('picked').as('listType');
@@ -23,6 +27,12 @@ var _cypressCucumberPreprocessor = require("@badeball/cypress-cucumber-preproces
23
27
  cy.get(`{${listSelector}-endintersectiondetector}`).scrollIntoView();
24
28
  });
25
29
  });
30
+ (0, _cypressCucumberPreprocessor.When)('the user adds an item by clicking the button', () => {
31
+ cy.contains('Increment options lists').click();
32
+ });
33
+ (0, _cypressCucumberPreprocessor.Then)('the last list item should be fully visible', () => {
34
+ cy.contains('ARI treated with antibiotics (pneumonia) new').should('be.visible');
35
+ });
26
36
  (0, _cypressCucumberPreprocessor.Then)('the callback for reaching the end should not be called', () => {
27
37
  cy.all(() => cy.window(), () => cy.get('@listType')).should(_ref => {
28
38
  let [win, listType] = _ref;
@@ -36,4 +46,29 @@ var _cypressCucumberPreprocessor = require("@badeball/cypress-cucumber-preproces
36
46
  const callback = listType === 'source' ? win.onEndReached : win.onEndReachedPicked;
37
47
  expect(callback).to.be.calledOnce;
38
48
  });
49
+ });
50
+ (0, _cypressCucumberPreprocessor.When)('the user scrolls down to the source list end', () => {
51
+ cy.get('[data-test="dhis2-uicore-transfer-sourceoptions"]').find('[data-test="dhis2-uicore-intersectiondetector"]').scrollIntoView();
52
+ });
53
+ (0, _cypressCucumberPreprocessor.Then)('the list end indicator of the source list should be visible', () => {
54
+ cy.get('[data-test="dhis2-uicore-transfer-sourceoptions"]').find('[data-test="dhis2-uicore-intersectiondetector"]').should('be.visible');
55
+ });
56
+ (0, _cypressCucumberPreprocessor.Then)('the list end indicator of the source list should not be visible', () => {
57
+ cy.get('[data-test="dhis2-uicore-transfer-sourceoptions"]').find('[data-test="dhis2-uicore-intersectiondetector"]').should('not.be.visible');
58
+ });
59
+ (0, _cypressCucumberPreprocessor.Then)('the callback for reaching the end of the source list should be called {int} times', function (int) {
60
+ cy.window().should(win => {
61
+ expect(win.onEndReached).to.have.callCount(int);
62
+ });
63
+ });
64
+ (0, _cypressCucumberPreprocessor.Then)('the selected item is being displayed in the picked list', () => {
65
+ cy.get('[data-test="dhis2-uicore-transfer-pickedoptions"]').contains('Option nr. 9').should('be.visible');
66
+ });
67
+ (0, _cypressCucumberPreprocessor.When)('the user selects option nr. {}', function (int) {
68
+ cy.contains(`Option nr. ${int}`).dblclick();
69
+ });
70
+ (0, _cypressCucumberPreprocessor.Then)('the callback for reaching the end of the picked list should be called {int} times', function (int) {
71
+ cy.window().should(win => {
72
+ expect(win.onEndReachedPicked).to.have.callCount(int);
73
+ });
39
74
  });
@@ -27,3 +27,38 @@ Feature: The source and picked option lists notify the consumer when the end has
27
27
  | type |
28
28
  | source |
29
29
  | picked |
30
+
31
+ Scenario: The list is short and items are added within the list container
32
+ Given the Transfer source options list does not fill the list completely
33
+ # Initial state: the list has 7 items and the list ends well above the container bottom
34
+ Then the list end indicator of the source list should be visible
35
+ Then the callback for reaching the end of the source list should be called 1 times
36
+ Then the callback for reaching the end of the picked list should be called 1 times
37
+ # Selected item is not in the options array but is present in the `selectedOptionsLookup`
38
+ Then the selected item is being displayed in the picked list
39
+ When the user adds an item by clicking the button
40
+ # The indicator is still just in view
41
+ Then the list end indicator of the source list should be visible
42
+ Then the callback for reaching the end of the source list should be called 2 times
43
+ When the user adds an item by clicking the button
44
+ # This adds val-9, which is a selected item so it gets added to picked options
45
+ # not source options, but the callback is still called
46
+ Then the list end indicator of the source list should be visible
47
+ Then the callback for reaching the end of the source list should be called 3 times
48
+ # The picked list callback does not get called because the picked item was already present
49
+ Then the callback for reaching the end of the picked list should be called 1 times
50
+ When the user adds an item by clicking the button
51
+ # The indicator is still in view but only just
52
+ Then the list end indicator of the source list should be visible
53
+ Then the callback for reaching the end of the source list should be called 4 times
54
+ When the user adds an item by clicking the button
55
+ # The indicator now is out of view, no more calls
56
+ Then the list end indicator of the source list should not be visible
57
+ Then the callback for reaching the end of the source list should be called 4 times
58
+ # But scrolling down does trigger a call
59
+ When the user scrolls down to the source list end
60
+ Then the list end indicator of the source list should be visible
61
+ Then the callback for reaching the end of the source list should be called 5 times
62
+ # And selecting an item triggers a call in the picked list
63
+ When the user selects option nr. 11
64
+ Then the callback for reaching the end of the picked list should be called 2 times
@@ -9,12 +9,13 @@ var _loader = require("@dhis2-ui/loader");
9
9
  var _propTypes = _interopRequireDefault(require("prop-types"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _endIntersectionDetector = require("./end-intersection-detector.js");
12
- var _useResizeCounter = require("./use-resize-counter.js");
12
+ var _useOptionsKeyMonitor = require("./transfer/use-options-key-monitor.js");
13
13
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
14
14
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
15
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
16
  const OptionsContainer = _ref => {
17
17
  let {
18
+ allOptionsKey,
18
19
  dataTest,
19
20
  emptyComponent,
20
21
  onEndReached,
@@ -27,19 +28,24 @@ const OptionsContainer = _ref => {
27
28
  selectionHandler,
28
29
  toggleHighlightedOption
29
30
  } = _ref;
30
- const optionsRef = (0, _react.useRef)(null);
31
- const wrapperRef = (0, _react.useRef)(null);
32
- const resizeCounter = (0, _useResizeCounter.useResizeCounter)(wrapperRef.current);
31
+ const scrollBoxRef = (0, _react.useRef)(null);
32
+ const listRef = (0, _react.useRef)(null);
33
+ (0, _useOptionsKeyMonitor.useOptionsKeyMonitor)({
34
+ scrollBoxRef,
35
+ listRef,
36
+ allOptionsKey,
37
+ onEndReached
38
+ });
33
39
  return /*#__PURE__*/_react.default.createElement("div", {
34
40
  className: "jsx-1882699425" + " " + "optionsContainer"
35
41
  }, loading && /*#__PURE__*/_react.default.createElement("div", {
36
42
  className: "jsx-1882699425" + " " + "loading"
37
43
  }, /*#__PURE__*/_react.default.createElement(_loader.CircularLoader, null)), /*#__PURE__*/_react.default.createElement("div", {
38
44
  "data-test": dataTest,
39
- ref: optionsRef,
45
+ ref: scrollBoxRef,
40
46
  className: "jsx-1882699425" + " " + "container"
41
47
  }, /*#__PURE__*/_react.default.createElement("div", {
42
- ref: wrapperRef,
48
+ ref: listRef,
43
49
  className: "jsx-1882699425" + " " + "content-container"
44
50
  }, !options.length && emptyComponent, options.map(option => {
45
51
  const highlighted = !!highlightedOptions.find(highlightedSourceOption => highlightedSourceOption === option.value);
@@ -53,8 +59,7 @@ const OptionsContainer = _ref => {
53
59
  }));
54
60
  }), onEndReached && /*#__PURE__*/_react.default.createElement(_endIntersectionDetector.EndIntersectionDetector, {
55
61
  dataTest: `${dataTest}-endintersectiondetector`,
56
- key: `key-${resizeCounter}`,
57
- rootRef: optionsRef,
62
+ rootRef: scrollBoxRef,
58
63
  onEndReached: onEndReached
59
64
  }))), /*#__PURE__*/_react.default.createElement(_style.default, {
60
65
  id: "1882699425"
@@ -62,6 +67,7 @@ const OptionsContainer = _ref => {
62
67
  };
63
68
  exports.OptionsContainer = OptionsContainer;
64
69
  OptionsContainer.propTypes = {
70
+ allOptionsKey: _propTypes.default.string.isRequired,
65
71
  dataTest: _propTypes.default.string.isRequired,
66
72
  getOptionClickHandlers: _propTypes.default.func.isRequired,
67
73
  emptyComponent: _propTypes.default.node,
@@ -145,4 +145,15 @@ Object.keys(_useHighlightedOptions).forEach(function (key) {
145
145
  return _useHighlightedOptions[key];
146
146
  }
147
147
  });
148
+ });
149
+ var _useOptionsKeyMonitor = require("./use-options-key-monitor.js");
150
+ Object.keys(_useOptionsKeyMonitor).forEach(function (key) {
151
+ if (key === "default" || key === "__esModule") return;
152
+ if (key in exports && exports[key] === _useOptionsKeyMonitor[key]) return;
153
+ Object.defineProperty(exports, key, {
154
+ enumerable: true,
155
+ get: function () {
156
+ return _useOptionsKeyMonitor[key];
157
+ }
158
+ });
148
159
  });
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useOptionsKeyMonitor = void 0;
7
+ var _react = require("react");
8
+ var _endIntersectionDetector = require("../end-intersection-detector.js");
9
+ const isEndIntersectionDetectorWithinScrollBox = (scrollBoxRef, listRef) => {
10
+ if (!scrollBoxRef.current || !listRef.current) {
11
+ return false;
12
+ }
13
+ const scrollBoxRect = scrollBoxRef.current.getBoundingClientRect();
14
+ const listRect = listRef.current.getBoundingClientRect();
15
+ return listRect.bottom - scrollBoxRect.bottom < _endIntersectionDetector.INTERSECTION_DETECTOR_HEIGHT;
16
+ };
17
+ const useOptionsKeyMonitor = _ref => {
18
+ let {
19
+ scrollBoxRef,
20
+ listRef,
21
+ allOptionsKey,
22
+ onEndReached
23
+ } = _ref;
24
+ /* Store in ref so this works even if a consumer does not pass a stable
25
+ * function reference */
26
+ const onEndReachedRef = (0, _react.useRef)(onEndReached);
27
+ const prevAllOptionsKey = (0, _react.useRef)(allOptionsKey);
28
+ onEndReachedRef.current = onEndReached;
29
+ (0, _react.useEffect)(() => {
30
+ /* When new options are loaded and the list end is (still) in
31
+ * view we need to call onEndReached, because the end of the list
32
+ * has indeed been reached but the interception detector will not pick
33
+ * up on this */
34
+ if (onEndReachedRef.current && prevAllOptionsKey.current !== allOptionsKey && isEndIntersectionDetectorWithinScrollBox(scrollBoxRef, listRef)) {
35
+ onEndReachedRef.current();
36
+ }
37
+ prevAllOptionsKey.current = allOptionsKey;
38
+ /* This effect will only run on mount and when allOptionsKey
39
+ * changes because scrollBoxRef, listRef are stable references */
40
+ }, [scrollBoxRef, listRef, allOptionsKey]);
41
+ };
42
+ exports.useOptionsKeyMonitor = useOptionsKeyMonitor;
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.Transfer = void 0;
7
7
  var _propTypes = _interopRequireDefault(require("prop-types"));
8
- var _react = _interopRequireDefault(require("react"));
8
+ var _react = _interopRequireWildcard(require("react"));
9
9
  var _actions = require("./actions.js");
10
10
  var _addAll = require("./add-all.js");
11
11
  var _addIndividual = require("./add-individual.js");
@@ -23,9 +23,12 @@ var _rightHeader = require("./right-header.js");
23
23
  var _rightSide = require("./right-side.js");
24
24
  var _index = require("./transfer/index.js");
25
25
  var _transferOption = require("./transfer-option.js");
26
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
27
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
26
28
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
27
29
  const identity = value => value;
28
30
  const defaultSelected = [];
31
+ const defaultSelectedOptionsLookup = {};
29
32
  const Transfer = _ref => {
30
33
  let {
31
34
  options,
@@ -49,6 +52,7 @@ const Transfer = _ref => {
49
52
  hideFilterInputPicked,
50
53
  initialSearchTerm = '',
51
54
  initialSearchTermPicked = '',
55
+ selectedOptionsLookup = defaultSelectedOptionsLookup,
52
56
  leftFooter,
53
57
  leftHeader,
54
58
  loadingPicked,
@@ -131,16 +135,15 @@ const Transfer = _ref => {
131
135
  * Actual picked options:
132
136
  * Extract the selected options. Can't use `options.filter`
133
137
  * because we need to keep the order of `selected`
138
+ * Note: Only map if selected is an array
134
139
  */
135
- let pickedOptions = [];
136
-
137
- // Only map if selected is an array
138
- if (Array.isArray(selected)) {
139
- pickedOptions = actualFilterPickedCallback(selected.map(value => options.find(option => value === option.value))
140
- // filter -> in case a selected value has been provided
141
- // that does not exist as option
142
- .filter(identity), actualFilterPicked);
143
- }
140
+ const pickedOptions = (0, _react.useMemo)(() => Array.isArray(selected) ? actualFilterPickedCallback(selected.map(value => {
141
+ var _selectedOptionsLooku;
142
+ return (_selectedOptionsLooku = selectedOptionsLookup[value]) !== null && _selectedOptionsLooku !== void 0 ? _selectedOptionsLooku : options.find(option => value === option.value);
143
+ })
144
+ // filter -> in case a selected value has been provided
145
+ // that does not exist as option
146
+ .filter(identity), actualFilterPicked) : [], [selected, options, actualFilterPicked, actualFilterPickedCallback, selectedOptionsLookup]);
144
147
 
145
148
  /*
146
149
  * Source options highlighting:
@@ -183,6 +186,18 @@ const Transfer = _ref => {
183
186
  const isAddIndividualDisabled = disabled || !highlightedSourceOptions.length;
184
187
  const isRemoveAllDisabled = disabled || !selected.length;
185
188
  const isRemoveIndividualDisabled = disabled || !highlightedPickedOptions.length;
189
+ const allOptionsKey = (0, _react.useMemo)(() => options.map(_ref4 => {
190
+ let {
191
+ value
192
+ } = _ref4;
193
+ return value;
194
+ }).join('|'), [options]);
195
+ const pickedOptionsKey = (0, _react.useMemo)(() => pickedOptions.map(_ref5 => {
196
+ let {
197
+ value
198
+ } = _ref5;
199
+ return value;
200
+ }).join('|'), [pickedOptions]);
186
201
  return /*#__PURE__*/_react.default.createElement(_container.Container, {
187
202
  dataTest: dataTest,
188
203
  className: className,
@@ -197,13 +212,14 @@ const Transfer = _ref => {
197
212
  placeholder: filterPlaceholder,
198
213
  dataTest: `${dataTest}-filter`,
199
214
  filter: actualFilter,
200
- onChange: onFilterChange ? onFilterChange : _ref4 => {
215
+ onChange: onFilterChange ? onFilterChange : _ref6 => {
201
216
  let {
202
217
  value
203
- } = _ref4;
218
+ } = _ref6;
204
219
  return setInternalFilter(value);
205
220
  }
206
221
  })), /*#__PURE__*/_react.default.createElement(_optionsContainer.OptionsContainer, {
222
+ allOptionsKey: allOptionsKey,
207
223
  dataTest: `${dataTest}-sourceoptions`,
208
224
  emptyComponent: sourceEmptyPlaceholder,
209
225
  getOptionClickHandlers: _index.getOptionClickHandlers,
@@ -271,14 +287,15 @@ const Transfer = _ref => {
271
287
  placeholder: filterPlaceholderPicked,
272
288
  dataTest: `${dataTest}-filter`,
273
289
  filter: actualFilterPicked,
274
- onChange: onFilterChangePicked ? onFilterChangePicked : _ref5 => {
290
+ onChange: onFilterChangePicked ? onFilterChangePicked : _ref7 => {
275
291
  let {
276
292
  value
277
- } = _ref5;
293
+ } = _ref7;
278
294
  return setInternalFilterPicked(value);
279
295
  }
280
296
  })), /*#__PURE__*/_react.default.createElement(_optionsContainer.OptionsContainer, {
281
297
  selected: true,
298
+ allOptionsKey: pickedOptionsKey,
282
299
  dataTest: `${dataTest}-pickedoptions`,
283
300
  emptyComponent: selectedEmptyComponent,
284
301
  getOptionClickHandlers: _index.getOptionClickHandlers,
@@ -358,6 +375,16 @@ Transfer.propTypes = {
358
375
  searchTermPicked: _propTypes.default.string,
359
376
  selected: _propTypes.default.arrayOf(_propTypes.default.string),
360
377
  selectedEmptyComponent: _propTypes.default.node,
378
+ /**
379
+ * To be used in scenarios where selected options may not be present
380
+ * in the options array. Like when having options that lazy load or can
381
+ * be filtered async.
382
+ */
383
+ selectedOptionsLookup: _propTypes.default.objectOf(_propTypes.default.shape({
384
+ label: _propTypes.default.string.isRequired,
385
+ value: _propTypes.default.string.isRequired,
386
+ disabled: _propTypes.default.bool
387
+ })),
361
388
  selectedWidth: _propTypes.default.string,
362
389
  sourceEmptyPlaceholder: _propTypes.default.node,
363
390
  onEndReached: _propTypes.default.func,
@@ -454,6 +454,8 @@ const pageSize = 5;
454
454
  * To keep the code as small as possible, handling selecting items is not
455
455
  included
456
456
  */
457
+ const preSelectedOptions = optionsPool.slice(optionsPool.length - 4);
458
+ const waitMs = async ms => new Promise(resolve => setTimeout(resolve, ms));
457
459
  const InfiniteLoading = args => {
458
460
  (0, _react.useEffect)(() => {
459
461
  console.clear();
@@ -461,59 +463,45 @@ const InfiniteLoading = args => {
461
463
 
462
464
  // state for whether the next page's options are being loaded
463
465
  const [loading, setLoading] = (0, _react.useState)(false);
464
- // captures the current page
465
- const [page, setPage] = (0, _react.useState)(0);
466
- // all options (incl. available AND selected options)
466
+ // prevent fetches after list is complete
467
+ const [isOptionsListComplete, setIsOptionsListComplete] = (0, _react.useState)(false);
467
468
  const [options, setOptions] = (0, _react.useState)([]);
468
469
  // selected options
469
470
  const [selected] = (0, _react.useState)(
470
- // second page is already selected
471
- optionsPool.slice(pageSize, pageSize * 2).map(_ref13 => {
471
+ // Last 4 options preselected
472
+ preSelectedOptions.map(_ref13 => {
472
473
  let {
473
474
  value
474
475
  } = _ref13;
475
476
  return value;
476
477
  }));
477
- const onEndReached = () => {
478
- // do nothing when loading already
479
- if (loading) {
480
- return;
481
- }
482
- setPage(page + 1);
483
- };
484
-
485
- // fake fetch request
486
- const fetchOptions = nextPage => new Promise(resolve => setTimeout(() => {
487
- const nextOptions = optionsPool.slice(options.length, nextPage * pageSize);
488
- resolve(nextOptions);
489
- }, 2000));
490
- const loadNextOptions = async () => {
478
+ const selectedOptionsLookup = (0, _react.useMemo)(() => preSelectedOptions.reduce((lookup, option) => {
479
+ lookup[option.value] = option;
480
+ return lookup;
481
+ }, {}), []);
482
+ const loadMoreOptions = (0, _react.useCallback)(async () => {
491
483
  setLoading(true);
492
- const nextOptions = await fetchOptions(page);
493
- setOptions([...options, ...nextOptions]);
494
- setLoading(false);
495
- const allAlreadySelected = nextOptions.length !== 0 && nextOptions.every(nextOption => {
496
- const {
497
- value
498
- } = nextOption;
499
- return selected.includes(value);
500
- });
501
- if (allAlreadySelected) {
502
- onEndReached();
484
+ await waitMs(2000);
485
+ const newOptions = optionsPool.slice(options.length, Math.min(options.length + pageSize, optionsPool.length));
486
+ const combinedOptions = [...options, ...newOptions];
487
+ setOptions(combinedOptions);
488
+ if (combinedOptions.length === optionsPool.length) {
489
+ setIsOptionsListComplete(true);
503
490
  }
504
- };
505
- (0, _react.useEffect)(() => {
506
- // prevent initial call
507
- if (page > 0) {
508
- loadNextOptions();
491
+ setLoading(false);
492
+ }, [options]);
493
+ const onEndReached = (0, _react.useCallback)(() => {
494
+ if (!isOptionsListComplete) {
495
+ loadMoreOptions();
509
496
  }
510
- }, [page]);
497
+ }, [loadMoreOptions, isOptionsListComplete]);
511
498
  return /*#__PURE__*/_react.default.createElement(_transfer.Transfer, _extends({}, args, {
512
499
  loading: loading,
513
500
  options: options,
514
501
  selected: selected,
515
502
  onChange: () => null /* noop */,
516
- onEndReached: onEndReached
503
+ onEndReached: onEndReached,
504
+ selectedOptionsLookup: selectedOptionsLookup
517
505
  }));
518
506
  };
519
507
  exports.InfiniteLoading = InfiniteLoading;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import { Transfer } from '../transfer.js';
3
3
  import { options } from './common/options.js';
4
4
  import { statefulDecorator } from './common/stateful-decorator.js';
@@ -75,4 +75,45 @@ PartialPickedList.story = {
75
75
  return value;
76
76
  })
77
77
  })]
78
+ };
79
+ const selectedOptionsLookup = {
80
+ 'val-9': {
81
+ value: 'val-9',
82
+ label: 'Option nr. 9'
83
+ }
84
+ };
85
+ export const OptionChangesForShortList = (_, _ref7) => {
86
+ let {
87
+ onChange,
88
+ selected
89
+ } = _ref7;
90
+ const [optionsCount, setOptionsCount] = useState(7);
91
+ const myOptions = useMemo(() => {
92
+ let counter = 1;
93
+ const newOptions = [];
94
+ while (counter <= optionsCount) {
95
+ newOptions.push({
96
+ value: `val-${counter}`,
97
+ label: `Option nr. ${counter}`
98
+ });
99
+ counter++;
100
+ }
101
+ return newOptions;
102
+ }, [optionsCount]);
103
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
104
+ onClick: () => setOptionsCount(curr => curr + 1)
105
+ }, "Increment options lists"), /*#__PURE__*/React.createElement(Transfer, {
106
+ filterable: true,
107
+ selected: selected,
108
+ selectedOptionsLookup: selectedOptionsLookup,
109
+ onChange: onChange,
110
+ options: myOptions,
111
+ onEndReached: window.onEndReached,
112
+ onEndReachedPicked: window.onEndReachedPicked
113
+ }));
114
+ };
115
+ OptionChangesForShortList.story = {
116
+ decorators: [statefulDecorator({
117
+ initialState: ['val-9']
118
+ })]
78
119
  };
@@ -2,6 +2,7 @@ import _JSXStyle from "styled-jsx/style";
2
2
  import { IntersectionDetector } from '@dhis2-ui/intersection-detector';
3
3
  import PropTypes from 'prop-types';
4
4
  import React from 'react';
5
+ export const INTERSECTION_DETECTOR_HEIGHT = 50;
5
6
  export const EndIntersectionDetector = _ref => {
6
7
  let {
7
8
  rootRef,
@@ -10,7 +11,7 @@ export const EndIntersectionDetector = _ref => {
10
11
  } = _ref;
11
12
  return /*#__PURE__*/React.createElement("div", {
12
13
  "data-test": dataTest,
13
- className: "jsx-2799340139"
14
+ className: "jsx-3586508456"
14
15
  }, /*#__PURE__*/React.createElement(IntersectionDetector, {
15
16
  rootRef: rootRef,
16
17
  onChange: _ref2 => {
@@ -20,8 +21,8 @@ export const EndIntersectionDetector = _ref => {
20
21
  return isIntersecting && onEndReached();
21
22
  }
22
23
  }), /*#__PURE__*/React.createElement(_JSXStyle, {
23
- id: "2799340139"
24
- }, ["div.jsx-2799340139{width:100%;height:50px;position:absolute;z-index:-1;bottom:0;inset-inline-start:0;}"]));
24
+ id: "3586508456"
25
+ }, [`div.jsx-3586508456{width:100%;height:${INTERSECTION_DETECTOR_HEIGHT}px;position:absolute;z-index:-1;bottom:0;inset-inline-start:0;}`]));
25
26
  };
26
27
  EndIntersectionDetector.propTypes = {
27
28
  rootRef: PropTypes.shape({