From d62b902fd07ef028eb976d12f987adedb8589a97 Mon Sep 17 00:00:00 2001 From: Jorge Date: Thu, 26 Sep 2019 10:34:17 +0100 Subject: [PATCH 1/7] Post rebases fixes (+6 squashed commits) Squashed commits: [4ee9051258] Avoid moving over other control points [479d199cfe] Add mechanism that allows themes disabling custom gradients. [37bc638743] Polish gradients further, and the gradient stops. Polish some of the gradient stop dimensions. Tweak the gradient presets to have fewer in the same hue. [9778bef873] Polish, better gradients, better spacing. This commit improves a few things: - It better spaces the items, so that there can be 6 swatches on a line. And works both with and without scrollbar. - It polishes the gradient customizer so that the handles are perfectly placed in the curves of the slider. - It removes a few of the more muddy gradient presets. [0c23c3d962] Improved a11y [c0fd4d6b97] Code refactor (+2 squashed commits) Squashed commits: [cc336649f] Refactor and improvements [f04445763] Add: Custom gradient component --- lib/experiments-page.php | 2 + package-lock.json | 6 + .../src/components/color-palette/control.scss | 4 - .../src/components/gradient-picker/control.js | 16 +- .../components/gradient-picker/control.scss | 4 - .../src/components/gradient-picker/index.js | 18 +- packages/block-editor/src/style.scss | 2 - packages/block-library/src/cover/edit.js | 1 + packages/block-library/src/cover/editor.scss | 2 +- packages/components/package.json | 1 + .../src/circular-option-picker/index.js | 2 + .../src/circular-option-picker/style.scss | 13 +- .../src/custom-gradient-picker/constants.js | 11 + .../custom-gradient-picker/control-points.js | 278 ++++++++++++++++++ .../src/custom-gradient-picker/index.js | 204 +++++++++++++ .../src/custom-gradient-picker/serializer.js | 31 ++ .../src/custom-gradient-picker/style.scss | 57 ++++ .../src/custom-gradient-picker/utils.js | 145 +++++++++ packages/components/src/dropdown/index.js | 3 + .../components/src/gradient-picker/index.js | 9 +- packages/components/src/index.js | 1 + packages/components/src/style.scss | 1 + .../editor/src/components/provider/index.js | 1 + 23 files changed, 787 insertions(+), 25 deletions(-) delete mode 100644 packages/block-editor/src/components/color-palette/control.scss delete mode 100644 packages/block-editor/src/components/gradient-picker/control.scss create mode 100644 packages/components/src/custom-gradient-picker/constants.js create mode 100644 packages/components/src/custom-gradient-picker/control-points.js create mode 100644 packages/components/src/custom-gradient-picker/index.js create mode 100644 packages/components/src/custom-gradient-picker/serializer.js create mode 100644 packages/components/src/custom-gradient-picker/style.scss create mode 100644 packages/components/src/custom-gradient-picker/utils.js diff --git a/lib/experiments-page.php b/lib/experiments-page.php index e02d522ad4e0c9..a37e9d37d220d8 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -154,6 +154,8 @@ function gutenberg_experiments_editor_settings( $settings ) { $experiments_settings['gradients'] = $gradient_presets; } + $experiments_settings['disableCustomGradients'] = get_theme_support( '__experimental-disable-custom-gradients' ); + return array_merge( $settings, $experiments_settings ); } add_filter( 'block_editor_settings', 'gutenberg_experiments_editor_settings' ); diff --git a/package-lock.json b/package-lock.json index ae42dbe6b0a774..3036560328e428 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7713,6 +7713,7 @@ "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", "downshift": "^3.3.4", + "gradient-parser": "^0.1.5", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", @@ -18198,6 +18199,11 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, + "gradient-parser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/gradient-parser/-/gradient-parser-0.1.5.tgz", + "integrity": "sha1-DH4heVWeXOfY1x9EI6+TcQCyJIw=" + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", diff --git a/packages/block-editor/src/components/color-palette/control.scss b/packages/block-editor/src/components/color-palette/control.scss deleted file mode 100644 index 249fc32607be17..00000000000000 --- a/packages/block-editor/src/components/color-palette/control.scss +++ /dev/null @@ -1,4 +0,0 @@ -.block-editor-color-palette-control__color-palette { - display: inline-block; - margin-top: 0.6rem; -} diff --git a/packages/block-editor/src/components/gradient-picker/control.js b/packages/block-editor/src/components/gradient-picker/control.js index a0eeecc6facc25..400d51587f2b0e 100644 --- a/packages/block-editor/src/components/gradient-picker/control.js +++ b/packages/block-editor/src/components/gradient-picker/control.js @@ -3,7 +3,7 @@ * External dependencies */ import classnames from 'classnames'; -import { isEmpty } from 'lodash'; +import { isEmpty, pick } from 'lodash'; /** * WordPress dependencies @@ -17,11 +17,14 @@ import { useSelect } from '@wordpress/data'; */ import GradientPicker from './'; -export default function( { className, label = __( 'Gradient Presets' ), ...props } ) { - const gradients = useSelect( ( select ) => ( - select( 'core/block-editor' ).getSettings().gradients +export default function( { className, value, onChange, label = __( 'Gradient Presets' ), ...props } ) { + const { gradients = [], disableCustomGradients } = useSelect( ( select ) => ( + pick( + select( 'core/block-editor' ).getSettings(), + [ 'gradients', 'disableCustomGradients' ] + ) ) ); - if ( isEmpty( gradients ) ) { + if ( isEmpty( gradients ) && disableCustomGradients ) { return null; } return ( @@ -35,8 +38,11 @@ export default function( { className, label = __( 'Gradient Presets' ), ...props { label } diff --git a/packages/block-editor/src/components/gradient-picker/control.scss b/packages/block-editor/src/components/gradient-picker/control.scss deleted file mode 100644 index e73745321e6c93..00000000000000 --- a/packages/block-editor/src/components/gradient-picker/control.scss +++ /dev/null @@ -1,4 +0,0 @@ -.block-editor-gradient-picker-control__gradient-picker-presets { - display: inline-block; - margin-top: 0.6rem; -} diff --git a/packages/block-editor/src/components/gradient-picker/index.js b/packages/block-editor/src/components/gradient-picker/index.js index a4b5fac2b98f7c..4b333aca8444df 100644 --- a/packages/block-editor/src/components/gradient-picker/index.js +++ b/packages/block-editor/src/components/gradient-picker/index.js @@ -1,3 +1,9 @@ + +/** + * External dependencies + */ +import { pick } from 'lodash'; + /** * WordPress dependencies */ @@ -5,19 +11,23 @@ import { __experimentalGradientPicker } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; function GradientPickerWithGradients( props ) { - const gradients = useSelect( ( select ) => ( - select( 'core/block-editor' ).getSettings().gradients + const { gradients, disableCustomGradients } = useSelect( ( select ) => ( + pick( + select( 'core/block-editor' ).getSettings(), + [ 'gradients', 'disableCustomGradients' ] + ) ) ); return ( <__experimentalGradientPicker + gradients={ props.gradients !== undefined ? props.gradient : gradients } + disableCustomGradients={ props.disableCustomGradients !== undefined ? props.disableCustomGradients : disableCustomGradients } { ...props } - gradients={ gradients } /> ); } export default function( props ) { - const ComponentToUse = props.gradients ? + const ComponentToUse = props.gradients !== undefined && props.disableCustomGradients !== undefined ? __experimentalGradientPicker : GradientPickerWithGradients; return ( ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 864e29414eb9b6..5220ffc3d8565b 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -17,10 +17,8 @@ @import "./components/block-toolbar/style.scss"; @import "./components/block-types-list/style.scss"; @import "./components/button-block-appender/style.scss"; -@import "./components/color-palette/control.scss"; @import "./components/contrast-checker/style.scss"; @import "./components/default-block-appender/style.scss"; -@import "./components/gradient-picker/control.scss"; @import "./components/link-control/style.scss"; @import "./components/inner-blocks/style.scss"; @import "./components/inserter-with-shortcuts/style.scss"; diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 647c94528b9743..bdb6aa9a7be917 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -461,6 +461,7 @@ function CoverEdit( { clearable={ false } /> <__experimentalGradientPicker + disableCustomGradients onChange={ ( newGradient ) => { setGradient( newGradient ); diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index 906ffe172b9f9a..a824f33c85b2f2 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -36,7 +36,7 @@ .wp-block-cover__placeholder-background-options { // wraps about 6 color swatches - max-width: 290px; + max-width: 260px; margin-top: 1em; width: 100%; } diff --git a/packages/components/package.json b/packages/components/package.json index bca18848d99f49..ce48be95d2aae5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -39,6 +39,7 @@ "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", "downshift": "^3.3.4", + "gradient-parser": "^0.1.5", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", diff --git a/packages/components/src/circular-option-picker/index.js b/packages/components/src/circular-option-picker/index.js index 204ebf515fa02c..3f7bf209b6ac39 100644 --- a/packages/components/src/circular-option-picker/index.js +++ b/packages/components/src/circular-option-picker/index.js @@ -90,10 +90,12 @@ export default function CircularOptionPicker( { actions, className, options, + children, } ) { return (
{ options } + { children } { actions && (
{ actions } diff --git a/packages/components/src/circular-option-picker/style.scss b/packages/components/src/circular-option-picker/style.scss index 4098ca3248d0d8..b567b376512371 100644 --- a/packages/components/src/circular-option-picker/style.scss +++ b/packages/components/src/circular-option-picker/style.scss @@ -1,12 +1,12 @@ $color-palette-circle-size: 28px; -$color-palette-circle-spacing: 14px; +$color-palette-circle-spacing: 12px; .components-circular-option-picker { - margin-right: -14px; - width: calc(100% + 14px); + display: inline-block; + margin-top: 0.6rem; + width: 100%; .components-circular-option-picker__custom-clear-wrapper { - width: calc(100% - 14px); display: flex; justify-content: flex-end; } @@ -22,6 +22,7 @@ $color-palette-circle-spacing: 14px; transform: scale(1); transition: 100ms transform ease; @include reduce-motion("transition"); + &:hover { transform: scale(1.2); } @@ -31,6 +32,10 @@ $color-palette-circle-spacing: 14px; height: 100%; width: 100%; } + + &:nth-child(6n+6) { + margin-right: 0; + } } .components-circular-option-picker__option-wrapper::before { diff --git a/packages/components/src/custom-gradient-picker/constants.js b/packages/components/src/custom-gradient-picker/constants.js new file mode 100644 index 00000000000000..234fd270874c32 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/constants.js @@ -0,0 +1,11 @@ +export const INSERT_POINT_WIDTH = 23; +export const GRADIENT_MARKERS_WIDTH = 18; +export const MINIMUM_DISTANCE_BETWEEN_INSERTER_AND_MARKER = ( INSERT_POINT_WIDTH + GRADIENT_MARKERS_WIDTH ) / 2; +export const MINIMUM_ABSOLUTE_LEFT_POSITION = 5; +export const MINIMUM_DISTANCE_BETWEEN_POINTS = 9; +export const MINIMUM_SIGNIFICANT_MOVE = 5; +export const DEFAULT_GRADIENT = 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)'; +export const COLOR_POPOVER_PROPS = { + className: 'components-custom-gradient-picker__color-picker-popover', + position: 'top', +}; diff --git a/packages/components/src/custom-gradient-picker/control-points.js b/packages/components/src/custom-gradient-picker/control-points.js new file mode 100644 index 00000000000000..75930b6b3e81e9 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/control-points.js @@ -0,0 +1,278 @@ + +/** + * External dependencies + */ +import { map } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Component, useCallback, useRef, useMemo } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { withInstanceId } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import Button from '../button'; +import ColorPicker from '../color-picker'; +import Dropdown from '../dropdown'; +import { serializeGradientColor, serializeGradientPosition } from './serializer'; +import { + getGradientWithColorAtIndexChanged, + getGradientWithControlPointRemoved, + getGradientWithPositionAtIndexChanged, + getGradientWithPositionAtIndexDecreased, + getGradientWithPositionAtIndexIncreased, + getHorizontalRelativeGradientPosition, + isControlPointOverlapping, +} from './utils'; +import { + COLOR_POPOVER_PROPS, + GRADIENT_MARKERS_WIDTH, + MINIMUM_SIGNIFICANT_MOVE, +} from './constants'; +import KeyboardShortcuts from '../keyboard-shortcuts'; + +export function useMarkerPoints( parsedGradient, maximumAbsolutePositionValue ) { + return useMemo( + () => { + if ( ! parsedGradient ) { + return []; + } + return map( parsedGradient.colorStops, ( colorStop ) => { + if ( ! colorStop || ! colorStop.length || colorStop.length.type !== '%' ) { + return null; + } + return { + color: serializeGradientColor( colorStop ), + position: serializeGradientPosition( colorStop.length ), + positionValue: parseInt( colorStop.length.value ), + }; + } ); + }, + [ parsedGradient, maximumAbsolutePositionValue ] + ); +} + +class ControlPointKeyboardMove extends Component { + constructor() { + super( ...arguments ); + this.increase = this.increase.bind( this ); + this.decrease = this.decrease.bind( this ); + this.shortcuts = { + right: () => this.increase(), + left: () => this.decrease(), + }; + } + increase() { + const { gradientIndex, onChange, parsedGradient } = this.props; + onChange( getGradientWithPositionAtIndexIncreased( parsedGradient, gradientIndex ) ); + } + + decrease() { + const { gradientIndex, onChange, parsedGradient } = this.props; + onChange( getGradientWithPositionAtIndexDecreased( parsedGradient, gradientIndex ) ); + } + render() { + const { children } = this.props; + return ( + + { children } + + ); + } +} + +const ControlPointButton = withInstanceId( + function( { + instanceId, + isOpen, + position, + color, + onChange, + gradientIndex, + parsedGradient, + ...additionalProps + } ) { + const descriptionId = `components-custom-gradient-picker__control-point-button-description-${ instanceId }`; + return ( + + + + ) } + popoverProps={ COLOR_POPOVER_PROPS } + /> + ) + ) + ); +} diff --git a/packages/components/src/custom-gradient-picker/index.js b/packages/components/src/custom-gradient-picker/index.js new file mode 100644 index 00000000000000..9204131ab8271e --- /dev/null +++ b/packages/components/src/custom-gradient-picker/index.js @@ -0,0 +1,204 @@ + +/** + * External dependencies + */ +import gradientParser from 'gradient-parser'; +import { some } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useCallback, useState, useRef, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import IconButton from '../icon-button'; +import ColorPicker from '../color-picker'; +import Dropdown from '../dropdown'; +import { useMarkerPoints, ControlPoints } from './control-points'; +import { + DEFAULT_GRADIENT, + INSERT_POINT_WIDTH, + COLOR_POPOVER_PROPS, + MINIMUM_DISTANCE_BETWEEN_POINTS, +} from './constants'; +import { serializeGradient } from './serializer'; +import { getHorizontalRelativeGradientPosition, getGradientWithColorStopAdded, getGradientWithColorAtPositionChanged } from './utils'; + +function InsertPoint( { + onChange, + parsedGradient, + insertPointPosition, + setIsInsertPointMoveEnabled, + isInsertingColorAtPosition, + setIsInsertingColorAtPosition, + setInsertPointPosition, +} ) { + const onColorPopoverClose = useCallback( + () => { + setIsInsertPointMoveEnabled( true ); + setIsInsertingColorAtPosition( null ); + setInsertPointPosition( null ); + }, + [ setIsInsertPointMoveEnabled, setIsInsertingColorAtPosition, setInsertPointPosition ] + ); + const renderToggle = useCallback( + ( { isOpen, onToggle } ) => ( + { + setIsInsertPointMoveEnabled( false ); + onToggle(); + } } + className="components-custom-gradient-picker__insert-point" + icon="insert" + style={ { + left: insertPointPosition !== null ? `${ insertPointPosition }%` : undefined, + } } + /> + ), + [ insertPointPosition, setIsInsertPointMoveEnabled ] + ); + const onColorChange = useCallback( + ( { rgb } ) => { + let newGradient; + if ( isInsertingColorAtPosition === null ) { + newGradient = getGradientWithColorStopAdded( parsedGradient, insertPointPosition, rgb ); + setIsInsertingColorAtPosition( insertPointPosition ); + } else { + newGradient = getGradientWithColorAtPositionChanged( parsedGradient, isInsertingColorAtPosition, rgb ); + } + onChange( newGradient ); + }, + [ insertPointPosition, isInsertingColorAtPosition, onChange, setIsInsertingColorAtPosition ] + ); + const renderContent = useCallback( + () => ( + + ), + [ onColorChange ] + ); + return ( + + ); +} + +export default function CustomGradientPicker( { value, onChange } ) { + let hasGradient = true; + if ( ! value ) { + hasGradient = false; + value = DEFAULT_GRADIENT; + } + const parsedGradient = useMemo( + () => { + try { + return gradientParser.parse( value )[ 0 ]; + } catch ( error ) { + hasGradient = false; + return gradientParser.parse( DEFAULT_GRADIENT )[ 0 ]; + } + }, + [ value ] + ); + const onGradientStructureChange = useCallback( + ( newGradientStructure ) => { + onChange( serializeGradient( newGradientStructure ) ); + }, + [ onChange ] + ); + const [ insertPointPosition, setInsertPointPosition ] = useState( null ); + const isInsertPointMoveEnabled = useRef( true ); + const setIsInsertPointMoveEnabled = useCallback( + ( isEnabled ) => { + isInsertPointMoveEnabled.current = isEnabled; + }, + [ isInsertPointMoveEnabled ] + ); + const [ isInsertingColorAtPosition, setIsInsertingColorAtPosition ] = useState( null ); + const gradientPickerDomRef = useRef(); + const markerPoints = useMarkerPoints( parsedGradient, gradientPickerDomRef ); + const updateInsertPointPosition = useCallback( + ( event ) => { + if ( ! gradientPickerDomRef.current || ! isInsertPointMoveEnabled.current ) { + return; + } + const insertPosition = getHorizontalRelativeGradientPosition( + event.clientX, + gradientPickerDomRef.current, + INSERT_POINT_WIDTH, + ); + + // If the insert point is close to an existing control point don't show it. + if ( some( + markerPoints, + ( { positionValue } ) => { + return Math.abs( insertPosition - positionValue ) < MINIMUM_DISTANCE_BETWEEN_POINTS; + } + ) ) { + setInsertPointPosition( null ); + return; + } + + setInsertPointPosition( insertPosition ); + }, + [ markerPoints, gradientPickerDomRef, setInsertPointPosition ] + ); + + const onMouseLeave = useCallback( + () => { + if ( ! isInsertPointMoveEnabled.current ) { + return; + } + setInsertPointPosition( null ); + }, + [ isInsertPointMoveEnabled, setInsertPointPosition ] + ); + + return ( +
+
+ { insertPointPosition !== null && ( + + ) } + +
+
+ ); +} diff --git a/packages/components/src/custom-gradient-picker/serializer.js b/packages/components/src/custom-gradient-picker/serializer.js new file mode 100644 index 00000000000000..d4f2beaa16320b --- /dev/null +++ b/packages/components/src/custom-gradient-picker/serializer.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { compact, get } from 'lodash'; + +export function serializeGradientColor( { type, value } ) { + return `${ type }( ${ value.join( ',' ) })`; +} + +export function serializeGradientPosition( { type, value } ) { + return `${ value }${ type }`; +} + +export function serializeGradientColorStop( { type, value, length } ) { + return `${ serializeGradientColor( { type, value } ) } ${ serializeGradientPosition( length ) }`; +} + +export function serializeGradientOrientation( orientation ) { + if ( ! orientation || orientation.type !== 'angular' ) { + return; + } + return `${ orientation.value }deg`; +} + +export function serializeGradient( { type, orientation, colorStops } ) { + const serializedOrientation = serializeGradientOrientation( orientation ); + const serializedColorStops = colorStops.sort( ( colorStop1, colorStop2 ) => { + return get( colorStop1, [ 'length', 'value' ], 0 ) - get( colorStop2, [ 'length', 'value' ], 0 ); + } ).map( serializeGradientColorStop ); + return `${ type }( ${ compact( [ serializedOrientation, ...serializedColorStops ] ).join( ', ' ) } )`; +} diff --git a/packages/components/src/custom-gradient-picker/style.scss b/packages/components/src/custom-gradient-picker/style.scss new file mode 100644 index 00000000000000..1590a8a6c07375 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/style.scss @@ -0,0 +1,57 @@ +$components-custom-gradient-picker__padding: 3px; // 24px container, 18px handles inside, that leaves 6px padding, half of which is 3. + +.components-custom-gradient-picker:not(.has-gradient) { + opacity: 0.4; +} + +.components-custom-gradient-picker { + width: 100%; + height: $icon-button-size-small; + border-radius: $icon-button-size-small; + margin-bottom: $grid-size; + padding-left: $components-custom-gradient-picker__padding; + padding-right: $icon-button-size-small - $components-custom-gradient-picker__padding; + + .components-custom-gradient-picker__markers-container { + position: relative; + } + + .components-custom-gradient-picker__insert-point { + border-radius: 50%; + background: $white; + padding: 2px; + width: $icon-button-size-small; + height: $icon-button-size-small; + position: relative; + } + + .components-custom-gradient-picker__control-point-button { + border: 2px solid $white; + border-radius: 50%; + height: 18px; + position: absolute; + width: 18px; + top: $components-custom-gradient-picker__padding; + + &.is-active { + background: #fafafa; + color: #23282d; + border-color: #999; + box-shadow: + inset 0 -1px 0 #999, + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; + } + } +} + +.components-custom-gradient-picker__color-picker-popover .components-custom-gradient-picker__remove-control-point { + margin-left: auto; + margin-right: auto; + display: block; + margin-bottom: 8px; +} + +.components-custom-gradient-picker__inserter { + width: 100%; +} diff --git a/packages/components/src/custom-gradient-picker/utils.js b/packages/components/src/custom-gradient-picker/utils.js new file mode 100644 index 00000000000000..250dc3807fb2d4 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/utils.js @@ -0,0 +1,145 @@ +/** + * External dependencies + */ +import { findIndex, some } from 'lodash'; + +/** + * Internal dependencies + */ +import { + INSERT_POINT_WIDTH, + MINIMUM_ABSOLUTE_LEFT_POSITION, + MINIMUM_DISTANCE_BETWEEN_POINTS, +} from './constants'; + +function tinyColorRgbToGradientColorStop( { r, g, b, a } ) { + if ( a === 1 ) { + return { + type: 'rgb', + value: [ r, g, b ], + }; + } + return { + type: 'rgba', + value: [ r, g, b, a ], + }; +} + +export function getGradientWithColorStopAdded( parsedGradient, relativePosition, rgbaColor ) { + const colorStop = tinyColorRgbToGradientColorStop( rgbaColor ); + colorStop.length = { + type: '%', + value: relativePosition, + }; + return { + ...parsedGradient, + colorStops: [ + ...parsedGradient.colorStops, + colorStop, + ], + }; +} + +export function getGradientWithPositionAtIndexChanged( parsedGradient, index, relativePosition ) { + return { + ...parsedGradient, + colorStops: parsedGradient.colorStops.map( + ( colorStop, colorStopIndex ) => { + if ( colorStopIndex !== index ) { + return colorStop; + } + return { + ...colorStop, + length: { + ...colorStop.length, + value: relativePosition, + }, + }; + } + ), + }; +} + +export function isControlPointOverlapping( parsedGradient, position, initialIndex ) { + const initialPosition = parseInt( parsedGradient.colorStops[ initialIndex ].length.value ); + const minPosition = Math.min( initialPosition, position ); + const maxPosition = Math.max( initialPosition, position ); + + return some( + parsedGradient.colorStops, + ( { length }, index ) => { + const itemPosition = parseInt( length.value ); + return index !== initialIndex && ( + Math.abs( itemPosition - position ) < MINIMUM_DISTANCE_BETWEEN_POINTS || + ( minPosition < itemPosition && itemPosition < maxPosition ) + ); + } + ); +} + +function getGradientWithPositionAtIndexSummed( parsedGradient, index, valueToSum ) { + const currentPosition = parsedGradient.colorStops[ index ].length.value; + const newPosition = Math.max( 0, Math.min( 100, parseInt( currentPosition ) + valueToSum ) ); + if ( isControlPointOverlapping( parsedGradient, newPosition, index ) ) { + return parsedGradient; + } + return getGradientWithPositionAtIndexChanged( parsedGradient, index, newPosition ); +} + +export function getGradientWithPositionAtIndexIncreased( parsedGradient, index ) { + return getGradientWithPositionAtIndexSummed( parsedGradient, index, MINIMUM_DISTANCE_BETWEEN_POINTS ); +} + +export function getGradientWithPositionAtIndexDecreased( parsedGradient, index ) { + return getGradientWithPositionAtIndexSummed( parsedGradient, index, -MINIMUM_DISTANCE_BETWEEN_POINTS ); +} + +export function getGradientWithColorAtIndexChanged( parsedGradient, index, rgbaColor ) { + return { + ...parsedGradient, + colorStops: parsedGradient.colorStops.map( + ( colorStop, colorStopIndex ) => { + if ( colorStopIndex !== index ) { + return colorStop; + } + return { + ...colorStop, + ...tinyColorRgbToGradientColorStop( rgbaColor ), + }; + } + ), + }; +} + +export function getGradientWithColorAtPositionChanged( parsedGradient, relativePositionValue, rgbaColor ) { + const index = findIndex( parsedGradient.colorStops, ( colorStop ) => { + return ( + colorStop && + colorStop.length && + colorStop.length.type === '%' && + colorStop.length.value === relativePositionValue.toString() + ); + } ); + return getGradientWithColorAtIndexChanged( parsedGradient, index, rgbaColor ); +} + +export function getGradientWithControlPointRemoved( parsedGradient, index ) { + return { + ...parsedGradient, + colorStops: parsedGradient.colorStops.filter( ( elem, elemIndex ) => { + return elemIndex !== index; + } ), + }; +} + +export function getHorizontalRelativeGradientPosition( mouseXCoordinate, containerElement, positionedElementWidth ) { + if ( ! containerElement ) { + return; + } + const { x, width } = containerElement.getBoundingClientRect(); + const absolutePositionValue = mouseXCoordinate - x - MINIMUM_ABSOLUTE_LEFT_POSITION - ( positionedElementWidth / 2 ); + const availableWidth = width - MINIMUM_ABSOLUTE_LEFT_POSITION - INSERT_POINT_WIDTH; + return Math.round( + Math.min( Math.max( ( absolutePositionValue * 100 ) / availableWidth, 0 ), 100 ) + ); +} diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index f809c7826121b9..02de89eda69143 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -67,6 +67,9 @@ class Dropdown extends Component { } close() { + if ( this.props.onClose ) { + this.props.onClose(); + } this.setState( { isOpen: false } ); } diff --git a/packages/components/src/gradient-picker/index.js b/packages/components/src/gradient-picker/index.js index e8ab40f07ada2b..1d4d5caf389065 100644 --- a/packages/components/src/gradient-picker/index.js +++ b/packages/components/src/gradient-picker/index.js @@ -13,6 +13,7 @@ import { useCallback, useMemo } from '@wordpress/element'; * Internal dependencies */ import CircularOptionPicker from '../circular-option-picker'; +import CustomGradientPicker from '../custom-gradient-picker'; export default function GradientPicker( { className, @@ -20,6 +21,7 @@ export default function GradientPicker( { onChange, value, clearable = true, + disableCustomGradients = false, } ) { const clearGradient = useCallback( () => onChange( undefined ), @@ -62,6 +64,11 @@ export default function GradientPicker( { { __( 'Clear' ) } ) } - /> + > + { ! disableCustomGradients && ( ) } + ); } diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 131eb6fc2120b1..02a2f62492ae2c 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -34,6 +34,7 @@ export { default as FormFileUpload } from './form-file-upload'; export { default as FormToggle } from './form-toggle'; export { default as FormTokenField } from './form-token-field'; export { default as __experimentalGradientPicker } from './gradient-picker'; +export { default as __experimentalCustomGradientPicker } from './custom-gradient-picker'; export { default as Guide } from './guide'; export { default as GuidePage } from './guide/page'; export { default as Icon } from './icon'; diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index f8b6fda6c54307..80ac1ac25c10e2 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -7,6 +7,7 @@ @import "./circular-option-picker/style.scss"; @import "./color-indicator/style.scss"; @import "./color-picker/style.scss"; +@import "./custom-gradient-picker/style.scss"; @import "./custom-select-control/style.scss"; @import "./dashicon/style.scss"; @import "./date-time/style.scss"; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 2ffbe229ee6f4a..1d3714e5c09885 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -92,6 +92,7 @@ class EditorProvider extends Component { 'colors', 'disableCustomColors', 'disableCustomFontSizes', + 'disableCustomGradients', 'focusMode', 'fontSizes', 'hasFixedToolbar', From 4a51675a920e7d1c3a662dce81fa57ed66fc3a31 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 3 Dec 2019 20:41:03 +0000 Subject: [PATCH 2/7] Refactor to state based --- .../custom-gradient-picker/control-points.js | 176 ++++------- .../src/custom-gradient-picker/index.js | 296 ++++++++++-------- .../src/custom-gradient-picker/serializer.js | 4 +- .../src/custom-gradient-picker/utils.js | 87 +++-- 4 files changed, 287 insertions(+), 276 deletions(-) diff --git a/packages/components/src/custom-gradient-picker/control-points.js b/packages/components/src/custom-gradient-picker/control-points.js index 75930b6b3e81e9..d8f99c249cb24f 100644 --- a/packages/components/src/custom-gradient-picker/control-points.js +++ b/packages/components/src/custom-gradient-picker/control-points.js @@ -2,13 +2,12 @@ /** * External dependencies */ -import { map } from 'lodash'; import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, useCallback, useRef, useMemo } from '@wordpress/element'; +import { Component, useRef } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { withInstanceId } from '@wordpress/compose'; @@ -18,7 +17,6 @@ import { withInstanceId } from '@wordpress/compose'; import Button from '../button'; import ColorPicker from '../color-picker'; import Dropdown from '../dropdown'; -import { serializeGradientColor, serializeGradientPosition } from './serializer'; import { getGradientWithColorAtIndexChanged, getGradientWithControlPointRemoved, @@ -35,45 +33,30 @@ import { } from './constants'; import KeyboardShortcuts from '../keyboard-shortcuts'; -export function useMarkerPoints( parsedGradient, maximumAbsolutePositionValue ) { - return useMemo( - () => { - if ( ! parsedGradient ) { - return []; - } - return map( parsedGradient.colorStops, ( colorStop ) => { - if ( ! colorStop || ! colorStop.length || colorStop.length.type !== '%' ) { - return null; - } - return { - color: serializeGradientColor( colorStop ), - position: serializeGradientPosition( colorStop.length ), - positionValue: parseInt( colorStop.length.value ), - }; - } ); - }, - [ parsedGradient, maximumAbsolutePositionValue ] - ); -} - class ControlPointKeyboardMove extends Component { constructor() { super( ...arguments ); this.increase = this.increase.bind( this ); this.decrease = this.decrease.bind( this ); this.shortcuts = { - right: () => this.increase(), - left: () => this.decrease(), + right: this.increase, + left: this.decrease, }; } - increase() { - const { gradientIndex, onChange, parsedGradient } = this.props; - onChange( getGradientWithPositionAtIndexIncreased( parsedGradient, gradientIndex ) ); + increase( event ) { + // Stop propagation of the key press event to avoid focus moving + // to another editor area. + event.stopPropagation(); + const { gradientIndex, onChange, gradientAST } = this.props; + onChange( getGradientWithPositionAtIndexIncreased( gradientAST, gradientIndex ) ); } - decrease() { - const { gradientIndex, onChange, parsedGradient } = this.props; - onChange( getGradientWithPositionAtIndexDecreased( parsedGradient, gradientIndex ) ); + decrease( event ) { + // Stop propagation of the key press event to avoid focus moving + // to another editor area. + event.stopPropagation(); + const { gradientIndex, onChange, gradientAST } = this.props; + onChange( getGradientWithPositionAtIndexDecreased( gradientAST, gradientIndex ) ); } render() { const { children } = this.props; @@ -93,7 +76,7 @@ const ControlPointButton = withInstanceId( color, onChange, gradientIndex, - parsedGradient, + gradientAST, ...additionalProps } ) { const descriptionId = `components-custom-gradient-picker__control-point-button-description-${ instanceId }`; @@ -101,7 +84,7 @@ const ControlPointButton = withInstanceId(
diff --git a/packages/components/src/custom-gradient-picker/utils.js b/packages/components/src/custom-gradient-picker/utils.js index 38c8cad5bc650a..0ea49d4b311e63 100644 --- a/packages/components/src/custom-gradient-picker/utils.js +++ b/packages/components/src/custom-gradient-picker/utils.js @@ -149,7 +149,7 @@ export function getHorizontalRelativeGradientPosition( mouseXCoordinate, contain } /** - * Get the connection state. + * Returns the marker points from a gradient AST. * * @param {Object} gradientAST An object representing the gradient AST. * From 7c6c5c01f54a4a8c190148a2bfc574e8740bbda2 Mon Sep 17 00:00:00 2001 From: Jorge Date: Thu, 5 Dec 2019 22:10:56 +0000 Subject: [PATCH 6/7] Clean up listeners on unmout --- .../custom-gradient-picker/control-points.js | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/components/src/custom-gradient-picker/control-points.js b/packages/components/src/custom-gradient-picker/control-points.js index c826057f848418..79c7e0017e3deb 100644 --- a/packages/components/src/custom-gradient-picker/control-points.js +++ b/packages/components/src/custom-gradient-picker/control-points.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, useRef } from '@wordpress/element'; +import { Component, useEffect, useRef } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { withInstanceId } from '@wordpress/compose'; @@ -137,7 +137,11 @@ export default function ControlPoints( { gradientPickerDomRef.current, GRADIENT_MARKERS_WIDTH, ); - const { gradientAST: referenceGradientAST, position, significantMoveHappened } = controlPointMoveState.current; + const { + gradientAST: referenceGradientAST, + position, + significantMoveHappened, + } = controlPointMoveState.current; if ( ! significantMoveHappened ) { const initialPosition = referenceGradientAST.colorStops[ position ].length.value; if ( Math.abs( initialPosition - relativePosition ) >= MINIMUM_SIGNIFICANT_MOVE ) { @@ -152,14 +156,24 @@ export default function ControlPoints( { } }; - const onMouseUp = () => { - if ( window && window.removeEventListener ) { + const cleanEventListeners = () => { + if ( + window && window.removeEventListener && + controlPointMoveState.current && controlPointMoveState.current.listenersActivated + ) { window.removeEventListener( 'mousemove', onMouseMove ); - window.removeEventListener( 'mouseup', onMouseUp ); + window.removeEventListener( 'mouseup', cleanEventListeners ); onStopControlPointChange(); + controlPointMoveState.current.listenersActivated = false; } }; + useEffect( () => { + return () => { + cleanEventListeners(); + }; + }, [] ); + return markerPoints.map( ( point, index ) => ( point && ignoreMarkerPosition !== point.positionValue && ( @@ -170,7 +184,10 @@ export default function ControlPoints( { { - if ( controlPointMoveState.current.significantMoveHappened ) { + if ( + controlPointMoveState.current && + controlPointMoveState.current.significantMoveHappened + ) { return; } onStartControlPointChange(); @@ -182,10 +199,11 @@ export default function ControlPoints( { gradientAST, position: index, significantMoveHappened: false, + listenersActivated: true, }; onStartControlPointChange(); window.addEventListener( 'mousemove', onMouseMove ); - window.addEventListener( 'mouseup', onMouseUp ); + window.addEventListener( 'mouseup', cleanEventListeners ); } } } isOpen={ isOpen } From dbb5f8b10b9131b5d9af87df12a72bc0adfb86ab Mon Sep 17 00:00:00 2001 From: Jorge Date: Thu, 5 Dec 2019 22:21:12 +0000 Subject: [PATCH 7/7] Revert include custom gradient picker on gradient picker component --- packages/components/src/gradient-picker/index.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/components/src/gradient-picker/index.js b/packages/components/src/gradient-picker/index.js index 1d4d5caf389065..e8ab40f07ada2b 100644 --- a/packages/components/src/gradient-picker/index.js +++ b/packages/components/src/gradient-picker/index.js @@ -13,7 +13,6 @@ import { useCallback, useMemo } from '@wordpress/element'; * Internal dependencies */ import CircularOptionPicker from '../circular-option-picker'; -import CustomGradientPicker from '../custom-gradient-picker'; export default function GradientPicker( { className, @@ -21,7 +20,6 @@ export default function GradientPicker( { onChange, value, clearable = true, - disableCustomGradients = false, } ) { const clearGradient = useCallback( () => onChange( undefined ), @@ -64,11 +62,6 @@ export default function GradientPicker( { { __( 'Clear' ) } ) } - > - { ! disableCustomGradients && ( ) } - + /> ); }