@bento-core/facet-filter 1.0.1-c3dc.6 → 1.0.1-c3dc.8

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.
@@ -61,7 +61,11 @@ const FacetFilterController = props => {
61
61
  minLowerBound,
62
62
  maxUpperBound
63
63
  } = sideBar;
64
- sideBar.facetValues = [minLowerBound, maxUpperBound];
64
+ if (minLowerBound === 0 && maxUpperBound === 0) {
65
+ sideBar.facetValues = [0, 0];
66
+ } else {
67
+ sideBar.facetValues = [minLowerBound, maxUpperBound];
68
+ }
65
69
  }
66
70
  });
67
71
  }
@@ -126,13 +130,15 @@ const FacetFilterController = props => {
126
130
  const lowerBound = data[apiForFiltering][ApiLowerBoundName];
127
131
  const upperBound = data[apiForFiltering][ApiUpperBoundName];
128
132
  if (lowerBound === 0 && upperBound === 0) {
129
- updateFacet.filterOut = true;
133
+ updateFacet.minLowerBound = 0;
134
+ updateFacet.maxUpperBound = 0;
135
+ updateFacet.facetValues = [];
130
136
  } else {
131
137
  updateFacet.minLowerBound = lowerBound;
132
138
  updateFacet.maxUpperBound = upperBound;
133
139
  updateFacet.facetValues = [lowerBound, upperBound];
134
- updateFacet.style = facet.style;
135
140
  }
141
+ updateFacet.style = facet.style;
136
142
  }
137
143
  }
138
144
  if (!updateFacet.filterOut) {
@@ -55,7 +55,9 @@ var _default = () => ({
55
55
  fontFamily: 'Nunito',
56
56
  fontSize: '11px',
57
57
  marginRight: '12px',
58
- marginLeft: '24px'
58
+ marginLeft: '24px',
59
+ display: 'flex',
60
+ alignItems: 'center'
59
61
  },
60
62
  sortGroupItem: {
61
63
  cursor: 'pointer',
@@ -65,8 +67,6 @@ var _default = () => ({
65
67
  },
66
68
 
67
69
  NonSortGroup: {
68
- marginBottom: '5px',
69
- borderTop: '1px solid #B1B1B1',
70
70
  textAlign: 'left',
71
71
  paddingLeft: '10px'
72
72
  },
@@ -82,17 +82,13 @@ const FacetView = _ref => {
82
82
  className: (0, _clsx.default)(classes.subSectionSummaryText, {
83
83
  ["activeFacet".concat(facet.section)]: isActiveFacet
84
84
  })
85
- }, facet.label)), facetValues.length === 0 && /*#__PURE__*/_react.default.createElement("div", {
86
- className: classes.NonSortGroup
87
- }, /*#__PURE__*/_react.default.createElement("span", {
88
- className: classes.NonSortGroupItem
89
- }, "No data for this field")), (facet.type === _Types.InputTypes.SLIDER || facetValues.length > 0) && /*#__PURE__*/_react.default.createElement("div", {
85
+ }, facet.label)), (facet.type === _Types.InputTypes.SLIDER || facetValues.length > 0) && /*#__PURE__*/_react.default.createElement("div", {
90
86
  className: facet.type === _Types.InputTypes.SLIDER ? classes.sortGroupSlider : classes.sortGroup
91
87
  }, /*#__PURE__*/_react.default.createElement("span", {
92
88
  className: classes.sortGroupIcon
93
89
  }, /*#__PURE__*/_react.default.createElement(_core.Icon, {
94
90
  style: {
95
- fontSize: 10
91
+ fontSize: 14
96
92
  },
97
93
  onClick: onClearSection
98
94
  }, /*#__PURE__*/_react.default.createElement("img", {
@@ -100,7 +96,11 @@ const FacetView = _ref => {
100
96
  height: 12,
101
97
  width: 12,
102
98
  alt: "clear-icon"
103
- }))), facet.type === _Types.InputTypes.CHECKBOX && facetValues.length > 0 && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
99
+ })), facetValues.length === 0 && /*#__PURE__*/_react.default.createElement("div", {
100
+ className: classes.NonSortGroup
101
+ }, /*#__PURE__*/_react.default.createElement("span", {
102
+ className: classes.NonSortGroupItem
103
+ }, "No data for this field"))), facet.type === _Types.InputTypes.CHECKBOX && facetValues.length > 0 && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
104
104
  className: (0, _clsx.default)(classes.sortGroupItem, {
105
105
  [classes.highlight]: sortBy === _Sort.sortType.ALPHABET
106
106
  }),
@@ -4,41 +4,128 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _react = _interopRequireDefault(require("react"));
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _core = require("@material-ui/core");
8
9
  var _ReduxCheckbox = _interopRequireDefault(require("./checkbox/ReduxCheckbox"));
9
10
  var _ReduxSlider = _interopRequireDefault(require("./slider/ReduxSlider"));
10
11
  var _Types = require("./Types");
11
12
  var _Sort = require("../../utils/Sort");
13
+ var _FilterItemStyle = _interopRequireDefault(require("./FilterItemStyle"));
12
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
16
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13
17
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
14
18
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
15
19
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
16
20
  function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
17
21
  function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
22
+ const INITIAL_ITEM_SIZE = 20;
18
23
  const FilterItems = _ref => {
19
24
  let {
20
25
  facet,
21
- sortBy
26
+ sortBy = null,
27
+ classes
22
28
  } = _ref;
23
29
  const {
24
30
  type,
25
31
  datafield,
26
32
  section
27
33
  } = facet;
34
+ const [displayCount, setDisplayCount] = (0, _react.useState)(INITIAL_ITEM_SIZE);
35
+ const scrollableRef = (0, _react.useRef)(null);
28
36
  const sortFilters = (0, _Sort.sortBySection)(_objectSpread(_objectSpread({}, facet), {}, {
29
37
  sortBy
30
38
  }));
39
+
40
+ // Memoized scroll handler
41
+ const handleScroll = (0, _react.useCallback)(uncheckedCount => e => {
42
+ if (displayCount < uncheckedCount && uncheckedCount > INITIAL_ITEM_SIZE) {
43
+ const {
44
+ scrollTop,
45
+ scrollHeight,
46
+ clientHeight
47
+ } = e.target;
48
+ if (scrollHeight > clientHeight) {
49
+ const position = Math.ceil(scrollTop / (scrollHeight - clientHeight) * 100);
50
+ if (position >= 90) {
51
+ setDisplayCount(prevCount => prevCount + INITIAL_ITEM_SIZE);
52
+ }
53
+ }
54
+ }
55
+ }, [displayCount]);
31
56
  const filterItems = () => {
32
57
  switch (type) {
33
58
  case _Types.InputTypes.CHECKBOX:
34
- return sortFilters.map((item, index) => /*#__PURE__*/_react.default.createElement(_ReduxCheckbox.default, {
35
- checkboxItem: _objectSpread(_objectSpread({}, item), {}, {
36
- index,
37
- section
38
- }),
39
- datafield: datafield,
40
- facet: facet
41
- }));
59
+ {
60
+ // Only use lazy loading if we have more items than the initial size
61
+ const uncheckedCount = sortFilters.filter(item => !item.isChecked).length;
62
+ if (uncheckedCount <= INITIAL_ITEM_SIZE) {
63
+ // Render all items normally if below threshold
64
+ return sortFilters.map((item, index) => /*#__PURE__*/_react.default.createElement(_ReduxCheckbox.default, {
65
+ key: "all-".concat(item.name, "-").concat(index),
66
+ checkboxItem: _objectSpread(_objectSpread({}, item), {}, {
67
+ index,
68
+ section
69
+ }),
70
+ datafield: datafield,
71
+ facet: facet
72
+ }));
73
+ }
74
+
75
+ // Single pass: separate checked and unchecked items with their indices - O(n) complexity
76
+ const checkedItemsWithIndices = [];
77
+ const uncheckedItemsWithIndices = [];
78
+ sortFilters.forEach((item, originalIndex) => {
79
+ if (item.isChecked) {
80
+ checkedItemsWithIndices.push({
81
+ item,
82
+ originalIndex
83
+ });
84
+ } else {
85
+ uncheckedItemsWithIndices.push({
86
+ item,
87
+ originalIndex
88
+ });
89
+ }
90
+ });
91
+
92
+ // Always show checked items first
93
+ const checkedItems = checkedItemsWithIndices.map(_ref2 => {
94
+ let {
95
+ item,
96
+ originalIndex
97
+ } = _ref2;
98
+ return /*#__PURE__*/_react.default.createElement(_ReduxCheckbox.default, {
99
+ key: "checked-".concat(item.name, "-").concat(originalIndex),
100
+ checkboxItem: _objectSpread(_objectSpread({}, item), {}, {
101
+ index: originalIndex,
102
+ section
103
+ }),
104
+ datafield: datafield,
105
+ facet: facet
106
+ });
107
+ });
108
+ const uncheckedItems = uncheckedItemsWithIndices.slice(0, displayCount).map(_ref3 => {
109
+ let {
110
+ item,
111
+ originalIndex
112
+ } = _ref3;
113
+ return /*#__PURE__*/_react.default.createElement(_ReduxCheckbox.default, {
114
+ key: "unchecked-".concat(item.name, "-").concat(originalIndex),
115
+ checkboxItem: _objectSpread(_objectSpread({}, item), {}, {
116
+ index: originalIndex,
117
+ section
118
+ }),
119
+ datafield: datafield,
120
+ facet: facet
121
+ });
122
+ });
123
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", null, checkedItems), /*#__PURE__*/_react.default.createElement("div", {
124
+ ref: scrollableRef,
125
+ className: classes.itemsContainer,
126
+ onScroll: handleScroll(uncheckedItemsWithIndices.length)
127
+ }, uncheckedItems));
128
+ }
42
129
  case _Types.InputTypes.SLIDER:
43
130
  return /*#__PURE__*/_react.default.createElement(_ReduxSlider.default, {
44
131
  facet: facet
@@ -47,7 +134,13 @@ const FilterItems = _ref => {
47
134
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null);
48
135
  }
49
136
  };
137
+ (0, _react.useEffect)(() => {
138
+ setDisplayCount(INITIAL_ITEM_SIZE);
139
+ if (scrollableRef.current) {
140
+ scrollableRef.current.scrollTo(0, 0);
141
+ }
142
+ }, [sortBy]);
50
143
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, filterItems());
51
144
  };
52
- var _default = FilterItems;
145
+ var _default = (0, _core.withStyles)(_FilterItemStyle.default)(FilterItems);
53
146
  exports.default = _default;
@@ -18,9 +18,11 @@ function InputMinMaxView(_ref) {
18
18
  minLowerBound,
19
19
  maxUpperBound,
20
20
  onInputChange,
21
- type
21
+ type,
22
+ disabled = false
22
23
  } = _ref;
23
24
  const handleInputChange = e => {
25
+ if (disabled) return;
24
26
  const minMaxRange = [lowerBoundVal, upperBoundVal];
25
27
  if (type === _Types.silderTypes.INPUT_MIN) {
26
28
  minMaxRange[0] = Number(e.target.value);
@@ -35,6 +37,7 @@ function InputMinMaxView(_ref) {
35
37
  id: "slider_".concat(type),
36
38
  className: classes["slider_".concat(type)],
37
39
  onChange: event => handleInputChange(event),
40
+ disabled: disabled,
38
41
  inputProps: {
39
42
  step: 1,
40
43
  min: minLowerBound,
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
+ var _clsx = _interopRequireDefault(require("clsx"));
8
9
  var _core = require("@material-ui/core");
9
10
  var _Types = require("../Types");
10
11
  var _InputMinMaxView = _interopRequireDefault(require("./InputMinMaxView"));
@@ -30,11 +31,14 @@ const SliderView = _ref => {
30
31
  datafield,
31
32
  facetValues
32
33
  } = facet;
34
+ // Check if bounds are invalid (both are 0)
35
+ const isBoundsInvalid = !facetValues || facetValues.length === 0 || facetValues[0] === 0 && facetValues[1] === 0;
33
36
  const lowerBoundValue = facetValues[0];
34
37
  const upperBoundValue = facetValues[1];
35
38
 
36
39
  // Determines whether the lower bound and upper bound values are valid
37
40
  const isValid = () => {
41
+ if (isBoundsInvalid) return false;
38
42
  const checks = [lowerBoundValue <= upperBoundValue, lowerBoundValue >= minLowerBound, upperBoundValue <= maxUpperBound];
39
43
  return checks.every(condition => condition === true);
40
44
  };
@@ -74,7 +78,8 @@ const SliderView = _ref => {
74
78
  minLowerBound: minLowerBound,
75
79
  maxUpperBound: maxUpperBound,
76
80
  type: _Types.silderTypes.INPUT_MIN,
77
- onInputChange: handleChangeCommittedSlider
81
+ onInputChange: handleChangeCommittedSlider,
82
+ disabled: isBoundsInvalid
78
83
  })), /*#__PURE__*/_react.default.createElement("div", {
79
84
  className: classes.maxValue
80
85
  }, /*#__PURE__*/_react.default.createElement(_core.Typography, {
@@ -86,23 +91,31 @@ const SliderView = _ref => {
86
91
  minLowerBound: minLowerBound,
87
92
  maxUpperBound: maxUpperBound,
88
93
  type: _Types.silderTypes.INPUT_MAX,
89
- onInputChange: handleChangeCommittedSlider
94
+ onInputChange: handleChangeCommittedSlider,
95
+ disabled: isBoundsInvalid
90
96
  }))), /*#__PURE__*/_react.default.createElement("div", {
91
97
  className: classes.slider
92
98
  }, /*#__PURE__*/_react.default.createElement(_core.Slider, {
93
99
  disableSwap: true,
100
+ disabled: isBoundsInvalid,
94
101
  getAriaValueText: valuetext,
95
- onChange: handleChangeSlider,
96
- onChangeCommitted: (event, value) => handleChangeCommittedSlider(value),
97
- value: [...sliderValue],
102
+ onChange: isBoundsInvalid ? undefined : handleChangeSlider,
103
+ onChangeCommitted: isBoundsInvalid ? undefined : (event, value) => handleChangeCommittedSlider(value),
104
+ value: maxUpperBound === 0 ? [0, 0] : [...sliderValue],
98
105
  valueLabelDisplay: "auto",
99
106
  min: minLowerBound,
100
- max: maxUpperBound,
107
+ max: maxUpperBound === 0 ? undefined : maxUpperBound,
101
108
  classes: {
102
- colorPrimary: classes.colorPrimary,
103
- rail: classes.rail,
104
- thumb: isValid() ? classes.thumb : classes.invalidThumb,
105
- track: isValid() ? classes.track : classes.invalidTrack
109
+ colorPrimary: (0, _clsx.default)("colorPrimary".concat(facet.section), classes.colorPrimary),
110
+ rail: (0, _clsx.default)("rail".concat(facet.section), classes.rail),
111
+ thumb: (0, _clsx.default)("thumb".concat(facet.section), {
112
+ isThumbValid: isValid(),
113
+ invalidThumb: !isValid()
114
+ }),
115
+ track: (0, _clsx.default)("track".concat(facet.section), {
116
+ isTrackValid: isValid(),
117
+ invalidTrack: !isValid()
118
+ })
106
119
  }
107
120
  })), /*#__PURE__*/_react.default.createElement(_core.Box, {
108
121
  className: classes.lowerUpperBound
@@ -110,7 +123,7 @@ const SliderView = _ref => {
110
123
  className: classes.lowerBound
111
124
  }, minLowerBound.toLocaleString()), /*#__PURE__*/_react.default.createElement(_core.Typography, {
112
125
  className: classes.upperBound
113
- }, maxUpperBound.toLocaleString()))), (sliderValue[0] > minLowerBound || sliderValue[1] < maxUpperBound) && /*#__PURE__*/_react.default.createElement(_core.Typography, {
126
+ }, minLowerBound === 0 && maxUpperBound === 0 ? '-' : maxUpperBound !== 0 ? maxUpperBound.toLocaleString() : '.'))), (sliderValue[0] > minLowerBound || sliderValue[1] < maxUpperBound) && /*#__PURE__*/_react.default.createElement(_core.Typography, {
114
127
  className: isValid() ? classes.sliderText : classes.invalidSliderText
115
128
  }, sliderValue[0], ' - ', sliderValue[1], "\xA0", quantifier));
116
129
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bento-core/facet-filter",
3
- "version": "1.0.1-c3dc.6",
3
+ "version": "1.0.1-c3dc.8",
4
4
  "description": "### Bento core sidebar design:",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -54,7 +54,11 @@ const FacetFilterController = (props) => {
54
54
  */
55
55
  if (sideBar.type === InputTypes.SLIDER) {
56
56
  const { minLowerBound, maxUpperBound } = sideBar;
57
- sideBar.facetValues = [minLowerBound, maxUpperBound];
57
+ if (minLowerBound === 0 && maxUpperBound === 0) {
58
+ sideBar.facetValues = [0, 0];
59
+ } else {
60
+ sideBar.facetValues = [minLowerBound, maxUpperBound];
61
+ }
58
62
  }
59
63
  });
60
64
  }
@@ -65,7 +69,7 @@ const FacetFilterController = (props) => {
65
69
  const sideBar = {};
66
70
 
67
71
  arr.forEach(({ section, ...item }) => {
68
- const { isExpanded } =facetSectionConfig[section];
72
+ const { isExpanded } = facetSectionConfig[section];
69
73
  if (!sideBar[section]) {
70
74
  sideBar[section] = {
71
75
  name: section,
@@ -110,14 +114,17 @@ const FacetFilterController = (props) => {
110
114
  if (facet.type === InputTypes.SLIDER) {
111
115
  const lowerBound = data[apiForFiltering][ApiLowerBoundName];
112
116
  const upperBound = data[apiForFiltering][ApiUpperBoundName];
117
+
113
118
  if (lowerBound === 0 && upperBound === 0) {
114
- updateFacet.filterOut = true;
119
+ updateFacet.minLowerBound = 0;
120
+ updateFacet.maxUpperBound = 0;
121
+ updateFacet.facetValues = [];
115
122
  } else {
116
123
  updateFacet.minLowerBound = lowerBound;
117
124
  updateFacet.maxUpperBound = upperBound;
118
125
  updateFacet.facetValues = [lowerBound, upperBound];
119
- updateFacet.style = facet.style;
120
126
  }
127
+ updateFacet.style = facet.style;
121
128
  }
122
129
  }
123
130
  if (!updateFacet.filterOut) {
@@ -50,6 +50,8 @@ export default () => ({
50
50
  fontSize: '11px',
51
51
  marginRight: '12px',
52
52
  marginLeft: '24px',
53
+ display: 'flex',
54
+ alignItems: 'center',
53
55
  },
54
56
  sortGroupItem: {
55
57
  cursor: 'pointer',
@@ -58,8 +60,6 @@ export default () => ({
58
60
  // marginRight: '32px',
59
61
  },
60
62
  NonSortGroup: {
61
- marginBottom: '5px',
62
- borderTop: '1px solid #B1B1B1',
63
63
  textAlign: 'left',
64
64
  paddingLeft: '10px',
65
65
  },
@@ -89,18 +89,7 @@ const FacetView = ({
89
89
  </div>
90
90
  </CustomAccordionSummary>
91
91
  )}
92
- {
93
- (facetValues.length === 0)
94
- && (
95
- <div className={classes.NonSortGroup}>
96
- <span
97
- className={classes.NonSortGroupItem}
98
- >
99
- No data for this field
100
- </span>
101
- </div>
102
- )
103
- }
92
+
104
93
  {
105
94
  (facet.type === InputTypes.SLIDER || facetValues.length > 0)
106
95
  && (
@@ -110,7 +99,7 @@ const FacetView = ({
110
99
  >
111
100
  <span className={classes.sortGroupIcon}>
112
101
  <Icon
113
- style={{ fontSize: 10 }}
102
+ style={{ fontSize: 14 }}
114
103
  onClick={onClearSection}
115
104
  >
116
105
  <img
@@ -120,6 +109,17 @@ const FacetView = ({
120
109
  alt="clear-icon"
121
110
  />
122
111
  </Icon>
112
+
113
+ {facetValues.length === 0
114
+ && (
115
+ <div className={classes.NonSortGroup}>
116
+ <span
117
+ className={classes.NonSortGroupItem}
118
+ >
119
+ No data for this field
120
+ </span>
121
+ </div>
122
+ )}
123
123
  </span>
124
124
  { (facet.type === InputTypes.CHECKBOX && facetValues.length > 0)
125
125
  && (
@@ -1,28 +1,105 @@
1
1
  /* eslint-disable react/jsx-wrap-multilines */
2
2
  /* eslint-disable react/jsx-indent */
3
- import React from 'react';
3
+ import React,
4
+ {
5
+ useEffect, useState, useRef, useCallback,
6
+ } from 'react';
7
+ import { withStyles } from '@material-ui/core';
4
8
  import ReduxCheckbox from './checkbox/ReduxCheckbox';
5
9
  import ReduxSlider from './slider/ReduxSlider';
6
10
  import { InputTypes } from './Types';
7
11
  import { sortBySection } from '../../utils/Sort';
12
+ import styles from './FilterItemStyle';
13
+
14
+ const INITIAL_ITEM_SIZE = 20;
8
15
 
9
16
  const FilterItems = ({
10
17
  facet,
11
- sortBy,
18
+ sortBy = null,
19
+ classes,
12
20
  }) => {
13
21
  const { type, datafield, section } = facet;
22
+ const [displayCount, setDisplayCount] = useState(INITIAL_ITEM_SIZE);
23
+ const scrollableRef = useRef(null);
14
24
  const sortFilters = sortBySection({ ...facet, sortBy });
15
25
 
26
+ // Memoized scroll handler
27
+ const handleScroll = useCallback((uncheckedCount) => (e) => {
28
+ if (displayCount < uncheckedCount && uncheckedCount > INITIAL_ITEM_SIZE) {
29
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
30
+ if (scrollHeight > clientHeight) {
31
+ const position = Math.ceil((scrollTop / (scrollHeight - clientHeight)) * 100);
32
+ if (position >= 90) {
33
+ setDisplayCount((prevCount) => prevCount + INITIAL_ITEM_SIZE);
34
+ }
35
+ }
36
+ }
37
+ }, [displayCount]);
38
+
16
39
  const filterItems = () => {
17
40
  switch (type) {
18
- case InputTypes.CHECKBOX:
19
- return sortFilters.map((item, index) => (
41
+ case InputTypes.CHECKBOX: {
42
+ // Only use lazy loading if we have more items than the initial size
43
+ const uncheckedCount = sortFilters.filter((item) => !item.isChecked).length;
44
+ if (uncheckedCount <= INITIAL_ITEM_SIZE) {
45
+ // Render all items normally if below threshold
46
+ return sortFilters.map((item, index) => (
47
+ <ReduxCheckbox
48
+ key={`all-${item.name}-${index}`}
49
+ checkboxItem={{ ...item, index, section }}
50
+ datafield={datafield}
51
+ facet={facet}
52
+ />
53
+ ));
54
+ }
55
+
56
+ // Single pass: separate checked and unchecked items with their indices - O(n) complexity
57
+ const checkedItemsWithIndices = [];
58
+ const uncheckedItemsWithIndices = [];
59
+
60
+ sortFilters.forEach((item, originalIndex) => {
61
+ if (item.isChecked) {
62
+ checkedItemsWithIndices.push({ item, originalIndex });
63
+ } else {
64
+ uncheckedItemsWithIndices.push({ item, originalIndex });
65
+ }
66
+ });
67
+
68
+ // Always show checked items first
69
+ const checkedItems = checkedItemsWithIndices.map(({ item, originalIndex }) => (
20
70
  <ReduxCheckbox
21
- checkboxItem={{ ...item, index, section }}
71
+ key={`checked-${item.name}-${originalIndex}`}
72
+ checkboxItem={{ ...item, index: originalIndex, section }}
22
73
  datafield={datafield}
23
74
  facet={facet}
24
75
  />
25
76
  ));
77
+ const uncheckedItems = uncheckedItemsWithIndices
78
+ .slice(0, displayCount)
79
+ .map(({ item, originalIndex }) => (
80
+ <ReduxCheckbox
81
+ key={`unchecked-${item.name}-${originalIndex}`}
82
+ checkboxItem={{ ...item, index: originalIndex, section }}
83
+ datafield={datafield}
84
+ facet={facet}
85
+ />
86
+ ));
87
+
88
+ return (
89
+ <>
90
+ <div>
91
+ {checkedItems}
92
+ </div>
93
+ <div
94
+ ref={scrollableRef}
95
+ className={classes.itemsContainer}
96
+ onScroll={handleScroll(uncheckedItemsWithIndices.length)}
97
+ >
98
+ {uncheckedItems}
99
+ </div>
100
+ </>
101
+ );
102
+ }
26
103
  case InputTypes.SLIDER:
27
104
  return (<ReduxSlider facet={facet} />);
28
105
  default:
@@ -30,6 +107,13 @@ const FilterItems = ({
30
107
  }
31
108
  };
32
109
 
110
+ useEffect(() => {
111
+ setDisplayCount(INITIAL_ITEM_SIZE);
112
+ if (scrollableRef.current) {
113
+ scrollableRef.current.scrollTo(0, 0);
114
+ }
115
+ }, [sortBy]);
116
+
33
117
  return (
34
118
  <>
35
119
  {filterItems()}
@@ -37,4 +121,4 @@ const FilterItems = ({
37
121
  );
38
122
  };
39
123
 
40
- export default FilterItems;
124
+ export default withStyles(styles)(FilterItems);
@@ -14,8 +14,10 @@ function InputMinMaxView({
14
14
  maxUpperBound,
15
15
  onInputChange,
16
16
  type,
17
+ disabled = false,
17
18
  }) {
18
19
  const handleInputChange = (e) => {
20
+ if (disabled) return;
19
21
  const minMaxRange = [lowerBoundVal, upperBoundVal];
20
22
  if (type === silderTypes.INPUT_MIN) {
21
23
  minMaxRange[0] = Number(e.target.value);
@@ -32,6 +34,7 @@ function InputMinMaxView({
32
34
  id={`slider_${type}`}
33
35
  className={classes[`slider_${type}`]}
34
36
  onChange={(event) => handleInputChange(event)}
37
+ disabled={disabled}
35
38
  inputProps={{
36
39
  step: 1,
37
40
  min: minLowerBound,
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable react/jsx-indent */
4
4
  /* eslint-disable object-curly-newline */
5
5
  import React, { useEffect, useState } from 'react';
6
+ import clsx from 'clsx';
6
7
  import { withStyles, Slider, Typography, Box } from '@material-ui/core';
7
8
  // import styles from './SliderStyle';
8
9
  import { silderTypes } from '../Types';
@@ -15,11 +16,16 @@ const SliderView = ({
15
16
  filterState,
16
17
  }) => {
17
18
  const { minLowerBound, maxUpperBound, quantifier, datafield, facetValues } = facet;
19
+ // Check if bounds are invalid (both are 0)
20
+ const isBoundsInvalid = !facetValues
21
+ || facetValues.length === 0
22
+ || (facetValues[0] === 0 && facetValues[1] === 0);
18
23
  const lowerBoundValue = facetValues[0];
19
24
  const upperBoundValue = facetValues[1];
20
25
 
21
26
  // Determines whether the lower bound and upper bound values are valid
22
27
  const isValid = () => {
28
+ if (isBoundsInvalid) return false;
23
29
  const checks = [
24
30
  lowerBoundValue <= upperBoundValue,
25
31
  lowerBoundValue >= minLowerBound,
@@ -66,6 +72,7 @@ const SliderView = ({
66
72
  maxUpperBound={maxUpperBound}
67
73
  type={silderTypes.INPUT_MIN}
68
74
  onInputChange={handleChangeCommittedSlider}
75
+ disabled={isBoundsInvalid}
69
76
  />
70
77
  </div>
71
78
  <div className={classes.maxValue}>
@@ -80,6 +87,7 @@ const SliderView = ({
80
87
  maxUpperBound={maxUpperBound}
81
88
  type={silderTypes.INPUT_MAX}
82
89
  onInputChange={handleChangeCommittedSlider}
90
+ disabled={isBoundsInvalid}
83
91
  />
84
92
  </div>
85
93
  </div>
@@ -87,18 +95,27 @@ const SliderView = ({
87
95
  {/* Change to red if invalid range */}
88
96
  <Slider
89
97
  disableSwap
98
+ disabled={isBoundsInvalid}
90
99
  getAriaValueText={valuetext}
91
- onChange={handleChangeSlider}
92
- onChangeCommitted={(event, value) => handleChangeCommittedSlider(value)}
93
- value={[...sliderValue]}
100
+ onChange={isBoundsInvalid ? undefined : handleChangeSlider}
101
+ onChangeCommitted={
102
+ isBoundsInvalid ? undefined : (event, value) => handleChangeCommittedSlider(value)
103
+ }
104
+ value={maxUpperBound === 0 ? [0, 0] : [...sliderValue]}
94
105
  valueLabelDisplay="auto"
95
106
  min={minLowerBound}
96
- max={maxUpperBound}
107
+ max={maxUpperBound === 0 ? undefined : maxUpperBound}
97
108
  classes={{
98
- colorPrimary: classes.colorPrimary,
99
- rail: classes.rail,
100
- thumb: isValid() ? classes.thumb : classes.invalidThumb,
101
- track: isValid() ? classes.track : classes.invalidTrack,
109
+ colorPrimary: clsx(`colorPrimary${facet.section}`, classes.colorPrimary),
110
+ rail: clsx(`rail${facet.section}`, classes.rail),
111
+ thumb: clsx(`thumb${facet.section}`, {
112
+ isThumbValid: isValid(),
113
+ invalidThumb: !isValid(),
114
+ }),
115
+ track: clsx(`track${facet.section}`, {
116
+ isTrackValid: isValid(),
117
+ invalidTrack: !isValid(),
118
+ }),
102
119
  }}
103
120
  />
104
121
  </div>
@@ -107,7 +124,7 @@ const SliderView = ({
107
124
  {minLowerBound.toLocaleString()}
108
125
  </Typography>
109
126
  <Typography className={classes.upperBound}>
110
- {maxUpperBound.toLocaleString()}
127
+ {(minLowerBound === 0 && maxUpperBound === 0) ? '-' : (maxUpperBound !== 0 ? maxUpperBound.toLocaleString() : '.')}
111
128
  </Typography>
112
129
  </Box>
113
130
  </div>