From f485b89aaa9a212c5793051c97ca45eba283bfa8 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:45:43 +1100 Subject: [PATCH 01/13] Try: Add Position support, decoupled from Layout --- docs/reference-guides/core-blocks.md | 2 +- lib/block-supports/position.php | 202 +++++++++++++ lib/class-wp-theme-json-gutenberg.php | 9 +- lib/compat/wordpress-6.2/blocks.php | 26 ++ lib/load.php | 2 + packages/block-editor/src/hooks/index.js | 1 + packages/block-editor/src/hooks/position.js | 303 ++++++++++++++++++++ packages/block-library/src/group/block.json | 3 + 8 files changed, 546 insertions(+), 2 deletions(-) create mode 100644 lib/block-supports/position.php create mode 100644 lib/compat/wordpress-6.2/blocks.php create mode 100644 packages/block-editor/src/hooks/position.js diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 25fe5746a04122..71c0c1c8b957ff 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -275,7 +275,7 @@ Gather blocks in a layout container. ([Source](https://github.com/WordPress/gute - **Name:** core/group - **Category:** design -- **Supports:** align (full, wide), anchor, ariaLabel, color (background, gradients, link, text), dimensions (minHeight), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), anchor, ariaLabel, color (background, gradients, link, text), dimensions (minHeight), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** tagName, templateLock ## Heading diff --git a/lib/block-supports/position.php b/lib/block-supports/position.php new file mode 100644 index 00000000000000..17e34d995dc411 --- /dev/null +++ b/lib/block-supports/position.php @@ -0,0 +1,202 @@ +attributes ) { + $block_type->attributes = array(); + } + + if ( $has_position_support && ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } +} + +/** + * Generates the CSS for position support from the style object. + * + * @param string $selector CSS selector. + * @param array $style Style object. + * @return string CSS styles on success. Else, empty string. + */ +function gutenberg_get_position_style( $selector, $style ) { + $position_styles = array(); + $position_type = _wp_array_get( $style, array( 'position' ), '' ); + + if ( + in_array( $position_type, array( 'fixed', 'sticky' ), true ) + ) { + $sides = array( 'top', 'right', 'bottom', 'left' ); + + foreach ( $sides as $side ) { + $side_value = _wp_array_get( $style, array( 'position', $side ) ); + if ( null !== $side_value ) { + /* + * For fixed or sticky top positions, + * ensure the value includes an offset for the logged in admin bar. + */ + if ( + 'top' === $side && + ( 'fixed' === $position_type || 'sticky' === $position_type ) + ) { + // Ensure 0 values can be used in `calc()` calculations. + if ( '0' === $side_value || 0 === $side_value ) { + $side_value = '0px'; + } + + // Ensure current side value also factors in the height of the logged in admin bar. + $side_value = "calc($side_value + var(--wp-admin--admin-bar--height, 0px))"; + } + + $position_styles[] = + array( + 'selector' => "$selector", + 'declarations' => array( + $side => $side_value, + ), + ); + } + } + + $position_styles[] = + array( + 'selector' => "$selector", + 'declarations' => array( + 'position' => $position_type, + 'z-index' => '250', // TODO: This hard-coded value should live somewhere else. + ), + ); + } + + if ( ! empty( $position_styles ) ) { + /* + * Add to the style engine store to enqueue and render position styles. + */ + return gutenberg_style_engine_get_stylesheet_from_css_rules( + $position_styles, + array( + 'context' => 'block-supports', + 'prettify' => false, + ) + ); + } + + return ''; +} + +/** + * Renders position styles to the block wrapper. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_render_position_support( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $has_position_support = block_has_support( $block_type, array( 'position' ), false ); + + if ( + ! $has_position_support || + empty( $block['attrs']['style']['position'] ) + ) { + return $block_content; + } + + $style_attribute = _wp_array_get( $block, array( 'attrs', 'style' ), null ); + $class_name = wp_unique_id( 'wp-container-' ); + $selector = ".$class_name"; + $position_styles = array(); + $position_type = _wp_array_get( $style_attribute, array( 'position', 'type' ), '' ); + + if ( + in_array( $position_type, array( 'fixed', 'sticky' ), true ) + ) { + $sides = array( 'top', 'right', 'bottom', 'left' ); + + foreach ( $sides as $side ) { + $side_value = _wp_array_get( $style_attribute, array( 'position', $side ) ); + if ( null !== $side_value ) { + /* + * For fixed or sticky top positions, + * ensure the value includes an offset for the logged in admin bar. + */ + if ( + 'top' === $side && + ( 'fixed' === $position_type || 'sticky' === $position_type ) + ) { + // Ensure 0 values can be used in `calc()` calculations. + if ( '0' === $side_value || 0 === $side_value ) { + $side_value = '0px'; + } + + // Ensure current side value also factors in the height of the logged in admin bar. + $side_value = "calc($side_value + var(--wp-admin--admin-bar--height, 0px))"; + } + + $position_styles[] = + array( + 'selector' => $selector, + 'declarations' => array( + $side => $side_value, + ), + ); + } + } + + $position_styles[] = + array( + 'selector' => $selector, + 'declarations' => array( + 'position' => $position_type, + 'z-index' => '10', // TODO: This hard-coded value should live somewhere else. + ), + ); + } + + if ( ! empty( $position_styles ) ) { + /* + * Add to the style engine store to enqueue and render position styles. + */ + gutenberg_style_engine_get_stylesheet_from_css_rules( + $position_styles, + array( + 'context' => 'block-supports', + 'prettify' => false, + ) + ); + + // Inject class name to block container markup. + $content = new WP_HTML_Tag_Processor( $block_content ); + $content->next_tag(); + $content->add_class( $class_name ); + return (string) $content; + } + + return $block_content; +} + +// Register the block support. (overrides core one). +WP_Block_Supports::get_instance()->register( + 'position', + array( + 'register_attribute' => 'gutenberg_register_position_support', + ) +); + +if ( function_exists( 'wp_render_position_support' ) ) { + remove_filter( 'render_block', 'wp_render_position_support' ); +} +add_filter( 'render_block', 'gutenberg_render_position_support', 10, 2 ); diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index ddad36e15fc02d..982832f33722c6 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -330,7 +330,7 @@ class WP_Theme_JSON_Gutenberg { * and `typography`, and renamed others according to the new schema. * @since 6.0.0 Added `color.defaultDuotone`. * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`. - * @since 6.2.0 Added `dimensions.minHeight`. + * @since 6.2.0 Added `dimensions.minHeight`, `position.fixed` and `position.sticky`. * @var array */ const VALID_SETTINGS = array( @@ -369,6 +369,10 @@ class WP_Theme_JSON_Gutenberg { 'definitions' => null, 'wideSize' => null, ), + 'position' => array( + 'fixed' => null, + 'sticky' => null, + ), 'spacing' => array( 'customSpacingSize' => null, 'spacingSizes' => null, @@ -536,6 +540,7 @@ public static function get_element_class_name( $element ) { * Options that settings.appearanceTools enables. * * @since 6.0.0 + * @since 6.2.0 Added `position.fixed` and `position.sticky`. * @var array */ const APPEARANCE_TOOLS_OPT_INS = array( @@ -545,6 +550,8 @@ public static function get_element_class_name( $element ) { array( 'border', 'width' ), array( 'color', 'link' ), array( 'dimensions', 'minHeight' ), + array( 'position', 'fixed' ), + array( 'position', 'sticky' ), array( 'spacing', 'blockGap' ), array( 'spacing', 'margin' ), array( 'spacing', 'padding' ), diff --git a/lib/compat/wordpress-6.2/blocks.php b/lib/compat/wordpress-6.2/blocks.php new file mode 100644 index 00000000000000..250ed70ff9b98a --- /dev/null +++ b/lib/compat/wordpress-6.2/blocks.php @@ -0,0 +1,26 @@ += 6.2. + * + * @param string[] $attrs Array of allowed CSS attributes. + * @return string[] CSS attributes. + */ +function gutenberg_safe_style_attrs_6_2( $attrs ) { + $attrs[] = 'position'; + $attrs[] = 'top'; + $attrs[] = 'right'; + $attrs[] = 'bottom'; + $attrs[] = 'left'; + $attrs[] = 'z-index'; + + return $attrs; +} +add_filter( 'safe_style_css', 'gutenberg_safe_style_attrs_6_2' ); diff --git a/lib/load.php b/lib/load.php index 1b296053965319..83af73adc53d11 100644 --- a/lib/load.php +++ b/lib/load.php @@ -75,6 +75,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.1/theme.php'; // WordPress 6.2 compat. +require __DIR__ . '/compat/wordpress-6.2/blocks.php'; require __DIR__ . '/compat/wordpress-6.2/script-loader.php'; require __DIR__ . '/compat/wordpress-6.2/block-template-utils.php'; require __DIR__ . '/compat/wordpress-6.2/get-global-styles-and-settings.php'; @@ -132,6 +133,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/typography.php'; require __DIR__ . '/block-supports/border.php'; require __DIR__ . '/block-supports/layout.php'; +require __DIR__ . '/block-supports/position.php'; require __DIR__ . '/block-supports/spacing.php'; require __DIR__ . '/block-supports/dimensions.php'; require __DIR__ . '/block-supports/duotone.php'; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index f6fa73a053ac58..6649578abdfd44 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -18,6 +18,7 @@ import './layout'; import './content-lock-ui'; import './metadata'; import './metadata-name'; +import './position'; export { useCustomSides } from './dimensions'; export { useLayoutClasses, useLayoutStyles } from './layout'; diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js new file mode 100644 index 00000000000000..6e8e7bb12abf02 --- /dev/null +++ b/packages/block-editor/src/hooks/position.js @@ -0,0 +1,303 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; +import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; +import { useContext, createPortal, Platform } from '@wordpress/element'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import BlockList from '../components/block-list'; +import useSetting from '../components/use-setting'; +import InspectorControls from '../components/inspector-controls'; +import { cleanEmptyObject } from './utils'; + +const POSITION_SUPPORT_KEY = 'position'; + +const POSITION_OPTIONS = [ + { + key: 'default', + label: __( 'Default' ), + value: '', + name: __( 'Default' ), + }, + { + key: 'sticky', + label: __( 'Sticky' ), + value: 'sticky', + name: __( 'Sticky' ), + }, +]; + +const POSITION_SIDES = [ 'top', 'right', 'bottom', 'left' ]; +const VALID_POSITION_TYPES = [ 'sticky', 'fixed' ]; + +/** + * Get calculated position CSS. + * + * @param {Object} props Component props. + * @param {string} props.selector Selector to use. + * @param {Object} props.style Style object. + * @return {string} The generated CSS rules. + */ +export function getPositionCSS( { selector, style } ) { + let output = ''; + + const { type: positionType } = style?.position || {}; + + if ( ! VALID_POSITION_TYPES.includes( positionType ) ) { + return output; + } + + output += `${ selector } {`; + output += `position: ${ positionType };`; + + POSITION_SIDES.forEach( ( side ) => { + if ( style?.position?.[ side ] !== undefined ) { + output += `${ side }: ${ style.position[ side ] };`; + } + } ); + + if ( positionType === 'sticky' || positionType === 'fixed' ) { + // TODO: Work out where to put the magic z-index value. + output += `z-index: 10`; + } + output += `}`; + + return output; +} + +/** + * Determines if there is position support. + * + * @param {string|Object} blockType Block name or Block Type object. + * + * @return {boolean} Whether there is support. + */ +export function hasPositionSupport( blockType ) { + const support = getBlockSupport( blockType, POSITION_SUPPORT_KEY ); + return !! support; +} + +/** + * Checks if there is a current value in the position block support attributes. + * + * @param {Object} props Block props. + * @return {boolean} Whether or not the block has a position value set. + */ +export function hasPositionValue( props ) { + return props.attributes.style?.position?.type !== undefined; +} + +/** + * Resets the position block support attributes. This can be used when disabling + * the position support controls for a block via a `ToolsPanel`. + * + * @param {Object} props Block props. + * @param {Object} props.attributes Block's attributes. + * @param {Object} props.setAttributes Function to set block's attributes. + */ +export function resetPosition( { attributes = {}, setAttributes } ) { + const { style = {} } = attributes; + + setAttributes( { + style: cleanEmptyObject( { + ...style, + position: { + ...style?.position, + type: undefined, + top: undefined, + right: undefined, + bottom: undefined, + left: undefined, + }, + } ), + } ); +} + +/** + * Custom hook that checks if position settings have been disabled. + * + * @param {string} name The name of the block. + * + * @return {boolean} Whether padding setting is disabled. + */ +export function useIsPositionDisabled( { name: blockName } = {} ) { + const allowFixed = useSetting( 'position.fixed' ); + const allowSticky = useSetting( 'position.sticky' ); + const isDisabled = ! allowFixed && ! allowSticky; + + return ! hasPositionSupport( blockName ) || isDisabled; +} + +/** + * Inspector control panel containing the padding related configuration + * + * @param {Object} props + * + * @return {WPElement} Padding edit element. + */ +export function PositionEdit( props ) { + const { + attributes: { style = {} }, + setAttributes, + } = props; + + if ( useIsPositionDisabled( props ) ) { + return null; + } + + const onChangeType = ( next ) => { + // For now, use a hard-coded `0px` value for the position. + // `0px` is preferred over `0` as it can be used in `calc()` functions. + // In the future, it could be useful to allow for an offset value. + const placementValue = '0px'; + + const newStyle = { + ...style, + position: { + ...style?.position, + type: next, + top: + next === 'sticky' || next === 'fixed' + ? placementValue + : undefined, + }, + }; + + setAttributes( { + style: cleanEmptyObject( newStyle ), + } ); + }; + + return Platform.select( { + web: ( + <> + { + onChangeType( newValue ); + } } + isBlock + > + { POSITION_OPTIONS.map( ( option ) => ( + + ) ) } + + + ), + native: null, + } ); +} + +/** + * Override the default edit UI to include layout controls + * + * @param {Function} BlockEdit Original component. + * + * @return {Function} Wrapped component. + */ +export const withInspectorControls = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { name: blockName } = props; + const positionSupport = hasBlockSupport( + blockName, + POSITION_SUPPORT_KEY + ); + + return [ + positionSupport && ( + + + + ), + , + ]; + }, + 'withInspectorControls' +); + +/** + * Override the default block element to add the layout styles. + * + * @param {Function} BlockListBlock Original component. + * + * @return {Function} Wrapped component. + */ +export const withPositionStyles = createHigherOrderComponent( + ( BlockListBlock ) => ( props ) => { + const { name, attributes } = props; + const hasPositionBlockSupport = hasBlockSupport( + name, + POSITION_SUPPORT_KEY + ); + + const id = useInstanceId( BlockListBlock ); + const element = useContext( BlockList.__unstableElementContext ); + + // Higher specificity to override defaults in editor UI. + const positionSelector = `.wp-container-position-${ id }.wp-container-position-${ id }`; + + // Get CSS string for the current position values. + let css; + if ( hasPositionBlockSupport ) { + css = + getPositionCSS( { + selector: positionSelector, + style: attributes?.style, + } ) || ''; + } + + // Attach a `wp-container-` id-based class name. + const className = classnames( props?.className, { + [ `wp-container-position-${ id }` ]: + hasPositionBlockSupport && !! css, // Only attach a container class if there is generated CSS to be attached. + } ); + + return ( + <> + { hasPositionBlockSupport && + element && + !! css && + createPortal( , element ) } + + + ); + } +); + +addFilter( + 'editor.BlockListBlock', + 'core/editor/position/with-position-styles', + withPositionStyles +); +addFilter( + 'editor.BlockEdit', + 'core/editor/position/with-inspector-controls', + withInspectorControls +); diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index a3997db0621c54..2b227a15847a27 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -56,6 +56,9 @@ "width": true } }, + "position": { + "sticky": true + }, "typography": { "fontSize": true, "lineHeight": true, From 4b5b03b2b6f0bb3f4fae193f5bf10cbdc7acfd8c Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 1 Dec 2022 17:13:37 +1100 Subject: [PATCH 02/13] Try a CustomSelectControl for the position input --- packages/block-editor/src/hooks/position.js | 143 +++++++++++++----- packages/block-editor/src/hooks/position.scss | 18 +++ packages/block-editor/src/style.scss | 1 + 3 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 packages/block-editor/src/hooks/position.scss diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 6e8e7bb12abf02..c4d6eb65f2f629 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -6,14 +6,16 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; -import { - __experimentalToggleGroupControl as ToggleGroupControl, - __experimentalToggleGroupControlOption as ToggleGroupControlOption, -} from '@wordpress/components'; +import { BaseControl, CustomSelectControl } from '@wordpress/components'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; -import { useContext, createPortal, Platform } from '@wordpress/element'; +import { + useContext, + useMemo, + createPortal, + Platform, +} from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; /** @@ -26,20 +28,36 @@ import { cleanEmptyObject } from './utils'; const POSITION_SUPPORT_KEY = 'position'; -const POSITION_OPTIONS = [ - { - key: 'default', - label: __( 'Default' ), - value: '', - name: __( 'Default' ), - }, - { - key: 'sticky', - label: __( 'Sticky' ), - value: 'sticky', - name: __( 'Sticky' ), - }, -]; +const OPTION_CLASSNAME = + 'block-editor-hooks__position-selection__select-control__option'; + +const DEFAULT_OPTION = { + key: 'static', + value: '', + name: __( 'Static' ), + className: OPTION_CLASSNAME, + __experimentalHint: __( 'The default position' ), +}; + +const STICKY_OPTION = { + key: 'sticky', + value: 'sticky', + name: __( 'Sticky' ), + className: OPTION_CLASSNAME, + __experimentalHint: __( + 'The block will scroll with the document but stick instead of exiting the viewport' + ), +}; + +const FIXED_OPTION = { + key: 'fixed', + value: 'fixed', + name: __( 'Fixed' ), + className: OPTION_CLASSNAME, + __experimentalHint: __( + 'The block will not move when the page is scrolled' + ), +}; const POSITION_SIDES = [ 'top', 'right', 'bottom', 'left' ]; const VALID_POSITION_TYPES = [ 'sticky', 'fixed' ]; @@ -79,6 +97,30 @@ export function getPositionCSS( { selector, style } ) { return output; } +/** + * Determines if there is sticky position support. + * + * @param {string|Object} blockType Block name or Block Type object. + * + * @return {boolean} Whether there is support. + */ +export function hasStickyPositionSupport( blockType ) { + const support = getBlockSupport( blockType, POSITION_SUPPORT_KEY ); + return !! ( true === support || support?.sticky ); +} + +/** + * Determines if there is fixed position support. + * + * @param {string|Object} blockType Block name or Block Type object. + * + * @return {boolean} Whether there is support. + */ +export function hasFixedPositionSupport( blockType ) { + const support = getBlockSupport( blockType, POSITION_SUPPORT_KEY ); + return !! ( true === support || support?.fixed ); +} + /** * Determines if there is position support. * @@ -152,9 +194,24 @@ export function useIsPositionDisabled( { name: blockName } = {} ) { export function PositionEdit( props ) { const { attributes: { style = {} }, + name: blockName, setAttributes, } = props; + const allowFixed = hasFixedPositionSupport( blockName ); + const allowSticky = hasStickyPositionSupport( blockName ); + + const options = useMemo( () => { + const availableOptions = [ DEFAULT_OPTION ]; + if ( allowSticky ) { + availableOptions.push( STICKY_OPTION ); + } + if ( allowFixed ) { + availableOptions.push( FIXED_OPTION ); + } + return availableOptions; + }, [ allowFixed, allowSticky ] ); + if ( useIsPositionDisabled( props ) ) { return null; } @@ -182,30 +239,34 @@ export function PositionEdit( props ) { } ); }; + const value = style?.position?.type; + const selectedOption = value + ? options.find( ( option ) => option.value === value ) + : DEFAULT_OPTION; + return Platform.select( { web: ( <> - { - onChangeType( newValue ); - } } - isBlock - > - { POSITION_OPTIONS.map( ( option ) => ( - - ) ) } - + + { + onChangeType( selectedItem.value ); + } } + size={ '__unstable-large' } + /> + ), native: null, diff --git a/packages/block-editor/src/hooks/position.scss b/packages/block-editor/src/hooks/position.scss new file mode 100644 index 00000000000000..b3bd6b1b9ef041 --- /dev/null +++ b/packages/block-editor/src/hooks/position.scss @@ -0,0 +1,18 @@ +.block-editor-hooks__position-selection__select-control { + .components-custom-select-control__hint { + display: none; + } +} + +.block-editor-hooks__position-selection__select-control__option { + &.has-hint { + grid-template-columns: auto 30px; + line-height: $default-line-height; + margin-bottom: 0; + } + + .components-custom-select-control__item-hint { + grid-row: 2; + text-align: left; + } +} diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 76da3c16325db9..3c2f60388e0df0 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -50,6 +50,7 @@ @import "./hooks/layout.scss"; @import "./hooks/border.scss"; @import "./hooks/dimensions.scss"; +@import "./hooks/position.scss"; @import "./hooks/typography.scss"; @import "./hooks/color.scss"; @import "./hooks/padding.scss"; From 994743d3b8634a31c4b1b6e73d4a33fa13e539be Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:33:36 +1100 Subject: [PATCH 03/13] Try moving the control to a separate Position PanelBody --- packages/block-editor/src/hooks/index.js | 2 +- packages/block-editor/src/hooks/position.js | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 6649578abdfd44..84b3ba3a95a33b 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -14,11 +14,11 @@ import './color'; import './duotone'; import './font-size'; import './border'; +import './position'; import './layout'; import './content-lock-ui'; import './metadata'; import './metadata-name'; -import './position'; export { useCustomSides } from './dimensions'; export { useLayoutClasses, useLayoutStyles } from './layout'; diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index c4d6eb65f2f629..bbaea07ddf035a 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -8,7 +8,11 @@ import classnames from 'classnames'; */ import { __, sprintf } from '@wordpress/i18n'; import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; -import { BaseControl, CustomSelectControl } from '@wordpress/components'; +import { + BaseControl, + CustomSelectControl, + PanelBody, +} from '@wordpress/components'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { useContext, @@ -253,6 +257,7 @@ export function PositionEdit( props ) { __next36pxDefaultSize className="block-editor-hooks__position-selection__select-control" label={ __( 'Position' ) } + hideLabelFromVision describedBy={ sprintf( // translators: %s: Currently selected font size. __( 'Currently selected position: %s' ), @@ -290,11 +295,10 @@ export const withInspectorControls = createHigherOrderComponent( return [ positionSupport && ( - - + + + + ), , From 95d1c673a3c8bdefcb5199c367cedf51cce0516e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:47:04 +1100 Subject: [PATCH 04/13] Add Position group, ensure Position panel always appears just before Advanced --- .../src/components/block-inspector/index.js | 2 + .../position-controls-panel.js | 37 +++++++++++++++++++ .../inspector-controls-tabs/settings-tab.js | 2 + .../use-inspector-controls-tabs.js | 2 + .../components/inspector-controls/groups.js | 2 + packages/block-editor/src/hooks/position.js | 15 +++----- 6 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 3e2aebb5d500a5..eb19c84e3ef47b 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -35,6 +35,7 @@ import { default as InspectorControls } from '../inspector-controls'; import { default as InspectorControlsTabs } from '../inspector-controls-tabs'; import useInspectorControlsTabs from '../inspector-controls-tabs/use-inspector-controls-tabs'; import AdvancedControls from '../inspector-controls-tabs/advanced-controls-panel'; +import PositionControls from '../inspector-controls-tabs/position-controls-panel'; function useContentBlocks( blockTypes, block ) { const contentBlocksObjectAux = useMemo( () => { @@ -377,6 +378,7 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { __experimentalGroup="border" label={ __( 'Border' ) } /> +
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js b/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js new file mode 100644 index 00000000000000..5fc71fab5960f4 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { + PanelBody, + __experimentalUseSlotFills as useSlotFills, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import InspectorControlsGroups from '../inspector-controls/groups'; +import { default as InspectorControls } from '../inspector-controls'; + +const PositionControls = () => { + const fills = useSlotFills( + InspectorControlsGroups.position.Slot.__unstableName + ); + const hasFills = Boolean( fills && fills.length ); + + if ( ! hasFills ) { + return null; + } + + return ( + + + + ); +}; + +export default PositionControls; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js index beac0f10a178a5..ec34035b754a91 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js @@ -2,11 +2,13 @@ * Internal dependencies */ import AdvancedControls from './advanced-controls-panel'; +import PositionControls from './position-controls-panel'; import { default as InspectorControls } from '../inspector-controls'; const SettingsTab = ( { showAdvancedControls = false } ) => ( <> + { showAdvancedControls && (
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js b/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js index fbe072fab11c58..bf7f61de4c8cb7 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js @@ -43,6 +43,7 @@ export default function useInspectorControlsTabs( blockName ) { default: defaultGroup, dimensions: dimensionsGroup, list: listGroup, + position: positionGroup, typography: typographyGroup, } = InspectorControlsGroups; @@ -71,6 +72,7 @@ export default function useInspectorControlsTabs( blockName ) { // or Advanced Controls slot, then add this tab. const settingsFills = [ ...( useSlotFills( defaultGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( positionGroup.Slot.__unstableName ) || [] ), ...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ), ]; diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js index cb03c1ff13fa57..46fca564925aa6 100644 --- a/packages/block-editor/src/components/inspector-controls/groups.js +++ b/packages/block-editor/src/components/inspector-controls/groups.js @@ -10,6 +10,7 @@ const InspectorControlsColor = createSlotFill( 'InspectorControlsColor' ); const InspectorControlsDimensions = createSlotFill( 'InspectorControlsDimensions' ); +const InspectorControlsPosition = createSlotFill( 'InspectorControlsPosition' ); const InspectorControlsTypography = createSlotFill( 'InspectorControlsTypography' ); @@ -23,6 +24,7 @@ const groups = { dimensions: InspectorControlsDimensions, list: InspectorControlsListView, typography: InspectorControlsTypography, + position: InspectorControlsPosition, }; export default groups; diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index bbaea07ddf035a..700686b9a6a7b8 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -8,11 +8,7 @@ import classnames from 'classnames'; */ import { __, sprintf } from '@wordpress/i18n'; import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; -import { - BaseControl, - CustomSelectControl, - PanelBody, -} from '@wordpress/components'; +import { BaseControl, CustomSelectControl } from '@wordpress/components'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { useContext, @@ -295,10 +291,11 @@ export const withInspectorControls = createHigherOrderComponent( return [ positionSupport && ( - - - - + + ), , From 9148c63113cf33cb6eea929699cb5cdc79870b72 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:27:39 +1100 Subject: [PATCH 05/13] Roll in fix for toolbar position --- .../src/components/block-popover/index.js | 29 +++++++++++ .../use-block-toolbar-popover-props.js | 50 ++++++++++++++++--- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-popover/index.js b/packages/block-editor/src/components/block-popover/index.js index 5382949684abd9..b84be381b82b61 100644 --- a/packages/block-editor/src/components/block-popover/index.js +++ b/packages/block-editor/src/components/block-popover/index.js @@ -7,6 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useMergeRefs } from '@wordpress/compose'; +import { getScrollContainer } from '@wordpress/dom'; import { Popover } from '@wordpress/components'; import { forwardRef, @@ -75,6 +76,34 @@ function BlockPopover( }; }, [ selectedElement ] ); + // Get the scrollable container that the block popover appears within. + const scrollContainer = useMemo( () => { + if ( ! __unstableContentRef?.current ) { + return; + } + return getScrollContainer( __unstableContentRef?.current ); + }, [ __unstableContentRef?.current ] ); + + // Force the block popover to re-render whenever the content area is scrolled. + // This ensures that the position of the popover is accurate for fixed or sticky blocks. + useLayoutEffect( () => { + if ( ! scrollContainer ) { + return; + } + + scrollContainer?.addEventListener?.( + 'scroll', + forceRecomputePopoverDimensions + ); + + return () => { + scrollContainer?.removeEventHandler?.( + 'scroll', + forceRecomputePopoverDimensions + ); + }; + }, [ scrollContainer ] ); + const style = useMemo( () => { if ( // popoverDimensionsRecomputeCounter is by definition always equal or greater diff --git a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js index d218b1104139cf..4703e059855290 100644 --- a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js +++ b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js @@ -3,7 +3,13 @@ */ import { useRefEffect } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; -import { useCallback, useLayoutEffect, useState } from '@wordpress/element'; +import { getScrollContainer } from '@wordpress/dom'; +import { + useCallback, + useLayoutEffect, + useMemo, + useState, +} from '@wordpress/element'; /** * Internal dependencies @@ -40,24 +46,40 @@ const RESTRICTED_HEIGHT_PROPS = { * * @param {Element} contentElement The DOM element that represents the editor content or canvas. * @param {Element} selectedBlockElement The outer DOM element of the first selected block. + * @param {Element} scrollContainer The scrollable container for the contentElement. * @param {number} toolbarHeight The height of the toolbar in pixels. * * @return {Object} The popover props used to determine the position of the toolbar. */ -function getProps( contentElement, selectedBlockElement, toolbarHeight ) { +function getProps( + contentElement, + selectedBlockElement, + scrollContainer, + toolbarHeight +) { if ( ! contentElement || ! selectedBlockElement ) { return DEFAULT_PROPS; } + // Get how far the content area has been scrolled. + const scrollTop = scrollContainer?.scrollTop || 0; + const blockRect = selectedBlockElement.getBoundingClientRect(); const contentRect = contentElement.getBoundingClientRect(); + // Get the vertical position of top of the visible content area. + const topOfContentElementInViewport = scrollTop + contentRect.top; + // The document element's clientHeight represents the viewport height. const viewportHeight = contentElement.ownerDocument.documentElement.clientHeight; - const hasSpaceForToolbarAbove = - blockRect.top - contentRect.top > toolbarHeight; + // The restricted height area is calculated as the sum of the + // vertical position of the visible content area, plus the height + // of the block toolbar. + const restrictedTopArea = topOfContentElementInViewport + toolbarHeight; + const hasSpaceForToolbarAbove = blockRect.top > restrictedTopArea; + const isBlockTallerThanViewport = blockRect.height > viewportHeight - toolbarHeight; @@ -83,8 +105,19 @@ export default function useBlockToolbarPopoverProps( { } ) { const selectedBlockElement = useBlockElement( clientId ); const [ toolbarHeight, setToolbarHeight ] = useState( 0 ); + const scrollContainer = useMemo( () => { + if ( ! contentElement ) { + return; + } + return getScrollContainer( contentElement ); + }, [ contentElement ] ); const [ props, setProps ] = useState( () => - getProps( contentElement, selectedBlockElement, toolbarHeight ) + getProps( + contentElement, + selectedBlockElement, + scrollContainer, + toolbarHeight + ) ); const blockIndex = useSelect( ( select ) => select( blockEditorStore ).getBlockIndex( clientId ), @@ -98,7 +131,12 @@ export default function useBlockToolbarPopoverProps( { const updateProps = useCallback( () => setProps( - getProps( contentElement, selectedBlockElement, toolbarHeight ) + getProps( + contentElement, + selectedBlockElement, + scrollContainer, + toolbarHeight + ) ), [ contentElement, selectedBlockElement, toolbarHeight ] ); From b4053c8f63bcb85c4cb403e0baca561df850cd84 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 4 Jan 2023 16:46:49 +1100 Subject: [PATCH 06/13] Remove no longer needed change to block popover --- .../src/components/block-popover/index.js | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/packages/block-editor/src/components/block-popover/index.js b/packages/block-editor/src/components/block-popover/index.js index b84be381b82b61..5382949684abd9 100644 --- a/packages/block-editor/src/components/block-popover/index.js +++ b/packages/block-editor/src/components/block-popover/index.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useMergeRefs } from '@wordpress/compose'; -import { getScrollContainer } from '@wordpress/dom'; import { Popover } from '@wordpress/components'; import { forwardRef, @@ -76,34 +75,6 @@ function BlockPopover( }; }, [ selectedElement ] ); - // Get the scrollable container that the block popover appears within. - const scrollContainer = useMemo( () => { - if ( ! __unstableContentRef?.current ) { - return; - } - return getScrollContainer( __unstableContentRef?.current ); - }, [ __unstableContentRef?.current ] ); - - // Force the block popover to re-render whenever the content area is scrolled. - // This ensures that the position of the popover is accurate for fixed or sticky blocks. - useLayoutEffect( () => { - if ( ! scrollContainer ) { - return; - } - - scrollContainer?.addEventListener?.( - 'scroll', - forceRecomputePopoverDimensions - ); - - return () => { - scrollContainer?.removeEventHandler?.( - 'scroll', - forceRecomputePopoverDimensions - ); - }; - }, [ scrollContainer ] ); - const style = useMemo( () => { if ( // popoverDimensionsRecomputeCounter is by definition always equal or greater From 3fccfe498e0c669be72d552254748f137bf3309c Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 4 Jan 2023 16:53:12 +1100 Subject: [PATCH 07/13] Add scrollContainer to deps array --- .../components/block-tools/use-block-toolbar-popover-props.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js index 4703e059855290..08f71989df9a1c 100644 --- a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js +++ b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js @@ -138,7 +138,7 @@ export default function useBlockToolbarPopoverProps( { toolbarHeight ) ), - [ contentElement, selectedBlockElement, toolbarHeight ] + [ contentElement, selectedBlockElement, scrollContainer, toolbarHeight ] ); // Update props when the block is moved. This also ensures the props are From db57fefdd6a5758fc04346e6302a9df3f1566642 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 5 Jan 2023 17:59:06 +1100 Subject: [PATCH 08/13] Remove redundant PHP code --- lib/block-supports/position.php | 72 --------------------------------- 1 file changed, 72 deletions(-) diff --git a/lib/block-supports/position.php b/lib/block-supports/position.php index 17e34d995dc411..a2459274dcb931 100644 --- a/lib/block-supports/position.php +++ b/lib/block-supports/position.php @@ -25,78 +25,6 @@ function gutenberg_register_position_support( $block_type ) { } } -/** - * Generates the CSS for position support from the style object. - * - * @param string $selector CSS selector. - * @param array $style Style object. - * @return string CSS styles on success. Else, empty string. - */ -function gutenberg_get_position_style( $selector, $style ) { - $position_styles = array(); - $position_type = _wp_array_get( $style, array( 'position' ), '' ); - - if ( - in_array( $position_type, array( 'fixed', 'sticky' ), true ) - ) { - $sides = array( 'top', 'right', 'bottom', 'left' ); - - foreach ( $sides as $side ) { - $side_value = _wp_array_get( $style, array( 'position', $side ) ); - if ( null !== $side_value ) { - /* - * For fixed or sticky top positions, - * ensure the value includes an offset for the logged in admin bar. - */ - if ( - 'top' === $side && - ( 'fixed' === $position_type || 'sticky' === $position_type ) - ) { - // Ensure 0 values can be used in `calc()` calculations. - if ( '0' === $side_value || 0 === $side_value ) { - $side_value = '0px'; - } - - // Ensure current side value also factors in the height of the logged in admin bar. - $side_value = "calc($side_value + var(--wp-admin--admin-bar--height, 0px))"; - } - - $position_styles[] = - array( - 'selector' => "$selector", - 'declarations' => array( - $side => $side_value, - ), - ); - } - } - - $position_styles[] = - array( - 'selector' => "$selector", - 'declarations' => array( - 'position' => $position_type, - 'z-index' => '250', // TODO: This hard-coded value should live somewhere else. - ), - ); - } - - if ( ! empty( $position_styles ) ) { - /* - * Add to the style engine store to enqueue and render position styles. - */ - return gutenberg_style_engine_get_stylesheet_from_css_rules( - $position_styles, - array( - 'context' => 'block-supports', - 'prettify' => false, - ) - ); - } - - return ''; -} - /** * Renders position styles to the block wrapper. * From 218acc276729ec3d0872a57682baabaf22621da0 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 6 Jan 2023 15:20:06 +1100 Subject: [PATCH 09/13] Switch Static to Default, display help text after selection, update text --- packages/block-editor/src/hooks/position.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 700686b9a6a7b8..f10c5d4fda7d2e 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -32,11 +32,10 @@ const OPTION_CLASSNAME = 'block-editor-hooks__position-selection__select-control__option'; const DEFAULT_OPTION = { - key: 'static', + key: 'default', value: '', - name: __( 'Static' ), + name: __( 'Default' ), className: OPTION_CLASSNAME, - __experimentalHint: __( 'The default position' ), }; const STICKY_OPTION = { @@ -45,7 +44,7 @@ const STICKY_OPTION = { name: __( 'Sticky' ), className: OPTION_CLASSNAME, __experimentalHint: __( - 'The block will scroll with the document but stick instead of exiting the viewport' + 'The block will stick to the top of the window instead of scrolling.' ), }; @@ -55,7 +54,7 @@ const FIXED_OPTION = { name: __( 'Fixed' ), className: OPTION_CLASSNAME, __experimentalHint: __( - 'The block will not move when the page is scrolled' + 'The block will not move when the page is scrolled.' ), }; @@ -247,7 +246,10 @@ export function PositionEdit( props ) { return Platform.select( { web: ( <> - + Date: Mon, 9 Jan 2023 11:24:44 +1100 Subject: [PATCH 10/13] Remove help text, update logic to gracefully handle illegal values --- packages/block-editor/src/hooks/position.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index f10c5d4fda7d2e..e080d7037bab71 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -199,17 +199,18 @@ export function PositionEdit( props ) { const allowFixed = hasFixedPositionSupport( blockName ); const allowSticky = hasStickyPositionSupport( blockName ); + const value = style?.position?.type; const options = useMemo( () => { const availableOptions = [ DEFAULT_OPTION ]; - if ( allowSticky ) { + if ( allowSticky || value === STICKY_OPTION.value ) { availableOptions.push( STICKY_OPTION ); } - if ( allowFixed ) { + if ( allowFixed || value === FIXED_OPTION.value ) { availableOptions.push( FIXED_OPTION ); } return availableOptions; - }, [ allowFixed, allowSticky ] ); + }, [ allowFixed, allowSticky, value ] ); if ( useIsPositionDisabled( props ) ) { return null; @@ -238,18 +239,14 @@ export function PositionEdit( props ) { } ); }; - const value = style?.position?.type; const selectedOption = value - ? options.find( ( option ) => option.value === value ) + ? options.find( ( option ) => option.value === value ) || DEFAULT_OPTION : DEFAULT_OPTION; return Platform.select( { web: ( <> - + Date: Mon, 9 Jan 2023 16:45:36 +1100 Subject: [PATCH 11/13] Try ensuring sticky/fixed blocks always flip the toolbar position --- .../use-block-toolbar-popover-props.js | 34 ++++++++++++++----- packages/block-editor/src/hooks/position.js | 14 +++++++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js index 08f71989df9a1c..f99323dd5c80a7 100644 --- a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js +++ b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js @@ -16,6 +16,7 @@ import { */ import { store as blockEditorStore } from '../../store'; import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; +import { hasStickyOrFixedPositionValue } from '../../hooks/position'; const COMMON_PROPS = { placement: 'top-start', @@ -48,6 +49,7 @@ const RESTRICTED_HEIGHT_PROPS = { * @param {Element} selectedBlockElement The outer DOM element of the first selected block. * @param {Element} scrollContainer The scrollable container for the contentElement. * @param {number} toolbarHeight The height of the toolbar in pixels. + * @param {boolean} isSticky Whether or not the selected block is sticky or fixed. * * @return {Object} The popover props used to determine the position of the toolbar. */ @@ -55,7 +57,8 @@ function getProps( contentElement, selectedBlockElement, scrollContainer, - toolbarHeight + toolbarHeight, + isSticky ) { if ( ! contentElement || ! selectedBlockElement ) { return DEFAULT_PROPS; @@ -83,7 +86,11 @@ function getProps( const isBlockTallerThanViewport = blockRect.height > viewportHeight - toolbarHeight; - if ( hasSpaceForToolbarAbove || isBlockTallerThanViewport ) { + // Sticky blocks are treated as if they will never have enough space for the toolbar above. + if ( + ! isSticky && + ( hasSpaceForToolbarAbove || isBlockTallerThanViewport ) + ) { return DEFAULT_PROPS; } @@ -105,6 +112,19 @@ export default function useBlockToolbarPopoverProps( { } ) { const selectedBlockElement = useBlockElement( clientId ); const [ toolbarHeight, setToolbarHeight ] = useState( 0 ); + const { blockIndex, isSticky } = useSelect( + ( select ) => { + const { getBlockIndex, getBlockAttributes } = + select( blockEditorStore ); + return { + blockIndex: getBlockIndex( clientId ), + isSticky: hasStickyOrFixedPositionValue( + getBlockAttributes( clientId ) + ), + }; + }, + [ clientId ] + ); const scrollContainer = useMemo( () => { if ( ! contentElement ) { return; @@ -116,13 +136,10 @@ export default function useBlockToolbarPopoverProps( { contentElement, selectedBlockElement, scrollContainer, - toolbarHeight + toolbarHeight, + isSticky ) ); - const blockIndex = useSelect( - ( select ) => select( blockEditorStore ).getBlockIndex( clientId ), - [ clientId ] - ); const popoverRef = useRefEffect( ( popoverNode ) => { setToolbarHeight( popoverNode.offsetHeight ); @@ -135,7 +152,8 @@ export default function useBlockToolbarPopoverProps( { contentElement, selectedBlockElement, scrollContainer, - toolbarHeight + toolbarHeight, + isSticky ) ), [ contentElement, selectedBlockElement, scrollContainer, toolbarHeight ] diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index e080d7037bab71..47c063d1fd7d11 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -136,12 +136,24 @@ export function hasPositionSupport( blockType ) { * Checks if there is a current value in the position block support attributes. * * @param {Object} props Block props. - * @return {boolean} Whether or not the block has a position value set. + * @return {boolean} Whether or not the block has a position value set. */ export function hasPositionValue( props ) { return props.attributes.style?.position?.type !== undefined; } +/** + * Checks if the block is currently set to a sticky or fixed position. + * This check is helpful for determining how to position block toolbars or other elements. + * + * @param {Object} attributes Block attributes. + * @return {boolean} Whether or not the block is set to a sticky or fixed position. + */ +export function hasStickyOrFixedPositionValue( attributes ) { + const positionType = attributes.style?.position?.type; + return positionType === 'sticky' || positionType === 'fixed'; +} + /** * Resets the position block support attributes. This can be used when disabling * the position support controls for a block via a `ToolsPanel`. From 911771ff08901704e73257ae2cf77019fd54b0b0 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:02:20 +1100 Subject: [PATCH 12/13] Ensure position UI and style output is only applied on themes that support it, tidy up comments --- lib/block-supports/position.php | 15 +++++++++++- packages/block-editor/src/hooks/position.js | 27 ++++++++++----------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/lib/block-supports/position.php b/lib/block-supports/position.php index a2459274dcb931..31f359cb797624 100644 --- a/lib/block-supports/position.php +++ b/lib/block-supports/position.php @@ -43,6 +43,19 @@ function gutenberg_render_position_support( $block_content, $block ) { return $block_content; } + $global_settings = gutenberg_get_global_settings(); + $theme_has_sticky_support = _wp_array_get( $global_settings, array( 'position', 'sticky' ), false ); + $theme_has_fixed_support = _wp_array_get( $global_settings, array( 'position', 'fixed' ), false ); + + // Only allow output for position types that the theme supports. + $allowed_position_types = array(); + if ( true === $theme_has_sticky_support ) { + $allowed_position_types[] = 'sticky'; + } + if ( true === $theme_has_fixed_support ) { + $allowed_position_types[] = 'fixed'; + } + $style_attribute = _wp_array_get( $block, array( 'attrs', 'style' ), null ); $class_name = wp_unique_id( 'wp-container-' ); $selector = ".$class_name"; @@ -50,7 +63,7 @@ function gutenberg_render_position_support( $block_content, $block ) { $position_type = _wp_array_get( $style_attribute, array( 'position', 'type' ), '' ); if ( - in_array( $position_type, array( 'fixed', 'sticky' ), true ) + in_array( $position_type, $allowed_position_types, true ) ) { $sides = array( 'top', 'right', 'bottom', 'left' ); diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 47c063d1fd7d11..82fe7654fa541e 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -195,8 +195,8 @@ export function useIsPositionDisabled( { name: blockName } = {} ) { return ! hasPositionSupport( blockName ) || isDisabled; } -/** - * Inspector control panel containing the padding related configuration +/* + * Position controls to be rendered in an inspector control panel. * * @param {Object} props * @@ -224,10 +224,6 @@ export function PositionEdit( props ) { return availableOptions; }, [ allowFixed, allowSticky, value ] ); - if ( useIsPositionDisabled( props ) ) { - return null; - } - const onChangeType = ( next ) => { // For now, use a hard-coded `0px` value for the position. // `0px` is preferred over `0` as it can be used in `calc()` functions. @@ -286,7 +282,7 @@ export function PositionEdit( props ) { } /** - * Override the default edit UI to include layout controls + * Override the default edit UI to include position controls. * * @param {Function} BlockEdit Original component. * @@ -299,9 +295,11 @@ export const withInspectorControls = createHigherOrderComponent( blockName, POSITION_SUPPORT_KEY ); + const showPositionControls = + positionSupport && ! useIsPositionDisabled( props ); return [ - positionSupport && ( + showPositionControls && ( - { hasPositionBlockSupport && + { allowPositionStyles && element && !! css && createPortal( , element ) } From a96933f48e6a3706c95c99168254d82d898015b7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:18:52 +1100 Subject: [PATCH 13/13] Clarify TODO comment for z-index values --- lib/block-supports/position.php | 2 +- packages/block-editor/src/hooks/position.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/block-supports/position.php b/lib/block-supports/position.php index 31f359cb797624..8d1436049a7e42 100644 --- a/lib/block-supports/position.php +++ b/lib/block-supports/position.php @@ -102,7 +102,7 @@ function gutenberg_render_position_support( $block_content, $block ) { 'selector' => $selector, 'declarations' => array( 'position' => $position_type, - 'z-index' => '10', // TODO: This hard-coded value should live somewhere else. + 'z-index' => '10', // TODO: Replace hard-coded z-index value with a z-index preset approach in theme.json. ), ); } diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 82fe7654fa541e..1e50c35bfd0e6f 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -88,7 +88,7 @@ export function getPositionCSS( { selector, style } ) { } ); if ( positionType === 'sticky' || positionType === 'fixed' ) { - // TODO: Work out where to put the magic z-index value. + // TODO: Replace hard-coded z-index value with a z-index preset approach in theme.json. output += `z-index: 10`; } output += `}`;