From 5bc76b4c6c9a88db961c4123e50d0417d08c5d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Mon, 28 Sep 2020 19:43:57 +0300 Subject: [PATCH 01/61] RichText: simplify withFormatTypes as hook (#23145) * RichText: change withFormatTypes to hook * Try rewrite * fix dispatch * Simplify --- packages/data/src/index.js | 5 +- packages/rich-text/src/component/index.js | 143 ++++++++--------- .../src/component/use-format-types.js | 102 ++++++++++++ .../src/component/with-format-types.js | 150 ------------------ 4 files changed, 167 insertions(+), 233 deletions(-) create mode 100644 packages/rich-text/src/component/use-format-types.js delete mode 100644 packages/rich-text/src/component/with-format-types.js diff --git a/packages/data/src/index.js b/packages/data/src/index.js index b4f2f71e6319bd..b0f1156db81d94 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -18,10 +18,7 @@ export { useRegistry, } from './components/registry-provider'; export { default as useSelect } from './components/use-select'; -export { - useDispatch, - useDispatchWithMap as __unstableUseDispatchWithMap, -} from './components/use-dispatch'; +export { useDispatch } from './components/use-dispatch'; export { AsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { createRegistrySelector, createRegistryControl } from './factory'; diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 57e8a794cb8143..816c2d167d3cf6 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { find, isNil, pickBy, startsWith } from 'lodash'; +import { find, isNil } from 'lodash'; /** * WordPress dependencies @@ -42,7 +42,7 @@ import { getActiveFormats } from '../get-active-formats'; import { updateFormats } from '../update-formats'; import { removeLineSeparator } from '../remove-line-separator'; import { isEmptyLine } from '../is-empty'; -import withFormatTypes from './with-format-types'; +import { useFormatTypes } from './use-format-types'; import { useBoundaryStyle } from './use-boundary-style'; import { useInlineWarning } from './use-inline-warning'; import { insert } from '../insert'; @@ -95,19 +95,12 @@ const defaultStyle = { whiteSpace }; const EMPTY_ACTIVE_FORMATS = []; -function createPrepareEditableTree( props, prefix ) { - const fns = Object.keys( props ).reduce( ( accumulator, key ) => { - if ( key.startsWith( prefix ) ) { - accumulator.push( props[ key ] ); - } - - return accumulator; - }, [] ); - +function createPrepareEditableTree( fns ) { return ( value ) => - fns.reduce( ( accumulator, fn ) => { - return fn( accumulator, value.text ); - }, value.formats ); + fns.reduce( + ( accumulator, fn ) => fn( accumulator, value.text ), + value.formats + ); } /** @@ -137,46 +130,59 @@ function fixPlaceholderSelection( defaultView ) { selection.collapseToStart(); } -function RichText( { - tagName: TagName = 'div', - value = '', - selectionStart, - selectionEnd, - children, - allowedFormats, - withoutInteractiveFormatting, - formatTypes, - style, - className, - placeholder, - disabled, - preserveWhiteSpace, - onPaste, - format = 'string', - onDelete, - onEnter, - onSelectionChange, - onChange, - unstableOnFocus: onFocus, - setFocusedElement, - instanceId, - __unstableMultilineTag: multilineTag, - __unstableMultilineRootTag: multilineRootTag, - __unstableDisableFormats: disableFormats, - __unstableDidAutomaticChange: didAutomaticChange, - __unstableInputRule: inputRule, - __unstableMarkAutomaticChange: markAutomaticChange, - __unstableAllowPrefixTransformations: allowPrefixTransformations, - __unstableUndo: undo, - __unstableIsCaretWithinFormattedText: isCaretWithinFormattedText, - __unstableOnEnterFormattedText: onEnterFormattedText, - __unstableOnExitFormattedText: onExitFormattedText, - __unstableOnCreateUndoLevel: onCreateUndoLevel, - __unstableIsSelected: isSelected, - forwardedRef: ref, - ...remainingProps -} ) { +function RichText( + { + tagName: TagName = 'div', + value = '', + selectionStart, + selectionEnd, + children, + allowedFormats, + withoutInteractiveFormatting, + style, + className, + placeholder, + disabled, + preserveWhiteSpace, + onPaste, + format = 'string', + onDelete, + onEnter, + onSelectionChange, + onChange, + unstableOnFocus: onFocus, + setFocusedElement, + instanceId, + clientId, + identifier, + __unstableMultilineTag: multilineTag, + __unstableMultilineRootTag: multilineRootTag, + __unstableDisableFormats: disableFormats, + __unstableDidAutomaticChange: didAutomaticChange, + __unstableInputRule: inputRule, + __unstableMarkAutomaticChange: markAutomaticChange, + __unstableAllowPrefixTransformations: allowPrefixTransformations, + __unstableUndo: undo, + __unstableIsCaretWithinFormattedText: isCaretWithinFormattedText, + __unstableOnEnterFormattedText: onEnterFormattedText, + __unstableOnExitFormattedText: onExitFormattedText, + __unstableOnCreateUndoLevel: onCreateUndoLevel, + __unstableIsSelected: isSelected, + ...remainingProps + }, + ref +) { const [ activeFormats = [], setActiveFormats ] = useState(); + const { + formatTypes, + prepareHandlers, + valueHandlers, + changeHandlers, + dependencies, + } = useFormatTypes( { + clientId, + identifier, + } ); // For backward compatibility, fall back to tagName if it's a string. // tagName can now be a component for light blocks. @@ -212,10 +218,7 @@ function RichText( { return string; } - const prepare = createPrepareEditableTree( - remainingProps, - 'format_value_functions' - ); + const prepare = createPrepareEditableTree( valueHandlers ); const result = create( { html: string, @@ -306,10 +309,7 @@ function RichText( { multilineTag, multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, - prepareEditableTree: createPrepareEditableTree( - remainingProps, - 'format_prepare_functions' - ), + prepareEditableTree: createPrepareEditableTree( prepareHandlers ), __unstableDomOnly: domOnly, placeholder, } ); @@ -913,9 +913,6 @@ function RichText( { applyRecord( newRecord ); const { start, end, activeFormats: newActiveFormats = [] } = newRecord; - const changeHandlers = pickBy( remainingProps, ( v, key ) => - key.startsWith( 'format_on_change_functions_' ) - ); Object.values( changeHandlers ).forEach( ( changeHandler ) => { changeHandler( newRecord.formats, newRecord.text ); @@ -1075,15 +1072,11 @@ function RichText( { } }, [ selectionStart, selectionEnd, isSelected ] ); - const prefix = 'format_prepare_props_'; - const predicate = ( v, key ) => key.startsWith( prefix ); - const prepareProps = pickBy( remainingProps, predicate ); - useEffect( () => { if ( didMount.current ) { applyFromProps(); } - }, Object.values( prepareProps ) ); + }, dependencies ); useLayoutEffect( () => { applyRecord( record.current, { domOnly: true } ); @@ -1105,16 +1098,12 @@ function RichText( { applyRecord( record.current ); } - const ariaProps = pickBy( remainingProps, ( v, key ) => - startsWith( key, 'aria-' ) - ); - const editableProps = { // Overridable props. role: 'textbox', 'aria-multiline': true, 'aria-label': placeholder, - ...ariaProps, + ...remainingProps, ref, style: style ? { ...style, whiteSpace } : defaultStyle, className: classnames( 'rich-text', className ), @@ -1170,12 +1159,8 @@ function RichText( { ); } -const RichTextWrapper = withFormatTypes( RichText ); - /** * Renders a rich content input, providing users with the option to format the * content. */ -export default forwardRef( ( props, ref ) => { - return ; -} ); +export default forwardRef( RichText ); diff --git a/packages/rich-text/src/component/use-format-types.js b/packages/rich-text/src/component/use-format-types.js new file mode 100644 index 00000000000000..285e5ee540eaf2 --- /dev/null +++ b/packages/rich-text/src/component/use-format-types.js @@ -0,0 +1,102 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; + +function formatTypesSelector( select ) { + return select( 'core/rich-text' ).getFormatTypes(); +} + +/** + * This hook provides RichText with the `formatTypes` and its derived props from + * experimental format type settings. + * + * @param {Object} $0 Options + * @param {string} $0.clientId Block client ID. + * @param {string} $0.identifier Block attribute. + */ +export function useFormatTypes( { clientId, identifier } ) { + const formatTypes = useSelect( formatTypesSelector, [] ); + const keyedSelected = useSelect( + ( select ) => + formatTypes.reduce( ( accumulator, type ) => { + if ( type.__experimentalGetPropsForEditableTreePreparation ) { + accumulator[ + type.name + ] = type.__experimentalGetPropsForEditableTreePreparation( + select, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ); + } + + return accumulator; + }, {} ), + [ formatTypes, clientId, identifier ] + ); + const dispatch = useDispatch(); + const prepareHandlers = []; + const valueHandlers = []; + const changeHandlers = []; + const dependencies = []; + + formatTypes.forEach( ( type ) => { + if ( type.__experimentalCreatePrepareEditableTree ) { + const selected = keyedSelected[ type.name ]; + const handler = type.__experimentalCreatePrepareEditableTree( + selected, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ); + + if ( type.__experimentalCreateOnChangeEditableValue ) { + valueHandlers.push( handler ); + } else { + prepareHandlers.push( handler ); + } + + for ( const key in selected ) { + dependencies.push( selected[ key ] ); + } + } + + if ( type.__experimentalCreateOnChangeEditableValue ) { + let dispatchers = {}; + + if ( type.__experimentalGetPropsForEditableTreeChangeHandler ) { + dispatchers = type.__experimentalGetPropsForEditableTreeChangeHandler( + dispatch, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ); + } + + changeHandlers.push( + type.__experimentalCreateOnChangeEditableValue( + { + ...( keyedSelected[ type.name ] || {} ), + ...dispatchers, + }, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ) + ); + } + } ); + + return { + formatTypes, + prepareHandlers, + valueHandlers, + changeHandlers, + dependencies, + }; +} diff --git a/packages/rich-text/src/component/with-format-types.js b/packages/rich-text/src/component/with-format-types.js deleted file mode 100644 index 9a5c78a52609fa..00000000000000 --- a/packages/rich-text/src/component/with-format-types.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * External dependencies - */ -import { mapKeys } from 'lodash'; - -/** - * WordPress dependencies - */ -import { useSelect, __unstableUseDispatchWithMap } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; - -function formatTypesSelector( select ) { - return select( 'core/rich-text' ).getFormatTypes(); -} - -/** - * This higher-order component provides RichText with the `formatTypes` prop - * and its derived props from experimental format type settings. - * - * @param {WPComponent} RichText The rich text component to add props for. - * - * @return {WPComponent} New enhanced component. - */ -export default function withFormatTypes( RichText ) { - return function WithFormatTypes( props ) { - const { clientId, identifier } = props; - const formatTypes = useSelect( formatTypesSelector, [] ); - const selectProps = useSelect( - ( select ) => { - return formatTypes.reduce( ( acc, settings ) => { - if ( - ! settings.__experimentalGetPropsForEditableTreePreparation - ) { - return acc; - } - - const selectPrefix = `format_prepare_props_(${ settings.name })_`; - return { - ...acc, - ...mapKeys( - settings.__experimentalGetPropsForEditableTreePreparation( - select, - { - richTextIdentifier: identifier, - blockClientId: clientId, - } - ), - ( value, key ) => selectPrefix + key - ), - }; - }, {} ); - }, - [ formatTypes, clientId, identifier ] - ); - const dispatchProps = __unstableUseDispatchWithMap( - ( dispatch ) => { - return formatTypes.reduce( ( acc, settings ) => { - if ( - ! settings.__experimentalGetPropsForEditableTreeChangeHandler - ) { - return acc; - } - - const dispatchPrefix = `format_on_change_props_(${ settings.name })_`; - return { - ...acc, - ...mapKeys( - settings.__experimentalGetPropsForEditableTreeChangeHandler( - dispatch, - { - richTextIdentifier: identifier, - blockClientId: clientId, - } - ), - ( value, key ) => dispatchPrefix + key - ), - }; - }, {} ); - }, - [ formatTypes, clientId, identifier ] - ); - const newProps = useMemo( () => { - return formatTypes.reduce( ( acc, settings ) => { - if ( ! settings.__experimentalCreatePrepareEditableTree ) { - return acc; - } - - const args = { - richTextIdentifier: identifier, - blockClientId: clientId, - }; - const combined = { - ...selectProps, - ...dispatchProps, - }; - - const { name } = settings; - const selectPrefix = `format_prepare_props_(${ name })_`; - const dispatchPrefix = `format_on_change_props_(${ name })_`; - const propsByPrefix = Object.keys( combined ).reduce( - ( accumulator, key ) => { - if ( key.startsWith( selectPrefix ) ) { - accumulator[ key.slice( selectPrefix.length ) ] = - combined[ key ]; - } - - if ( key.startsWith( dispatchPrefix ) ) { - accumulator[ key.slice( dispatchPrefix.length ) ] = - combined[ key ]; - } - - return accumulator; - }, - {} - ); - - if ( settings.__experimentalCreateOnChangeEditableValue ) { - return { - ...acc, - [ `format_value_functions_(${ name })` ]: settings.__experimentalCreatePrepareEditableTree( - propsByPrefix, - args - ), - [ `format_on_change_functions_(${ name })` ]: settings.__experimentalCreateOnChangeEditableValue( - propsByPrefix, - args - ), - }; - } - - return { - ...acc, - [ `format_prepare_functions_(${ name })` ]: settings.__experimentalCreatePrepareEditableTree( - propsByPrefix, - args - ), - }; - }, {} ); - }, [ formatTypes, clientId, identifier, selectProps, dispatchProps ] ); - - return ( - - ); - }; -} From 2d5106a894eb172eb63805b758275171ca1b483b Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Mon, 28 Sep 2020 11:04:39 -0700 Subject: [PATCH 02/61] Fix: range control direct entry in input field (#25609) * add test for RangeControl to permit invalid entry in input field * have InputControl use focus state to inform value syncing * remove the wrapper RangeControl has around NumberControl * update tests with InputControl to focus input before change events * prevent RangeControl from calling onChange with invalid values Additionally: - Test updates - A formatting fix. * blur the active element when AnglePickerControl's dial is manipulated * mend logic for number input value resolution in RangeControl - on blur use handleOnReset if allowRest is true - call onChange with a clamped value if new value is out-of-range - update test to use invalid value instead of out-of-range value - aside: remove an extraneous prop from number input * fix and update readme --- .../src/angle-picker-control/angle-circle.js | 3 +- .../components/src/box-control/test/index.js | 16 +- .../components/src/input-control/index.js | 15 +- .../src/input-control/input-field.js | 38 +-- .../src/input-control/test/index.js | 18 +- .../src/number-control/test/index.js | 303 ++++++----------- .../components/src/range-control/README.md | 19 +- .../components/src/range-control/index.js | 39 ++- .../src/range-control/input-field.js | 86 ----- .../src/range-control/test/index.js | 50 ++- .../components/src/unit-control/test/index.js | 315 +++++++----------- 11 files changed, 327 insertions(+), 575 deletions(-) delete mode 100644 packages/components/src/range-control/input-field.js diff --git a/packages/components/src/angle-picker-control/angle-circle.js b/packages/components/src/angle-picker-control/angle-circle.js index 6122cda215daf1..f2a761284d96e2 100644 --- a/packages/components/src/angle-picker-control/angle-circle.js +++ b/packages/components/src/angle-picker-control/angle-circle.js @@ -31,7 +31,8 @@ function AngleCircle( { value, onChange, ...props } ) { // Prevent (drag) mouse events from selecting and accidentally // triggering actions from other elements. event.preventDefault(); - + // Ensure the input isn't focused as preventDefault would leave it + document.activeElement.blur(); onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) ); }; diff --git a/packages/components/src/box-control/test/index.js b/packages/components/src/box-control/test/index.js index 613aac6092fe65..81b1ab7bd2bf68 100644 --- a/packages/components/src/box-control/test/index.js +++ b/packages/components/src/box-control/test/index.js @@ -28,6 +28,7 @@ describe( 'BoxControl', () => { const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + input.focus(); fireEvent.change( input, { target: { value: '100%' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); @@ -41,14 +42,17 @@ describe( 'BoxControl', () => { const { container, getByText } = render( ); const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + const reset = getByText( /Reset/ ); + input.focus(); fireEvent.change( input, { target: { value: '100px' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); expect( input.value ).toBe( '100' ); expect( unitSelect.value ).toBe( 'px' ); - fireEvent.click( getByText( /Reset/ ) ); + reset.focus(); + fireEvent.click( reset ); expect( input.value ).toBe( '' ); expect( unitSelect.value ).toBe( 'px' ); @@ -68,14 +72,17 @@ describe( 'BoxControl', () => { const { container, getByText } = render( ); const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + const reset = getByText( /Reset/ ); + input.focus(); fireEvent.change( input, { target: { value: '100px' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); expect( input.value ).toBe( '100' ); expect( unitSelect.value ).toBe( 'px' ); - fireEvent.click( getByText( /Reset/ ) ); + reset.focus(); + fireEvent.click( reset ); expect( input.value ).toBe( '' ); expect( unitSelect.value ).toBe( 'px' ); @@ -102,14 +109,17 @@ describe( 'BoxControl', () => { const { container, getByText } = render( ); const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + const reset = getByText( /Reset/ ); + input.focus(); fireEvent.change( input, { target: { value: '100px' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); expect( input.value ).toBe( '100' ); expect( unitSelect.value ).toBe( 'px' ); - fireEvent.click( getByText( /Reset/ ) ); + reset.focus(); + fireEvent.click( reset ); expect( input.value ).toBe( '' ); expect( unitSelect.value ).toBe( 'px' ); diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js index bd6cb82d8be982..e56e86e8a384b5 100644 --- a/packages/components/src/input-control/index.js +++ b/packages/components/src/input-control/index.js @@ -33,9 +33,7 @@ export function InputControl( isPressEnterToChange = false, label, labelPosition = 'top', - onBlur = noop, onChange = noop, - onFocus = noop, onValidate = noop, onKeyDown = noop, prefix, @@ -51,16 +49,6 @@ export function InputControl( const id = useUniqueId( idProp ); const classes = classNames( 'components-input-control', className ); - const handleOnBlur = ( event ) => { - onBlur( event ); - setIsFocused( false ); - }; - - const handleOnFocus = ( event ) => { - onFocus( event ); - setIsFocused( true ); - }; - return ( state, value: valueProp, ...props @@ -63,35 +66,27 @@ function InputField( const { _event, value, isDragging, isDirty } = state; - const valueRef = useRef( value ); const dragCursor = useDragCursor( isDragging, dragDirection ); - useEffect( () => { - /** - * Handles syncing incoming value changes with internal state. - * This effectively enables a "controlled" state. - * https://reactjs.org/docs/forms.html#controlled-components - */ - if ( valueProp !== valueRef.current ) { - update( valueProp ); - valueRef.current = valueProp; - - // Quick return to avoid firing the onChange callback + /* + * Syncs value state using the focus state to determine the direction. + * Without focus it updates the value from the props. With focus it + * propagates the value and event through onChange. + */ + useUpdateEffect( () => { + if ( valueProp === value ) { return; } - - /** - * Fires the onChange callback when internal state value changes. - */ - if ( value !== valueRef.current && ! isDirty ) { + if ( ! isFocused ) { + update( valueProp ); + } else if ( ! isDirty ) { onChange( value, { event: _event } ); - - valueRef.current = value; } - }, [ value, isDirty, valueProp ] ); + }, [ value, isDirty, isFocused, valueProp ] ); const handleOnBlur = ( event ) => { onBlur( event ); + setIsFocused( false ); /** * If isPressEnterToChange is set, this commits the value to @@ -108,6 +103,7 @@ function InputField( const handleOnFocus = ( event ) => { onFocus( event ); + setIsFocused( true ); }; const handleOnChange = ( event ) => { diff --git a/packages/components/src/input-control/test/index.js b/packages/components/src/input-control/test/index.js index 86615318497fc6..4198a2308fd473 100644 --- a/packages/components/src/input-control/test/index.js +++ b/packages/components/src/input-control/test/index.js @@ -49,7 +49,7 @@ describe( 'InputControl', () => { render( ); const input = getInput(); - + input.focus(); fireEvent.change( input, { target: { value: 'There' } } ); expect( input.value ).toBe( 'There' ); @@ -59,21 +59,23 @@ describe( 'InputControl', () => { it( 'should work as a controlled component', () => { const spy = jest.fn(); const { rerender } = render( - + ); const input = getInput(); - fireEvent.change( input, { target: { value: 'State' } } ); + input.focus(); + fireEvent.change( input, { target: { value: 'two' } } ); - // Assuming is controlled... + // Ensuring is controlled + fireEvent.blur( input ); // Updating the value - rerender( ); + rerender( ); - expect( input.value ).toBe( 'New' ); + expect( input.value ).toBe( 'three' ); - /** + /* * onChange called only once. onChange is not called when a * parent component explicitly passed a (new value) change down to * the . @@ -89,7 +91,7 @@ describe( 'InputControl', () => { const input = getInput(); - // Assuming is controlled... + // Assuming is controlled (not focused) // Updating the value rerender( ); diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js index 8c006647bd2ff3..883b7f6d9591fd 100644 --- a/packages/components/src/number-control/test/index.js +++ b/packages/components/src/number-control/test/index.js @@ -1,8 +1,7 @@ /** * External dependencies */ -import { render, unmountComponentAtNode } from 'react-dom'; -import { act, Simulate } from 'react-dom/test-utils'; +import { render, screen, fireEvent } from '@testing-library/react'; /** * WordPress dependencies @@ -13,24 +12,16 @@ import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ -import NumberControl from '../'; +import BaseNumberControl from '../'; -let container = null; +const getInput = () => screen.getByTestId( 'input' ); -function getInput() { - return container.querySelector( 'input' ); -} - -beforeEach( () => { - container = document.createElement( 'div' ); - document.body.appendChild( container ); -} ); +const fireKeyDown = ( data ) => + fireEvent.keyDown( document.activeElement || document.body, data ); -afterEach( () => { - unmountComponentAtNode( container ); - container.remove(); - container = null; -} ); +const NumberControl = ( props ) => ( + +); function StatefulNumberControl( props ) { const [ value, setValue ] = useState( props.value ); @@ -48,86 +39,56 @@ function StatefulNumberControl( props ) { describe( 'NumberControl', () => { describe( 'Basic rendering', () => { it( 'should render', () => { - act( () => { - render( , container ); - } ); - - const input = getInput(); - - expect( input ).not.toBeNull(); + render( ); + expect( getInput() ).not.toBeNull(); } ); it( 'should render custom className', () => { - act( () => { - render( , container ); - } ); - - const input = container.querySelector( '.hello' ); - - expect( input ).toBeTruthy(); + render( ); + expect( getInput() ).toBeTruthy(); } ); } ); describe( 'onChange handling', () => { it( 'should provide onChange callback with number value', () => { const spy = jest.fn(); - act( () => { - render( - , - container - ); - } ); - - const input = getInput(); - - input.value = 10; - act( () => { - Simulate.change( input ); - } ); + render( + spy( v ) } /> + ); - const changeValue = spy.mock.calls[ 0 ][ 0 ]; + const input = getInput(); + input.focus(); + fireEvent.change( input, { target: { value: 10 } } ); - expect( changeValue ).toBe( '10' ); + expect( spy ).toHaveBeenCalledWith( '10' ); } ); } ); describe( 'Validation', () => { it( 'should clamp value within range on ENTER keypress', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - input.value = -100; - - act( () => { - Simulate.change( input ); - Simulate.keyDown( input, { keyCode: ENTER } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: -100 } } ); + fireKeyDown( { keyCode: ENTER } ); /** * This is zero because the value has been adjusted to * respect the min/max range of the input. */ + expect( input.value ).toBe( '0' ); } ); it( 'should parse to number value on ENTER keypress', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - input.value = '10 abc'; - - act( () => { - Simulate.change( input ); - Simulate.keyDown( input, { keyCode: ENTER } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '10 abc' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( input.value ).toBe( '0' ); } ); @@ -136,122 +97,83 @@ describe( 'NumberControl', () => { describe( 'Key UP interactions', () => { it( 'should fire onKeyDown callback', () => { const spy = jest.fn(); - act( () => { - render( - , - container - ); - } ); - const input = getInput(); + render( ); - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: UP } ); expect( spy ).toHaveBeenCalled(); } ); it( 'should increment by step on key UP press', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP } ); expect( input.value ).toBe( '6' ); } ); it( 'should increment from a negative value', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP } ); expect( input.value ).toBe( '-4' ); } ); it( 'should increment by shiftStep on key UP + shift press', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '20' ); } ); it( 'should increment by custom shiftStep on key UP + shift press', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '100' ); } ); it( 'should increment but be limited by max on shiftStep', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '99' ); } ); it( 'should not increment by shiftStep if disabled', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '6' ); } ); @@ -260,119 +182,82 @@ describe( 'NumberControl', () => { describe( 'Key DOWN interactions', () => { it( 'should fire onKeyDown callback', () => { const spy = jest.fn(); - act( () => { - render( - , - container - ); - } ); + render( ); - const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: DOWN } ); expect( spy ).toHaveBeenCalled(); } ); it( 'should decrement by step on key DOWN press', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN } ); expect( input.value ).toBe( '4' ); } ); it( 'should decrement from a negative value', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN } ); expect( input.value ).toBe( '-6' ); } ); it( 'should decrement by shiftStep on key DOWN + shift press', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '0' ); } ); it( 'should decrement by custom shiftStep on key DOWN + shift press', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '-100' ); } ); it( 'should decrement but be limited by min on shiftStep', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '4' ); } ); it( 'should not decrement by shiftStep if disabled', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '4' ); } ); diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index c42614583dc2e1..f5e38538369823 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -213,8 +213,7 @@ const MyRangeControl() { #### onChange -A function that receives the new value. -If allowReset is true, when onChange is called without any parameter passed it should reset the value. +A function that receives the new value. The value will be less than `max` and more than `min` unless a reset (enabled by `allowReset`) has occured. In which case the value will be either that of `resetFallbackValue` if it has been specified or otherwise `undefined`. - Type: `function` - Required: Yes @@ -222,14 +221,22 @@ If allowReset is true, when onChange is called without any parameter passed it s #### min -The minimum value accepted. If smaller values are inserted onChange will not be called and the value gets reverted when blur event fires. +The minimum `value` allowed. - Type: `Number` - Required: No +- Default: 0 - Platform: Web | Mobile #### max +The maximum `value` allowed. + +- Type: `Number` +- Required: No +- Default: 100 +- Platform: Web | Mobile + #### railColor Customizes the (background) color of the rail element. @@ -238,12 +245,6 @@ Customizes the (background) color of the rail element. - Required: No - Platform: Web -The maximum value accepted. If higher values are inserted onChange will not be called and the value gets reverted when blur event fires. - -- Type: `Number` -- Required: No -- Platform: Web | Mobile - #### renderTooltipContent A way to customize the rendered UI of the value. Example: diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 536b0d84ba0a86..90f55c3c0cd555 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -26,13 +26,13 @@ import { ActionRightWrapper, AfterIconWrapper, BeforeIconWrapper, + InputNumber, Root, Track, ThumbWrapper, Thumb, Wrapper, } from './styles/range-control-styles'; -import InputField from './input-field'; import { useRTL } from '../utils/rtl'; function RangeControl( @@ -77,6 +77,7 @@ function RangeControl( value: valueProp, initial: initialPosition, } ); + const isResetPendent = useRef( false ); const [ showTooltip, setShowTooltip ] = useState( showTooltipProp ); const [ isFocused, setIsFocused ] = useState( false ); @@ -119,17 +120,33 @@ function RangeControl( const handleOnRangeChange = ( event ) => { const nextValue = parseFloat( event.target.value ); - handleOnChange( nextValue ); + setValue( nextValue ); + onChange( nextValue ); }; const handleOnChange = ( nextValue ) => { - if ( isNaN( nextValue ) ) { - handleOnReset(); - return; + nextValue = parseFloat( nextValue ); + setValue( nextValue ); + /* + * Calls onChange only when nextValue is numeric + * otherwise may queue a reset for the blur event. + */ + if ( ! isNaN( nextValue ) ) { + if ( nextValue < min || nextValue > max ) { + nextValue = floatClamp( nextValue, min, max ); + } + onChange( nextValue ); + isResetPendent.current = false; + } else if ( allowReset ) { + isResetPendent.current = true; } + }; - setValue( nextValue ); - onChange( nextValue ); + const handleOnInputNumberBlur = () => { + if ( isResetPendent.current ) { + handleOnReset(); + isResetPendent.current = false; + } }; const handleOnReset = () => { @@ -256,14 +273,16 @@ function RangeControl( ) } { withInputField && ( - to be updated independently before the - * value is applied and propagated. This independent updating action is - * necessary to accommodate individual keystroke values that may not - * be considered "valid" (e.g. within the min - max range). - */ - const [ value, setValue ] = useControlledState( valueProp ); - - const handleOnReset = ( event ) => { - onReset( event ); - setValue( '' ); - }; - - const handleOnCommit = ( event ) => { - const nextValue = parseFloat( event.target.value ); - - if ( isNaN( nextValue ) ) { - handleOnReset(); - return; - } - - setValue( nextValue ); - onChange( nextValue ); - }; - - const handleOnBlur = ( event ) => { - onBlur( event ); - handleOnCommit( event ); - }; - - const handleOnChange = ( next ) => { - handleOnCommit( { target: { value: next } } ); - }; - - const handleOnKeyDown = ( event ) => { - const { keyCode } = event; - onKeyDown( event ); - - if ( keyCode === ENTER ) { - event.preventDefault(); - handleOnCommit( event ); - } - }; - - return ( - - ); -} diff --git a/packages/components/src/range-control/test/index.js b/packages/components/src/range-control/test/index.js index e3d75083f4e4f4..2ccb8d90293fa9 100644 --- a/packages/components/src/range-control/test/index.js +++ b/packages/components/src/range-control/test/index.js @@ -27,10 +27,11 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + rangeInput.focus(); fireEvent.change( rangeInput, { target: { value: '5' } } ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: '10' } } ); - fireEvent.blur( numberInput ); expect( onChange ).toHaveBeenCalledWith( 5 ); expect( onChange ).toHaveBeenCalledWith( 10 ); @@ -57,7 +58,7 @@ describe( 'RangeControl', () => { } ); describe( 'validation', () => { - it( 'should not apply new value is lower than minimum', () => { + it( 'should not apply if new value is lower than minimum', () => { const { container } = render( ); const rangeInput = getRangeInput( container ); @@ -69,7 +70,7 @@ describe( 'RangeControl', () => { expect( rangeInput.value ).not.toBe( '10' ); } ); - it( 'should not apply new value is greater than maximum', () => { + it( 'should not apply if new value is greater than maximum', () => { const { container } = render( ); const rangeInput = getRangeInput( container ); @@ -81,20 +82,38 @@ describe( 'RangeControl', () => { expect( rangeInput.value ).not.toBe( '21' ); } ); - it( 'should call onChange if new value is valid', () => { + it( 'should not call onChange if new value is invalid', () => { const onChange = jest.fn(); const { container } = render( ); + const numberInput = getNumberInput( container ); + + numberInput.focus(); + fireEvent.change( numberInput, { target: { value: '25e' } } ); + + expect( onChange ).not.toHaveBeenCalled(); + } ); + + it( 'should keep invalid values in number input until loss of focus', () => { + const onChange = jest.fn(); + const { container } = render( + + ); + const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); - fireEvent.change( numberInput, { target: { value: '15' } } ); - fireEvent.blur( numberInput ); + numberInput.focus(); + fireEvent.change( numberInput, { target: { value: '-1.1' } } ); + + expect( numberInput.value ).toBe( '-1.1' ); + expect( rangeInput.value ).toBe( '-1' ); - expect( onChange ).toHaveBeenCalledWith( 15 ); - expect( rangeInput.value ).toBe( '15' ); + fireEvent.blur( numberInput ); + expect( onChange ).toHaveBeenCalledWith( -1 ); + expect( numberInput.value ).toBe( '-1' ); } ); it( 'should validate when provided a max or min of zero', () => { @@ -105,6 +124,7 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: '1' } } ); fireEvent.blur( numberInput ); @@ -119,19 +139,15 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); - fireEvent.change( numberInput, { target: { value: '-101' } } ); - fireEvent.blur( numberInput ); + numberInput.focus(); + fireEvent.change( numberInput, { target: { value: '-101' } } ); expect( rangeInput.value ).toBe( '-100' ); fireEvent.change( numberInput, { target: { value: '-49' } } ); - fireEvent.blur( numberInput ); - expect( rangeInput.value ).toBe( '-50' ); fireEvent.change( numberInput, { target: { value: '-50' } } ); - fireEvent.blur( numberInput ); - expect( rangeInput.value ).toBe( '-50' ); } ); @@ -148,14 +164,13 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: '0.125' } } ); - fireEvent.blur( numberInput ); expect( onChange ).toHaveBeenCalledWith( 0.125 ); expect( rangeInput.value ).toBe( '0.125' ); fireEvent.change( numberInput, { target: { value: '0.225' } } ); - fireEvent.blur( numberInput ); expect( onChange ).toHaveBeenCalledWith( 0.225 ); expect( rangeInput.value ).toBe( '0.225' ); @@ -229,13 +244,14 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + rangeInput.focus(); fireEvent.change( rangeInput, { target: { value: 13 } } ); expect( rangeInput.value ).toBe( '13' ); expect( numberInput.value ).toBe( '13' ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: 7 } } ); - fireEvent.blur( numberInput ); expect( rangeInput.value ).toBe( '7' ); expect( numberInput.value ).toBe( '7' ); diff --git a/packages/components/src/unit-control/test/index.js b/packages/components/src/unit-control/test/index.js index 8eac58f0dfdbaf..21d102a6f97e34 100644 --- a/packages/components/src/unit-control/test/index.js +++ b/packages/components/src/unit-control/test/index.js @@ -1,46 +1,32 @@ /** * External dependencies */ -import { render, unmountComponentAtNode } from 'react-dom'; -import { act, Simulate } from 'react-dom/test-utils'; -import { render as testRender } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies */ -import { UP, DOWN } from '@wordpress/keycodes'; +import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ import UnitControl from '../'; -let container = null; - -beforeEach( () => { - container = document.createElement( 'div' ); - document.body.appendChild( container ); -} ); - -afterEach( () => { - unmountComponentAtNode( container ); - container.remove(); - container = null; -} ); - const getComponent = () => - container.querySelector( '.components-unit-control' ); + document.body.querySelector( '.components-unit-control' ); const getInput = () => - container.querySelector( '.components-unit-control input' ); + document.body.querySelector( '.components-unit-control input' ); const getSelect = () => - container.querySelector( '.components-unit-control select' ); + document.body.querySelector( '.components-unit-control select' ); + +const fireKeyDown = ( data ) => + fireEvent.keyDown( document.activeElement || document.body, data ); describe( 'UnitControl', () => { describe( 'Basic rendering', () => { it( 'should render', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); const select = getSelect(); @@ -49,9 +35,7 @@ describe( 'UnitControl', () => { } ); it( 'should render custom className', () => { - act( () => { - render( , container ); - } ); + render( ); const el = getComponent(); @@ -59,9 +43,7 @@ describe( 'UnitControl', () => { } ); it( 'should not render select, if units are disabled', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); const select = getSelect(); @@ -72,61 +54,39 @@ describe( 'UnitControl', () => { describe( 'Value', () => { it( 'should update value on change', () => { - let state = 50; - const setState = ( nextState ) => ( state = nextState ); + let state = '50px'; + const setState = jest.fn( ( value ) => ( state = value ) ); - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); + input.focus(); + fireEvent.change( input, { target: { value: 62 } } ); - act( () => { - Simulate.change( input, { target: { value: 62 } } ); - } ); - + expect( setState ).toHaveBeenCalledTimes( 1 ); expect( state ).toBe( '62px' ); } ); it( 'should increment value on UP press', () => { - let state = 50; + let state = '50px'; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); - - const input = getInput(); + render( ); - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: UP } ); expect( state ).toBe( '51px' ); } ); it( 'should increment value on UP + SHIFT press, with step', () => { - let state = 50; + let state = '50px'; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); + render( ); - const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( state ).toBe( '60px' ); } ); @@ -135,18 +95,10 @@ describe( 'UnitControl', () => { let state = 50; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); - - const input = getInput(); + render( ); - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: DOWN } ); expect( state ).toBe( '49px' ); } ); @@ -155,18 +107,10 @@ describe( 'UnitControl', () => { let state = 50; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); + render( ); - const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( state ).toBe( '40px' ); } ); @@ -177,18 +121,11 @@ describe( 'UnitControl', () => { let state = 'px'; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); + render( ); const select = getSelect(); - - act( () => { - Simulate.change( select, { target: { value: 'em' } } ); - } ); + select.focus(); + fireEvent.change( select, { target: { value: 'em' } } ); expect( state ).toBe( 'em' ); } ); @@ -198,9 +135,8 @@ describe( 'UnitControl', () => { { value: 'pt', label: 'pt', default: 0 }, { value: 'vmax', label: 'vmax', default: 10 }, ]; - act( () => { - render( , container ); - } ); + + render( ); const select = getSelect(); const options = select.querySelectorAll( 'option' ); @@ -221,29 +157,24 @@ describe( 'UnitControl', () => { { value: 'pt', label: 'pt', default: 25 }, { value: 'vmax', label: 'vmax', default: 75 }, ]; - act( () => { - render( - , - container - ); - } ); + + render( + + ); const select = getSelect(); + select.focus(); - act( () => { - Simulate.change( select, { target: { value: 'vmax' } } ); - } ); + fireEvent.change( select, { target: { value: 'vmax' } } ); expect( state ).toBe( '75vmax' ); - act( () => { - Simulate.change( select, { target: { value: 'pt' } } ); - } ); + fireEvent.change( select, { target: { value: 'pt' } } ); expect( state ).toBe( '25pt' ); } ); @@ -256,146 +187,136 @@ describe( 'UnitControl', () => { { value: 'pt', label: 'pt', default: 25 }, { value: 'vmax', label: 'vmax', default: 75 }, ]; - act( () => { - render( - , - container - ); - } ); + + render( + + ); const select = getSelect(); + select.focus(); - act( () => { - Simulate.change( select, { target: { value: 'vmax' } } ); - } ); + fireEvent.change( select, { target: { value: 'vmax' } } ); expect( state ).toBe( '50vmax' ); - act( () => { - Simulate.change( select, { target: { value: 'pt' } } ); - } ); + fireEvent.change( select, { target: { value: 'pt' } } ); expect( state ).toBe( '50pt' ); } ); } ); + describe( 'Unit Parser', () => { let state = '10px'; - const setState = ( nextState ) => ( state = nextState ); + const setState = jest.fn( ( nextState ) => ( state = nextState ) ); it( 'should parse unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '55 em' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '55 em' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '55em' ); } ); it( 'should parse PX unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '61 PX' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '61 PX' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '61px' ); } ); it( 'should parse EM unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '55 em' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '55 em' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '55em' ); } ); it( 'should parse % unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '-10 %' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '-10 %' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '-10%' ); } ); it( 'should parse REM unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { - target: { value: '123 rEm ' }, - } ); + input.focus(); + fireEvent.change( input, { + target: { value: '123 rEm ' }, } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '123rem' ); } ); it( 'should update unit after initial render and with new unit prop', () => { - const { container: testContainer, rerender } = testRender( - - ); + const { rerender } = render( ); - const select = testContainer.querySelector( 'select' ); + const select = getSelect(); expect( select.value ).toBe( '%' ); - rerender( ); + rerender( ); expect( select.value ).toBe( 'em' ); } ); it( 'should fallback to default unit if parsed unit is invalid', () => { - const { container: testContainer } = testRender( - - ); - - const select = testContainer.querySelector( 'select' ); + render( ); - expect( select.value ).toBe( 'px' ); + expect( getSelect().value ).toBe( 'px' ); } ); } ); } ); From 3f2c0e31a87887a938e174a9546aba5190b88c50 Mon Sep 17 00:00:00 2001 From: roo2 Date: Tue, 29 Sep 2020 05:40:45 +1000 Subject: [PATCH 03/61] wp-env: Add docs for inspecting the docker compose file (#25666) --- packages/env/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/env/README.md b/packages/env/README.md index 28c61f1bb6d405..7daa813fd92559 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -169,6 +169,25 @@ $ wp-env destroy $ wp-env start ``` +### 7. Debug mode and inspecting the generated dockerfile. + +`wp-env` uses docker behind the scenes. Inspecting the generated docker-compose file can help to understand what's going on. + +Start `wp-env` in debug mode + +```sh +wp-env start --debug +``` + +`wp-env` will output its config which includes `dockerComposeConfigPath`. + +```sh +ℹ Config: + ... + "dockerComposeConfigPath": "/Users/$USERNAME/.wp-env/5a619d332a92377cd89feb339c67b833/docker-compose.yml", + ... +``` + ## Command reference `wp-env` creates generated files in the `wp-env` home directory. By default, this is `~/.wp-env`. The exception is Linux, where files are placed at `~/wp-env` [for compatibility with Snap Packages](https://github.com/WordPress/gutenberg/issues/20180#issuecomment-587046325). The `wp-env` home directory contains a subdirectory for each project named `/$md5_of_project_path`. To change the `wp-env` home directory, set the `WP_ENV_HOME` environment variable. For example, running `WP_ENV_HOME="something" wp-env start` will download the project files to the directory `./something/$md5_of_project_path` (relative to the current directory). From ed848a0ac49257c1c8162fef09440a36c48a8375 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 28 Sep 2020 17:16:43 -0400 Subject: [PATCH 04/61] Fix image block render in Xcode 12 (#25470) * Fix image block render in Xcode 12 This PR fixes #25431 which is a bug that prevents Image blocks from rendering their images. This only affects builds made using Xcode 12. The bug itself is in React Native itself and was fixed in RN 0.63. Since we're on RN 0.61.5, we need to apply a patch (as suggested here https://github.com/facebook/react-native/issues/29279#issuecomment-657201709). The patch was applied using the following steps found here: https://github.com/facebook/react-native/issues/29279#issuecomment-657201709 * Remove unnecessary changes to RN patch file --- patches/react-native+0.61.5.patch | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/patches/react-native+0.61.5.patch b/patches/react-native+0.61.5.patch index b3c8c8ff3d257d..72f668835cf2d4 100644 --- a/patches/react-native+0.61.5.patch +++ b/patches/react-native+0.61.5.patch @@ -21,6 +21,20 @@ index 65fa2bb..c5f265b 100644 index++; } +diff --git a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m +index 01aa75f..572572c 100644 +--- a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m ++++ b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m +@@ -266,6 +266,9 @@ - (void)displayDidRefresh:(CADisplayLink *)displayLink + + - (void)displayLayer:(CALayer *)layer + { ++ if (!_currentFrame) { ++ _currentFrame = self.image; ++ } + if (_currentFrame) { + layer.contentsScale = self.animatedImageScale; + layer.contents = (__bridge id)_currentFrame.CGImage; diff --git a/node_modules/react-native/React/Views/RCTShadowView.m b/node_modules/react-native/React/Views/RCTShadowView.m index 40c0cda..646f137 100644 --- a/node_modules/react-native/React/Views/RCTShadowView.m From b9ff7ea0adf17dcc188055726dc4cf9354b0b669 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Mon, 28 Sep 2020 15:41:04 -0700 Subject: [PATCH 05/61] RN: Add Insert Button Mobile Component. (#25204) * RN: Add Icon Button Block Editor component. * Add the missing styles * Add readme descriptions Co-authored-by: Gerardo Pacheco --- .../block-variation-picker/index.native.js | 4 +- .../src/components/inserter/menu.native.js | 13 ++-- .../src/components/inserter/style.native.scss | 64 ------------------- packages/components/src/index.native.js | 2 +- .../src/mobile/inserter-button/README.md | 55 ++++++++++++++++ .../mobile/inserter-button/index.native.js} | 16 +++-- .../mobile/inserter-button/style.native.scss | 64 +++++++++++++++++++ 7 files changed, 141 insertions(+), 77 deletions(-) create mode 100644 packages/components/src/mobile/inserter-button/README.md rename packages/{block-editor/src/components/inserter/menu-item.native.js => components/src/mobile/inserter-button/index.native.js} (83%) create mode 100644 packages/components/src/mobile/inserter-button/style.native.scss diff --git a/packages/block-editor/src/components/block-variation-picker/index.native.js b/packages/block-editor/src/components/block-variation-picker/index.native.js index 0884b5aebdaed2..9ad927c0b4796a 100644 --- a/packages/block-editor/src/components/block-variation-picker/index.native.js +++ b/packages/block-editor/src/components/block-variation-picker/index.native.js @@ -21,6 +21,7 @@ import { PanelBody, BottomSheet, FooterMessageControl, + InserterButton, } from '@wordpress/components'; import { Icon, close } from '@wordpress/icons'; import { useMemo } from '@wordpress/element'; @@ -28,7 +29,6 @@ import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ -import MenuItem from '../inserter/menu-item'; import styles from './style.scss'; const hitSlop = { top: 22, bottom: 22, left: 22, right: 22 }; @@ -103,7 +103,7 @@ function BlockVariationPicker( { isVisible, onClose, clientId, variations } ) { > { variations.map( ( v ) => { return ( - onVariationSelect( v ) } diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 76b1557ddfd68e..c05ae07048b676 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -17,13 +17,16 @@ import { Component } from '@wordpress/element'; import { createBlock, rawHandler } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; -import { BottomSheet, BottomSheetConsumer } from '@wordpress/components'; +import { + BottomSheet, + BottomSheetConsumer, + InserterButton, +} from '@wordpress/components'; /** * Internal dependencies */ import styles from './style.scss'; -import MenuItem from './menu-item.native'; const MIN_COL_NUM = 3; @@ -62,8 +65,8 @@ export class InserterMenu extends Component { const { paddingLeft: itemPaddingLeft, paddingRight: itemPaddingRight, - } = styles.modalItem; - const { width: itemWidth } = styles.modalIconWrapper; + } = InserterButton.Styles.modalItem; + const { width: itemWidth } = InserterButton.Styles.modalIconWrapper; return itemWidth + itemPaddingLeft + itemPaddingRight; } @@ -112,7 +115,7 @@ export class InserterMenu extends Component { const { itemWidth, maxWidth } = this.state; const { onSelect } = this.props; return ( - + Some rendered content here + } } onSelect={ function( item ) { console.log( 'selected' ); } } /> + + ); +} +``` + +_Note:_ + +## Props + +### `maxWidth` +* **Type:** `String` +* **Default:** `undefined` + +The max-width of the button. + +### `itemWidth` +* **Type:** `String` +* **Default:** `undefined` + +The button width. + +### `onSelect` +* **Type:** `Function` +* **Required** `true` + +The function that is called once the InserterButton has been selected. + +### `item` +* **Type:** `Object` +* **Required** `true` + +The object that gets selected. + +## Examples + + diff --git a/packages/block-editor/src/components/inserter/menu-item.native.js b/packages/components/src/mobile/inserter-button/index.native.js similarity index 83% rename from packages/block-editor/src/components/inserter/menu-item.native.js rename to packages/components/src/mobile/inserter-button/index.native.js index be47452eae1fb9..2712b7b6a198fa 100644 --- a/packages/block-editor/src/components/inserter/menu-item.native.js +++ b/packages/components/src/mobile/inserter-button/index.native.js @@ -55,13 +55,14 @@ class MenuItem extends Component { ); const isClipboardBlock = item.id === 'clipboard'; + const blockTitle = isClipboardBlock ? __( 'Copied block' ) : item.title; return ( @@ -82,13 +83,18 @@ class MenuItem extends Component { /> - - { isClipboardBlock ? __( 'Copied block' ) : item.title } - + { blockTitle } ); } } -export default withPreferredColorScheme( MenuItem ); +const InserterButton = withPreferredColorScheme( MenuItem ); + +InserterButton.Styles = { + modalItem: styles.modalItem, + modalIconWrapper: styles.modalIconWrapper, +}; + +export default InserterButton; diff --git a/packages/components/src/mobile/inserter-button/style.native.scss b/packages/components/src/mobile/inserter-button/style.native.scss new file mode 100644 index 00000000000000..1a8644ad78d8ad --- /dev/null +++ b/packages/components/src/mobile/inserter-button/style.native.scss @@ -0,0 +1,64 @@ +/** @format */ +.touchableArea { + border-radius: 8px 8px 8px 8px; +} + +.modalIconWrapper { + width: 104px; + height: 64px; + background-color: $gray-light; //#f3f6f8 + border-radius: 8px 8px 8px 8px; + justify-content: center; + align-items: center; +} + +.modalIconWrapperDark { + background-color: rgba($white, 0.07); +} + +.modalIcon { + width: 32px; + height: 32px; + justify-content: center; + align-items: center; + fill: $gray-dark; +} + +.modalIconDark { + fill: $white; +} + +.modalItemLabel { + background-color: transparent; + padding-left: 2; + padding-right: 2; + padding-top: 4; + padding-bottom: 0; + justify-content: center; + font-size: 12; + color: $gray-dark; +} + +.modalItemLabelDark { + color: $white; +} + +.clipboardBlock { + background-color: transparent; + border-width: 1px; + border-color: $light-gray-400; +} + +.clipboardBlockDark { + border-color: $gray-70; +} + +.modalItem { + flex-direction: column; + justify-content: flex-start; + align-items: center; + padding-left: $grid-unit-20 / 2; + padding-right: $grid-unit-20 / 2; + padding-top: 0; + padding-bottom: 0; +} From f7b9201fbd4d7ddea0963f71db099a2fe5cda406 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 29 Sep 2020 11:48:14 +1000 Subject: [PATCH 06/61] Add move markers to list view. (#25205) * Add move markers to list view. * Select block before moving. --- .../src/components/block-actions/index.js | 2 ++ .../block-navigation/block-contents.js | 27 ++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index 9f5e6593eb3e3d..e7de0fa5c40f32 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -54,6 +54,7 @@ export default function BlockActions( { flashBlock, setBlockMovingClientId, setNavigationMode, + selectBlock, } = useDispatch( 'core/block-editor' ); const notifyCopy = useNotifyCopy(); @@ -78,6 +79,7 @@ export default function BlockActions( { }, onMoveTo() { setNavigationMode( true ); + selectBlock( clientIds[ 0 ] ); setBlockMovingClientId( clientIds[ 0 ] ); }, onGroup() { diff --git a/packages/block-editor/src/components/block-navigation/block-contents.js b/packages/block-editor/src/components/block-navigation/block-contents.js index 8bf5baea7985b5..dc215cd461b189 100644 --- a/packages/block-editor/src/components/block-navigation/block-contents.js +++ b/packages/block-editor/src/components/block-navigation/block-contents.js @@ -37,14 +37,29 @@ const BlockNavigationBlockContents = forwardRef( const { clientId } = block; - const rootClientId = useSelect( - ( select ) => - select( 'core/block-editor' ).getBlockRootClientId( - clientId - ) || '', + const { + rootClientId, + blockMovingClientId, + selectedBlockInBlockEditor, + } = useSelect( + ( select ) => { + const { + getBlockRootClientId, + hasBlockMovingClientId, + getSelectedBlockClientId, + } = select( 'core/block-editor' ); + return { + rootClientId: getBlockRootClientId( clientId ) || '', + blockMovingClientId: hasBlockMovingClientId(), + selectedBlockInBlockEditor: getSelectedBlockClientId(), + }; + }, [ clientId ] ); + const isBlockMoveTarget = + blockMovingClientId && selectedBlockInBlockEditor === clientId; + const { rootClientId: dropTargetRootClientId, clientId: dropTargetClientId, @@ -65,7 +80,7 @@ const BlockNavigationBlockContents = forwardRef( const className = classnames( 'block-editor-block-navigation-block-contents', { - 'is-dropping-before': isDroppingBefore, + 'is-dropping-before': isDroppingBefore || isBlockMoveTarget, 'is-dropping-after': isDroppingAfter, 'is-dropping-to-inner-blocks': isDroppingToInnerBlocks, } From 96577a54069197a12296d2bac0307bbb1a512df7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 29 Sep 2020 08:45:44 +0100 Subject: [PATCH 07/61] Bump plugin version to 9.1.0-rc.1 --- changelog.txt | 5 +++++ gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 3ac19d9866268a..07d7b4cd0214ae 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,10 @@ == Changelog == += 9.1.0-rc.1 = + +TODO: Changelog will be provided later. + + = 9.0.0 = ### Features diff --git a/gutenberg.php b/gutenberg.php index 2cda8621d0e063..54e972091ed194 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.3 * Requires PHP: 5.6 - * Version: 9.0.0 + * Version: 9.1.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 3b882d659ec8b8..cd429d019d8838 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.0.0", + "version": "9.1.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a220aefdb0a7be..9e1fadc5512373 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.0.0", + "version": "9.1.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index 2c80db360fe879..7d664a8b98a05b 100644 --- a/readme.txt +++ b/readme.txt @@ -57,4 +57,4 @@ View release page. +To read the changelog for Gutenberg 9.1.0-rc.1, please navigate to the release page. From 29255c42cc531ea9739b9d744e4279856c5e7d84 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 29 Sep 2020 16:12:59 +0800 Subject: [PATCH 08/61] Move widget-area to edit-widgets (#25673) * Move widget-area to edit-widgets * Update blocks.php * Move css * Delete e2e tests --- lib/blocks.php | 3 ++- packages/block-library/src/editor.scss | 1 - packages/block-library/src/index.js | 2 -- .../fixtures/blocks/core__widget-area.html | 1 - .../fixtures/blocks/core__widget-area.json | 10 ---------- .../blocks/core__widget-area.parsed.json | 18 ------------------ .../blocks/core__widget-area.serialized.html | 1 - .../src/blocks}/widget-area/block.json | 0 .../src/blocks}/widget-area/edit/index.js | 0 .../blocks}/widget-area/edit/inner-blocks.js | 0 .../src/blocks}/widget-area/editor.scss | 0 .../src/blocks}/widget-area/index.js | 0 packages/edit-widgets/src/index.js | 2 ++ packages/edit-widgets/src/style.scss | 1 + 14 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.html delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.json delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/block.json (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/edit/index.js (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/edit/inner-blocks.js (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/editor.scss (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/index.js (100%) diff --git a/lib/blocks.php b/lib/blocks.php index 0aa7c62e725b9d..a35f6a659bd294 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -45,7 +45,6 @@ function gutenberg_reregister_core_block_types() { 'text-columns', 'verse', 'video', - 'widget-area', ), 'block_names' => array_merge( array( @@ -96,9 +95,11 @@ function gutenberg_reregister_core_block_types() { dirname( __FILE__ ) . '/../build/edit-widgets/blocks/' => array( 'block_folders' => array( 'legacy-widget', + 'widget-area', ), 'block_names' => array( 'legacy-widget.php' => 'core/legacy-widget', + 'widget-area.php' => 'core/widget-area', ), ), ); diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 2d4b530ce0a112..ee185fde8bdc92 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -49,7 +49,6 @@ @import "./text-columns/editor.scss"; @import "./verse/editor.scss"; @import "./video/editor.scss"; -@import "./widget-area/editor.scss"; @import "./query-loop/editor.scss"; @import "./query/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index d92cde6ca38d5a..e99a917ec839b0 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -61,7 +61,6 @@ import * as tagCloud from './tag-cloud'; import * as classic from './classic'; import * as socialLinks from './social-links'; import * as socialLink from './social-link'; -import * as widgetArea from './widget-area'; // Full Site Editing Blocks import * as siteLogo from './site-logo'; @@ -191,7 +190,6 @@ export const __experimentalRegisterExperimentalCoreBlocks = const { __experimentalEnableFullSiteEditing } = settings; [ - widgetArea, navigation, navigationLink, diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.html b/packages/e2e-tests/fixtures/blocks/core__widget-area.html deleted file mode 100644 index 3d2e16369b8194..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.json b/packages/e2e-tests/fixtures/blocks/core__widget-area.json deleted file mode 100644 index 578fdf0f1ab758..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "clientId": "_clientId_0", - "name": "core/widget-area", - "isValid": true, - "attributes": {}, - "innerBlocks": [], - "originalContent": "" - } -] diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json b/packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json deleted file mode 100644 index 930bf95031a0fe..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "blockName": "core/widget-area", - "attrs": {}, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - }, - { - "blockName": null, - "attrs": {}, - "innerBlocks": [], - "innerHTML": "\n", - "innerContent": [ - "\n" - ] - } -] diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html b/packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html deleted file mode 100644 index 3d2e16369b8194..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/block-library/src/widget-area/block.json b/packages/edit-widgets/src/blocks/widget-area/block.json similarity index 100% rename from packages/block-library/src/widget-area/block.json rename to packages/edit-widgets/src/blocks/widget-area/block.json diff --git a/packages/block-library/src/widget-area/edit/index.js b/packages/edit-widgets/src/blocks/widget-area/edit/index.js similarity index 100% rename from packages/block-library/src/widget-area/edit/index.js rename to packages/edit-widgets/src/blocks/widget-area/edit/index.js diff --git a/packages/block-library/src/widget-area/edit/inner-blocks.js b/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js similarity index 100% rename from packages/block-library/src/widget-area/edit/inner-blocks.js rename to packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js diff --git a/packages/block-library/src/widget-area/editor.scss b/packages/edit-widgets/src/blocks/widget-area/editor.scss similarity index 100% rename from packages/block-library/src/widget-area/editor.scss rename to packages/edit-widgets/src/blocks/widget-area/editor.scss diff --git a/packages/block-library/src/widget-area/index.js b/packages/edit-widgets/src/blocks/widget-area/index.js similarity index 100% rename from packages/block-library/src/widget-area/index.js rename to packages/edit-widgets/src/blocks/widget-area/index.js diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index c3d5b063c86af2..b4a958d0fb104e 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -18,6 +18,7 @@ import { import './store'; import './hooks'; import { create as createLegacyWidget } from './blocks/legacy-widget'; +import * as widgetArea from './blocks/widget-area'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; /** @@ -31,6 +32,7 @@ export function initialize( id, settings ) { if ( process.env.GUTENBERG_PHASE === 2 ) { __experimentalRegisterExperimentalCoreBlocks( settings ); registerBlock( createLegacyWidget( settings ) ); + registerBlock( widgetArea ); } render( , diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 0b1af92ed323a3..2454b1e3356758 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -1,6 +1,7 @@ @import "../../interface/src/style.scss"; @import "./blocks/legacy-widget/editor.scss"; +@import "./blocks/widget-area/editor.scss"; @import "./components/header/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/notices/style.scss"; From b3a6f4db276e564a47f2c9ed32867ec1afae6ca2 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 29 Sep 2020 16:13:26 +0800 Subject: [PATCH 09/61] Use h3 in the legacy widget title (#25690) --- .../edit-widgets/src/blocks/legacy-widget/edit/handler.js | 4 ++-- packages/edit-widgets/src/blocks/legacy-widget/editor.scss | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js index e4fb1843ecca0f..af0822bd54c4f7 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js @@ -96,9 +96,9 @@ class LegacyWidgetEditHandler extends Component { { ...componentProps } > { title && ( -
+

{ title } -

+ ) }
Date: Tue, 29 Sep 2020 12:19:12 +0300 Subject: [PATCH 10/61] Show PostFeaturedImage in editor (#25412) * show PostFeaturedImage in editor * change wording * add placeholder chip --- packages/block-library/src/editor.scss | 1 + .../src/post-featured-image/block.json | 3 +- .../src/post-featured-image/edit.js | 35 ++++++++++++------- .../src/post-featured-image/editor.scss | 19 ++++++++++ .../src/responsive-wrapper/style.scss | 4 +++ 5 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 packages/block-library/src/post-featured-image/editor.scss diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index ee185fde8bdc92..1f7387b6b89bb1 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -51,6 +51,7 @@ @import "./video/editor.scss"; @import "./query-loop/editor.scss"; @import "./query/editor.scss"; +@import "./post-featured-image/editor.scss"; /** * Import styles from internal editor components used by the blocks. diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 106cdc3c76f510..15506f7ce33c40 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -2,7 +2,8 @@ "name": "core/post-featured-image", "category": "design", "usesContext": [ - "postId" + "postId", + "postType" ], "supports": { "html": false diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js index c244a3b83b9344..5ae60aa2999850 100644 --- a/packages/block-library/src/post-featured-image/edit.js +++ b/packages/block-library/src/post-featured-image/edit.js @@ -1,35 +1,46 @@ /** * WordPress dependencies */ -import { useEntityProp, useEntityId } from '@wordpress/core-data'; +import { useEntityProp } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { ResponsiveWrapper } from '@wordpress/components'; +import { ResponsiveWrapper, Icon } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { postFeaturedImage as icon } from '@wordpress/icons'; -function PostFeaturedImageDisplay() { +function PostFeaturedImageDisplay( { context: { postId, postType } } ) { const [ featuredImage ] = useEntityProp( 'postType', - 'post', - 'featured_media' + postType, + 'featured_media', + postId ); const media = useSelect( ( select ) => featuredImage && select( 'core' ).getMedia( featuredImage ), [ featuredImage ] ); - return media ? ( + if ( ! media ) { + return ( +
+ +

{ __( 'Featured Image' ) }

+
+ ); + } + const alt = media.alt_text || __( 'No alternative text set' ); + return ( - Post Featured Media + { - ) : null; + ); } -export default function PostFeaturedImageEdit() { - if ( ! useEntityId( 'postType', 'post' ) ) { - return __( 'Post Featured Image' ); +export default function PostFeaturedImageEdit( props ) { + if ( ! props.context?.postId ) { + return __( 'Featured Image' ); } - return ; + return ; } diff --git a/packages/block-library/src/post-featured-image/editor.scss b/packages/block-library/src/post-featured-image/editor.scss new file mode 100644 index 00000000000000..99bc213a4170a2 --- /dev/null +++ b/packages/block-library/src/post-featured-image/editor.scss @@ -0,0 +1,19 @@ +div[data-type="core/post-featured-image"] { + .post-featured-image_placeholder { + display: flex; + flex-direction: row; + align-items: flex-start; + border-radius: $radius-block-ui; + background-color: $white; + box-shadow: inset 0 0 0 $border-width $gray-900; + padding: $grid-unit-15; + svg { + margin-right: $grid-unit-15; + } + p { + font-family: $default-font; + font-size: $default-font-size; + margin: 0; + } + } +} diff --git a/packages/components/src/responsive-wrapper/style.scss b/packages/components/src/responsive-wrapper/style.scss index b6a556bf3425d0..35b65c8637d597 100644 --- a/packages/components/src/responsive-wrapper/style.scss +++ b/packages/components/src/responsive-wrapper/style.scss @@ -5,6 +5,10 @@ & > span { display: block; } + &, + & > img { + width: auto; + } } .components-responsive-wrapper__content { From eba79f121b8455abb302500e25fece3c0031ece2 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Tue, 29 Sep 2020 12:17:38 +0200 Subject: [PATCH 11/61] Echo before/after_widget in block widget render method (#25693) --- lib/class-wp-widget-block.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index e29797d4271b2c..0e1bca7335841f 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -53,7 +53,9 @@ public function __construct() { * @global WP_Post $post Global post object. */ public function widget( $args, $instance ) { + echo $args['before_widget']; echo do_blocks( $instance['content'] ); + echo $args['after_widget']; } /** From f8f86075e5c323a0e215545d595c48a6c3c08d37 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 29 Sep 2020 13:18:33 +0300 Subject: [PATCH 12/61] Update and move some Query filters (#25674) * update/move Query filters * Change max fetched terms limit * get default posts_per_page from options * fix php linting * Update InputControl to align label/control to the edges and have custom widths. * fallback for undefined categoryIds Co-authored-by: Jon Q --- lib/edit-site-page.php | 1 + .../test/__snapshots__/index.js.snap | 2 +- .../src/components/unit-control/README.md | 2 +- packages/block-library/src/query-loop/edit.js | 6 +- packages/block-library/src/query/block.json | 2 +- packages/block-library/src/query/constants.js | 2 + .../block-library/src/query/edit/index.js | 14 ++ .../query/edit/query-inspector-controls.js | 82 ++++++++- .../src/query/edit/query-toolbar.js | 157 +++++------------- .../components/src/input-control/README.md | 2 +- .../components/src/input-control/index.js | 2 + .../src/input-control/input-base.js | 5 + .../styles/input-control-styles.js | 46 +++-- .../components/src/number-control/README.md | 2 +- .../components/src/unit-control/README.md | 2 +- .../fixtures/blocks/core__query.json | 2 +- 16 files changed, 189 insertions(+), 140 deletions(-) diff --git a/lib/edit-site-page.php b/lib/edit-site-page.php index 689e628c6eeae7..83089981f59756 100644 --- a/lib/edit-site-page.php +++ b/lib/edit-site-page.php @@ -131,6 +131,7 @@ function gutenberg_edit_site_init( $hook ) { 'isRTL' => is_rtl(), 'maxUploadFileSize' => $max_upload_size, 'siteUrl' => site_url(), + 'postsPerPage' => get_option( 'posts_per_page' ), ); $settings['styles'] = gutenberg_get_editor_styles(); diff --git a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap index e0233344f6352d..08c0a5a88f75b6 100644 --- a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; +exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; diff --git a/packages/block-editor/src/components/unit-control/README.md b/packages/block-editor/src/components/unit-control/README.md index 89e66b7f3a444d..4d99d85254decb 100644 --- a/packages/block-editor/src/components/unit-control/README.md +++ b/packages/block-editor/src/components/unit-control/README.md @@ -71,7 +71,7 @@ If this property is added, a label will be generated using label property as the #### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/block-library/src/query-loop/edit.js b/packages/block-library/src/query-loop/edit.js index 6e2113b496bbf0..fbf92894f03cc0 100644 --- a/packages/block-library/src/query-loop/edit.js +++ b/packages/block-library/src/query-loop/edit.js @@ -15,7 +15,11 @@ import { */ import { useQueryContext } from '../query'; -const TEMPLATE = [ [ 'core/post-title' ], [ 'core/post-content' ] ]; +const TEMPLATE = [ + [ 'core/post-title' ], + [ 'core/post-date' ], + [ 'core/post-excerpt' ], +]; export default function QueryLoopEdit( { clientId, context: { diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index 63d489ad0d5525..3e6244bc67be91 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -8,7 +8,7 @@ "query": { "type": "object", "default": { - "perPage": 3, + "perPage": null, "pages": 1, "offset": 0, "categoryIds": [], diff --git a/packages/block-library/src/query/constants.js b/packages/block-library/src/query/constants.js index 311232afe3adcc..9f606c1c6920c3 100644 --- a/packages/block-library/src/query/constants.js +++ b/packages/block-library/src/query/constants.js @@ -1,5 +1,7 @@ export const MAX_FETCHED_TERMS = 100; +export const DEFAULTS_POSTS_PER_PAGE = 3; export default { MAX_FETCHED_TERMS, + DEFAULTS_POSTS_PER_PAGE, }; diff --git a/packages/block-library/src/query/edit/index.js b/packages/block-library/src/query/edit/index.js index e43c16074346d2..b0580889d5601c 100644 --- a/packages/block-library/src/query/edit/index.js +++ b/packages/block-library/src/query/edit/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { useSelect } from '@wordpress/data'; import { useInstanceId } from '@wordpress/compose'; import { useEffect } from '@wordpress/element'; import { @@ -15,6 +16,7 @@ import { import QueryToolbar from './query-toolbar'; import QueryProvider from './query-provider'; import QueryInspectorControls from './query-inspector-controls'; +import { DEFAULTS_POSTS_PER_PAGE } from '../constants'; const TEMPLATE = [ [ 'core/query-loop' ], [ 'core/query-pagination' ] ]; export default function QueryEdit( { @@ -23,6 +25,18 @@ export default function QueryEdit( { } ) { const instanceId = useInstanceId( QueryEdit ); const blockWrapperProps = useBlockWrapperProps(); + const { postsPerPage } = useSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + return { + postsPerPage: + +getSettings().postsPerPage || DEFAULTS_POSTS_PER_PAGE, + }; + }, [] ); + useEffect( () => { + if ( ! query.perPage && postsPerPage ) { + updateQuery( { perPage: postsPerPage } ); + } + }, [ query.perPage ] ); // We need this for multi-query block pagination. // Query parameters for each block are scoped to their ID. useEffect( () => { diff --git a/packages/block-library/src/query/edit/query-inspector-controls.js b/packages/block-library/src/query/edit/query-inspector-controls.js index ec6a28d6721897..632a80ce4a4c26 100644 --- a/packages/block-library/src/query/edit/query-inspector-controls.js +++ b/packages/block-library/src/query/edit/query-inspector-controls.js @@ -1,19 +1,68 @@ +/** + * External dependencies + */ +import { debounce } from 'lodash'; + /** * WordPress dependencies */ -import { PanelBody, QueryControls } from '@wordpress/components'; + +import { + PanelBody, + QueryControls, + TextControl, + FormTokenField, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; +import { useEffect, useState, useCallback } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { getTermsInfo } from '../utils'; +import { MAX_FETCHED_TERMS } from '../constants'; export default function QueryInspectorControls( { query, setQuery } ) { const { order, orderBy, author: selectedAuthorId } = query; - const { authorList } = useSelect( ( select ) => { + const { authorList, categories, tags } = useSelect( ( select ) => { const { getEntityRecords } = select( 'core' ); + const termsQuery = { per_page: MAX_FETCHED_TERMS }; + const _categories = getEntityRecords( + 'taxonomy', + 'category', + termsQuery + ); + const _tags = getEntityRecords( 'taxonomy', 'post_tag', termsQuery ); return { + categories: getTermsInfo( _categories ), + tags: getTermsInfo( _tags ), authorList: getEntityRecords( 'root', 'user', { per_page: -1 } ), }; }, [] ); + + // Handles categories and tags changes. + const onTermsChange = ( terms, queryProperty ) => ( newTermValues ) => { + const termIds = newTermValues.reduce( ( accumulator, termValue ) => { + const termId = termValue?.id || terms.mapByName[ termValue ]?.id; + if ( termId ) accumulator.push( termId ); + return accumulator; + }, [] ); + setQuery( { [ queryProperty ]: termIds } ); + }; + const onCategoriesChange = onTermsChange( categories, 'categoryIds' ); + const onTagsChange = onTermsChange( tags, 'tagIds' ); + + const [ querySearch, setQuerySearch ] = useState( query.search ); + const onChangeDebounced = useCallback( + debounce( () => setQuery( { search: querySearch } ), 250 ), + [ querySearch ] + ); + useEffect( () => { + onChangeDebounced(); + return onChangeDebounced.cancel; + }, [ querySearch, onChangeDebounced ] ); return ( @@ -29,6 +78,35 @@ export default function QueryInspectorControls( { query, setQuery } ) { } ) } /> + { categories?.terms?.length > 0 && ( + ( { + id: categoryId, + value: categories.mapById[ categoryId ].name, + } ) + ) } + suggestions={ categories.names } + onChange={ onCategoriesChange } + /> + ) } + { tags?.terms?.length > 0 && ( + ( { + id: tagId, + value: tags.mapById[ tagId ].name, + } ) ) } + suggestions={ tags.names } + onChange={ onTagsChange } + /> + ) } + ); diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index 3436348975e240..8d519befd697c1 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -1,70 +1,22 @@ -/** - * External dependencies - */ -import { debounce } from 'lodash'; - /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; -import { useEffect, useState, useCallback } from '@wordpress/element'; import { - Toolbar, + ToolbarGroup, Dropdown, ToolbarButton, RangeControl, - TextControl, - FormTokenField, + BaseControl, + __experimentalNumberControl as NumberControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { postList } from '@wordpress/icons'; -/** - * Internal dependencies - */ -import { getTermsInfo } from '../utils'; -import { MAX_FETCHED_TERMS } from '../constants'; - export default function QueryToolbar( { query, setQuery } ) { - const { categories, tags } = useSelect( ( select ) => { - const { getEntityRecords } = select( 'core' ); - const termsQuery = { per_page: MAX_FETCHED_TERMS }; - const _categories = getEntityRecords( - 'taxonomy', - 'category', - termsQuery - ); - const _tags = getEntityRecords( 'taxonomy', 'post_tag', termsQuery ); - return { - categories: getTermsInfo( _categories ), - tags: getTermsInfo( _tags ), - }; - }, [] ); - const [ querySearch, setQuerySearch ] = useState( query.search ); - const onChangeDebounced = useCallback( - debounce( () => setQuery( { search: querySearch } ), 250 ), - [ querySearch ] - ); - useEffect( () => { - onChangeDebounced(); - return onChangeDebounced.cancel; - }, [ querySearch, onChangeDebounced ] ); - - // Handles categories and tags changes. - const onTermsChange = ( terms, queryProperty ) => ( newTermValues ) => { - const termIds = newTermValues.reduce( ( accumulator, termValue ) => { - const termId = termValue?.id || terms.mapByName[ termValue ]?.id; - if ( termId ) accumulator.push( termId ); - return accumulator; - }, [] ); - setQuery( { [ queryProperty ]: termIds } ); - }; - const onCategoriesChange = onTermsChange( categories, 'categoryIds' ); - const onTagsChange = onTermsChange( tags, 'tagIds' ); - return ( - + ( ( <> - - setQuery( { perPage: value ?? -1 } ) - } - /> - - setQuery( { pages: value ?? -1 } ) - } - /> - - setQuery( { offset: value ?? 0 } ) - } - /> - { categories?.terms && ( - ( { - id: categoryId, - value: - categories.mapById[ categoryId ] - .name, - } ) - ) } - suggestions={ categories.names } - onChange={ onCategoriesChange } + + + setQuery( { perPage: +value ?? -1 } ) + } + step="1" + value={ query.perPage } + isDragEnabled={ false } + /> + + + + setQuery( { offset: +value } ) + } + step="1" + value={ query.offset } + isDragEnabled={ false } /> - ) } - { tags?.terms && ( - ( { - id: tagId, - value: tags.mapById[ tagId ].name, - } ) - ) } - suggestions={ tags.names } - onChange={ onTagsChange } + + + + setQuery( { pages: value ?? -1 } ) + } /> - ) } - setQuerySearch( value ) } - /> + ) } /> - + ); } diff --git a/packages/components/src/input-control/README.md b/packages/components/src/input-control/README.md index 6af3626a09f1dd..fef516a7a3f187 100644 --- a/packages/components/src/input-control/README.md +++ b/packages/components/src/input-control/README.md @@ -54,7 +54,7 @@ If this property is added, a label will be generated using label property as the ### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js index e56e86e8a384b5..56eb23d950f704 100644 --- a/packages/components/src/input-control/index.js +++ b/packages/components/src/input-control/index.js @@ -26,6 +26,7 @@ function useUniqueId( idProp ) { export function InputControl( { __unstableStateReducer: stateReducer = ( state ) => state, + __unstableInputWidth, className, disabled = false, hideLabelFromVision = false, @@ -51,6 +52,7 @@ export function InputControl( return ( @@ -60,9 +63,11 @@ export function InputBase( { prefix && ( diff --git a/packages/components/src/input-control/styles/input-control-styles.js b/packages/components/src/input-control/styles/input-control-styles.js index cd57f1ef4ca308..ca48b2d32d7ace 100644 --- a/packages/components/src/input-control/styles/input-control-styles.js +++ b/packages/components/src/input-control/styles/input-control-styles.js @@ -33,6 +33,10 @@ const rootLabelPositionStyles = ( { labelPosition } ) => { align-items: flex-start; flex-direction: column-reverse; `; + case 'edge': + return css` + justify-content: space-between; + `; default: return ''; } @@ -42,9 +46,9 @@ export const Root = styled( Flex )` position: relative; border-radius: 2px; - ${ rootFloatLabelStyles }; - ${ rootFocusedStyles }; - ${ rootLabelPositionStyles }; + ${ rootFloatLabelStyles } + ${ rootFocusedStyles } + ${ rootLabelPositionStyles } `; const containerDisabledStyles = ( { disabled } ) => { @@ -55,12 +59,18 @@ const containerDisabledStyles = ( { disabled } ) => { return css( { backgroundColor } ); }; -const containerWidthStyles = ( { labelPosition } ) => { +const containerWidthStyles = ( { __unstableInputWidth, labelPosition } ) => { + if ( ! __unstableInputWidth ) return css( { width: '100%' } ); + if ( labelPosition === 'side' ) return ''; - return css` - width: 100%; - `; + if ( labelPosition === 'edge' ) { + return css( { + flex: `0 0 ${ __unstableInputWidth }`, + } ); + } + + return css( { width: __unstableInputWidth } ); }; export const Container = styled.div` @@ -71,8 +81,8 @@ export const Container = styled.div` flex: 1; position: relative; - ${ containerDisabledStyles }; - ${ containerWidthStyles }; + ${ containerDisabledStyles } + ${ containerWidthStyles } `; const disabledStyles = ( { disabled } ) => { @@ -156,8 +166,8 @@ const dragStyles = ( { isDragging, dragCursor } ) => { } return css` - ${ defaultArrowStyles }; - ${ activeDragCursorStyles }; + ${ defaultArrowStyles } + ${ activeDragCursorStyles } `; }; @@ -178,12 +188,12 @@ export const Input = styled.input` padding-right: 8px; width: 100%; - ${ dragStyles }; - ${ disabledStyles }; - ${ fontSizeStyles }; - ${ sizeStyles }; + ${ dragStyles } + ${ disabledStyles } + ${ fontSizeStyles } + ${ sizeStyles } - ${ placeholderStyles }; + ${ placeholderStyles } } `; @@ -206,7 +216,7 @@ const BaseLabel = styled( Text )` padding-top: 0; z-index: 1; - ${ labelTruncation }; + ${ labelTruncation } } `; @@ -252,7 +262,7 @@ export const BackdropUI = styled.div` right: 0; top: 0; - ${ backdropFocusedStyles }; + ${ backdropFocusedStyles } ${ rtl( { paddingLeft: 2 } ) } } `; diff --git a/packages/components/src/number-control/README.md b/packages/components/src/number-control/README.md index 36b21f63a40a83..efb77dd18a40a8 100644 --- a/packages/components/src/number-control/README.md +++ b/packages/components/src/number-control/README.md @@ -72,7 +72,7 @@ If this property is added, a label will be generated using label property as the ### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/components/src/unit-control/README.md b/packages/components/src/unit-control/README.md index ad60d0abd445ca..b024e6aaf5e5ee 100644 --- a/packages/components/src/unit-control/README.md +++ b/packages/components/src/unit-control/README.md @@ -50,7 +50,7 @@ If this property is added, a label will be generated using label property as the ### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/e2e-tests/fixtures/blocks/core__query.json b/packages/e2e-tests/fixtures/blocks/core__query.json index c225bc2053ea8a..3848bc06e77a76 100644 --- a/packages/e2e-tests/fixtures/blocks/core__query.json +++ b/packages/e2e-tests/fixtures/blocks/core__query.json @@ -5,7 +5,7 @@ "isValid": true, "attributes": { "query": { - "perPage": 3, + "perPage": null, "pages": 1, "offset": 0, "categoryIds": [], From a193029e630ba1ff6140481c220ca33fade5d7a5 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 29 Sep 2020 15:21:06 +0300 Subject: [PATCH 13/61] Remove myself from codeowners (#25696) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2773fa8ea7581a..2832c860de002a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,7 +12,7 @@ # Blocks /packages/block-library @Soean @ajitbohra @talldan -/packages/block-library/src/gallery @mkevins @pinarol +/packages/block-library/src/gallery @mkevins /packages/block-library/src/social-links @mkaz /packages/block-library/src/social-link @mkaz /packages/block-library/src/image @ajlende From 4c04c6e91da206c4665ea36ab005aa4cb4798c36 Mon Sep 17 00:00:00 2001 From: Jacopo Tomasone Date: Tue, 29 Sep 2020 15:25:02 +0200 Subject: [PATCH 14/61] Site Editor: Update Navigation Panel Toggle UI (#25622) Update the toggle UI and the back navigation of the Site Editor navigation panel. --- .../src/navigation/back-button/index.js | 17 +++--- .../header/navigation-toggle/index.js | 4 +- .../header/navigation-toggle/style.scss | 57 +++++++++++++------ .../left-sidebar/navigation-panel/index.js | 21 ++++--- .../left-sidebar/navigation-panel/style.scss | 27 ++++++++- 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/packages/components/src/navigation/back-button/index.js b/packages/components/src/navigation/back-button/index.js index 3752dcfa635f7e..c4e10ab6f157be 100644 --- a/packages/components/src/navigation/back-button/index.js +++ b/packages/components/src/navigation/back-button/index.js @@ -6,6 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { forwardRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon, chevronLeft } from '@wordpress/icons'; @@ -15,13 +16,10 @@ import { Icon, chevronLeft } from '@wordpress/icons'; import { useNavigationContext } from '../context'; import { MenuBackButtonUI } from '../styles/navigation-styles'; -export default function NavigationBackButton( { - backButtonLabel, - className, - href, - onClick, - parentMenu, -} ) { +function NavigationBackButton( + { backButtonLabel, className, href, onClick, parentMenu }, + ref +) { const { setActiveMenu, navigationTree } = useNavigationContext(); const classes = classnames( @@ -34,14 +32,17 @@ export default function NavigationBackButton( { return ( parentMenu ? setActiveMenu( parentMenu, 'right' ) : onClick } + ref={ ref } > { backButtonLabel || parentMenuTitle || __( 'Back' ) } ); } + +export default forwardRef( NavigationBackButton ); diff --git a/packages/edit-site/src/components/header/navigation-toggle/index.js b/packages/edit-site/src/components/header/navigation-toggle/index.js index 8a382823fe5508..59107570866113 100644 --- a/packages/edit-site/src/components/header/navigation-toggle/index.js +++ b/packages/edit-site/src/components/header/navigation-toggle/index.js @@ -35,7 +35,7 @@ function NavigationToggle( { icon, isOpen, onClick } ) { return null; } - let buttonIcon = ; + let buttonIcon = ; if ( siteIconUrl ) { buttonIcon = ( @@ -48,7 +48,7 @@ function NavigationToggle( { icon, isOpen, onClick } ) { } else if ( isRequestingSiteIcon ) { buttonIcon = null; } else if ( icon ) { - buttonIcon = ; + buttonIcon = ; } return ( diff --git a/packages/edit-site/src/components/header/navigation-toggle/style.scss b/packages/edit-site/src/components/header/navigation-toggle/style.scss index d09ba381b481ed..1cabaef56c0c59 100644 --- a/packages/edit-site/src/components/header/navigation-toggle/style.scss +++ b/packages/edit-site/src/components/header/navigation-toggle/style.scss @@ -2,43 +2,64 @@ display: none; @include break-medium() { - display: flex; align-items: center; - background-color: #1e1e1e; - height: 61px; + background: $gray-900; border-radius: 0; + display: flex; + height: $header-height + $border-width; // Cover header border + width: $header-height; } } .edit-site-navigation-toggle.is-open { + flex-shrink: 0; + height: $header-height + $border-width; // Cover header border width: 300px; + + // Delay to sync with `NavigationPanel` animation + transition: width 80ms linear; + transition-delay: 20ms; + @include reduce-motion("transition"); + + .edit-site-navigation-toggle__button { + background: $gray-900; + } } .edit-site-navigation-toggle__button { - color: #fff; - margin-left: 14px; - margin-right: 14px; + align-items: center; + background: #23282e; // WP-admin gray. + color: $white; + height: $header-height + $border-width; // Cover header border + width: $header-height; + + &.has-icon { + min-width: $header-height; - &:hover { - color: #ddd; + &:hover { + background: #32373d; // WP-admin light-gray. + color: $white; + } + &:active { + color: $white; + } + &:focus { + box-shadow: inset 0 0 0 $border-width-focus var(--wp-admin-theme-color), inset 0 0 0 3px $white; + } } } -.edit-site-navigation-toggle.components-button.has-icon { - justify-content: flex-start; - padding: 0; - height: 32px; - width: 32px; - min-width: 32px; +.edit-site-navigation-toggle__site-icon { + width: 36px; } .edit-site-navigation-toggle__site-title { font-style: normal; font-weight: 600; - font-size: 13px; - line-height: 16px; - color: #ddd; - margin-right: 14px; + font-size: $default-font-size; + line-height: $default-line-height; + color: $gray-300; + margin: 0 $grid-unit-20 0 $grid-unit-10; display: -webkit-box; -webkit-line-clamp: 2; diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js index 9f3e2c5c120ac2..2e48ec5e041121 100644 --- a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js @@ -1,29 +1,36 @@ /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { __experimentalNavigation as Navigation, + __experimentalNavigationBackButton as NavigationBackButton, __experimentalNavigationGroup as NavigationGroup, __experimentalNavigationItem as NavigationItem, __experimentalNavigationMenu as NavigationMenu, } from '@wordpress/components'; import { getBlockType, getBlockFromExample } from '@wordpress/blocks'; import { BlockPreview } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; const NavigationPanel = () => { const [ showPreview, setShowPreview ] = useState( false ); + const ref = useRef(); + + useEffect( () => { + ref.current.focus(); + }, [ ref ] ); return (
+ - - Date: Tue, 29 Sep 2020 16:13:48 +0200 Subject: [PATCH 15/61] Site Editor: Add template switcher to navigation panel (#25615) --- packages/e2e-tests/experimental-features.js | 11 + .../experiments/multi-entity-editing.test.js | 21 +- .../experiments/multi-entity-saving.test.js | 9 +- .../specs/experiments/template-part.test.js | 13 +- .../edit-site/src/components/header/index.js | 23 -- .../left-sidebar/navigation-panel/index.js | 80 ++++--- .../left-sidebar/navigation-panel/style.scss | 24 ++ .../template-switcher/index.js | 206 +++++++++++++++++ .../template-switcher/style.scss | 45 ++++ .../template-switcher/template-preview.js | 35 +++ .../template-switcher/theme-preview.js | 13 +- .../src/components/template-switcher/index.js | 213 ------------------ .../components/template-switcher/style.scss | 67 ------ .../template-switcher/template-preview.js | 33 --- packages/edit-site/src/style.scss | 2 +- 15 files changed, 398 insertions(+), 397 deletions(-) create mode 100644 packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js create mode 100644 packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss create mode 100644 packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js rename packages/edit-site/src/components/{ => left-sidebar/navigation-panel}/template-switcher/theme-preview.js (70%) delete mode 100644 packages/edit-site/src/components/template-switcher/index.js delete mode 100644 packages/edit-site/src/components/template-switcher/style.scss delete mode 100644 packages/edit-site/src/components/template-switcher/template-preview.js diff --git a/packages/e2e-tests/experimental-features.js b/packages/e2e-tests/experimental-features.js index 3c33f930012dde..2a270a8431b066 100644 --- a/packages/e2e-tests/experimental-features.js +++ b/packages/e2e-tests/experimental-features.js @@ -37,3 +37,14 @@ export function useExperimentalFeatures( features ) { beforeAll( () => setExperimentalFeaturesState( features, true ) ); afterAll( () => setExperimentalFeaturesState( features, false ) ); } + +export const openNavigation = async () => { + const isOpen = !! ( await page.$( + '.edit-site-navigation-toggle.is-open' + ) ); + + if ( ! isOpen ) { + await page.click( '.edit-site-navigation-toggle__button' ); + await page.waitForSelector( '.edit-site-navigation-panel' ); + } +}; diff --git a/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js b/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js index df8aa129a8e7d7..6804e87d8759a0 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js @@ -18,7 +18,10 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useExperimentalFeatures } from '../../experimental-features'; +import { + useExperimentalFeatures, + openNavigation, +} from '../../experimental-features'; const visitSiteEditor = async () => { const query = addQueryArgs( '', { @@ -31,19 +34,11 @@ const visitSiteEditor = async () => { ); }; -const openTemplateDropdown = async () => { - // Open the dropdown menu. - const templateDropdown = - 'button.components-dropdown-menu__toggle[aria-label="Switch Template"]'; - await page.click( templateDropdown ); - await page.waitForSelector( '.edit-site-template-switcher__popover' ); -}; - const getTemplateDropdownElement = async ( itemName ) => { - await openTemplateDropdown(); - const [ item ] = await page.$x( - `//div[contains(@class, "edit-site-template-switcher__popover")]//button[contains(., "${ itemName }")]` - ); + await openNavigation(); + const selector = `//div[contains(@class, "edit-site-navigation-panel")]//button[contains(., "${ itemName }")]`; + await page.waitForXPath( selector ); + const [ item ] = await page.$x( selector ); return item; }; diff --git a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js index 43bb2eb7c27e74..af8cb346e19f83 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js @@ -13,7 +13,10 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useExperimentalFeatures } from '../../experimental-features'; +import { + useExperimentalFeatures, + openNavigation, +} from '../../experimental-features'; describe( 'Multi-entity save flow', () => { // Selectors - usable between Post/Site editors. @@ -222,8 +225,6 @@ describe( 'Multi-entity save flow', () => { const saveSiteSelector = '.edit-site-save-button__button'; const activeSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=false]`; const disabledSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=true]`; - const templateDropdownSelector = - '.components-dropdown-menu__toggle[aria-label="Switch Template"]'; const saveA11ySelector = '.edit-site-editor__toggle-save-panel-button'; it( 'Should be enabled after edits', async () => { @@ -234,7 +235,7 @@ describe( 'Multi-entity save flow', () => { await visitAdminPage( 'admin.php', query ); // Ensure we are on 'front-page' demo template. - await page.click( templateDropdownSelector ); + await openNavigation(); const demoTemplateButton = await page.waitForXPath( demoTemplateSelector ); diff --git a/packages/e2e-tests/specs/experiments/template-part.test.js b/packages/e2e-tests/specs/experiments/template-part.test.js index f18f13e3e87249..dfec46717a562d 100644 --- a/packages/e2e-tests/specs/experiments/template-part.test.js +++ b/packages/e2e-tests/specs/experiments/template-part.test.js @@ -13,7 +13,10 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useExperimentalFeatures } from '../../experimental-features'; +import { + useExperimentalFeatures, + openNavigation, +} from '../../experimental-features'; describe( 'Template Part', () => { useExperimentalFeatures( [ @@ -43,9 +46,7 @@ describe( 'Template Part', () => { it( 'Should load customizations when in a template even if only the slug and theme attributes are set.', async () => { // Switch to editing the header template part. - await page.click( - '.components-dropdown-menu__toggle[aria-label="Switch Template"]' - ); + await openNavigation(); const switchToHeaderTemplatePartButton = await page.waitForXPath( '//button[contains(text(), "header")]' ); @@ -63,9 +64,7 @@ describe( 'Template Part', () => { ); // Switch back to the front page template. - await page.click( - '.components-dropdown-menu__toggle[aria-label="Switch Template"]' - ); + await openNavigation(); const [ switchToFrontPageTemplateButton ] = await page.$x( '//button[contains(text(), "front-page")]' ); diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index 9f1b861b190d0a..5f74540f02d575 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -22,7 +22,6 @@ import { Button } from '@wordpress/components'; */ import MoreMenu from './more-menu'; import PageSwitcher from '../page-switcher'; -import TemplateSwitcher from '../template-switcher'; import SaveButton from '../save-button'; import UndoButton from './undo-redo/undo'; import RedoButton from './undo-redo/redo'; @@ -40,9 +39,6 @@ export default function Header( { deviceType, hasFixedToolbar, template, - templateId, - templatePartId, - templateType, page, showOnFront, } = useSelect( ( select ) => { @@ -76,10 +72,6 @@ export default function Header( { const { __experimentalSetPreviewDeviceType: setPreviewDeviceType, - setTemplate, - addTemplate, - removeTemplate, - setTemplatePart, setPage, } = useDispatch( 'core/edit-site' ); @@ -123,21 +115,6 @@ export default function Header( { activePage={ page } onActivePageChange={ setPage } /> -
- / -
-
diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js index 2e48ec5e041121..36464bcab8d9ec 100644 --- a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js @@ -1,60 +1,80 @@ /** * WordPress dependencies */ -import { useEffect, useRef, useState } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { __experimentalNavigation as Navigation, - __experimentalNavigationBackButton as NavigationBackButton, - __experimentalNavigationGroup as NavigationGroup, - __experimentalNavigationItem as NavigationItem, __experimentalNavigationMenu as NavigationMenu, + __experimentalNavigationBackButton as NavigationBackButton, } from '@wordpress/components'; -import { getBlockType, getBlockFromExample } from '@wordpress/blocks'; -import { BlockPreview } from '@wordpress/block-editor'; +import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import TemplateSwitcher from './template-switcher'; + const NavigationPanel = () => { - const [ showPreview, setShowPreview ] = useState( false ); const ref = useRef(); useEffect( () => { ref.current.focus(); }, [ ref ] ); + const { templateId, templatePartId, templateType, page } = useSelect( + ( select ) => { + const { + getTemplateId, + getTemplatePartId, + getTemplateType, + getPage, + } = select( 'core/edit-site' ); + + return { + templateId: getTemplateId(), + templatePartId: getTemplatePartId(), + templateType: getTemplateType(), + page: getPage(), + }; + }, + [] + ); + + const { + setTemplate, + addTemplate, + removeTemplate, + setTemplatePart, + } = useDispatch( 'core/edit-site' ); + return (
- + + - - setShowPreview( true ) } - onMouseLeave={ () => setShowPreview( false ) } - /> - + - - { showPreview && ( -
- -
- ) }
); }; diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss b/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss index 5265d5d982514a..ae871bbf5b7ab0 100644 --- a/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss @@ -17,6 +17,29 @@ .components-navigation { height: 100%; } + + .components-navigation__item { + position: relative; + + .edit-site-template-switcher__label-home-icon svg path { + color: inherit; + } + + &.is-active { + .edit-site-template-switcher__label-customized-dot { + background: #fff; + } + } + } + + .edit-site-template-switcher__label-home-icon { + top: 50%; + transform: translateY(-50%); + } + + .edit-site-template-switcher__label-customized-dot { + right: 8px; + } } .edit-site-navigation-panel__back-to-dashboard.components-button.is-tertiary { @@ -44,6 +67,7 @@ position: absolute; top: $grid-unit-15; left: calc(100% + #{$grid-unit-15}); + color: $gray-900; @include break-medium { display: block; diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js new file mode 100644 index 00000000000000..570ce7640e4383 --- /dev/null +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js @@ -0,0 +1,206 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { + Button, + Tooltip, + __experimentalNavigationGroup as NavigationGroup, + __experimentalNavigationItem as NavigationItem, +} from '@wordpress/components'; +import { Icon, home, plus, undo } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import TemplatePreview from './template-preview'; +import ThemePreview from './theme-preview'; + +const TEMPLATE_OVERRIDES = { + page: ( slug ) => `page-${ slug }`, + category: ( slug ) => `category-${ slug }`, + post: ( slug ) => `single-post-${ slug }`, +}; + +function TemplateNavigationItemWithIcon( { + item, + icon, + iconLabel, + homeId, + template, + title, + ...props +} ) { + if ( ! icon && ! iconLabel && template ) { + if ( template.id === homeId ) { + icon = home; + iconLabel = __( 'Home' ); + } else if ( template.status !== 'auto-draft' ) { + icon = ( + + ); + iconLabel = __( 'Customized' ); + } + } + + return ( + + + + ); +} + +export default function TemplateSwitcher( { + page, + activeId, + onActiveIdChange, + onActiveTemplatePartIdChange, + onAddTemplate, + onRemoveTemplate, +} ) { + const [ hoveredTemplatePartId, setHoveredTemplatePartId ] = useState(); + const [ themePreviewVisible, setThemePreviewVisible ] = useState( false ); + + const onMouseEnterTemplatePart = ( id ) => setHoveredTemplatePartId( id ); + const onMouseLeaveTemplatePart = () => setHoveredTemplatePartId( null ); + + const onMouseEnterTheme = () => setThemePreviewVisible( true ); + const onMouseLeaveTheme = () => setThemePreviewVisible( false ); + + const { currentTheme, template, templateParts, homeId } = useSelect( + ( select ) => { + const { + getCurrentTheme, + getEntityRecord, + getEntityRecords, + } = select( 'core' ); + + const _template = getEntityRecord( + 'postType', + 'wp_template', + activeId + ); + + const { getHomeTemplateId } = select( 'core/edit-site' ); + + return { + currentTheme: getCurrentTheme(), + template: _template, + templateParts: _template + ? getEntityRecords( 'postType', 'wp_template_part', { + resolved: true, + template: _template.slug, + } ) + : null, + homeId: getHomeTemplateId(), + }; + }, + [ activeId ] + ); + + const overwriteSlug = + page && + TEMPLATE_OVERRIDES[ page.type ] && + page.slug && + TEMPLATE_OVERRIDES[ page.type ]( page.slug ); + + const overwriteTemplate = () => + onAddTemplate( { + slug: overwriteSlug, + title: overwriteSlug, + status: 'publish', + content: template.content.raw, + } ); + const revertToParent = () => { + onRemoveTemplate( activeId ); + }; + + return ( + <> + + onActiveIdChange( activeId ) } + /> + + { overwriteSlug && + template && + overwriteSlug !== template.slug && ( + + ) } + + { template && overwriteSlug === template.slug && ( + + ) } + + + + { templateParts?.map( ( templatePart ) => { + const key = `template-part-${ templatePart.id }`; + + return ( + + onActiveTemplatePartIdChange( templatePart.id ) + } + onMouseEnter={ () => + onMouseEnterTemplatePart( templatePart.id ) + } + onMouseLeave={ onMouseLeaveTemplatePart } + /> + ); + } ) } + + { ( ! templateParts || templateParts.length === 0 ) && ( + + ) } + + + + + + + { hoveredTemplatePartId && ( + + ) } + + { currentTheme && themePreviewVisible && ( + + ) } + + ); +} diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss new file mode 100644 index 00000000000000..2e0a1fb6d1a43a --- /dev/null +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss @@ -0,0 +1,45 @@ +.edit-site-template-switcher__navigation-item { + position: relative; + + &.is-active { + .edit-site-template-switcher__customized-dot { + background: #fff; + } + } + + .edit-site-template-switcher__navigation-item-icon svg path { + color: inherit; + } +} + +.edit-site-template-switcher__navigation-item-icon { + width: 24px; + height: 24px; + position: absolute; + right: 20px; + top: 50%; + transform: translateY(-50%); +} + +.edit-site-template-switcher__customized-dot { + position: absolute; + right: 8px; + top: 50%; + margin-top: -4px; + width: 8px; + height: 8px; + display: block; + background: var(--wp-admin-theme-color); + border-radius: 50%; +} + +.edit-site-template-switcher__theme-preview-name { + font-weight: 500; + font-size: $big-font-size; +} + +.edit-site-template-switcher__theme-preview-screenshot { + margin-bottom: $grid-unit-15; + margin-top: $grid-unit-15; + max-width: 100%; +} diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js new file mode 100644 index 00000000000000..56193a6bef08e0 --- /dev/null +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { parse } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { BlockPreview } from '@wordpress/block-editor'; +import { useMemo } from '@wordpress/element'; + +export default function TemplatePreview( { entityId } ) { + const template = useSelect( + ( select ) => + select( 'core' ).getEntityRecord( + 'postType', + 'wp_template_part', + entityId + ), + [ entityId ] + ); + + const templateRawContent = template?.content?.raw || ''; + const blocks = useMemo( + () => ( template ? parse( templateRawContent ) : [] ), + [ templateRawContent ] + ); + + if ( ! blocks || blocks.length === 0 ) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/packages/edit-site/src/components/template-switcher/theme-preview.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/theme-preview.js similarity index 70% rename from packages/edit-site/src/components/template-switcher/theme-preview.js rename to packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/theme-preview.js index 0ef8c2068f84af..23d6c2889dc09c 100644 --- a/packages/edit-site/src/components/template-switcher/theme-preview.js +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/theme-preview.js @@ -8,11 +8,11 @@ import { truncate } from 'lodash'; */ import { __, sprintf } from '@wordpress/i18n'; -function ThemePreview( { +export default function ThemePreview( { theme: { author, description, name, screenshot, version }, } ) { return ( -
+
{ truncate( - /* Not using description.rendered here, as we might contain after an opening HTML tag. */ + // We can't use `description.rendered` here because we are truncating the string + // `description.rendered` might contain HTML tags which doesn't play nicely with truncating + // truncate function might truncate in the middle of an HTML tag so we never + // close the HTML tag we are already in description.raw, { length: 120, @@ -47,5 +50,3 @@ function ThemePreview( {
); } - -export default ThemePreview; diff --git a/packages/edit-site/src/components/template-switcher/index.js b/packages/edit-site/src/components/template-switcher/index.js deleted file mode 100644 index a1537253dbe0b2..00000000000000 --- a/packages/edit-site/src/components/template-switcher/index.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; -import { useState } from '@wordpress/element'; -import { - Tooltip, - DropdownMenu, - MenuGroup, - MenuItemsChoice, - MenuItem, -} from '@wordpress/components'; -import { Icon, home, plus, undo } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import TemplatePreview from './template-preview'; -import ThemePreview from './theme-preview'; - -const TEMPLATE_OVERRIDES = { - page: ( slug ) => `page-${ slug }`, - category: ( slug ) => `category-${ slug }`, - post: ( slug ) => `single-post-${ slug }`, -}; - -function TemplateLabel( { template, homeId } ) { - return ( - <> - { template.slug }{ ' ' } - { template.id === homeId && ( - -
- -
-
- ) } - { template.status !== 'auto-draft' && ( - - - - ) } - - ); -} - -export default function TemplateSwitcher( { - page, - activeId, - activeTemplatePartId, - isTemplatePart, - onActiveIdChange, - onActiveTemplatePartIdChange, - onAddTemplate, - onRemoveTemplate, -} ) { - const [ hoveredTemplate, setHoveredTemplate ] = useState(); - const [ themePreviewVisible, setThemePreviewVisible ] = useState( false ); - - const onHoverTemplatePart = ( id ) => { - setHoveredTemplate( { id, type: 'template-part' } ); - }; - const onMouseEnterTheme = () => { - setThemePreviewVisible( () => true ); - }; - const onMouseLeaveTheme = () => { - setThemePreviewVisible( () => false ); - }; - - const { currentTheme, template, templateParts, homeId } = useSelect( - ( select ) => { - const { - getCurrentTheme, - getEntityRecord, - getEntityRecords, - } = select( 'core' ); - - const _template = getEntityRecord( - 'postType', - 'wp_template', - activeId - ); - - const { getHomeTemplateId } = select( 'core/edit-site' ); - - return { - currentTheme: getCurrentTheme(), - template: _template, - templateParts: _template - ? getEntityRecords( 'postType', 'wp_template_part', { - resolved: true, - template: _template.slug, - } ) - : null, - homeId: getHomeTemplateId(), - }; - }, - [ activeId ] - ); - - const templateItem = { - label: template ? ( - - ) : ( - __( 'Loading…' ) - ), - value: activeId, - slug: template ? template.slug : __( 'Loading…' ), - content: template?.content, - }; - - const templatePartItems = templateParts?.map( ( templatePart ) => ( { - label: , - value: templatePart.id, - slug: templatePart.slug, - } ) ); - - const overwriteSlug = - page && - TEMPLATE_OVERRIDES[ page.type ] && - page.slug && - TEMPLATE_OVERRIDES[ page.type ]( page.slug ); - - const overwriteTemplate = () => - onAddTemplate( { - slug: overwriteSlug, - title: overwriteSlug, - status: 'publish', - content: templateItem.content.raw, - } ); - const revertToParent = async () => { - onRemoveTemplate( activeId ); - }; - return ( - <> - - choice.value === - ( isTemplatePart ? activeTemplatePartId : activeId ) - ).slug, - } } - > - { () => ( - <> - - onActiveIdChange( activeId ) } - > - { templateItem.label } - - { overwriteSlug && - overwriteSlug !== templateItem.slug && ( - - { __( 'Overwrite Template' ) } - - ) } - { overwriteSlug === templateItem.slug && ( - - { __( 'Revert to Parent' ) } - - ) } - - - - - - - { currentTheme.name.raw } - - - { !! hoveredTemplate?.id && ( - - ) } - { themePreviewVisible && ( - - ) } -
- - ) } - - - ); -} diff --git a/packages/edit-site/src/components/template-switcher/style.scss b/packages/edit-site/src/components/template-switcher/style.scss deleted file mode 100644 index fef006af3f1578..00000000000000 --- a/packages/edit-site/src/components/template-switcher/style.scss +++ /dev/null @@ -1,67 +0,0 @@ -.edit-site-template-switcher__popover .components-popover__content { - overflow: visible; -} - -.edit-site-template-switcher__popover .components-menu-item__button { - position: relative; -} - -.edit-site-template-switcher__label-home-icon { - width: 24px; - height: 24px; - position: absolute; - right: 20px; -} - -.edit-site-template-switcher__label-customized-dot { - position: absolute; - right: 4px; - top: 50%; - margin-top: -4px; - width: 8px; - height: 8px; - display: block; - background: var(--wp-admin-theme-color); - border-radius: 50%; -} - -.edit-site-template-switcher__label-customized-icon-icon { - width: 100%; -} - -/* - * This doesn't contain anything but it's needed because of dropdown jumpiness - * when there's a div after the last menu group. - */ -.edit-site-template-switcher__footer { - margin-bottom: -$grid-unit-15; -} - -.edit-site-template-switcher__template-preview, -.edit-site-template-switcher__theme-preview { - display: none; - border: $border-width solid $gray-400; - width: 300px; - padding: $grid-unit-20; - background: $white; - box-shadow: $shadow-popover; - border-radius: $radius-block-ui; - position: absolute; - top: -$border-width; - left: calc(100% + #{$grid-unit-15}); - - @include break-medium { - display: block; - } -} - -.edit-site-template-switcher__theme-preview-name { - font-weight: 500; - font-size: $big-font-size; -} - -.edit-site-template-switcher__theme-preview-screenshot { - margin-bottom: $grid-unit-15; - margin-top: $grid-unit-15; - max-width: 100%; -} diff --git a/packages/edit-site/src/components/template-switcher/template-preview.js b/packages/edit-site/src/components/template-switcher/template-preview.js deleted file mode 100644 index c53b4ec1c0795f..00000000000000 --- a/packages/edit-site/src/components/template-switcher/template-preview.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * WordPress dependencies - */ -import { parse } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; -import { BlockPreview } from '@wordpress/block-editor'; -import { useMemo } from '@wordpress/element'; - -function TemplatePreview( { item } ) { - const template = useSelect( - ( select ) => { - return select( 'core' ).getEntityRecord( - 'postType', - item.type === 'template' ? 'wp_template' : 'wp_template_part', - item.id - ); - }, - [ item ] - ); - const blocks = useMemo( - () => ( template ? parse( template?.content?.raw || '' ) : [] ), - [ template ] - ); - return ( -
- { !! blocks && ( - - ) } -
- ); -} - -export default TemplatePreview; diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index a503424588b916..86c502bbe7360c 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -8,10 +8,10 @@ @import "./components/header/navigation-toggle/style.scss"; @import "./components/left-sidebar/inserter-panel/style.scss"; @import "./components/left-sidebar/navigation-panel/style.scss"; +@import "./components/left-sidebar/navigation-panel/template-switcher/style.scss"; @import "./components/notices/style.scss"; @import "./components/page-switcher/style.scss"; @import "./components/sidebar/style.scss"; -@import "./components/template-switcher/style.scss"; @import "./components/editor/style.scss"; // In order to use mix-blend-mode, this element needs to have an explicitly set background-color. From 363df9f92a94718444ff335582a98220589c522d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Tue, 29 Sep 2020 17:32:12 +0300 Subject: [PATCH 16/61] RichText: remove native props for web (#25700) * Fix passing native props * Fix comment --- .../src/components/rich-text/index.js | 35 +++++++++++-------- packages/rich-text/src/component/index.js | 9 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 2e007b629da62c..faf3f79140ab8e 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -106,7 +106,6 @@ function RichTextWrapper( multiline, inlineToolbar, wrapperClassName, - className, autocompleters, onReplace, placeholder, @@ -120,10 +119,6 @@ function RichTextWrapper( __unstableOnSplitAtEnd: onSplitAtEnd, __unstableOnSplitMiddle: onSplitMiddle, identifier, - // To do: find a better way to implicitly inherit props. - start: startAttr, - reversed, - style, preserveWhiteSpace, __unstableEmbedURLOnPaste, __unstableDisableFormats: disableFormats, @@ -557,9 +552,6 @@ function RichTextWrapper( selectionEnd={ selectionEnd } onSelectionChange={ onSelectionChange } tagName={ tagName } - className={ classnames( classes, className, { - 'keep-placeholder-on-focus': keepPlaceholderOnFocus, - } ) } placeholder={ placeholder } allowedFormats={ adjustedAllowedFormats } withoutInteractiveFormatting={ withoutInteractiveFormatting } @@ -577,11 +569,8 @@ function RichTextWrapper( __unstableDidAutomaticChange={ didAutomaticChange } __unstableUndo={ undo } __unstableDisableFormats={ disableFormats } - style={ style } preserveWhiteSpace={ preserveWhiteSpace } disabled={ disabled } - start={ startAttr } - reversed={ reversed } unstableOnFocus={ unstableOnFocus } __unstableAllowPrefixTransformations={ __unstableAllowPrefixTransformations @@ -611,9 +600,11 @@ function RichTextWrapper( maxWidth={ maxWidth } onBlur={ onBlur } setRef={ setRef } - // Destructuring the id prop before { ...props } doesn't work - // correctly on web https://github.com/WordPress/gutenberg/pull/25624 + // Props to be set on the editable container are destructured on the + // element itself for web (see below), but passed through rich text + // for native. id={ props.id } + style={ props.style } > { ( { isSelected: nestedIsSelected, @@ -643,13 +634,27 @@ function RichTextWrapper( { onKeyDown( event ); editableProps.onKeyDown( event ); diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 816c2d167d3cf6..04fcb77f92b606 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import classnames from 'classnames'; import { find, isNil } from 'lodash'; /** @@ -139,8 +138,6 @@ function RichText( children, allowedFormats, withoutInteractiveFormatting, - style, - className, placeholder, disabled, preserveWhiteSpace, @@ -168,7 +165,6 @@ function RichText( __unstableOnExitFormattedText: onExitFormattedText, __unstableOnCreateUndoLevel: onCreateUndoLevel, __unstableIsSelected: isSelected, - ...remainingProps }, ref ) { @@ -1103,10 +1099,9 @@ function RichText( role: 'textbox', 'aria-multiline': true, 'aria-label': placeholder, - ...remainingProps, ref, - style: style ? { ...style, whiteSpace } : defaultStyle, - className: classnames( 'rich-text', className ), + style: defaultStyle, + className: 'rich-text', onPaste: handlePaste, onInput: handleInput, onCompositionStart: handleCompositionStart, From 1719dc261a8088c62d5411b827d64aef15373279 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill Date: Tue, 29 Sep 2020 09:40:35 -0500 Subject: [PATCH 17/61] Block Editor: use optional chaining and nullish coalescing instead of _.get. (#23632) --- .../src/components/block-icon/index.js | 3 +-- .../src/components/block-icon/index.native.js | 3 +-- .../src/components/colors/with-colors.js | 6 ++---- .../components/default-style-picker/index.js | 17 +++------------ .../use-simulated-media-query/index.js | 21 ++++++++----------- packages/block-editor/src/hooks/align.js | 8 ++----- packages/block-editor/src/store/actions.js | 12 +++++------ packages/block-editor/src/store/reducer.js | 7 +------ packages/block-editor/src/store/selectors.js | 13 +++--------- 9 files changed, 27 insertions(+), 63 deletions(-) diff --git a/packages/block-editor/src/components/block-icon/index.js b/packages/block-editor/src/components/block-icon/index.js index 5c43846229ad25..f605e7f375d5d9 100644 --- a/packages/block-editor/src/components/block-icon/index.js +++ b/packages/block-editor/src/components/block-icon/index.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { get } from 'lodash'; /** * WordPress dependencies @@ -11,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { blockDefault } from '@wordpress/icons'; export default function BlockIcon( { icon, showColors = false, className } ) { - if ( get( icon, [ 'src' ] ) === 'block-default' ) { + if ( icon?.src === 'block-default' ) { icon = { src: blockDefault, }; diff --git a/packages/block-editor/src/components/block-icon/index.native.js b/packages/block-editor/src/components/block-icon/index.native.js index fcb66f8f3d1e3e..f0845e6b74f8ad 100644 --- a/packages/block-editor/src/components/block-icon/index.native.js +++ b/packages/block-editor/src/components/block-icon/index.native.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { get } from 'lodash'; import { View } from 'react-native'; /** @@ -11,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { blockDefault } from '@wordpress/icons'; export default function BlockIcon( { icon, showColors = false } ) { - if ( get( icon, [ 'src' ] ) === 'block-default' ) { + if ( icon?.src === 'block-default' ) { icon = { src: blockDefault, }; diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index b152047fa94b96..324c31b72a4812 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, isString, kebabCase, reduce, upperFirst } from 'lodash'; +import { isString, kebabCase, reduce, upperFirst } from 'lodash'; /** * WordPress dependencies @@ -161,9 +161,7 @@ function createColorHOC( colorTypes, withColorPalette ) { const previousColorObject = previousState[ colorAttributeName ]; - const previousColor = get( previousColorObject, [ - 'color', - ] ); + const previousColor = previousColorObject?.color; /** * The "and previousColorObject" condition checks that a previous color object was already computed. * At the start previousColorObject and colorValue are both equal to undefined diff --git a/packages/block-editor/src/components/default-style-picker/index.js b/packages/block-editor/src/components/default-style-picker/index.js index 5880d8bd5850b3..5dc15cc4a9e6b5 100644 --- a/packages/block-editor/src/components/default-style-picker/index.js +++ b/packages/block-editor/src/components/default-style-picker/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { get } from 'lodash'; - /** * WordPress dependencies */ @@ -22,15 +17,9 @@ export default function DefaultStylePicker( { blockName } ) { const preferredStyleVariations = settings.__experimentalPreferredStyleVariations; return { - preferredStyle: get( preferredStyleVariations, [ - 'value', - blockName, - ] ), - onUpdatePreferredStyleVariations: get( - preferredStyleVariations, - [ 'onChange' ], - null - ), + preferredStyle: preferredStyleVariations?.value?.[ blockName ], + onUpdatePreferredStyleVariations: + preferredStyleVariations?.onChange ?? null, styles: select( 'core/blocks' ).getBlockStyles( blockName ), }; }, diff --git a/packages/block-editor/src/components/use-simulated-media-query/index.js b/packages/block-editor/src/components/use-simulated-media-query/index.js index 52ab33e24f8bf4..b5ef9a2a519fe0 100644 --- a/packages/block-editor/src/components/use-simulated-media-query/index.js +++ b/packages/block-editor/src/components/use-simulated-media-query/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, get } from 'lodash'; +import { filter } from 'lodash'; import { match } from 'css-mediaquery'; /** @@ -20,18 +20,15 @@ function getStyleSheetsThatMatchHostname() { return []; } - return filter( - get( window, [ 'document', 'styleSheets' ], [] ), - ( styleSheet ) => { - if ( ! styleSheet.href ) { - return false; - } - return ( - getProtocol( styleSheet.href ) === window.location.protocol && - getAuthority( styleSheet.href ) === window.location.host - ); + return filter( window?.document?.styleSheets ?? [], ( styleSheet ) => { + if ( ! styleSheet.href ) { + return false; } - ); + return ( + getProtocol( styleSheet.href ) === window.location.protocol && + getAuthority( styleSheet.href ) === window.location.host + ); + } ); } function isReplaceableMediaRule( rule ) { diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index f46b7fc029bc24..9d4e66d6556c06 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, has, without } from 'lodash'; +import { has, without } from 'lodash'; /** * WordPress dependencies @@ -135,11 +135,7 @@ export const withToolbarControls = createHigherOrderComponent( const updateAlignment = ( nextAlign ) => { if ( ! nextAlign ) { const blockType = getBlockType( props.name ); - const blockDefaultAlign = get( blockType, [ - 'attributes', - 'align', - 'default', - ] ); + const blockDefaultAlign = blockType.attributes?.align?.default; if ( blockDefaultAlign ) { nextAlign = ''; } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 9465f5b764d0f6..cd1365f38fd0c5 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, first, get, last, some } from 'lodash'; +import { castArray, first, last, some } from 'lodash'; /** * WordPress dependencies @@ -252,11 +252,9 @@ export function toggleSelection( isSelectionEnabled = true ) { } function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { - const preferredStyleVariations = get( - blockEditorSettings, - [ '__experimentalPreferredStyleVariations', 'value' ], - {} - ); + const preferredStyleVariations = + blockEditorSettings?.__experimentalPreferredStyleVariations?.value ?? + {}; return blocks.map( ( block ) => { const blockName = block.name; if ( ! hasBlockSupport( blockName, 'defaultStylePicker', true ) ) { @@ -265,7 +263,7 @@ function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { if ( ! preferredStyleVariations[ blockName ] ) { return block; } - const className = get( block, [ 'attributes', 'className' ] ); + const className = block.attributes?.className; if ( className?.includes( 'is-style-' ) ) { return block; } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 058f0794ab2d13..85898175da7dac 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -12,7 +12,6 @@ import { keys, isEqual, isEmpty, - get, identity, difference, omitBy, @@ -411,11 +410,7 @@ function withPersistentBlockChange( reducer ) { markNextChangeAsNotPersistent = action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'; - const nextIsPersistentChange = get( - state, - [ 'isPersistentChange' ], - true - ); + const nextIsPersistentChange = state?.isPersistentChange ?? true; if ( state.isPersistentChange === nextIsPersistentChange ) { return state; } diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 85c4a17d94bd4e..b84a7ff24aed79 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -5,7 +5,6 @@ import { castArray, flatMap, first, - get, isArray, isBoolean, last, @@ -1273,9 +1272,7 @@ const canInsertBlockTypeUnmemoized = ( return false; } - const parentAllowedBlocks = get( parentBlockListSettings, [ - 'allowedBlocks', - ] ); + const parentAllowedBlocks = parentBlockListSettings?.allowedBlocks; const hasParentAllowedBlock = checkAllowList( parentAllowedBlocks, blockName @@ -1345,7 +1342,7 @@ export function canInsertBlocks( state, clientIds, rootClientId = null ) { * the number of inserts that have occurred. */ function getInsertUsage( state, id ) { - return get( state.preferences.insertUsage, [ id ], null ); + return state.preferences.insertUsage?.[ id ] ?? null; } /** @@ -1735,11 +1732,7 @@ export function __experimentalGetLastBlockAttributeChanges( state ) { * @return {Array} Reusable blocks */ function getReusableBlocks( state ) { - return get( - state, - [ 'settings', '__experimentalReusableBlocks' ], - EMPTY_ARRAY - ); + return state?.settings?.__experimentalReusableBlocks ?? EMPTY_ARRAY; } /** From 290666bcc19cc1d01a4e00d95efabaf559a56087 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad Date: Tue, 29 Sep 2020 11:00:53 -0400 Subject: [PATCH 18/61] Remove the right margin for the right-most list items in the lastest posts block. (#25688) --- packages/block-library/src/latest-posts/style.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/style.scss b/packages/block-library/src/latest-posts/style.scss index e3a34c1387529c..cee628dbb20d38 100644 --- a/packages/block-library/src/latest-posts/style.scss +++ b/packages/block-library/src/latest-posts/style.scss @@ -28,7 +28,11 @@ @include break-small { @for $i from 2 through 6 { &.columns-#{ $i } li { - width: calc((100% / #{ $i }) - 1.25em); + width: calc((100% / #{ $i }) - 1.25em + (1.25em / #{ $i })); + + &:nth-child( #{ $i }n ) { + margin-right: 0; + } } } } From 1cad0e16c1fd8e46a57c861e69a86a4d3ee8bbd8 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Tue, 29 Sep 2020 18:41:01 +0300 Subject: [PATCH 19/61] Use `UnitControl` instead of `RangeControl` for column width (#24711) Co-authored-by: Nik Tsekouras Co-authored-by: Jon Q --- .../test/__snapshots__/index.js.snap | 2 +- packages/block-library/src/column/block.json | 4 +- .../block-library/src/column/deprecated.js | 50 +++++++++++++++++++ packages/block-library/src/column/edit.js | 31 +++++++----- packages/block-library/src/column/index.js | 2 + packages/block-library/src/column/save.js | 4 +- .../block-library/src/columns/variations.js | 14 +++--- .../src/input-control/input-base.js | 1 + .../styles/input-control-styles.js | 13 ++++- 9 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 packages/block-library/src/column/deprecated.js diff --git a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap index 08c0a5a88f75b6..c885154f55d763 100644 --- a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; +exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json index bc8d16aa9601c2..0a4f951d1f7482 100644 --- a/packages/block-library/src/column/block.json +++ b/packages/block-library/src/column/block.json @@ -9,9 +9,7 @@ "type": "string" }, "width": { - "type": "number", - "min": 0, - "max": 100 + "type": "string" } }, "supports": { diff --git a/packages/block-library/src/column/deprecated.js b/packages/block-library/src/column/deprecated.js new file mode 100644 index 00000000000000..52d3f675df2997 --- /dev/null +++ b/packages/block-library/src/column/deprecated.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +const deprecated = [ + { + attributes: { + verticalAlignment: { + type: 'string', + }, + width: { + type: 'number', + min: 0, + max: 100, + }, + }, + migrate( attributes ) { + return { + ...attributes, + width: `${ attributes.width }%`, + }; + }, + save( { attributes } ) { + const { verticalAlignment, width } = attributes; + + const wrapperClasses = classnames( { + [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + } ); + + let style; + if ( Number.isFinite( width ) ) { + style = { flexBasis: width + '%' }; + } + + return ( +
+ +
+ ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index 9798a4b398340c..a14f37dc135547 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -13,7 +13,10 @@ import { InspectorControls, __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; -import { PanelBody, RangeControl } from '@wordpress/components'; +import { + PanelBody, + __experimentalUnitControl as UnitControl, +} from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -51,10 +54,9 @@ function ColumnEdit( { } ); }; - const hasWidth = Number.isFinite( width ); const blockWrapperProps = useBlockWrapperProps( { className: classes, - style: hasWidth ? { flexBasis: width + '%' } : undefined, + style: width ? { flexBasis: width } : undefined, } ); return ( @@ -67,20 +69,23 @@ function ColumnEdit( { - { + nextWidth = + 0 > parseFloat( nextWidth ) ? '0' : nextWidth; setAttributes( { width: nextWidth } ); } } - min={ 0 } - max={ 100 } - step={ 0.1 } - required - allowReset - placeholder={ - width === undefined ? __( 'Auto' ) : undefined - } + units={ [ + { value: '%', label: '%', default: '' }, + { value: 'px', label: 'px', default: '' }, + { value: 'em', label: 'em', default: '' }, + { value: 'rem', label: 'rem', default: '' }, + { value: 'vw', label: 'vw', default: '' }, + ] } /> diff --git a/packages/block-library/src/column/index.js b/packages/block-library/src/column/index.js index b437c249a82e56..2297974e89fc40 100644 --- a/packages/block-library/src/column/index.js +++ b/packages/block-library/src/column/index.js @@ -7,6 +7,7 @@ import { column as icon } from '@wordpress/icons'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import metadata from './block.json'; import save from './save'; @@ -21,4 +22,5 @@ export const settings = { description: __( 'A single column within a columns block.' ), edit, save, + deprecated, }; diff --git a/packages/block-library/src/column/save.js b/packages/block-library/src/column/save.js index d0dda9de3174b3..cba4f436c176fc 100644 --- a/packages/block-library/src/column/save.js +++ b/packages/block-library/src/column/save.js @@ -16,8 +16,8 @@ export default function save( { attributes } ) { } ); let style; - if ( Number.isFinite( width ) ) { - style = { flexBasis: width + '%' }; + if ( width ) { + style = { flexBasis: width }; } return ( diff --git a/packages/block-library/src/columns/variations.js b/packages/block-library/src/columns/variations.js index 31b58cc71893c0..68ce6583ed4b23 100644 --- a/packages/block-library/src/columns/variations.js +++ b/packages/block-library/src/columns/variations.js @@ -53,8 +53,8 @@ const variations = [ ), innerBlocks: [ - [ 'core/column', { width: 33.33 } ], - [ 'core/column', { width: 66.66 } ], + [ 'core/column', { width: '33.33%' } ], + [ 'core/column', { width: '66.66%' } ], ], scope: [ 'block' ], }, @@ -77,8 +77,8 @@ const variations = [ ), innerBlocks: [ - [ 'core/column', { width: 66.66 } ], - [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: '66.66%' } ], + [ 'core/column', { width: '33.33%' } ], ], scope: [ 'block' ], }, @@ -124,9 +124,9 @@ const variations = [ ), innerBlocks: [ - [ 'core/column', { width: 25 } ], - [ 'core/column', { width: 50 } ], - [ 'core/column', { width: 25 } ], + [ 'core/column', { width: '25%' } ], + [ 'core/column', { width: '50%' } ], + [ 'core/column', { width: '25%' } ], ], scope: [ 'block' ], }, diff --git a/packages/components/src/input-control/input-base.js b/packages/components/src/input-control/input-base.js index 5f0da486f9adb5..49ce733912b496 100644 --- a/packages/components/src/input-control/input-base.js +++ b/packages/components/src/input-control/input-base.js @@ -56,6 +56,7 @@ export function InputBase(