@@ -27,6 +27,8 @@ import { __ } from '@wordpress/i18n'
2727import {
2828 Fragment , useState , memo ,
2929} from '@wordpress/element'
30+ import { settings } from '@wordpress/icons'
31+ import { dispatch } from '@wordpress/data'
3032
3133/**
3234 * External dependencies
@@ -40,6 +42,7 @@ import {
4042 useBlockAttributesContext ,
4143 useDeviceType ,
4244 useBlockHoverState ,
45+ useBlockSetAttributesContext ,
4346} from '~stackable/hooks'
4447
4548const isEqualInitial = ( props , value , firstValue ) => {
@@ -51,6 +54,17 @@ const isEqualInitial = ( props, value, firstValue ) => {
5154 return isEqual
5255}
5356
57+ // The value can be in the format '10px' or '10.0em' or '10rem'.
58+ // Return an array with the number and the unit.
59+ const extractNumberAndUnit = value => {
60+ // Match the last characters that are not numbers.
61+ const matches = value . match ( / ( [ \d . ] + ) ( \D * ) $ / )
62+ if ( ! matches ) {
63+ return [ value , '' ]
64+ }
65+ return [ matches [ 1 ] , matches [ 2 ] ]
66+ }
67+
5468const FourRangeControl = memo ( props => {
5569 const [ _value , _onChange ] = useControlHandlers ( props . attribute , props . responsive , props . hover , props . valueCallback , props . changeCallback )
5670 const [ propsToPass , controlProps ] = extractControlProps ( props )
@@ -98,6 +112,7 @@ const FourRangeControl = memo( props => {
98112 label = { isLocked ? __ ( 'Individual sides' , i18n ) : __ ( 'All sides' , i18n ) }
99113 />
100114
115+ const setAttributes = useBlockSetAttributesContext ( )
101116 const hasUnits = ! ! props . units ?. length
102117 const unitAttrName = useAttributeName ( `${ props . attribute } Unit` , props . responsive , props . hover )
103118
@@ -180,6 +195,25 @@ const FourRangeControl = memo( props => {
180195 : props . enableBottom ? { desktop : _valueDesktop ?. bottom , tablet : _valueTablet ?. bottom }
181196 : { desktop : _valueDesktop ?. left , tablet : _valueTablet ?. left }
182197
198+ // Is value at first render the same as a step value? If so, do mark mode
199+ // at the start, or show custom
200+ const isMarkValue = {
201+ first : ! ! props . marks ,
202+ top : ! ! props . marks ,
203+ right : ! ! props . marks ,
204+ bottom : ! ! props . marks ,
205+ left : ! ! props . marks ,
206+ }
207+ if ( props . marks && value ) {
208+ // Check if the current value exsits in the marks
209+ isMarkValue . first = isMarkValue . first && props . marks . some ( mark => mark . value === firstValue + unit )
210+ isMarkValue . top = isMarkValue . top && props . marks . some ( mark => mark . value === value . top + unit )
211+ isMarkValue . right = isMarkValue . right && props . marks . some ( mark => mark . value === value . right + unit )
212+ isMarkValue . bottom = isMarkValue . bottom && props . marks . some ( mark => mark . value === value . bottom + unit )
213+ isMarkValue . left = isMarkValue . left && props . marks . some ( mark => mark . value === value . left + unit )
214+ }
215+ const [ isFourMarkMode , setIsFourMarkMode ] = useState ( isMarkValue )
216+
183217 const onChangeAll = newValue => {
184218 onChange ( {
185219 top : props . enableTop ? newValue : value . top ,
@@ -242,15 +276,114 @@ const FourRangeControl = memo( props => {
242276 left : newValue ,
243277 } )
244278 }
279+ // Support for steps. Modify the props to make the range control show steps.
280+ const stepSupport = ( isMarkMode , initialValue , initialOnChange ) => {
281+ const newProps = { ...propsToPass }
282+
283+ if ( props . marks && isMarkMode ) {
284+ // Steps only have 1 increment values
285+ newProps . min = 0
286+ newProps . max = props . marks . length - 1
287+ newProps . sliderMin = 0
288+ newProps . sliderMax = props . marks . length - 1
289+ newProps . step = 1
290+
291+ // Show the marks and labels
292+ newProps . marks = props . marks . reduce ( ( acc , mark , index ) => {
293+ return [
294+ {
295+ value : index ,
296+ label : undefined ,
297+ } ,
298+ ...acc ,
299+ ]
300+ } , [ ] )
301+ newProps . renderTooltipContent = value => {
302+ return props . marks [ value ] ?. label || ''
303+ }
304+
305+ // Other necessary props for steps.
306+ newProps . withInputField = false
307+ controlProps . units = false
308+ } else {
309+ newProps . marks = undefined
310+ }
311+
312+ if ( props . marks ) {
313+ controlProps . className = controlProps . className || ''
314+ controlProps . className += 'stk-range-control--with-marks'
315+ controlProps . className += isMarkMode ? ' stk-range-control--mark-mode' : ''
316+ }
317+
318+ // We need to change the way we handle the value and onChange if we're doing marks
319+ let rangeValue = initialValue
320+ let rangeOnChange = initialOnChange
321+ if ( props . marks && isMarkMode ) {
322+ rangeValue = props . marks . findIndex ( mark => {
323+ const [ _value , _unit ] = extractNumberAndUnit ( mark . value )
324+ return _value === value
325+ } )
326+ rangeOnChange = value => {
327+ if ( value === '' ) {
328+ return initialOnChange ( value )
329+ }
330+
331+ // Extract the unit and value.
332+ const markValue = props . marks [ value ] ?. value || '0'
333+ const [ _newValue , unit ] = extractNumberAndUnit ( markValue )
334+ const newValue = _newValue
335+
336+ // Update the unit.
337+ dispatch ( 'core/block-editor' ) . __unstableMarkNextChangeAsNotPersistent ( )
338+ setAttributes ( { [ unitAttrName ] : unit } )
339+
340+ initialOnChange ( newValue )
341+ }
342+ }
343+
344+ return [
345+ newProps , rangeValue , rangeOnChange ,
346+ ]
347+ }
348+
349+ const [ propsToPassFirst , rangeValueFirst , rangeOnChangeFirst ] = stepSupport (
350+ isFourMarkMode . first ,
351+ firstValue ,
352+ onChangeAll ,
353+ )
354+
355+ const [ propsToPassTop , rangeValueTop , rangeOnChangeTop ] = stepSupport (
356+ isFourMarkMode . top ,
357+ value . top ,
358+ onChangeTop ,
359+ )
360+
361+ const [ propsToPassRight , rangeValueRight , rangeOnChangeRight ] = stepSupport (
362+ isFourMarkMode . right ,
363+ value . right ,
364+ onChangeRight ,
365+ )
366+
367+ const [ propsToPassBottom , rangeValueBottom , rangeOnChangeBottom ] = stepSupport (
368+ isFourMarkMode . bottom ,
369+ value . bottom ,
370+ onChangeBottom ,
371+ )
372+
373+ const [ propsToPassLeft , rangeValueLeft , rangeOnChangeLeft ] = stepSupport (
374+ isFourMarkMode . left ,
375+ value . left ,
376+ onChangeLeft ,
377+ )
245378
246379 return (
247380 < AdvancedControl { ...controlProps } >
248381 { isLocked && ! props . vhMode && (
249382 < Fragment >
250383 < RangeControl
251- { ...propsToPass }
252- value = { firstValue }
253- onChange = { onChangeAll }
384+ { ...propsToPassFirst }
385+ value = { rangeValueFirst }
386+ onChange = { rangeOnChangeFirst }
254387 allowReset = { false }
255388 initialPosition = { ( ( ) => {
256389 if ( currentHoverState !== 'normal' ) {
@@ -278,7 +411,21 @@ const FourRangeControl = memo( props => {
278411
279412 return propsToPass . placeholder
280413 } ) ( ) }
281- />
414+ __nextHasNoMarginBottom
415+ >
416+ { props . allowCustom && props . marks && (
417+ < Button
418+ className = "stk-range-control__custom-button"
419+ size = "small"
420+ variant = "tertiary"
421+ onClick = { ( ) => setIsFourMarkMode ( prev => {
422+ return { ...prev , first : ! prev . first }
423+ } ) }
424+ icon = { settings }
425+ >
426+ </ Button >
427+ ) }
428+ </ RangeControl >
282429 < ResetButton
283430 allowReset = { props . allowReset }
284431 value = { firstValue }
@@ -384,9 +531,9 @@ const FourRangeControl = memo( props => {
384531 < span className = "ugb-four-range-control__icon" > { props . isCorner ? < SVGUpperLeftImage /> : < SVGTopImage /> } </ span >
385532 </ Tooltip >
386533 < RangeControl
387- { ...propsToPass }
388- value = { value . top }
389- onChange = { onChangeTop }
534+ { ...propsToPassTop }
535+ value = { rangeValueTop }
536+ onChange = { rangeOnChangeTop }
390537 allowReset = { false }
391538 initialPosition = { ( ( ) => {
392539 if ( currentHoverState !== 'normal' ) {
@@ -414,7 +561,21 @@ const FourRangeControl = memo( props => {
414561
415562 return typeof props . placeholderTop === 'undefined' ? propsToPass . placeholder : props . placeholderTop
416563 } ) ( ) }
417- />
564+ __nextHasNoMarginBottom
565+ >
566+ { props . allowCustom && props . marks && (
567+ < Button
568+ className = "stk-range-control__custom-button"
569+ size = "small"
570+ variant = "tertiary"
571+ onClick = { ( ) => setIsFourMarkMode ( prev => {
572+ return { ...prev , top : ! prev . top }
573+ } ) }
574+ icon = { settings }
575+ >
576+ </ Button >
577+ ) }
578+ </ RangeControl >
418579 < ResetButton
419580 allowReset = { props . allowReset }
420581 value = { value . top }
@@ -429,9 +590,9 @@ const FourRangeControl = memo( props => {
429590 < span className = "ugb-four-range-control__icon" > { props . isCorner ? < SVGUpperRightImage /> : < SVGRightImage /> } </ span >
430591 </ Tooltip >
431592 < RangeControl
432- { ...propsToPass }
433- value = { value . right }
434- onChange = { onChangeRight }
593+ { ...propsToPassRight }
594+ value = { rangeValueRight }
595+ onChange = { rangeOnChangeRight }
435596 allowReset = { false }
436597 initialPosition = { ( ( ) => {
437598 if ( currentHoverState !== 'normal' ) {
@@ -459,7 +620,21 @@ const FourRangeControl = memo( props => {
459620
460621 return typeof props . placeholderRight === 'undefined' ? propsToPass . placeholder : props . placeholderRight
461622 } ) ( ) }
462- />
623+ __nextHasNoMarginBottom
624+ >
625+ { props . allowCustom && props . marks && (
626+ < Button
627+ className = "stk-range-control__custom-button"
628+ size = "small"
629+ variant = "tertiary"
630+ onClick = { ( ) => setIsFourMarkMode ( prev => {
631+ return { ...prev , right : ! prev . right }
632+ } ) }
633+ icon = { settings }
634+ >
635+ </ Button >
636+ ) }
637+ </ RangeControl >
463638 < ResetButton
464639 allowReset = { props . allowReset }
465640 value = { value . right }
@@ -474,9 +649,9 @@ const FourRangeControl = memo( props => {
474649 < span className = "ugb-four-range-control__icon" > { props . isCorner ? < SVGLowerLeftImage /> : < SVGBottomImage /> } </ span >
475650 </ Tooltip >
476651 < RangeControl
477- { ...propsToPass }
478- value = { value . bottom }
479- onChange = { onChangeBottom }
652+ { ...propsToPassBottom }
653+ value = { rangeValueBottom }
654+ onChange = { rangeOnChangeBottom }
480655 allowReset = { false }
481656 initialPosition = { ( ( ) => {
482657 if ( currentHoverState !== 'normal' ) {
@@ -504,7 +679,21 @@ const FourRangeControl = memo( props => {
504679
505680 return typeof props . placeholderBottom === 'undefined' ? propsToPass . placeholder : props . placeholderBottom
506681 } ) ( ) }
507- />
682+ __nextHasNoMarginBottom
683+ >
684+ { props . allowCustom && props . marks && (
685+ < Button
686+ className = "stk-range-control__custom-button"
687+ size = "small"
688+ variant = "tertiary"
689+ onClick = { ( ) => setIsFourMarkMode ( prev => {
690+ return { ...prev , bottom : ! prev . bottom }
691+ } ) }
692+ icon = { settings }
693+ >
694+ </ Button >
695+ ) }
696+ </ RangeControl >
508697 < ResetButton
509698 allowReset = { props . allowReset }
510699 value = { value . bottom }
@@ -519,9 +708,9 @@ const FourRangeControl = memo( props => {
519708 < span className = "ugb-four-range-control__icon" > { props . isCorner ? < SVGLowerRightImage /> : < SVGLeftImage /> } </ span >
520709 </ Tooltip >
521710 < RangeControl
522- { ...propsToPass }
523- value = { value . left }
524- onChange = { onChangeLeft }
711+ { ...propsToPassLeft }
712+ value = { rangeValueLeft }
713+ onChange = { rangeOnChangeLeft }
525714 allowReset = { false }
526715 initialPosition = { ( ( ) => {
527716 if ( currentHoverState !== 'normal' ) {
@@ -549,7 +738,21 @@ const FourRangeControl = memo( props => {
549738
550739 return typeof props . placeholderLeft === 'undefined' ? propsToPass . placeholder : props . placeholderLeft
551740 } ) ( ) }
552- />
741+ __nextHasNoMarginBottom
742+ >
743+ { props . allowCustom && props . marks && (
744+ < Button
745+ className = "stk-range-control__custom-button"
746+ size = "small"
747+ variant = "tertiary"
748+ onClick = { ( ) => setIsFourMarkMode ( prev => {
749+ return { ...prev , left : ! prev . left }
750+ } ) }
751+ icon = { settings }
752+ >
753+ </ Button >
754+ ) }
755+ </ RangeControl >
553756 < ResetButton
554757 allowReset = { props . allowReset }
555758 value = { value . left }
@@ -598,6 +801,9 @@ FourRangeControl.defaultProps = {
598801 onChange : undefined ,
599802
600803 isCorner : false ,
804+
805+ marks : undefined ,
806+ allowCustom : true ,
601807}
602808
603809export default memo ( FourRangeControl )
0 commit comments