react-native-color-picker-palette 1.0.0

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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -0
  3. package/package.json +70 -0
  4. package/src/core/hooks/index.ts +2 -0
  5. package/src/core/hooks/useColor.ts +93 -0
  6. package/src/core/hooks/useComponentLayout.ts +38 -0
  7. package/src/core/index.ts +19 -0
  8. package/src/core/services/ColorService.ts +338 -0
  9. package/src/core/services/index.ts +1 -0
  10. package/src/core/types/index.ts +211 -0
  11. package/src/core/utils/clamp.ts +16 -0
  12. package/src/core/utils/format.ts +59 -0
  13. package/src/core/utils/index.ts +8 -0
  14. package/src/full/components/Alpha.tsx +221 -0
  15. package/src/full/components/ColorPicker.tsx +206 -0
  16. package/src/full/components/Fields/HexField.tsx +125 -0
  17. package/src/full/components/Fields/RgbFields.tsx +192 -0
  18. package/src/full/components/Fields/index.tsx +70 -0
  19. package/src/full/components/Hue.tsx +188 -0
  20. package/src/full/components/RectangleSaturation.tsx +203 -0
  21. package/src/full/components/Saturation.tsx +258 -0
  22. package/src/full/components/Thumb.tsx +47 -0
  23. package/src/full/components/Value.tsx +192 -0
  24. package/src/full/components/index.ts +8 -0
  25. package/src/full/index.ts +69 -0
  26. package/src/index.ts +19 -0
  27. package/src/lite/components/Alpha.tsx +228 -0
  28. package/src/lite/components/ColorPicker.tsx +209 -0
  29. package/src/lite/components/Fields/HexField.tsx +103 -0
  30. package/src/lite/components/Fields/RgbFields.tsx +138 -0
  31. package/src/lite/components/Fields/index.tsx +53 -0
  32. package/src/lite/components/Hue.tsx +192 -0
  33. package/src/lite/components/RectangleSaturation.tsx +238 -0
  34. package/src/lite/components/Saturation.tsx +289 -0
  35. package/src/lite/components/Thumb.tsx +47 -0
  36. package/src/lite/components/Value.tsx +201 -0
  37. package/src/lite/components/index.ts +8 -0
  38. package/src/lite/index.ts +75 -0
@@ -0,0 +1,192 @@
1
+ import React, { memo, useState, useCallback, useEffect } from 'react';
2
+ import { View, TextInput, Text, StyleSheet } from 'react-native';
3
+ import type { IFieldsProps } from '../../../core/types';
4
+ import { ColorService } from '../../../core/services';
5
+ import { clamp, safeParseInt } from '../../../core/utils';
6
+
7
+ interface RgbInputProps {
8
+ label: string;
9
+ value: number;
10
+ onChange: (value: number) => void;
11
+ onComplete: () => void;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Single RGB value input
17
+ */
18
+ const RgbInput = memo(
19
+ ({ label, value, onChange, onComplete, disabled }: RgbInputProps) => {
20
+ const [inputValue, setInputValue] = useState(String(value));
21
+ const [isFocused, setIsFocused] = useState(false);
22
+
23
+ // Update input when value changes externally
24
+ useEffect(() => {
25
+ if (!isFocused) {
26
+ setInputValue(String(value));
27
+ }
28
+ }, [value, isFocused]);
29
+
30
+ const handleChangeText = useCallback((text: string) => {
31
+ // Only allow numbers
32
+ const numericValue = text.replace(/[^0-9]/g, '');
33
+ setInputValue(numericValue);
34
+ }, []);
35
+
36
+ const handleBlur = useCallback(() => {
37
+ setIsFocused(false);
38
+ const numValue = clamp(safeParseInt(inputValue), 0, 255);
39
+ setInputValue(String(numValue));
40
+ onChange(numValue);
41
+ onComplete();
42
+ }, [inputValue, onChange, onComplete]);
43
+
44
+ const handleFocus = useCallback(() => {
45
+ setIsFocused(true);
46
+ }, []);
47
+
48
+ return (
49
+ <View style={styles.inputContainer}>
50
+ <Text style={styles.label}>{label}</Text>
51
+ <TextInput
52
+ style={[
53
+ styles.input,
54
+ disabled && styles.inputDisabled,
55
+ ]}
56
+ value={inputValue}
57
+ onChangeText={handleChangeText}
58
+ onBlur={handleBlur}
59
+ onFocus={handleFocus}
60
+ editable={!disabled}
61
+ keyboardType="number-pad"
62
+ maxLength={3}
63
+ placeholder="0"
64
+ placeholderTextColor="#999999"
65
+ />
66
+ </View>
67
+ );
68
+ }
69
+ );
70
+
71
+ /**
72
+ * RGB color input fields
73
+ *
74
+ * Features:
75
+ * - Three inputs for R, G, B values (0-255)
76
+ * - Validates input on blur
77
+ * - Only allows numeric input
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * <RgbFields
82
+ * color={color}
83
+ * onChange={handleChange}
84
+ * onChangeComplete={handleChangeComplete}
85
+ * />
86
+ * ```
87
+ */
88
+ export const RgbFields = memo(
89
+ ({
90
+ color,
91
+ onChange,
92
+ onChangeComplete,
93
+ disabled = false,
94
+ }: IFieldsProps) => {
95
+ const handleRChange = useCallback(
96
+ (r: number) => {
97
+ const nextColor = ColorService.convert('rgb', {
98
+ ...color.rgb,
99
+ r,
100
+ });
101
+ onChange(nextColor);
102
+ },
103
+ [color.rgb, onChange]
104
+ );
105
+
106
+ const handleGChange = useCallback(
107
+ (g: number) => {
108
+ const nextColor = ColorService.convert('rgb', {
109
+ ...color.rgb,
110
+ g,
111
+ });
112
+ onChange(nextColor);
113
+ },
114
+ [color.rgb, onChange]
115
+ );
116
+
117
+ const handleBChange = useCallback(
118
+ (b: number) => {
119
+ const nextColor = ColorService.convert('rgb', {
120
+ ...color.rgb,
121
+ b,
122
+ });
123
+ onChange(nextColor);
124
+ },
125
+ [color.rgb, onChange]
126
+ );
127
+
128
+ const handleComplete = useCallback(() => {
129
+ onChangeComplete?.(color);
130
+ }, [color, onChangeComplete]);
131
+
132
+ return (
133
+ <View style={styles.container}>
134
+ <RgbInput
135
+ label="R"
136
+ value={color.rgb.r}
137
+ onChange={handleRChange}
138
+ onComplete={handleComplete}
139
+ disabled={disabled}
140
+ />
141
+ <RgbInput
142
+ label="G"
143
+ value={color.rgb.g}
144
+ onChange={handleGChange}
145
+ onComplete={handleComplete}
146
+ disabled={disabled}
147
+ />
148
+ <RgbInput
149
+ label="B"
150
+ value={color.rgb.b}
151
+ onChange={handleBChange}
152
+ onComplete={handleComplete}
153
+ disabled={disabled}
154
+ />
155
+ </View>
156
+ );
157
+ }
158
+ );
159
+
160
+ const styles = StyleSheet.create({
161
+ container: {
162
+ flexDirection: 'row',
163
+ gap: 8,
164
+ },
165
+ inputContainer: {
166
+ flex: 1,
167
+ },
168
+ label: {
169
+ fontSize: 10,
170
+ fontWeight: '600',
171
+ color: '#666666',
172
+ marginBottom: 4,
173
+ textAlign: 'center',
174
+ },
175
+ input: {
176
+ height: 36,
177
+ borderWidth: 1,
178
+ borderColor: '#E0E0E0',
179
+ borderRadius: 6,
180
+ paddingHorizontal: 8,
181
+ fontSize: 14,
182
+ color: '#333333',
183
+ backgroundColor: '#FFFFFF',
184
+ textAlign: 'center',
185
+ },
186
+ inputDisabled: {
187
+ backgroundColor: '#F5F5F5',
188
+ color: '#999999',
189
+ },
190
+ });
191
+
192
+ RgbFields.displayName = 'RgbFields';
@@ -0,0 +1,70 @@
1
+ import React, { memo } from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import type { IFieldsProps } from '../../../core/types';
4
+ import { HexField } from './HexField';
5
+ import { RgbFields } from './RgbFields';
6
+
7
+ /**
8
+ * Color input fields container
9
+ *
10
+ * Includes:
11
+ * - HEX field for hex color input
12
+ * - RGB fields for R, G, B value inputs
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <Fields
17
+ * color={color}
18
+ * onChange={handleChange}
19
+ * onChangeComplete={handleChangeComplete}
20
+ * />
21
+ * ```
22
+ */
23
+ export const Fields = memo(
24
+ ({
25
+ color,
26
+ onChange,
27
+ onChangeComplete,
28
+ disabled = false,
29
+ }: IFieldsProps) => {
30
+ return (
31
+ <View style={styles.container}>
32
+ <View style={styles.hexContainer}>
33
+ <HexField
34
+ color={color}
35
+ onChange={onChange}
36
+ onChangeComplete={onChangeComplete}
37
+ disabled={disabled}
38
+ />
39
+ </View>
40
+ <View style={styles.rgbContainer}>
41
+ <RgbFields
42
+ color={color}
43
+ onChange={onChange}
44
+ onChangeComplete={onChangeComplete}
45
+ disabled={disabled}
46
+ />
47
+ </View>
48
+ </View>
49
+ );
50
+ }
51
+ );
52
+
53
+ const styles = StyleSheet.create({
54
+ container: {
55
+ flexDirection: 'row',
56
+ gap: 12,
57
+ alignItems: 'flex-end',
58
+ },
59
+ hexContainer: {
60
+ flex: 1,
61
+ },
62
+ rgbContainer: {
63
+ flex: 2,
64
+ },
65
+ });
66
+
67
+ Fields.displayName = 'Fields';
68
+
69
+ export { HexField } from './HexField';
70
+ export { RgbFields } from './RgbFields';
@@ -0,0 +1,188 @@
1
+ import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ PanResponder,
6
+ GestureResponderEvent,
7
+ LayoutChangeEvent,
8
+ } from 'react-native';
9
+ import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
10
+ import type { IHueProps } from '../../core/types';
11
+ import { ColorService } from '../../core/services';
12
+ import { clamp } from '../../core/utils';
13
+ import { Thumb } from './Thumb';
14
+
15
+ /**
16
+ * Hue slider bar component
17
+ *
18
+ * Features:
19
+ * - Uses react-native-svg for smooth rainbow gradient
20
+ * - Caches measurements for better performance
21
+ * - Horizontal rainbow gradient bar
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <Hue
26
+ * color={color}
27
+ * onChange={handleChange}
28
+ * barHeight={10}
29
+ * thumbSize={24}
30
+ * />
31
+ * ```
32
+ */
33
+ export const Hue = memo(
34
+ ({
35
+ color,
36
+ onChange,
37
+ onChangeComplete,
38
+ barHeight,
39
+ thumbSize,
40
+ disabled = false,
41
+ }: IHueProps) => {
42
+ const containerRef = useRef<View>(null);
43
+ const [width, setWidth] = useState(1);
44
+ const measurementCache = useRef<number | null>(null);
45
+
46
+ // Calculate thumb position from hue value
47
+ const thumbPosition = useMemo(() => {
48
+ return (color.hsv.h / 360) * width;
49
+ }, [color.hsv.h, width]);
50
+
51
+ // Update measurement cache
52
+ const updateMeasurement = useCallback(() => {
53
+ return new Promise<number>((resolve) => {
54
+ if (measurementCache.current !== null) {
55
+ resolve(measurementCache.current);
56
+ return;
57
+ }
58
+ containerRef.current?.measureInWindow((x) => {
59
+ measurementCache.current = x;
60
+ resolve(x);
61
+ });
62
+ });
63
+ }, []);
64
+
65
+ // Convert touch position to hue
66
+ // Also sets saturation to 100% since the bar represents the edge of the wheel
67
+ const updateColorFromPosition = useCallback(
68
+ async (pageX: number, isFinal: boolean) => {
69
+ const wx = await updateMeasurement();
70
+ const x = clamp(pageX - wx, 0, width);
71
+ const hue = (x / width) * 360;
72
+
73
+ const nextColor = ColorService.convert('hsv', {
74
+ ...color.hsv,
75
+ h: clamp(hue, 0, 360),
76
+ s: 100, // Move to edge of wheel (full saturation)
77
+ });
78
+
79
+ onChange(nextColor);
80
+
81
+ if (isFinal) {
82
+ onChangeComplete?.(nextColor);
83
+ }
84
+ },
85
+ [color.hsv, width, onChange, onChangeComplete, updateMeasurement]
86
+ );
87
+
88
+ // PanResponder for touch handling
89
+ const panResponder = useMemo(
90
+ () =>
91
+ PanResponder.create({
92
+ onStartShouldSetPanResponder: () => !disabled,
93
+ onMoveShouldSetPanResponder: () => !disabled,
94
+ onPanResponderGrant: (evt: GestureResponderEvent) => {
95
+ measurementCache.current = null;
96
+ updateColorFromPosition(evt.nativeEvent.pageX, false);
97
+ },
98
+ onPanResponderMove: (evt: GestureResponderEvent) => {
99
+ updateColorFromPosition(evt.nativeEvent.pageX, false);
100
+ },
101
+ onPanResponderRelease: (evt: GestureResponderEvent) => {
102
+ updateColorFromPosition(evt.nativeEvent.pageX, true);
103
+ },
104
+ }),
105
+ [disabled, updateColorFromPosition]
106
+ );
107
+
108
+ // Handle layout to get width and clear cache
109
+ const handleLayout = useCallback((event: LayoutChangeEvent) => {
110
+ setWidth(event.nativeEvent.layout.width);
111
+ measurementCache.current = null;
112
+ }, []);
113
+
114
+ return (
115
+ <View
116
+ ref={containerRef}
117
+ style={[
118
+ styles.container,
119
+ {
120
+ height: barHeight + thumbSize,
121
+ opacity: disabled ? 0.5 : 1,
122
+ },
123
+ ]}
124
+ onLayout={handleLayout}
125
+ {...panResponder.panHandlers}
126
+ >
127
+ {/* Gradient bar using SVG */}
128
+ <View style={[styles.barContainer, { height: barHeight, top: thumbSize / 2 }]}>
129
+ <Svg width="100%" height={barHeight}>
130
+ <Defs>
131
+ <LinearGradient id="hueGradient" x1="0%" y1="0%" x2="100%" y2="0%">
132
+ <Stop offset="0%" stopColor="#FF0000" />
133
+ <Stop offset="16.67%" stopColor="#FFFF00" />
134
+ <Stop offset="33.33%" stopColor="#00FF00" />
135
+ <Stop offset="50%" stopColor="#00FFFF" />
136
+ <Stop offset="66.67%" stopColor="#0000FF" />
137
+ <Stop offset="83.33%" stopColor="#FF00FF" />
138
+ <Stop offset="100%" stopColor="#FF0000" />
139
+ </LinearGradient>
140
+ </Defs>
141
+ <Rect
142
+ x="0"
143
+ y="0"
144
+ width="100%"
145
+ height={barHeight}
146
+ rx={barHeight / 2}
147
+ ry={barHeight / 2}
148
+ fill="url(#hueGradient)"
149
+ />
150
+ </Svg>
151
+ </View>
152
+
153
+ {/* Thumb indicator - centered vertically with bar */}
154
+ <View
155
+ style={[
156
+ styles.thumbContainer,
157
+ {
158
+ left: thumbPosition - thumbSize / 2,
159
+ top: barHeight / 2,
160
+ },
161
+ ]}
162
+ pointerEvents="none"
163
+ >
164
+ <Thumb size={thumbSize} color={`hsl(${color.hsv.h}, 100%, 50%)`} />
165
+ </View>
166
+ </View>
167
+ );
168
+ }
169
+ );
170
+
171
+ const styles = StyleSheet.create({
172
+ container: {
173
+ position: 'relative',
174
+ width: '100%',
175
+ },
176
+ barContainer: {
177
+ position: 'absolute',
178
+ left: 0,
179
+ right: 0,
180
+ overflow: 'hidden',
181
+ },
182
+ thumbContainer: {
183
+ position: 'absolute',
184
+ zIndex: 10,
185
+ },
186
+ });
187
+
188
+ Hue.displayName = 'Hue';
@@ -0,0 +1,203 @@
1
+ import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ PanResponder,
6
+ GestureResponderEvent,
7
+ LayoutChangeEvent,
8
+ } from 'react-native';
9
+ import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
10
+ import type { IRectangleSaturationProps } from '../../core/types';
11
+ import { ColorService } from '../../core/services';
12
+ import { clamp } from '../../core/utils';
13
+ import { Thumb } from './Thumb';
14
+
15
+ /**
16
+ * Rectangle Saturation/Value Picker - SVG Version
17
+ *
18
+ * Uses react-native-svg for smooth gradients.
19
+ *
20
+ * Layout:
21
+ * - Top-right: Current hue at full saturation (100% S, 100% V)
22
+ * - Top-left: White (0% S, 100% V)
23
+ * - Bottom-left: Black (0% S, 0% V)
24
+ * - Bottom-right: Black (100% S, 0% V)
25
+ *
26
+ * X-axis: Saturation (0% to 100%)
27
+ * Y-axis: Value/Brightness (100% to 0%)
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * <RectangleSaturation
32
+ * color={color}
33
+ * onChange={handleChange}
34
+ * width={250}
35
+ * height={150}
36
+ * thumbSize={24}
37
+ * />
38
+ * ```
39
+ */
40
+ export const RectangleSaturation = memo(
41
+ ({
42
+ color,
43
+ onChange,
44
+ onChangeComplete,
45
+ width,
46
+ height,
47
+ thumbSize,
48
+ disabled = false,
49
+ }: IRectangleSaturationProps) => {
50
+ const containerRef = useRef<View>(null);
51
+ const measurementCache = useRef<{ x: number; y: number } | null>(null);
52
+ const [isLayoutReady, setIsLayoutReady] = useState(false);
53
+
54
+ // Calculate thumb position from HSV values
55
+ const thumbPosition = useMemo(() => {
56
+ const x = (color.hsv.s / 100) * width - thumbSize / 2;
57
+ const y = ((100 - color.hsv.v) / 100) * height - thumbSize / 2;
58
+ return {
59
+ x: clamp(x, -thumbSize / 2, width - thumbSize / 2),
60
+ y: clamp(y, -thumbSize / 2, height - thumbSize / 2),
61
+ };
62
+ }, [color.hsv.s, color.hsv.v, width, height, thumbSize]);
63
+
64
+ // Update measurement cache
65
+ const updateMeasurement = useCallback(() => {
66
+ return new Promise<{ x: number; y: number }>((resolve) => {
67
+ if (measurementCache.current) {
68
+ resolve(measurementCache.current);
69
+ return;
70
+ }
71
+ containerRef.current?.measureInWindow((x, y) => {
72
+ measurementCache.current = { x, y };
73
+ resolve({ x, y });
74
+ });
75
+ });
76
+ }, []);
77
+
78
+ // Convert touch coordinates to HSV
79
+ const updateColorFromPosition = useCallback(
80
+ async (pageX: number, pageY: number, isFinal: boolean) => {
81
+ const measurement = await updateMeasurement();
82
+
83
+ const x = clamp(pageX - measurement.x, 0, width);
84
+ const y = clamp(pageY - measurement.y, 0, height);
85
+
86
+ const saturation = (x / width) * 100;
87
+ const value = 100 - (y / height) * 100;
88
+
89
+ const nextColor = ColorService.convert('hsv', {
90
+ h: color.hsv.h,
91
+ s: clamp(saturation, 0, 100),
92
+ v: clamp(value, 0, 100),
93
+ a: color.hsv.a,
94
+ });
95
+
96
+ onChange(nextColor);
97
+
98
+ if (isFinal) {
99
+ onChangeComplete?.(nextColor);
100
+ }
101
+ },
102
+ [color.hsv.h, color.hsv.a, width, height, onChange, onChangeComplete, updateMeasurement]
103
+ );
104
+
105
+ // Handle layout
106
+ const handleLayout = useCallback((event: LayoutChangeEvent) => {
107
+ measurementCache.current = null;
108
+ setIsLayoutReady(true);
109
+ }, []);
110
+
111
+ // PanResponder
112
+ const panResponder = useMemo(
113
+ () =>
114
+ PanResponder.create({
115
+ onStartShouldSetPanResponder: () => !disabled,
116
+ onMoveShouldSetPanResponder: () => !disabled,
117
+ onPanResponderGrant: (evt: GestureResponderEvent) => {
118
+ measurementCache.current = null;
119
+ updateColorFromPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY, false);
120
+ },
121
+ onPanResponderMove: (evt: GestureResponderEvent) => {
122
+ updateColorFromPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY, false);
123
+ },
124
+ onPanResponderRelease: (evt: GestureResponderEvent) => {
125
+ updateColorFromPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY, true);
126
+ },
127
+ }),
128
+ [disabled, updateColorFromPosition]
129
+ );
130
+
131
+ // Current hue color at full saturation
132
+ const hueColor = `hsl(${color.hsv.h}, 100%, 50%)`;
133
+
134
+ return (
135
+ <View
136
+ ref={containerRef}
137
+ onLayout={handleLayout}
138
+ style={[
139
+ styles.container,
140
+ {
141
+ width,
142
+ height,
143
+ opacity: disabled ? 0.5 : 1,
144
+ },
145
+ ]}
146
+ {...panResponder.panHandlers}
147
+ >
148
+ <Svg width={width} height={height}>
149
+ <Defs>
150
+ {/* White gradient: left (white) to right (transparent) */}
151
+ <LinearGradient id="whiteGradient" x1="0%" y1="0%" x2="100%" y2="0%">
152
+ <Stop offset="0%" stopColor="#FFFFFF" stopOpacity="1" />
153
+ <Stop offset="100%" stopColor="#FFFFFF" stopOpacity="0" />
154
+ </LinearGradient>
155
+
156
+ {/* Black gradient: top (transparent) to bottom (black) */}
157
+ <LinearGradient id="blackGradient" x1="0%" y1="0%" x2="0%" y2="100%">
158
+ <Stop offset="0%" stopColor="#000000" stopOpacity="0" />
159
+ <Stop offset="100%" stopColor="#000000" stopOpacity="1" />
160
+ </LinearGradient>
161
+ </Defs>
162
+
163
+ {/* Base hue color */}
164
+ <Rect x="0" y="0" width={width} height={height} fill={hueColor} rx={8} ry={8} />
165
+
166
+ {/* White gradient overlay */}
167
+ <Rect x="0" y="0" width={width} height={height} fill="url(#whiteGradient)" rx={8} ry={8} />
168
+
169
+ {/* Black gradient overlay */}
170
+ <Rect x="0" y="0" width={width} height={height} fill="url(#blackGradient)" rx={8} ry={8} />
171
+ </Svg>
172
+
173
+ {/* Thumb */}
174
+ <View
175
+ style={[
176
+ styles.thumbContainer,
177
+ {
178
+ left: thumbPosition.x,
179
+ top: thumbPosition.y,
180
+ },
181
+ ]}
182
+ pointerEvents="none"
183
+ >
184
+ <Thumb size={thumbSize} color={color.hex} />
185
+ </View>
186
+ </View>
187
+ );
188
+ }
189
+ );
190
+
191
+ const styles = StyleSheet.create({
192
+ container: {
193
+ position: 'relative',
194
+ borderRadius: 8,
195
+ overflow: 'hidden',
196
+ },
197
+ thumbContainer: {
198
+ position: 'absolute',
199
+ zIndex: 10,
200
+ },
201
+ });
202
+
203
+ RectangleSaturation.displayName = 'RectangleSaturation';