@@ -10,6 +10,7 @@ import {
1010 useAttributeName ,
1111 useBlockAttributesContext ,
1212 useBlockHoverState ,
13+ useBlockSetAttributesContext ,
1314 useDeviceType ,
1415} from '~stackable/hooks'
1516
@@ -21,7 +22,10 @@ import { isEqual } from 'lodash'
2122/**
2223 * WordPress dependencies
2324 */
24- import { memo } from '@wordpress/element'
25+ import { memo , useState } from '@wordpress/element'
26+ import { Button } from '@wordpress/components'
27+ import { settings } from '@wordpress/icons'
28+ import { dispatch } from '@wordpress/data'
2529
2630const AdvancedRangeControl = props => {
2731 const [ value , onChange ] = useControlHandlers ( props . attribute , props . responsive , props . hover , props . valueCallback , props . changeCallback )
@@ -30,6 +34,7 @@ const AdvancedRangeControl = props => {
3034 const deviceType = useDeviceType ( )
3135 const [ currentHoverState ] = useBlockHoverState ( )
3236 const hasUnits = ! ! props . units ?. length
37+ const setAttributes = useBlockSetAttributesContext ( )
3338 const unitAttrName = useAttributeName ( `${ props . attribute } Unit` , props . responsive , props . hover )
3439 const {
3540 unitAttribute,
@@ -98,12 +103,28 @@ const AdvancedRangeControl = props => {
98103 placeholderRender = null
99104 }
100105
106+ // Is value at first render the same as a step value?
107+ const isMarkValue = props . marks && true
108+ const [ isMarkMode , setIsMarkMode ] = useState ( isMarkValue )
109+
101110 // If this supports dynamic content, then the value should be saved as a String.
102111 // Important, the attribute type for this option should be a string.
103112 const _onChange = value => {
104113 const onChangeFunc = typeof props . onChange === 'undefined' ? onChange : props . onChange
105114 let newValue = props . isDynamic ? value . toString ( ) : value
106115
116+ // Support for steps. For steps, the value is an index, but the actual value is in the marks.
117+ if ( newValue !== '' && isMarkMode && props . marks ) {
118+ // Extract the unit and value.
119+ const markValue = props . marks [ value ] ?. value || '0'
120+ const [ _newValue , unit ] = extractNumberAndUnit ( markValue )
121+ newValue = _newValue
122+
123+ // Update the unit.
124+ dispatch ( 'core/block-editor' ) . __unstableMarkNextChangeAsNotPersistent ( )
125+ setAttributes ( { [ unitAttrName ] : unit } )
126+ }
127+
107128 // On reset, allow overriding the value.
108129 if ( newValue === '' ) {
109130 const overrideValue = props . onOverrideReset ?. ( )
@@ -121,6 +142,45 @@ const AdvancedRangeControl = props => {
121142 onChange : _onChange ,
122143 } )
123144
145+ // Support for steps. Modify the props to make the range control show steps.
146+ if ( props . marks && isMarkMode ) {
147+ // Steps only have 1 increment values
148+ propsToPass . min = 0
149+ propsToPass . min = 0
150+ propsToPass . max = props . marks . length - 1
151+ propsToPass . sliderMax = props . marks . length - 1
152+ propsToPass . step = 1
153+
154+ // Show the marks and labels
155+ propsToPass . marks = props . marks . reduce ( ( acc , mark , index ) => {
156+ return [
157+ {
158+ value : index ,
159+ label : undefined ,
160+ } ,
161+ ...acc ,
162+ ]
163+ } , [ ] )
164+ propsToPass . renderTooltipContent = value => {
165+ return props . marks [ value ] ?. label || ''
166+ }
167+
168+ // Other necessary props for steps.
169+ propsToPass . withInputField = false
170+ } else {
171+ propsToPass . marks = undefined
172+ }
173+
174+ if ( props . marks ) {
175+ controlProps . className = controlProps . className || ''
176+ controlProps . className += 'stk-range-control--with-marks'
177+ controlProps . className += isMarkMode ? ' stk-range-control--mark-mode' : ''
178+ }
179+
180+ if ( isMarkMode ) {
181+ controlProps . units = false
182+ }
183+
124184 return (
125185 < AdvancedControl { ...controlProps } >
126186 < DynamicContentControl
@@ -134,7 +194,19 @@ const AdvancedRangeControl = props => {
134194 onChange = { _onChange }
135195 allowReset = { false }
136196 placeholderRender = { placeholderRender }
137- />
197+ __nextHasNoMarginBottom
198+ >
199+ { props . allowCustom && props . marks && (
200+ < Button
201+ className = "stk-range-control__custom-button"
202+ size = "small"
203+ variant = "tertiary"
204+ onClick = { ( ) => setIsMarkMode ( ! isMarkMode ) }
205+ icon = { settings }
206+ >
207+ </ Button >
208+ ) }
209+ </ RangeControl >
138210 </ DynamicContentControl >
139211 < ResetButton
140212 allowReset = { props . allowReset }
@@ -159,6 +231,20 @@ AdvancedRangeControl.defaultProps = {
159231 onChange : undefined ,
160232 onOverrideReset : undefined ,
161233 forcePlaceholder : false ,
234+
235+ marks : undefined , // [{ value: '14px', label: 'S' }, { value: '16px', label: 'M' }]
236+ allowCustom : true ,
162237}
163238
164239export default memo ( AdvancedRangeControl , isEqual )
240+
241+ // The value can be in the format '10px' or '10.0em' or '10rem'.
242+ // Return an array with the number and the unit.
243+ const extractNumberAndUnit = value => {
244+ // Match the last characters that are not numbers.
245+ const matches = value . match ( / ( [ \d . ] + ) ( \D * ) $ / )
246+ if ( ! matches ) {
247+ return [ value , '' ]
248+ }
249+ return [ matches [ 1 ] , matches [ 2 ] ]
250+ }
0 commit comments