@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.
- package/CHANGELOG.md +711 -0
- package/dist/components/common/field-selector.js +62 -17
- package/dist/components/common/field-token.js +4 -12
- package/dist/components/common/icons/chevron-right.js +69 -0
- package/dist/components/common/icons/index.js +9 -1
- package/dist/components/common/item-selector/dropdown-list.js +137 -21
- package/dist/components/common/item-selector/item-selector-submenu.js +133 -0
- package/dist/components/common/item-selector/item-selector.js +7 -4
- package/dist/components/common/item-selector/typeahead.js +16 -16
- package/dist/components/side-panel/layer-panel/layer-configurator.js +11 -4
- package/dist/components/side-panel/layer-panel/vis-config-by-field-selector.js +7 -3
- package/dist/constants/default-settings.js +33 -3
- package/dist/constants/index.js +7 -1
- package/dist/styles/base.js +4 -4
- package/package.json +1 -1
- package/src/components/common/field-selector.js +62 -21
- package/src/components/common/field-token.js +13 -14
- package/src/components/common/icons/chevron-right.js +47 -0
- package/src/components/common/icons/index.js +1 -0
- package/src/components/common/item-selector/dropdown-list.js +135 -36
- package/src/components/common/item-selector/item-selector-submenu.js +88 -0
- package/src/components/common/item-selector/item-selector.d.ts +2 -1
- package/src/components/common/item-selector/item-selector.js +5 -2
- package/src/components/common/item-selector/typeahead.js +27 -21
- package/src/components/side-panel/layer-panel/layer-configurator.js +17 -1
- package/src/components/side-panel/layer-panel/vis-config-by-field-selector.js +5 -1
- package/src/constants/default-settings.js +51 -0
- package/src/constants/index.js +2 -1
- package/src/styles/base.js +4 -0
- 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.
|
|
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 =>
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
185
|
+
return fieldsWithAggregators;
|
|
146
186
|
}
|
|
147
187
|
const filters = Array.isArray(filterFieldTypes) ? filterFieldTypes : [filterFieldTypes];
|
|
148
|
-
return
|
|
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
|
|
24
|
+
function FieldTokenFactory(fieldTypeDisplay) {
|
|
25
25
|
const FieldTag = styled.div`
|
|
26
|
-
background-color:
|
|
26
|
+
background-color: #f8f9f9;
|
|
27
|
+
color: #888b8c;
|
|
27
28
|
border-radius: 2px;
|
|
28
|
-
border: 1px solid
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
font-size:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
width:
|
|
36
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
<
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
{fixedOptions.map((value, i) => (
|
|
106
|
-
<div
|
|
204
|
+
{this.props.options.map((value, i) => (
|
|
205
|
+
<Item
|
|
107
206
|
className={classNames(classList.listItem, {
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
216
|
+
{this.props.withSubOptions && value.aggregators && <ChevronRight />}
|
|
217
|
+
</Item>
|
|
117
218
|
))}
|
|
118
219
|
</div>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
);
|