@carto/kepler.gl 0.0.42 → 0.0.43

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 (30) hide show
  1. package/CHANGELOG.md +711 -0
  2. package/dist/components/common/field-selector.js +62 -17
  3. package/dist/components/common/field-token.js +4 -12
  4. package/dist/components/common/icons/chevron-right.js +69 -0
  5. package/dist/components/common/icons/index.js +9 -1
  6. package/dist/components/common/item-selector/dropdown-list.js +137 -21
  7. package/dist/components/common/item-selector/item-selector-submenu.js +133 -0
  8. package/dist/components/common/item-selector/item-selector.js +7 -4
  9. package/dist/components/common/item-selector/typeahead.js +16 -16
  10. package/dist/components/side-panel/layer-panel/layer-configurator.js +11 -4
  11. package/dist/components/side-panel/layer-panel/vis-config-by-field-selector.js +7 -3
  12. package/dist/constants/default-settings.js +33 -3
  13. package/dist/constants/index.js +7 -1
  14. package/dist/styles/base.js +4 -4
  15. package/package.json +1 -1
  16. package/src/components/common/field-selector.js +62 -21
  17. package/src/components/common/field-token.js +13 -14
  18. package/src/components/common/icons/chevron-right.js +47 -0
  19. package/src/components/common/icons/index.js +1 -0
  20. package/src/components/common/item-selector/dropdown-list.js +135 -36
  21. package/src/components/common/item-selector/item-selector-submenu.js +88 -0
  22. package/src/components/common/item-selector/item-selector.d.ts +2 -1
  23. package/src/components/common/item-selector/item-selector.js +5 -2
  24. package/src/components/common/item-selector/typeahead.js +27 -21
  25. package/src/components/side-panel/layer-panel/layer-configurator.js +17 -1
  26. package/src/components/side-panel/layer-panel/vis-config-by-field-selector.js +5 -1
  27. package/src/constants/default-settings.js +51 -0
  28. package/src/constants/index.js +2 -1
  29. package/src/styles/base.js +4 -0
  30. package/umd/keplergl.min.js +25 -25
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@carto/kepler.gl",
3
3
  "author": "CARTO",
4
4
  "homepage": "https://github.com/CartoDB/carto-kepler#readme",
5
- "version": "0.0.42",
5
+ "version": "0.0.43",
6
6
  "description": "kepler.gl is a webgl based application to visualize large scale location data in the browser",
7
7
  "license": "MIT",
8
8
  "main": "dist/index.js",
@@ -28,32 +28,47 @@ import {classList} from './item-selector/dropdown-list';
28
28
  import {toArray} from 'utils/utils';
29
29
  import {notNullorUndefined} from 'utils/data-utils';
30
30
  import FieldTokenFactory from '../common/field-token';
31
+ import {FIELD_TYPES_AGGREGATORS, FIELD_AGGREGATORS} from 'constants/default-settings';
31
32
 
32
- const defaultDisplayOption = d => d.displayName || d.name;
33
+ const defaultDisplayOption = d => {
34
+ const optionName = d.displayName || d.name;
35
+ const aggregator = d.aggregator;
36
+ const aggegatorDisplay = FIELD_AGGREGATORS.find(a => a.id === aggregator)?.displayName;
37
+ if (aggegatorDisplay) {
38
+ return `${aggegatorDisplay}(${optionName})`;
39
+ }
40
+ return optionName;
41
+ };
33
42
  const defaultValueOption = d => d.name;
34
43
 
35
44
  const StyledToken = styled.div`
36
45
  display: inline-block;
37
- margin: 0 ${props => props.theme.fieldTokenRightMargin}px 0 0;
46
+ margin: 0 0 0 auto;
38
47
  `;
39
48
  const StyledFieldListItem = styled.div`
49
+ display: flex;
50
+ flex: 1 1 auto;
51
+ margin-right: 10px;
40
52
  line-height: 0;
53
+ align-items: center;
41
54
  `;
42
55
 
43
56
  FieldListItemFactoryFactory.deps = [FieldTokenFactory];
44
57
  // custom list Item
45
58
  export function FieldListItemFactoryFactory(FieldToken) {
46
59
  const FieldListItemFactory = (showToken = true) => {
47
- const FieldListItem = ({value, displayOption = defaultDisplayOption}) => (
48
- <StyledFieldListItem className="field-selector_list-item">
49
- {showToken ? (
50
- <StyledToken>
51
- <FieldToken type={value.type} />
52
- </StyledToken>
53
- ) : null}
54
- <span className={classList.listItemAnchor}>{displayOption(value)}</span>
55
- </StyledFieldListItem>
56
- );
60
+ const FieldListItem = ({value, displayOption = defaultDisplayOption}) => {
61
+ return (
62
+ <StyledFieldListItem className="field-selector_list-item">
63
+ <span className={classList.listItemAnchor}>{displayOption(value)}</span>
64
+ {showToken && value.type ? (
65
+ <StyledToken>
66
+ <FieldToken type={value.type} />
67
+ </StyledToken>
68
+ ) : null}
69
+ </StyledFieldListItem>
70
+ );
71
+ };
57
72
  return FieldListItem;
58
73
  };
59
74
  return FieldListItemFactory;
@@ -86,6 +101,8 @@ function FieldSelectorFactory(FieldListItemFactory) {
86
101
  onSelect: PropTypes.func.isRequired,
87
102
  placement: PropTypes.string,
88
103
  value: FieldType,
104
+ aggregator: PropTypes.string,
105
+ withAggregator: PropTypes.bool,
89
106
  filterFieldTypes: PropTypes.oneOfType([FieldType, PropTypes.arrayOf(FieldType)]),
90
107
  inputTheme: PropTypes.string,
91
108
  placeholder: PropTypes.string,
@@ -106,6 +123,8 @@ function FieldSelectorFactory(FieldListItemFactory) {
106
123
  onSelect: () => {},
107
124
  placement: 'bottom',
108
125
  value: null,
126
+ aggregator: null,
127
+ withAggregator: false,
109
128
  multiSelect: false,
110
129
  closeOnSelect: true,
111
130
  showToken: true,
@@ -114,38 +133,59 @@ function FieldSelectorFactory(FieldListItemFactory) {
114
133
 
115
134
  fieldsSelector = props => props.fields;
116
135
  filteredFieldsSelector = props =>
117
- props.fields.filter(
136
+ props.withAggregator ? props.fields : props.fields.filter(
118
137
  field =>
119
138
  !toArray(props.value).find(d => (d.name ? d.name === field.name : d === field.name))
120
139
  );
121
140
  valueSelector = props => props.value;
141
+ aggregatorSelector = props => props.aggregator;
122
142
  filterFieldTypesSelector = props => props.filterFieldTypes;
123
143
  showTokenSelector = props => props.showToken;
144
+ withAggregatorSelector = props => props.withAggregator
124
145
 
125
146
  selectedItemsSelector = createSelector(
126
147
  this.fieldsSelector,
127
148
  this.valueSelector,
128
- (fields, value) =>
149
+ this.aggregatorSelector,
150
+ (fields, value, aggregator) =>
129
151
  toArray(value)
130
- .map(d =>
131
- fields.find(f =>
152
+ .map(d => {
153
+ const field = fields.find(f =>
132
154
  notNullorUndefined(d) && d.name
133
155
  ? d.name === defaultValueOption(f)
134
156
  : d === defaultValueOption(f)
135
- )
136
- )
157
+ );
158
+
159
+ if (field && aggregator) {
160
+ return {...field, aggregator};
161
+ }
162
+ return field;
163
+ })
137
164
  .filter(d => d)
138
165
  );
139
166
 
140
167
  fieldOptionsSelector = createSelector(
141
168
  this.filteredFieldsSelector,
169
+ this.valueSelector,
142
170
  this.filterFieldTypesSelector,
143
- (fields, filterFieldTypes) => {
171
+ this.aggregatorSelector,
172
+ this.withAggregatorSelector,
173
+ (fields, value, filterFieldTypes, aggregator, withAggregator) => {
174
+ const fieldsWithAggregators = fields.map(field => {
175
+ return {
176
+ ...field,
177
+ ...(withAggregator && {
178
+ aggregators: FIELD_TYPES_AGGREGATORS[field.type]?.filter(agg => {
179
+ return field.id === value ? agg !== aggregator : true;
180
+ })
181
+ })
182
+ };
183
+ });
144
184
  if (!filterFieldTypes) {
145
- return fields;
185
+ return fieldsWithAggregators;
146
186
  }
147
187
  const filters = Array.isArray(filterFieldTypes) ? filterFieldTypes : [filterFieldTypes];
148
- return fields.filter(f => filters.includes(f.type));
188
+ return fieldsWithAggregators.filter(f => filters.includes(f.type));
149
189
  }
150
190
  );
151
191
 
@@ -174,6 +214,7 @@ function FieldSelectorFactory(FieldListItemFactory) {
174
214
  DropDownLineItemRenderComponent={this.fieldListItemSelector(this.props)}
175
215
  DropdownHeaderComponent={this.props.suggested ? SuggestedFieldHeader : null}
176
216
  CustomChickletComponent={this.props.CustomChickletComponent}
217
+ withSubOptions={this.props.withAggregator}
177
218
  />
178
219
  </div>
179
220
  );
@@ -21,25 +21,24 @@
21
21
  import React from 'react';
22
22
  import styled from 'styled-components';
23
23
  import {FILED_TYPE_DISPLAY, FIELD_COLORS} from 'constants/default-settings';
24
- function FieldTokenFactory(fieldTypeDisplay, fieldColors) {
24
+ function FieldTokenFactory(fieldTypeDisplay) {
25
25
  const FieldTag = styled.div`
26
- background-color: rgba(${props => props.color}, 0.2);
26
+ background-color: #f8f9f9;
27
+ color: #888b8c;
27
28
  border-radius: 2px;
28
- border: 1px solid rgb(${props => props.color});
29
- color: rgb(${props => props.color});
30
- display: inline-block;
31
- font-size: 10px;
32
- font-weight: 400;
33
- padding: 0 5px;
34
- text-align: center;
35
- width: 40px;
36
- line-height: 20px;
29
+ border: 1px solid #cbcdcf;
30
+ font-family: 'Overpass Mono', monospace;
31
+ font-weight: 600;
32
+ font-size: 12px;
33
+ height: 15px;
34
+ line-height: 15px;
35
+ padding: 0 4px;
36
+ width: auto;
37
+ text-transform: none;
37
38
  `;
38
39
 
39
40
  const FieldToken = ({type}) => (
40
- <FieldTag
41
- color={(fieldTypeDisplay[type] && fieldTypeDisplay[type].color) || fieldColors.default}
42
- >
41
+ <FieldTag>
43
42
  {fieldTypeDisplay[type].label}
44
43
  </FieldTag>
45
44
  );
@@ -0,0 +1,47 @@
1
+ // Copyright (c) 2022 Uber Technologies, Inc.
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in
11
+ // all copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ // THE SOFTWARE.
20
+
21
+ import React, {Component} from 'react';
22
+ import PropTypes from 'prop-types';
23
+ import Base from './base';
24
+
25
+ export default class ChevronRight extends Component {
26
+ static propTypes = {
27
+ /** Set the height of the icon, ex. '16px' */
28
+ height: PropTypes.string,
29
+ predefinedClassName: PropTypes.string,
30
+ viewBox: PropTypes.string,
31
+ style: PropTypes.object
32
+ };
33
+
34
+ static defaultProps = {
35
+ height: '16px',
36
+ predefinedClassName: 'data-ex-icons-chevron-right',
37
+ viewBox: '0 0 18 18'
38
+ };
39
+
40
+ render() {
41
+ return (
42
+ <Base {...this.props}>
43
+ <path d="M7.125 12.75 10.875 9l-3.75-3.75z" fill="#2C3032"/>
44
+ </Base>
45
+ );
46
+ }
47
+ }
@@ -32,6 +32,7 @@ export {default as Base} from './base';
32
32
  export {default as Bug} from './bug';
33
33
  export {default as Cancel} from './cancel';
34
34
  export {default as Checkmark} from './checkmark';
35
+ export {default as ChevronRight} from './chevron-right';
35
36
  export {default as Clipboard} from './clipboard';
36
37
  export {default as Clock} from './clock';
37
38
  export {default as Close} from './close';
@@ -18,10 +18,13 @@
18
18
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  // THE SOFTWARE.
20
20
 
21
- import React, {Component} from 'react';
21
+ import React, {Component, createRef} from 'react';
22
22
  import PropTypes from 'prop-types';
23
23
  import classNames from 'classnames';
24
24
  import styled from 'styled-components';
25
+ import ItemSelectorSubmenu from './item-selector-submenu';
26
+ import {ChevronRight} from 'components/common/icons';
27
+ import {FIELD_AGGREGATORS} from 'constants/default-settings';
25
28
 
26
29
  export const classList = {
27
30
  list: 'list-selector',
@@ -45,9 +48,34 @@ const DropdownListWrapper = styled.div`
45
48
  ${props => (props.light ? props.theme.dropdownListLT : props.theme.dropdownList)};
46
49
  `;
47
50
 
51
+ const DropdownListContainer = styled.div`
52
+ position: relative;
53
+ `;
54
+
55
+ const Item = styled.div`
56
+ position: relative;
57
+ padding-right: 25px;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: space-between;
61
+ svg {
62
+ position: absolute;
63
+ right: 0;
64
+ top: 50%;
65
+ transform: translateY(-50%);
66
+ opacity: 0.6;
67
+ }
68
+ &:hover, &.hover {
69
+ svg {
70
+ opacity: 1;
71
+ }
72
+ }
73
+ `;
74
+
48
75
  export default class DropdownList extends Component {
49
76
  static propTypes = {
50
77
  options: PropTypes.arrayOf(PropTypes.any),
78
+ subOptions: PropTypes.arrayOf(PropTypes.any),
51
79
  allowCustomValues: PropTypes.number,
52
80
  customClasses: PropTypes.object,
53
81
  customValues: PropTypes.arrayOf(PropTypes.any),
@@ -59,7 +87,9 @@ export default class DropdownList extends Component {
59
87
  defaultClassNames: PropTypes.bool,
60
88
  areResultsTruncated: PropTypes.bool,
61
89
  resultsTruncatedMessage: PropTypes.string,
62
- listItemComponent: PropTypes.func
90
+ listItemComponent: PropTypes.func,
91
+ searchEntryValue: PropTypes.string,
92
+ withSubOptions: PropTypes.bool
63
93
  };
64
94
 
65
95
  static defaultProps = {
@@ -71,12 +101,62 @@ export default class DropdownList extends Component {
71
101
  displayOption: defaultDisplay,
72
102
  onOptionSelected: () => {},
73
103
  defaultClassNames: true,
74
- selectionIndex: null
104
+ selectionIndex: null,
105
+ withSubOptions: false
75
106
  };
76
107
 
77
- _onClick(result, event) {
78
- event.preventDefault();
79
- this.props.onOptionSelected(result, event);
108
+ state = {
109
+ currentOption: {},
110
+ submenuVisible: false,
111
+ submenuPosition: {}
112
+ };
113
+
114
+ hasSubOptions = !!this.props.subOptions
115
+
116
+ ref = createRef();
117
+
118
+ _resetState(callback) {
119
+ this.setState({currentOption: {}, submenuVisible: false, submenuPosition: {}}, callback);
120
+ }
121
+
122
+ _onClick(option, subOption, event) {
123
+ const optionSelected =
124
+ typeof option !== 'object'
125
+ ? option
126
+ : {
127
+ ...option,
128
+ selectedAggregator: subOption?.id
129
+ };
130
+ if (
131
+ (this.props.withSubOptions && (subOption?.id || !option.aggregators)) ||
132
+ !this.props.withSubOptions
133
+ ) {
134
+ this.props.onOptionSelected(optionSelected, event);
135
+ }
136
+ }
137
+
138
+ _onMouseOver(option, event) {
139
+ const el = event.target
140
+ this.setState((previousState) => ({
141
+ currentOption: option,
142
+ submenuVisible: true,
143
+ submenuPosition: {
144
+ top: el.getBoundingClientRect().top,
145
+ left: previousState.submenuPosition.left ?previousState.submenuPosition.left : this.ref.current?.getBoundingClientRect().right
146
+ }
147
+ }));
148
+ }
149
+
150
+ componentDidUpdate(prevProps) {
151
+ if (prevProps.searchEntryValue !== this.props.searchEntryValue) {
152
+ this._resetState();
153
+ }
154
+ }
155
+
156
+ componentDidMount() {
157
+ this.setState({
158
+ subMenuPosition: { left: this.ref.current?.getBoundingClientRect().right }
159
+ })
80
160
  }
81
161
 
82
162
  render() {
@@ -93,44 +173,63 @@ export default class DropdownList extends Component {
93
173
  // For some reason onClick is not fired when clicked on an option
94
174
  // onMouseDown is used here as a workaround of #205 and other
95
175
  return (
96
- <DropdownListWrapper className={classList.list} light={light}>
97
- {this.props.customListHeaderComponent ? (
98
- <div className={classList.listHeader}>
99
- <this.props.customListHeaderComponent />
100
- </div>
101
- ) : null}
176
+ <DropdownListContainer onMouseLeave={() => this._resetState()}>
177
+ <DropdownListWrapper className={classList.list} light={light}>
178
+ {this.props.customListHeaderComponent ? (
179
+ <div className={classList.listHeader}>
180
+ <this.props.customListHeaderComponent />
181
+ </div>
182
+ ) : null}
183
+ <div ref={this.ref}>
184
+ {valueOffset > 0 ? (
185
+ <div className={classList.listSection}>
186
+ {fixedOptions.map((value, i) => (
187
+ <div
188
+ className={classNames(classList.listItem, {
189
+ active: this.props.selectionIndex === i,
190
+ hover: value.id && (this.state.currentOption?.id === value.id),
191
+ fixed: true
192
+ })}
193
+ key={`${display(value)}_${i}`}
194
+ onMouseDown={e => !this.hasSubOptions && this._onClick(value, null, e)}
195
+ onClick={e => !this.hasSubOptions && this._onClick(value, null, e)}
196
+ onMouseEnter={e => this._onMouseOver(value, e)}
197
+ >
198
+ <this.props.customListItemComponent value={value} displayOption={display} />
199
+ </div>
200
+ ))}
201
+ </div>
202
+ ) : null}
102
203
 
103
- {valueOffset > 0 ? (
104
- <div className={classList.listSection}>
105
- {fixedOptions.map((value, i) => (
106
- <div
204
+ {this.props.options.map((value, i) => (
205
+ <Item
107
206
  className={classNames(classList.listItem, {
108
- hover: this.props.selectionIndex === i,
109
- fixed: true
207
+ active: this.props.selectionIndex === i + valueOffset,
208
+ hover: value.id && (this.state.currentOption?.id === value.id)
110
209
  })}
111
210
  key={`${display(value)}_${i}`}
112
- onMouseDown={e => this._onClick(value, e)}
113
- onClick={e => this._onClick(value, e)}
211
+ onMouseDown={e => !this.hasSubOptions && this._onClick(value, null, e)}
212
+ onClick={e => !this.hasSubOptions && this._onClick(value, null, e)}
213
+ onMouseEnter={e => this._onMouseOver(value, e)}
114
214
  >
115
215
  <this.props.customListItemComponent value={value} displayOption={display} />
116
- </div>
216
+ {this.props.withSubOptions && value.aggregators && <ChevronRight />}
217
+ </Item>
117
218
  ))}
118
219
  </div>
119
- ) : null}
120
-
121
- {this.props.options.map((value, i) => (
122
- <div
123
- className={classNames(classList.listItem, {
124
- hover: this.props.selectionIndex === i + valueOffset
125
- })}
126
- key={`${display(value)}_${i}`}
127
- onMouseDown={e => this._onClick(value, e)}
128
- onClick={e => this._onClick(value, e)}
129
- >
130
- <this.props.customListItemComponent value={value} displayOption={display} />
131
- </div>
132
- ))}
133
- </DropdownListWrapper>
220
+ </DropdownListWrapper>
221
+ {this.props.withSubOptions &&
222
+ this.state.currentOption &&
223
+ this.state.currentOption.aggregators && (
224
+ <ItemSelectorSubmenu
225
+ position={this.state.submenuPosition}
226
+ options={this.state.currentOption.aggregators.map(a =>
227
+ FIELD_AGGREGATORS.find(am => am.id === a)
228
+ )}
229
+ onClick={(aggregator, e) => this._onClick(this.state.currentOption, aggregator, e)}
230
+ />
231
+ )}
232
+ </DropdownListContainer>
134
233
  );
135
234
  }
136
235
  }
@@ -0,0 +1,88 @@
1
+ // Copyright (c) 2022 Uber Technologies, Inc.
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in
11
+ // all copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ // THE SOFTWARE.
20
+
21
+ import React, {Component, createRef} from 'react';
22
+ import PropTypes from 'prop-types';
23
+ import styled from 'styled-components';
24
+
25
+ const ItemSelectorSubmenuStyled = styled.div`
26
+ padding: 5px 0;
27
+ position: fixed;
28
+ min-width: 100px;
29
+ top: ${props => props.position.top}px;
30
+ left: ${props => props.position.left}px;
31
+ z-index: 999;
32
+ font-weight: 500;
33
+ transform: translateY(${props => props.verticalTranslation}px);
34
+ ${props => (props.light ? props.theme.dropdownListLT : props.theme.dropdownList)};
35
+ background-color: ${props =>
36
+ props.light ? props.theme.dropdownListBgdLT : props.theme.dropdownListBgd};
37
+ `
38
+
39
+ class ItemSelectorSubmenu extends Component {
40
+ static propTypes = {
41
+ position: PropTypes.object,
42
+ options: PropTypes.array,
43
+ onClick: PropTypes.func
44
+ }
45
+
46
+ state = {
47
+ verticalTranslation: 0
48
+ }
49
+
50
+ container = createRef()
51
+
52
+ _adjustPosition() {
53
+ this.setState({ verticalTranslation: 0 }, () => {
54
+ const containerOffsetBottom = window.innerHeight - this.container.current.getBoundingClientRect().bottom;
55
+ if (containerOffsetBottom < 0) {
56
+ this.setState({ verticalTranslation: containerOffsetBottom })
57
+ }
58
+ })
59
+ }
60
+
61
+ componentDidMount() {
62
+ this._adjustPosition()
63
+ }
64
+
65
+ componentDidUpdate(prevProps) {
66
+ if (this.props.position.top !== prevProps.position.top) {
67
+ this._adjustPosition()
68
+ }
69
+ }
70
+
71
+ render() {
72
+ return (
73
+ <ItemSelectorSubmenuStyled
74
+ position={this.props.position}
75
+ ref={this.container}
76
+ verticalTranslation={this.state.verticalTranslation}
77
+ >
78
+ {this.props.options.map((option, i) => (
79
+ <div className="list__item" key={i} onClick={e => this.props.onClick(option, e)}>
80
+ {option.displayName}
81
+ </div>
82
+ ))}
83
+ </ItemSelectorSubmenuStyled>
84
+ )
85
+ }
86
+ }
87
+
88
+ export default ItemSelectorSubmenu;
@@ -25,7 +25,8 @@ export type ItemSelectorProps = {
25
25
  DropdownHeaderComponent?: ComponentType<any> | null,
26
26
  DropDownRenderComponent?: ComponentType<any>,
27
27
  DropDownLineItemRenderComponent?: ComponentType<any>,
28
- CustomChickletComponent?: ComponentType<any>
28
+ CustomChickletComponent?: ComponentType<any>,
29
+ withSubOptions?: boolean
29
30
  };
30
31
 
31
32
  const ItemSelector: FunctionComponent<ItemSelectorProps>;
@@ -136,7 +136,8 @@ class ItemSelector extends Component {
136
136
  DropdownHeaderComponent: PropTypes.func,
137
137
  DropDownRenderComponent: PropTypes.func,
138
138
  DropDownLineItemRenderComponent: PropTypes.func,
139
- CustomChickletComponent: PropTypes.func
139
+ CustomChickletComponent: PropTypes.func,
140
+ withSubOptions: PropTypes.bool
140
141
  };
141
142
 
142
143
  static defaultProps = {
@@ -156,7 +157,8 @@ class ItemSelector extends Component {
156
157
  dropdownHeader: null,
157
158
  DropdownHeaderComponent: null,
158
159
  DropDownRenderComponent: DropdownList,
159
- DropDownLineItemRenderComponent: ListItem
160
+ DropDownLineItemRenderComponent: ListItem,
161
+ withSubOptions: false
160
162
  };
161
163
 
162
164
  state = {
@@ -265,6 +267,7 @@ class ItemSelector extends Component {
265
267
  showOptionsWhenEmpty
266
268
  selectedItems={toArray(this.props.selectedItems)}
267
269
  light={this.props.inputTheme === 'light'}
270
+ withSubOptions={this.props.withSubOptions}
268
271
  />
269
272
  </DropdownWrapper>
270
273
  );