1
- import { toValue , UnitValue } from "@webstudio-is/css-engine" ;
1
+ import { toValue , UnitValue , type RgbValue } from "@webstudio-is/css-engine" ;
2
2
import { Root , Range , Thumb , Track } from "@radix-ui/react-slider" ;
3
3
import { useState , useCallback } from "react" ;
4
4
import {
@@ -8,6 +8,10 @@ import {
8
8
} from "@webstudio-is/css-data" ;
9
9
import { styled , theme , Flex } from "@webstudio-is/design-system" ;
10
10
import { ChevronBigUpIcon } from "@webstudio-is/icons" ;
11
+ import { colord , extend } from "colord" ;
12
+ import mixPlugin from "colord/plugins/mix" ;
13
+
14
+ extend ( [ mixPlugin ] ) ;
11
15
12
16
type GradientControlProps = {
13
17
gradient : ParsedGradient ;
@@ -76,6 +80,62 @@ export const GradientControl = (props: GradientControlProps) => {
76
80
[ stops , selectedStop ]
77
81
) ;
78
82
83
+ const handlePointerDown = useCallback (
84
+ ( event : React . MouseEvent < HTMLSpanElement > ) => {
85
+ if ( event . target === undefined || event . target === null ) {
86
+ return ;
87
+ }
88
+
89
+ // radix-slider automatically brings the closest thumb to the clicked position.
90
+ // But, we want it be prevented. So, we can add a new color-stop where the user is cliked.
91
+ // And handle the even for scrubing when the user is dragging the thumb.
92
+ const sliderWidth = event . currentTarget . offsetWidth ;
93
+ const clickedPosition =
94
+ event . clientX - event . currentTarget . getBoundingClientRect ( ) . left ;
95
+ const newPosition = Math . ceil ( ( clickedPosition / sliderWidth ) * 100 ) ;
96
+ const isExistingPosition = positions . some (
97
+ ( position ) => Math . abs ( newPosition - position ) <= 8
98
+ ) ;
99
+
100
+ if ( isExistingPosition === true ) {
101
+ return ;
102
+ }
103
+
104
+ event . preventDefault ( ) ;
105
+ const newStopIndex = positions . findIndex (
106
+ ( position ) => position > newPosition
107
+ ) ;
108
+
109
+ const index = newStopIndex === - 1 ? stops . length : newStopIndex ;
110
+ const prevColor = stops [ index === 0 ? 0 : index - 1 ] . color ;
111
+ const nextColor =
112
+ stops [ index === positions . length ? index - 1 : index ] . color ;
113
+
114
+ const interpolationColor = colord ( toValue ( prevColor ) )
115
+ . mix ( colord ( toValue ( nextColor ) ) , newPosition / 100 )
116
+ . toRgb ( ) ;
117
+
118
+ const newColorStop : RgbValue = {
119
+ type : "rgb" ,
120
+ alpha : interpolationColor . a ,
121
+ r : interpolationColor . r ,
122
+ g : interpolationColor . g ,
123
+ b : interpolationColor . b ,
124
+ } ;
125
+
126
+ const newStops : GradientStop [ ] = [
127
+ ...stops . slice ( 0 , index ) ,
128
+ {
129
+ color : newColorStop ,
130
+ position : { type : "unit" , value : newPosition , unit : "%" } ,
131
+ } ,
132
+ ...stops . slice ( index ) ,
133
+ ] ;
134
+ setStops ( newStops ) ;
135
+ } ,
136
+ [ stops , positions ]
137
+ ) ;
138
+
79
139
if ( isEveryStopHasAPosition === false ) {
80
140
return ;
81
141
}
@@ -95,6 +155,7 @@ export const GradientControl = (props: GradientControlProps) => {
95
155
value = { positions }
96
156
onValueChange = { handleValueChange }
97
157
onKeyDown = { handleKeyDown }
158
+ onPointerDown = { handlePointerDown }
98
159
>
99
160
< Track >
100
161
< SliderRange css = { { cursor : "copy" } } />
@@ -119,7 +180,7 @@ export const GradientControl = (props: GradientControlProps) => {
119
180
with a different color-stop. So, we are not allowing the user to move the hint along the slider.
120
181
121
182
None of the tools are even displaying the hints at the moment. We are just displaying them so users can know
122
- they are hints associated too .
183
+ they are hints associated with stops if they managed to add gradient from the advanced tab .
123
184
*/ }
124
185
{ hints . map ( ( hint ) => {
125
186
return (
0 commit comments