diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index 1db3cdb27938f3..40412dbe61b997 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -7,7 +7,7 @@ exports[`ColorPalette Dropdown .renderContent should render dropdown content 1`]
- - { + const { ownerDocument } = container.current; + + if ( ! mouseDown ) { + return; + } + + ownerDocument.addEventListener( 'mousemove', handleChange ); + ownerDocument.addEventListener( 'mouseup', handleMouseUp ); + + return () => { + ownerDocument.removeEventListener( 'mousemove', handleChange ); + ownerDocument.removeEventListener( 'mouseup', handleMouseUp ); + }; + }, [ mouseDown ] ); + + function increase( amount = 0.01 ) { amount = parseInt( amount * 100, 10 ); const change = { h: hsl.h, @@ -73,8 +99,7 @@ export class Alpha extends Component { onChange( change ); } - decrease( amount = 0.01 ) { - const { hsl, onChange = noop } = this.props; + function decrease( amount = 0.01 ) { const intValue = parseInt( hsl.a * 100, 10 ) - parseInt( amount * 100, 10 ); const change = { @@ -87,98 +112,53 @@ export class Alpha extends Component { onChange( change ); } - handleChange( e ) { - const { onChange = noop } = this.props; - const change = calculateAlphaChange( - e, - this.props, - this.container.current - ); - if ( change ) { - onChange( change, e ); - } - } - - handleMouseDown( e ) { - this.handleChange( e ); - window.addEventListener( 'mousemove', this.handleChange ); - window.addEventListener( 'mouseup', this.handleMouseUp ); - } - - handleMouseUp() { - this.unbindEventListeners(); - } - - preventKeyEvents( event ) { - if ( event.keyCode === TAB ) { - return; - } - event.preventDefault(); - } - - unbindEventListeners() { - window.removeEventListener( 'mousemove', this.handleChange ); - window.removeEventListener( 'mouseup', this.handleMouseUp ); - } - - render() { - const { rgb } = this.props; - const rgbString = `${ rgb.r },${ rgb.g },${ rgb.b }`; - const gradient = { - background: `linear-gradient(to right, rgba(${ rgbString }, 0) 0%, rgba(${ rgbString }, 1) 100%)`, - }; - const pointerLocation = { left: `${ rgb.a * 100 }%` }; - - const shortcuts = { - up: () => this.increase(), - right: () => this.increase(), - 'shift+up': () => this.increase( 0.1 ), - 'shift+right': () => this.increase( 0.1 ), - pageup: () => this.increase( 0.1 ), - end: () => this.increase( 1 ), - down: () => this.decrease(), - left: () => this.decrease(), - 'shift+down': () => this.decrease( 0.1 ), - 'shift+left': () => this.decrease( 0.1 ), - pagedown: () => this.decrease( 0.1 ), - home: () => this.decrease( 1 ), - }; - - return ( - -
+ const shortcuts = { + up: () => increase(), + right: () => increase(), + 'shift+up': () => increase( 0.1 ), + 'shift+right': () => increase( 0.1 ), + pageup: () => increase( 0.1 ), + end: () => increase( 1 ), + down: () => decrease(), + left: () => decrease(), + 'shift+down': () => decrease( 0.1 ), + 'shift+left': () => decrease( 0.1 ), + pagedown: () => decrease( 0.1 ), + home: () => decrease( 1 ), + }; + + return ( + +
+
+ { /* eslint-disable jsx-a11y/no-static-element-interactions */ } +
setMouseDown( true ) } + onTouchMove={ handleChange } + onTouchStart={ handleChange } + >
- { /* eslint-disable jsx-a11y/no-static-element-interactions */ } -
-
-
- { /* eslint-enable jsx-a11y/no-static-element-interactions */ }
- - ); - } + { /* eslint-enable jsx-a11y/no-static-element-interactions */ } +
+ + ); } - -export default pure( Alpha ); diff --git a/packages/components/src/color-picker/hue.js b/packages/components/src/color-picker/hue.js index 7261b729a29477..275737fb2c4091 100644 --- a/packages/components/src/color-picker/hue.js +++ b/packages/components/src/color-picker/hue.js @@ -33,9 +33,9 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { compose, pure, withInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { Component, createRef } from '@wordpress/element'; +import { useState, useEffect, useRef } from '@wordpress/element'; +import { useInstanceId } from '@wordpress/compose'; import { TAB } from '@wordpress/keycodes'; /** @@ -45,24 +45,49 @@ import { calculateHueChange } from './utils'; import KeyboardShortcuts from '../keyboard-shortcuts'; import VisuallyHidden from '../visually-hidden'; -export class Hue extends Component { - constructor() { - super( ...arguments ); +export default function Hue( { hsl, onChange = noop } ) { + const [ mouseDown, setMouseDown ] = useState( false ); + const container = useRef(); - this.container = createRef(); - this.increase = this.increase.bind( this ); - this.decrease = this.decrease.bind( this ); - this.handleChange = this.handleChange.bind( this ); - this.handleMouseDown = this.handleMouseDown.bind( this ); - this.handleMouseUp = this.handleMouseUp.bind( this ); + const instanceId = useInstanceId( Hue ); + + const pointerLocation = { left: `${ ( hsl.h * 100 ) / 360 }%` }; + + function handleChange( e ) { + const change = calculateHueChange( e, { hsl }, container.current ); + if ( change ) { + onChange( change, e ); + } } - componentWillUnmount() { - this.unbindEventListeners(); + function handleMouseUp() { + setMouseDown( false ); + } + + function preventKeyEvents( event ) { + if ( event.keyCode === TAB ) { + return; + } + event.preventDefault(); } - increase( amount = 1 ) { - const { hsl, onChange = noop } = this.props; + useEffect( () => { + const { ownerDocument } = container.current; + + if ( ! mouseDown ) { + return; + } + + ownerDocument.addEventListener( 'mousemove', handleChange ); + ownerDocument.addEventListener( 'mouseup', handleMouseUp ); + + return () => { + ownerDocument.removeEventListener( 'mousemove', handleChange ); + ownerDocument.removeEventListener( 'mouseup', handleMouseUp ); + }; + }, [ mouseDown ] ); + + function increase( amount = 1 ) { const change = { h: hsl.h + amount >= 359 ? 359 : hsl.h + amount, s: hsl.s, @@ -73,8 +98,7 @@ export class Hue extends Component { onChange( change ); } - decrease( amount = 1 ) { - const { hsl, onChange = noop } = this.props; + function decrease( amount = 1 ) { const change = { h: hsl.h <= amount ? 0 : hsl.h - amount, s: hsl.s, @@ -85,100 +109,56 @@ export class Hue extends Component { onChange( change ); } - handleChange( e ) { - const { onChange = noop } = this.props; - const change = calculateHueChange( - e, - this.props, - this.container.current - ); - if ( change ) { - onChange( change, e ); - } - } - - handleMouseDown( e ) { - this.handleChange( e ); - window.addEventListener( 'mousemove', this.handleChange ); - window.addEventListener( 'mouseup', this.handleMouseUp ); - } - - handleMouseUp() { - this.unbindEventListeners(); - } - - preventKeyEvents( event ) { - if ( event.keyCode === TAB ) { - return; - } - event.preventDefault(); - } - - unbindEventListeners() { - window.removeEventListener( 'mousemove', this.handleChange ); - window.removeEventListener( 'mouseup', this.handleMouseUp ); - } - - render() { - const { hsl = {}, instanceId } = this.props; - - const pointerLocation = { left: `${ ( hsl.h * 100 ) / 360 }%` }; - const shortcuts = { - up: () => this.increase(), - right: () => this.increase(), - 'shift+up': () => this.increase( 10 ), - 'shift+right': () => this.increase( 10 ), - pageup: () => this.increase( 10 ), - end: () => this.increase( 359 ), - down: () => this.decrease(), - left: () => this.decrease(), - 'shift+down': () => this.decrease( 10 ), - 'shift+left': () => this.decrease( 10 ), - pagedown: () => this.decrease( 10 ), - home: () => this.decrease( 359 ), - }; - - return ( - -
-
- { /* eslint-disable jsx-a11y/no-static-element-interactions */ } + const shortcuts = { + up: () => increase(), + right: () => increase(), + 'shift+up': () => increase( 10 ), + 'shift+right': () => increase( 10 ), + pageup: () => increase( 10 ), + end: () => increase( 359 ), + down: () => decrease(), + left: () => decrease(), + 'shift+down': () => decrease( 10 ), + 'shift+left': () => decrease( 10 ), + pagedown: () => decrease( 10 ), + home: () => decrease( 359 ), + }; + return ( + +
+
+ { /* eslint-disable jsx-a11y/no-static-element-interactions */ } +
setMouseDown( true ) } + onTouchMove={ handleChange } + onTouchStart={ handleChange } + >
+ -
- - { __( - 'Move the arrow left or right to change hue.' - ) } - -
- { /* eslint-enable jsx-a11y/no-static-element-interactions */ } + { __( 'Move the arrow left or right to change hue.' ) } +
- - ); - } + { /* eslint-enable jsx-a11y/no-static-element-interactions */ } +
+ + ); } - -export default compose( pure, withInstanceId )( Hue ); diff --git a/packages/components/src/color-picker/index.js b/packages/components/src/color-picker/index.js index 957a4e578e1f59..1aca3d1037ef58 100644 --- a/packages/components/src/color-picker/index.js +++ b/packages/components/src/color-picker/index.js @@ -29,13 +29,13 @@ * External dependencies */ import classnames from 'classnames'; -import { debounce, noop, partial } from 'lodash'; +import { noop } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; - +import { useState, useCallback } from '@wordpress/element'; +import { useDebounce } from '@wordpress/compose'; /** * Internal dependencies */ @@ -45,8 +45,11 @@ import Inputs from './inputs'; import Saturation from './saturation'; import { colorToState, simpleCheckForValidColor, isValidHex } from './utils'; -const toLowerCase = ( value ) => String( value ).toLowerCase(); -const isValueEmpty = ( data ) => { +function toLowerCase( value ) { + return String( value ).toLowerCase(); +} + +function isValueEmpty( data ) { if ( data.source === 'hex' && ! data.hex ) { return true; } else if ( @@ -63,9 +66,13 @@ const isValueEmpty = ( data ) => { return true; } return false; -}; -const isValidColor = ( colors ) => - colors.hex ? isValidHex( colors.hex ) : simpleCheckForValidColor( colors ); +} + +function isValidColor( colors ) { + return colors.hex + ? isValidHex( colors.hex ) + : simpleCheckForValidColor( colors ); +} /** * Function that creates the new color object @@ -95,7 +102,7 @@ const isValidColor = ( colors ) => * @return {Object} A new color object for a specific source. For example: * { source: 'rgb', r: 1, g: 2, b:3, a:0 } */ -const dataToColors = ( oldColors, { source, valueKey, value } ) => { +function dataToColors( oldColors, { source, valueKey, value } ) { if ( source === 'hex' ) { return { source, @@ -106,140 +113,137 @@ const dataToColors = ( oldColors, { source, valueKey, value } ) => { source, ...{ ...oldColors[ source ], ...{ [ valueKey ]: value } }, }; -}; - -export default class ColorPicker extends Component { - constructor( { color = '0071a1' } ) { - super( ...arguments ); - const colors = colorToState( color ); - this.state = { - ...colors, - draftHex: toLowerCase( colors.hex ), - draftRgb: colors.rgb, - draftHsl: colors.hsl, - }; - this.commitValues = this.commitValues.bind( this ); - this.setDraftValues = this.setDraftValues.bind( this ); - this.resetDraftValues = this.resetDraftValues.bind( this ); - this.handleInputChange = this.handleInputChange.bind( this ); - } - - commitValues( data ) { - const { oldHue, onChangeComplete = noop } = this.props; +} +export default function ColorPicker( { + color = '0071a1', + className, + disableAlpha, + oldHue, + onChangeComplete = noop, +} ) { + const initialColor = colorToState( color ); + const [ colors, setColors ] = useState( { + ...initialColor, + draftHex: toLowerCase( initialColor.hex ), + draftRgb: initialColor.rgb, + draftHsl: initialColor.hsl, + } ); + + const debouncedOnChangeComplete = useCallback( + useDebounce( onChangeComplete, 100 ), + [ onChangeComplete ] + ); + + const { hex, hsl, hsv, rgb, draftHex, draftHsl, draftRgb } = colors; + + const classes = classnames( className, { + 'components-color-picker': true, + 'is-alpha-disabled': disableAlpha, + 'is-alpha-enabled': ! disableAlpha, + } ); + + function commitValues( data ) { if ( isValidColor( data ) ) { - const colors = colorToState( data, data.h || oldHue ); - this.setState( - { - ...colors, - draftHex: toLowerCase( colors.hex ), - draftHsl: colors.hsl, - draftRgb: colors.rgb, - }, - debounce( partial( onChangeComplete, colors ), 100 ) - ); + const newColors = colorToState( data, data.h || oldHue ); + setColors( { + ...newColors, + draftHex: toLowerCase( newColors.hex ), + draftHsl: newColors.hsl, + draftRgb: newColors.rgb, + } ); + + debouncedOnChangeComplete( newColors ); } } - resetDraftValues() { - this.setState( { - draftHex: this.state.hex, - draftHsl: this.state.hsl, - draftRgb: this.state.rgb, + function resetDraftValues() { + setColors( { + ...colors, + draftHex: hex, + draftHsl: hsl, + draftRgb: rgb, } ); } - setDraftValues( data ) { + function setDraftValues( data ) { switch ( data.source ) { case 'hex': - this.setState( { draftHex: toLowerCase( data.hex ) } ); + setColors( { + ...colors, + draftHex: toLowerCase( data.hex ), + } ); break; case 'rgb': - this.setState( { draftRgb: data } ); + setColors( { + ...colors, + draftRgb: data, + } ); break; case 'hsl': - this.setState( { draftHsl: data } ); + setColors( { + ...colors, + draftHsl: data, + } ); break; } } - handleInputChange( data ) { + function handleInputChange( data ) { switch ( data.state ) { case 'reset': - this.resetDraftValues(); + resetDraftValues(); break; case 'commit': - const colors = dataToColors( this.state, data ); - if ( ! isValueEmpty( colors ) ) { - this.commitValues( colors ); + const newColors = dataToColors( colors, data ); + if ( ! isValueEmpty( newColors ) ) { + commitValues( newColors ); } break; case 'draft': - this.setDraftValues( dataToColors( this.state, data ) ); + setDraftValues( dataToColors( colors, data ) ); break; } } - render() { - const { className, disableAlpha } = this.props; - const { - color, - hsl, - hsv, - rgb, - draftHex, - draftHsl, - draftRgb, - } = this.state; - const classes = classnames( className, { - 'components-color-picker': true, - 'is-alpha-disabled': disableAlpha, - 'is-alpha-enabled': ! disableAlpha, - } ); + return ( +
+
+ +
- return ( -
-
- -
+
+
+
+
+
-
-
-
-
+ + { disableAlpha ? null : ( + -
- -
- - { disableAlpha ? null : ( - - ) } -
+ ) }
- -
+ +
- ); - } +
+ ); } diff --git a/packages/components/src/color-picker/inputs.js b/packages/components/src/color-picker/inputs.js index aa8d9ff7900367..578a9383716484 100644 --- a/packages/components/src/color-picker/inputs.js +++ b/packages/components/src/color-picker/inputs.js @@ -1,16 +1,10 @@ -/** - * External dependencies - */ -import { omit } from 'lodash'; - /** * WordPress dependencies */ import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { DOWN, ENTER, UP } from '@wordpress/keycodes'; -import { pure } from '@wordpress/compose'; import { chevronDown } from '@wordpress/icons'; /** @@ -22,16 +16,16 @@ import VisuallyHidden from '../visually-hidden'; import { isValidHex } from './utils'; /* Wrapper for TextControl, only used to handle intermediate state while typing. */ -export class Input extends Component { - constructor() { - super( ...arguments ); - this.handleBlur = this.handleBlur.bind( this ); - this.handleChange = this.handleChange.bind( this ); - this.handleKeyDown = this.handleKeyDown.bind( this ); - } - handleBlur() { - const { value, valueKey, onChange, source } = this.props; +export function Input( { + label, + value, + valueKey, + onChange, + source, + ...props +} ) { + function handleBlur() { onChange( { source, state: 'commit', @@ -40,30 +34,28 @@ export class Input extends Component { } ); } - handleChange( value ) { - const { valueKey, onChange, source } = this.props; - if ( value.length > 4 && isValidHex( value ) ) { + function handleChange( newValue ) { + if ( newValue.length > 4 && isValidHex( newValue ) ) { onChange( { source, state: 'commit', - value, + value: newValue, valueKey, } ); } else { onChange( { source, state: 'draft', - value, + value: newValue, valueKey, } ); } } - handleKeyDown( { keyCode } ) { + function handleKeyDown( { keyCode } ) { if ( keyCode !== ENTER && keyCode !== UP && keyCode !== DOWN ) { return; } - const { value, valueKey, onChange, source } = this.props; onChange( { source, state: 'commit', @@ -72,234 +64,221 @@ export class Input extends Component { } ); } - render() { - const { label, value, ...props } = this.props; - return ( - this.handleChange( newValue ) } - onBlur={ this.handleBlur } - onKeyDown={ this.handleKeyDown } - { ...omit( props, [ 'onChange', 'valueKey', 'source' ] ) } - /> - ); - } + return ( + handleChange( newValue ) } + onBlur={ handleBlur } + onKeyDown={ handleKeyDown } + { ...props } + /> + ); } -const PureButton = pure( Button ); - -export class Inputs extends Component { - constructor( { hsl } ) { - super( ...arguments ); - - const view = hsl.a === 1 ? 'hex' : 'rgb'; - this.state = { view }; +function normalizeValue( valueKey, value ) { + if ( valueKey !== 'a' ) { + return value; + } - this.toggleViews = this.toggleViews.bind( this ); - this.resetDraftValues = this.resetDraftValues.bind( this ); - this.handleChange = this.handleChange.bind( this ); - this.normalizeValue = this.normalizeValue.bind( this ); + if ( value < 0 ) { + return 0; + } else if ( value > 1 ) { + return 1; } + return Math.round( value * 100 ) / 100; +} - static getDerivedStateFromProps( props, state ) { - if ( props.hsl.a !== 1 && state.view === 'hex' ) { - return { view: 'rgb' }; - } - return null; +export default function Inputs( { + hex, + rgb, + hsl, + onChange, + disableAlpha = false, +} ) { + const [ view, setView ] = useState( hsl.a === 1 ? 'hex' : 'rgb' ); + + if ( hsl.a !== 1 && view === 'hex' ) { + setView( 'rgb' ); } - toggleViews() { - if ( this.state.view === 'hex' ) { - this.setState( { view: 'rgb' }, this.resetDraftValues ); + function toggleViews() { + if ( view === 'hex' ) { + setView( 'rgb' ); + resetDraftValues(); speak( __( 'RGB mode active' ) ); - } else if ( this.state.view === 'rgb' ) { - this.setState( { view: 'hsl' }, this.resetDraftValues ); + } else if ( view === 'rgb' ) { + setView( 'hsl' ); + resetDraftValues(); speak( __( 'Hue/saturation/lightness mode active' ) ); - } else if ( this.state.view === 'hsl' ) { - if ( this.props.hsl.a === 1 ) { - this.setState( { view: 'hex' }, this.resetDraftValues ); + } else if ( view === 'hsl' ) { + if ( hsl.a === 1 ) { + setView( 'hex' ); + resetDraftValues(); speak( __( 'Hex color mode active' ) ); } else { - this.setState( { view: 'rgb' }, this.resetDraftValues ); + setView( 'rgb' ); + resetDraftValues(); speak( __( 'RGB mode active' ) ); } } } - resetDraftValues() { - return this.props.onChange( { + function resetDraftValues() { + return onChange( { state: 'reset', } ); } - normalizeValue( valueKey, value ) { - if ( valueKey !== 'a' ) { - return value; - } - - if ( value < 0 ) { - return 0; - } else if ( value > 1 ) { - return 1; - } - return Math.round( value * 100 ) / 100; - } - - handleChange( { source, state, value, valueKey } ) { - this.props.onChange( { + function handleChange( { source, state, value, valueKey } ) { + onChange( { source, state, valueKey, - value: this.normalizeValue( valueKey, value ), + value: normalizeValue( valueKey, value ), } ); } - renderFields() { - const { disableAlpha = false } = this.props; - if ( this.state.view === 'hex' ) { - return ( + let fields; + + if ( view === 'hex' ) { + fields = ( +
+ +
+ ); + } else if ( view === 'rgb' ) { + const legend = disableAlpha + ? __( 'Color value in RGB' ) + : __( 'Color value in RGBA' ); + fields = ( +
+ { legend }
-
- ); - } else if ( this.state.view === 'rgb' ) { - const legend = disableAlpha - ? __( 'Color value in RGB' ) - : __( 'Color value in RGBA' ); - return ( -
- { legend } -
- - - - { disableAlpha ? null : ( - - ) } -
-
- ); - } else if ( this.state.view === 'hsl' ) { - const legend = disableAlpha - ? __( 'Color value in HSL' ) - : __( 'Color value in HSLA' ); - return ( -
- { legend } -
- + + + { disableAlpha ? null : ( + ) } +
+
+ ); + } else if ( view === 'hsl' ) { + const legend = disableAlpha + ? __( 'Color value in HSL' ) + : __( 'Color value in HSLA' ); + fields = ( +
+ { legend } +
+ + + + { disableAlpha ? null : ( - { disableAlpha ? null : ( - - ) } -
-
- ); - } - } - - render() { - return ( -
- { this.renderFields() } -
- + ) }
-
+
); } -} -export default Inputs; + return ( +
+ { fields } +
+
+
+ ); +} diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index 3b9666d734588f..3843b4b4c18dab 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -28,15 +28,15 @@ /** * External dependencies */ -import { clamp, noop, throttle } from 'lodash'; +import { clamp, noop } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, createRef } from '@wordpress/element'; +import { useState, useEffect, useRef, useCallback } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; -import { compose, pure, withInstanceId } from '@wordpress/compose'; +import { useThrottle, useInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -46,29 +46,21 @@ import Button from '../button'; import KeyboardShortcuts from '../keyboard-shortcuts'; import VisuallyHidden from '../visually-hidden'; -export class Saturation extends Component { - constructor( props ) { - super( props ); +export default function Saturation( { hsv, hsl, onChange = noop } ) { + const container = useRef(); + const instanceId = useInstanceId( Saturation ); + const [ mouseDown, setMouseDown ] = useState( false ); - this.throttle = throttle( ( fn, data, e ) => { - fn( data, e ); - }, 50 ); + const pointerLocation = { + top: `${ -hsv.v + 100 }%`, + left: `${ hsv.s }%`, + }; - this.container = createRef(); - this.saturate = this.saturate.bind( this ); - this.brighten = this.brighten.bind( this ); - this.handleChange = this.handleChange.bind( this ); - this.handleMouseDown = this.handleMouseDown.bind( this ); - this.handleMouseUp = this.handleMouseUp.bind( this ); - } - - componentWillUnmount() { - this.throttle.cancel(); - this.unbindEventListeners(); - } + const throttledOnChange = useCallback( useThrottle( onChange, 50 ), [ + onChange, + ] ); - saturate( amount = 0.01 ) { - const { hsv, onChange = noop } = this.props; + function saturate( amount = 0.01 ) { const intSaturation = clamp( hsv.s + Math.round( amount * 100 ), 0, @@ -85,8 +77,7 @@ export class Saturation extends Component { onChange( change ); } - brighten( amount = 0.01 ) { - const { hsv, onChange = noop } = this.props; + function brighten( amount = 0.01 ) { const intValue = clamp( hsv.v + Math.round( amount * 100 ), 0, 100 ); const change = { h: hsv.h, @@ -99,92 +90,87 @@ export class Saturation extends Component { onChange( change ); } - handleChange( e ) { - const { onChange = noop } = this.props; + function handleChange( e ) { const change = calculateSaturationChange( e, - this.props, - this.container.current + { hsl }, + container.current ); - this.throttle( onChange, change, e ); + throttledOnChange( change, e ); } - handleMouseDown( e ) { - this.handleChange( e ); - window.addEventListener( 'mousemove', this.handleChange ); - window.addEventListener( 'mouseup', this.handleMouseUp ); + function handleMouseUp() { + setMouseDown( false ); } - handleMouseUp() { - this.unbindEventListeners(); + function preventKeyEvents( e ) { + if ( e.keyCode === TAB ) { + return; + } + e.preventDefault(); } - preventKeyEvents( event ) { - if ( event.keyCode === TAB ) { + useEffect( () => { + const { ownerDocument } = container.current; + + if ( ! mouseDown ) { return; } - event.preventDefault(); - } - unbindEventListeners() { - window.removeEventListener( 'mousemove', this.handleChange ); - window.removeEventListener( 'mouseup', this.handleMouseUp ); - } + ownerDocument.addEventListener( 'mousemove', handleChange ); + ownerDocument.addEventListener( 'mouseup', handleMouseUp ); - render() { - const { hsv, hsl, instanceId } = this.props; - const pointerLocation = { - top: `${ -hsv.v + 100 }%`, - left: `${ hsv.s }%`, - }; - const shortcuts = { - up: () => this.brighten(), - 'shift+up': () => this.brighten( 0.1 ), - pageup: () => this.brighten( 1 ), - down: () => this.brighten( -0.01 ), - 'shift+down': () => this.brighten( -0.1 ), - pagedown: () => this.brighten( -1 ), - right: () => this.saturate(), - 'shift+right': () => this.saturate( 0.1 ), - end: () => this.saturate( 1 ), - left: () => this.saturate( -0.01 ), - 'shift+left': () => this.saturate( -0.1 ), - home: () => this.saturate( -1 ), + return () => { + ownerDocument.removeEventListener( 'mousemove', handleChange ); + ownerDocument.removeEventListener( 'mouseup', handleMouseUp ); }; - - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - return ( - -
brighten(), + 'shift+up': () => brighten( 0.1 ), + pageup: () => brighten( 1 ), + down: () => brighten( -0.01 ), + 'shift+down': () => brighten( -0.1 ), + pagedown: () => brighten( -1 ), + right: () => saturate(), + 'shift+right': () => saturate( 0.1 ), + end: () => saturate( 1 ), + left: () => saturate( -0.01 ), + 'shift+left': () => saturate( -0.1 ), + home: () => saturate( -1 ), + }; + + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ + return ( + +
setMouseDown( true ) } + onTouchMove={ handleChange } + onTouchStart={ handleChange } + role="application" + > +
+
+
- - ); - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ - } + { __( + 'Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation.' + ) } + +
+ + ); + /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } - -export default compose( pure, withInstanceId )( Saturation ); diff --git a/packages/components/src/color-picker/test/index.js b/packages/components/src/color-picker/test/index.js index 11244c8a27e67a..866f1f73634d46 100644 --- a/packages/components/src/color-picker/test/index.js +++ b/packages/components/src/color-picker/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import TestRenderer from 'react-test-renderer'; +import { create, act } from 'react-test-renderer'; /** * WordPress dependencies @@ -13,106 +13,183 @@ import { DOWN, ENTER, UP } from '@wordpress/keycodes'; */ import ColorPicker from '../'; +function createNodeMock( element ) { + if ( + element.type === 'div' && + [ + 'components-color-picker__alpha-bar', + 'components-color-picker__hue-bar', + 'components-color-picker__saturation-color', + ].includes( element.props.className ) + ) { + return { + ownerDocument: document, + }; + } + + return null; +} + describe( 'ColorPicker', () => { - const color = '#FFF'; - let base; - - beforeEach( () => { - base = TestRenderer.create( - {} } - disableAlpha - /> - ); - } ); + test( 'should render color picker', () => { + const color = '#FFF'; + let renderer; + + act( () => { + renderer = create( + {} } + disableAlpha + />, + { createNodeMock } + ); + } ); + }); test( 'should render color picker', () => { expect( base.toJSON() ).toMatchSnapshot(); } ); test( 'should only update input view for draft changes', () => { - const testRenderer = TestRenderer.create( - {} } - disableAlpha - /> - ); - testRenderer.root - .findByType( 'input' ) - .props.onChange( { target: { value: '#ABC' } } ); - - expect( testRenderer.toJSON() ).toMatchDiffSnapshot( base.toJSON() ); + const color = '#FFF'; + let testRenderer; + + act( () => { + testRenderer = create( + {} } + disableAlpha + />, + { createNodeMock } + ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + } ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); test( 'should commit changes to all views on blur', () => { - const testRenderer = TestRenderer.create( - {} } - disableAlpha - /> - ); - testRenderer.root - .findByType( 'input' ) - .props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root.findByType( 'input' ).props.onBlur(); - - expect( testRenderer.toJSON() ).toMatchDiffSnapshot( base.toJSON() ); + const color = '#FFF'; + let testRenderer; + + act( () => { + testRenderer = create( + {} } + disableAlpha + />, + { createNodeMock } + ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + } ); + + act( () => { + testRenderer.root.findByType( 'input' ).props.onBlur(); + } ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); test( 'should commit changes to all views on keyDown = UP', () => { - const testRenderer = TestRenderer.create( - {} } - disableAlpha - /> - ); - testRenderer.root - .findByType( 'input' ) - .props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root - .findByType( 'input' ) - .props.onKeyDown( { keyCode: UP } ); - - expect( testRenderer.toJSON() ).toMatchDiffSnapshot( base.toJSON() ); + const color = '#FFF'; + let testRenderer; + + act( () => { + testRenderer = create( + {} } + disableAlpha + />, + { createNodeMock } + ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onKeyDown( { keyCode: UP } ); + } ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); test( 'should commit changes to all views on keyDown = DOWN', () => { - const testRenderer = TestRenderer.create( - {} } - disableAlpha - /> - ); - testRenderer.root - .findByType( 'input' ) - .props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root - .findByType( 'input' ) - .props.onKeyDown( { keyCode: DOWN } ); - - expect( testRenderer.toJSON() ).toMatchDiffSnapshot( base.toJSON() ); + const color = '#FFF'; + let testRenderer; + + act( () => { + testRenderer = create( + {} } + disableAlpha + />, + { createNodeMock } + ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onKeyDown( { keyCode: DOWN } ); + } ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); test( 'should commit changes to all views on keyDown = ENTER', () => { - const testRenderer = TestRenderer.create( - {} } - disableAlpha - /> - ); - testRenderer.root - .findByType( 'input' ) - .props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root - .findByType( 'input' ) - .props.onKeyDown( { keyCode: ENTER } ); - - expect( testRenderer.toJSON() ).toMatchDiffSnapshot( base.toJSON() ); + const color = '#FFF'; + let testRenderer; + + act( () => { + testRenderer = create( + {} } + disableAlpha + />, + { createNodeMock } + ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + } ); + + act( () => { + testRenderer.root + .findByType( 'input' ) + .props.onKeyDown( { keyCode: ENTER } ); + } ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); } ); diff --git a/packages/compose/README.md b/packages/compose/README.md index a5d36e2bae5ee8..d38b7a9b7546ea 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -391,7 +391,7 @@ _Returns_ # **useThrottle** -Throttles a function with Lodash's `throttle`. A new throttled function will +Throttle a function with Lodash's `throttle`. A new throttled function will be returned and any scheduled calls cancelled if any of the arguments change, including the function to throttle, so please wrap functions created on render in components in `useCallback`. @@ -402,7 +402,7 @@ _Parameters_ _Returns_ -- `Function`: Throttled function. +- `Function`: returns the throttled function # **useViewportMatch** diff --git a/packages/compose/src/hooks/use-throttle/index.js b/packages/compose/src/hooks/use-throttle/index.js index e6436424d71212..5ce2d1d04b8eb7 100644 --- a/packages/compose/src/hooks/use-throttle/index.js +++ b/packages/compose/src/hooks/use-throttle/index.js @@ -10,14 +10,14 @@ import { useMemoOne } from 'use-memo-one'; import { useEffect } from '@wordpress/element'; /** - * Throttles a function with Lodash's `throttle`. A new throttled function will + * Throttle a function with Lodash's `throttle`. A new throttled function will * be returned and any scheduled calls cancelled if any of the arguments change, * including the function to throttle, so please wrap functions created on * render in components in `useCallback`. * * @param {...any} args Arguments passed to Lodash's `throttle`. * - * @return {Function} Throttled function. + * @return {Function} returns the throttled function */ export default function useThrottle( ...args ) { const throttled = useMemoOne( () => throttle( ...args ), args ); diff --git a/packages/compose/src/index.native.js b/packages/compose/src/index.native.js index 2471f9d27a45b8..a954f064f748d5 100644 --- a/packages/compose/src/index.native.js +++ b/packages/compose/src/index.native.js @@ -29,3 +29,4 @@ export { default as usePreferredColorSchemeStyle } from './hooks/use-preferred-c export { default as useResizeObserver } from './hooks/use-resize-observer'; export { default as useDebounce } from './hooks/use-debounce'; export { default as useMergeRefs } from './hooks/use-merge-refs'; +export { default as useThrottle } from './hooks/use-throttle';