Skip to content

Commit e4bdc69

Browse files
authored
[Maps] Safely handle empty string and invalid strings from EuiColorPicker (#62507) (#62533)
* [Maps] Safely handle empty string and invalid strings from EuiColorPicker * move RGBA_0000 to constants
1 parent a64de84 commit e4bdc69

File tree

9 files changed

+117
-62
lines changed

9 files changed

+117
-62
lines changed

x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export class ColorMapSelect extends Component {
9999
<ColorStopsOrdinal
100100
colorStops={this.state.customColorMap}
101101
onChange={this._onCustomColorMapChange}
102+
swatches={this.props.swatches}
102103
/>
103104
);
104105
} else
@@ -108,6 +109,7 @@ export class ColorMapSelect extends Component {
108109
field={this.props.styleProperty.getField()}
109110
getValueSuggestions={this.props.styleProperty.getValueSuggestions}
110111
onChange={this._onCustomColorMapChange}
112+
swatches={this.props.swatches}
111113
/>
112114
);
113115

x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js

Lines changed: 51 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,8 @@ import _ from 'lodash';
88
import React from 'react';
99
import { removeRow, isColorInvalid } from './color_stops_utils';
1010
import { i18n } from '@kbn/i18n';
11-
import { EuiButtonIcon, EuiColorPicker, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';
12-
13-
function getColorStopRow({ index, errors, stopInput, onColorChange, color, deleteButton, onAdd }) {
14-
const colorPickerButtons = (
15-
<div>
16-
{deleteButton}
17-
<EuiButtonIcon
18-
iconType="plusInCircle"
19-
color="primary"
20-
aria-label="Add"
21-
title="Add"
22-
onClick={onAdd}
23-
/>
24-
</div>
25-
);
26-
return (
27-
<EuiFormRow
28-
key={index}
29-
className="mapColorStop"
30-
isInvalid={errors.length !== 0}
31-
error={errors}
32-
display="rowCompressed"
33-
>
34-
<EuiFlexGroup alignItems="center" gutterSize="xs">
35-
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
36-
{stopInput}
37-
</EuiFlexItem>
38-
<EuiFlexItem>
39-
<EuiColorPicker
40-
onChange={onColorChange}
41-
color={color}
42-
compressed
43-
append={colorPickerButtons}
44-
/>
45-
</EuiFlexItem>
46-
</EuiFlexGroup>
47-
</EuiFormRow>
48-
);
49-
}
50-
51-
export function getDeleteButton(onRemove) {
52-
return (
53-
<EuiButtonIcon
54-
iconType="trash"
55-
color="danger"
56-
aria-label={i18n.translate('xpack.maps.styles.colorStops.deleteButtonAriaLabel', {
57-
defaultMessage: 'Delete',
58-
})}
59-
title={i18n.translate('xpack.maps.styles.colorStops.deleteButtonLabel', {
60-
defaultMessage: 'Delete',
61-
})}
62-
onClick={onRemove}
63-
/>
64-
);
65-
}
11+
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';
12+
import { MbValidatedColorPicker } from './mb_validated_color_picker';
6613

6714
export const ColorStops = ({
6815
onChange,
@@ -72,6 +19,7 @@ export const ColorStops = ({
7219
renderStopInput,
7320
addNewRow,
7421
canDeleteStop,
22+
swatches,
7523
}) => {
7624
function getStopInput(stop, index) {
7725
const onStopChange = newStopValue => {
@@ -134,10 +82,56 @@ export const ColorStops = ({
13482
isInvalid: isStopsInvalid(newColorStops),
13583
});
13684
};
137-
deleteButton = getDeleteButton(onRemove);
85+
deleteButton = (
86+
<EuiButtonIcon
87+
iconType="trash"
88+
color="danger"
89+
aria-label={i18n.translate('xpack.maps.styles.colorStops.deleteButtonAriaLabel', {
90+
defaultMessage: 'Delete',
91+
})}
92+
title={i18n.translate('xpack.maps.styles.colorStops.deleteButtonLabel', {
93+
defaultMessage: 'Delete',
94+
})}
95+
onClick={onRemove}
96+
/>
97+
);
13898
}
13999

140-
return getColorStopRow({ index, errors, stopInput, onColorChange, color, deleteButton, onAdd });
100+
const colorPickerButtons = (
101+
<div>
102+
{deleteButton}
103+
<EuiButtonIcon
104+
iconType="plusInCircle"
105+
color="primary"
106+
aria-label="Add"
107+
title="Add"
108+
onClick={onAdd}
109+
/>
110+
</div>
111+
);
112+
return (
113+
<EuiFormRow
114+
key={index}
115+
className="mapColorStop"
116+
isInvalid={errors.length !== 0}
117+
error={errors}
118+
display="rowCompressed"
119+
>
120+
<EuiFlexGroup alignItems="center" gutterSize="xs">
121+
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
122+
{stopInput}
123+
</EuiFlexItem>
124+
<EuiFlexItem>
125+
<MbValidatedColorPicker
126+
onChange={onColorChange}
127+
color={color}
128+
swatches={swatches}
129+
append={colorPickerButtons}
130+
/>
131+
</EuiFlexItem>
132+
</EuiFlexGroup>
133+
</EuiFormRow>
134+
);
141135
});
142136

143137
return <div>{rows}</div>;

x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const ColorStopsCategorical = ({
2727
field,
2828
onChange,
2929
getValueSuggestions,
30+
swatches,
3031
}) => {
3132
const getStopError = (stop, index) => {
3233
let count = 0;
@@ -81,6 +82,7 @@ export const ColorStopsCategorical = ({
8182
renderStopInput={renderStopInput}
8283
canDeleteStop={canDeleteStop}
8384
addNewRow={addCategoricalRow}
85+
swatches={swatches}
8486
/>
8587
);
8688
};

x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { i18n } from '@kbn/i18n';
2020
export const ColorStopsOrdinal = ({
2121
colorStops = [{ stop: 0, color: DEFAULT_CUSTOM_COLOR }],
2222
onChange,
23+
swatches,
2324
}) => {
2425
const getStopError = (stop, index) => {
2526
let error;
@@ -69,6 +70,7 @@ export const ColorStopsOrdinal = ({
6970
renderStopInput={renderStopInput}
7071
canDeleteStop={canDeleteStop}
7172
addNewRow={addOrdinalRow}
73+
swatches={swatches}
7274
/>
7375
);
7476
};

x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function DynamicColorForm({
1818
onDynamicStyleChange,
1919
staticDynamicSelect,
2020
styleProperty,
21+
swatches,
2122
}) {
2223
const styleOptions = styleProperty.getOptions();
2324

@@ -101,6 +102,7 @@ export function DynamicColorForm({
101102
useCustomColorMap={_.get(styleOptions, 'useCustomColorRamp', false)}
102103
styleProperty={styleProperty}
103104
showColorMapTypeToggle={showColorMapTypeToggle}
105+
swatches={swatches}
104106
/>
105107
);
106108
} else if (styleProperty.isCategorical()) {
@@ -118,6 +120,7 @@ export function DynamicColorForm({
118120
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
119121
styleProperty={styleProperty}
120122
showColorMapTypeToggle={showColorMapTypeToggle}
123+
swatches={swatches}
121124
/>
122125
);
123126
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { Component } from 'react';
8+
import { isValidHex, EuiColorPicker, EuiFormControlLayoutProps } from '@elastic/eui';
9+
10+
export const RGBA_0000 = 'rgba(0,0,0,0)';
11+
12+
interface Props {
13+
onChange: (color: string) => void;
14+
color: string;
15+
swatches?: string[];
16+
append?: EuiFormControlLayoutProps['append'];
17+
}
18+
19+
interface State {
20+
colorInputValue: string;
21+
}
22+
23+
// EuiColorPicker treats '' or invalid colors as transparent.
24+
// Mapbox logs errors for '' or invalid colors.
25+
// MbValidatedColorPicker is a wrapper around EuiColorPicker that reconciles the behavior difference
26+
// between the two by returning a Mapbox safe RGBA_0000 for '' or invalid colors
27+
// while keeping invalid state local so EuiColorPicker's input properly handles text input.
28+
export class MbValidatedColorPicker extends Component<Props, State> {
29+
state = {
30+
colorInputValue: this.props.color === RGBA_0000 ? '' : this.props.color,
31+
};
32+
33+
_onColorChange = (color: string) => {
34+
// reflect all user input, whether valid or not
35+
this.setState({ colorInputValue: color });
36+
// Only surface mapbox valid input to caller
37+
this.props.onChange(isValidHex(color) ? color : RGBA_0000);
38+
};
39+
40+
render() {
41+
return (
42+
<EuiColorPicker
43+
onChange={this._onColorChange}
44+
color={this.state.colorInputValue}
45+
swatches={this.props.swatches}
46+
append={this.props.append}
47+
compressed
48+
/>
49+
);
50+
}
51+
}

x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*/
66

77
import React from 'react';
8-
import { EuiColorPicker, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
8+
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
9+
import { MbValidatedColorPicker } from './mb_validated_color_picker';
910

1011
export function StaticColorForm({
1112
onStaticStyleChange,
@@ -23,11 +24,10 @@ export function StaticColorForm({
2324
{staticDynamicSelect}
2425
</EuiFlexItem>
2526
<EuiFlexItem>
26-
<EuiColorPicker
27+
<MbValidatedColorPicker
2728
onChange={onColorChange}
2829
color={styleProperty.getOptions().color}
2930
swatches={swatches}
30-
compressed
3131
/>
3232
</EuiFlexItem>
3333
</EuiFlexGroup>

x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ import {
1818
EuiTextColor,
1919
} from '@elastic/eui';
2020
import { Category } from '../components/legend/category';
21-
import { COLOR_MAP_TYPE } from '../../../../../common/constants';
21+
import { COLOR_MAP_TYPE, RGBA_0000 } from '../../../../../common/constants';
2222
import { isCategoricalStopsInvalid } from '../components/color/color_stops_utils';
2323

2424
const EMPTY_STOPS = { stops: [], defaultColor: null };
25-
const RGBA_0000 = 'rgba(0,0,0,0)';
2625

2726
export class DynamicColorProperty extends DynamicStyleProperty {
2827
syncCircleColorWithMb(mbLayerId, mbMap, alpha) {

x-pack/plugins/maps/common/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,5 @@ export enum SCALING_TYPES {
213213
CLUSTERS = 'CLUSTERS',
214214
TOP_HITS = 'TOP_HITS',
215215
}
216+
217+
export const RGBA_0000 = 'rgba(0,0,0,0)';

0 commit comments

Comments
 (0)