| @@ -25,10 +25,75 @@ | ||
| 25 | 25 | } |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | 28 | /** |
| 29 | + * Returns the CSS rules for a position style configuration. | |
| 30 | + * | |
| 31 | + * @param string $selector CSS selector to scope the rules to. | |
| 32 | + * @param mixed $position Position style configuration. | |
| 33 | + * @param array $allowed_position_types Position types the theme supports. | |
| 34 | + * @return array CSS rules, or an empty array when the configuration is not allowed. | |
| 35 | + */ | |
| 36 | +function gutenberg_get_position_support_styles( $selector, $position, $allowed_position_types ) { | |
| 37 | + $styles = array(); | |
| 38 | + | |
| 39 | + if ( ! is_array( $position ) ) { | |
| 40 | + return $styles; | |
| 41 | + } | |
| 42 | + | |
| 43 | + $position_type = $position['type'] ?? ''; | |
| 44 | + | |
| 45 | + if ( ! in_array( $position_type, $allowed_position_types, true ) ) { | |
| 46 | + return $styles; | |
| 47 | + } | |
| 48 | + | |
| 49 | + $sides = array( 'top', 'right', 'bottom', 'left' ); | |
| 50 | + | |
| 51 | + foreach ( $sides as $side ) { | |
| 52 | + $side_value = $position[ $side ] ?? null; | |
| 53 | + if ( null !== $side_value ) { | |
| 54 | + /* | |
| 55 | + * For fixed or sticky top positions, | |
| 56 | + * ensure the value includes an offset for the logged in admin bar. | |
| 57 | + */ | |
| 58 | + if ( 'top' === $side ) { | |
| 59 | + // Ensure 0 values can be used in `calc()` calculations. | |
| 60 | + if ( '0' === $side_value || 0 === $side_value ) { | |
| 61 | + $side_value = '0px'; | |
| 62 | + } | |
| 63 | + | |
| 64 | + // Ensure current side value also factors in the height of the logged in admin bar. | |
| 65 | + $side_value = "calc($side_value + var(--wp-admin--admin-bar--position-offset, 0px))"; | |
| 66 | + } | |
| 67 | + | |
| 68 | + $styles[] = | |
| 69 | + array( | |
| 70 | + 'selector' => $selector, | |
| 71 | + 'declarations' => array( | |
| 72 | + $side => $side_value, | |
| 73 | + ), | |
| 74 | + ); | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | + $styles[] = | |
| 79 | + array( | |
| 80 | + 'selector' => $selector, | |
| 81 | + 'declarations' => array( | |
| 82 | + 'position' => $position_type, | |
| 83 | + 'z-index' => '10', // TODO: Replace hard-coded z-index value with a z-index preset approach in theme.json. | |
| 84 | + ), | |
| 85 | + ); | |
| 86 | + | |
| 87 | + return $styles; | |
| 88 | +} | |
| 89 | + | |
| 90 | +/** | |
| 29 | 91 | * Renders position styles to the block wrapper. |
| 30 | 92 | * |
| 93 | + * @since 6.2.0 | |
| 94 | + * @since 7.2.0 Added support for viewport states. | |
| 95 | + * | |
| 31 | 96 | * @param string $block_content Rendered block content. |
| 32 | 97 | * @param array $block Block object. |
| 33 | 98 | * @return string Filtered block content. |
| 34 | 99 | */ |
| @@ -35,15 +100,36 @@ | ||
| 35 | 100 | function gutenberg_render_position_support( $block_content, $block ) { |
| 36 | 101 | $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); |
| 37 | 102 | $has_position_support = block_has_support( $block_type, array( 'position' ), false ); |
| 38 | 103 | |
| 39 | - if ( | |
| 40 | - ! $has_position_support || | |
| 41 | - empty( $block['attrs']['style']['position'] ) | |
| 42 | - ) { | |
| 104 | + $style_attribute = $block['attrs']['style'] ?? null; | |
| 105 | + | |
| 106 | + if ( ! $has_position_support || ! is_array( $style_attribute ) ) { | |
| 43 | 107 | return $block_content; |
| 44 | 108 | } |
| 45 | 109 | |
| 110 | + /* | |
| 111 | + * Position styles can exist in either the default state or a viewport state. | |
| 112 | + */ | |
| 113 | + $has_position_style = ! empty( $style_attribute['position'] ); | |
| 114 | + if ( ! $has_position_style ) { | |
| 115 | + foreach ( $style_attribute as $key => $style ) { | |
| 116 | + if ( | |
| 117 | + is_string( $key ) && | |
| 118 | + str_starts_with( $key, '@' ) && | |
| 119 | + is_array( $style ) && | |
| 120 | + ! empty( $style['position'] ) | |
| 121 | + ) { | |
| 122 | + $has_position_style = true; | |
| 123 | + break; | |
| 124 | + } | |
| 125 | + } | |
| 126 | + } | |
| 127 | + | |
| 128 | + if ( ! $has_position_style ) { | |
| 129 | + return $block_content; | |
| 130 | + } | |
| 131 | + | |
| 46 | 132 | $global_settings = gutenberg_get_global_settings(); |
| 47 | 133 | $theme_has_sticky_support = $global_settings['position']['sticky'] ?? false; |
| 48 | 134 | $theme_has_fixed_support = $global_settings['position']['fixed'] ?? false; |
| 49 | 135 | |
| @@ -55,60 +141,73 @@ | ||
| 55 | 141 | if ( true === $theme_has_fixed_support ) { |
| 56 | 142 | $allowed_position_types[] = 'fixed'; |
| 57 | 143 | } |
| 58 | 144 | |
| 59 | - $style_attribute = $block['attrs']['style'] ?? null; | |
| 145 | + $viewport_settings = $global_settings['viewport'] ?? null; | |
| 146 | + $responsive_media_queries = WP_Theme_JSON_Gutenberg::get_viewport_media_queries( $viewport_settings ); | |
| 147 | + | |
| 60 | 148 | $class_name = wp_unique_id( 'wp-container-' ); |
| 61 | 149 | $selector = ".$class_name"; |
| 62 | 150 | $position_styles = array(); |
| 63 | - $position_type = $style_attribute['position']['type'] ?? ''; | |
| 64 | 151 | $wrapper_classes = array(); |
| 152 | + $base_position = $style_attribute['position'] ?? null; | |
| 65 | 153 | |
| 66 | - if ( | |
| 67 | - in_array( $position_type, $allowed_position_types, true ) | |
| 68 | - ) { | |
| 69 | - $wrapper_classes[] = $class_name; | |
| 70 | - $wrapper_classes[] = 'is-position-' . $position_type; | |
| 71 | - $sides = array( 'top', 'right', 'bottom', 'left' ); | |
| 154 | + // Default viewport (base) position styles. | |
| 155 | + $base_styles = gutenberg_get_position_support_styles( | |
| 156 | + $selector, | |
| 157 | + $base_position, | |
| 158 | + $allowed_position_types | |
| 159 | + ); | |
| 72 | 160 | |
| 73 | - foreach ( $sides as $side ) { | |
| 74 | - $side_value = $style_attribute['position'][ $side ] ?? null; | |
| 75 | - if ( null !== $side_value ) { | |
| 76 | - /* | |
| 77 | - * For fixed or sticky top positions, | |
| 78 | - * ensure the value includes an offset for the logged in admin bar. | |
| 79 | - */ | |
| 80 | - if ( | |
| 81 | - 'top' === $side && | |
| 82 | - ( 'fixed' === $position_type || 'sticky' === $position_type ) | |
| 83 | - ) { | |
| 84 | - // Ensure 0 values can be used in `calc()` calculations. | |
| 85 | - if ( '0' === $side_value || 0 === $side_value ) { | |
| 86 | - $side_value = '0px'; | |
| 87 | - } | |
| 161 | + if ( ! empty( $base_styles ) ) { | |
| 162 | + $position_styles = $base_styles; | |
| 163 | + $wrapper_classes[] = 'is-position-' . $base_position['type']; | |
| 164 | + } | |
| 88 | 165 | |
| 89 | - // Ensure current side value also factors in the height of the logged in admin bar. | |
| 90 | - $side_value = "calc($side_value + var(--wp-admin--admin-bar--position-offset, 0px))"; | |
| 91 | - } | |
| 166 | + /* | |
| 167 | + * Responsive viewport state styles. A viewport state inherits any values | |
| 168 | + * it does not set from the default state, so a state that only overrides | |
| 169 | + * e.g. the `top` offset keeps the base position type. | |
| 170 | + */ | |
| 171 | + foreach ( $responsive_media_queries as $breakpoint => $media_query ) { | |
| 172 | + $viewport_position_style = $style_attribute[ $breakpoint ]['position'] ?? null; | |
| 92 | 173 | |
| 93 | - $position_styles[] = | |
| 94 | - array( | |
| 95 | - 'selector' => $selector, | |
| 96 | - 'declarations' => array( | |
| 97 | - $side => $side_value, | |
| 98 | - ), | |
| 99 | - ); | |
| 100 | - } | |
| 174 | + if ( empty( $viewport_position_style ) || ! is_array( $viewport_position_style ) ) { | |
| 175 | + continue; | |
| 101 | 176 | } |
| 102 | 177 | |
| 103 | - $position_styles[] = | |
| 104 | - array( | |
| 105 | - 'selector' => $selector, | |
| 106 | - 'declarations' => array( | |
| 107 | - 'position' => $position_type, | |
| 108 | - 'z-index' => '10', // TODO: Replace hard-coded z-index value with a z-index preset approach in theme.json. | |
| 178 | + $viewport_position = is_array( $base_position ) | |
| 179 | + ? array_replace( $base_position, $viewport_position_style ) | |
| 180 | + : $viewport_position_style; | |
| 181 | + | |
| 182 | + $viewport_styles = gutenberg_get_position_support_styles( | |
| 183 | + $selector, | |
| 184 | + $viewport_position, | |
| 185 | + $allowed_position_types | |
| 186 | + ); | |
| 187 | + | |
| 188 | + if ( ! empty( $viewport_styles ) ) { | |
| 189 | + $wrapper_classes[] = 'is-position-' . $viewport_position['type']; | |
| 190 | + } elseif ( ! empty( $base_styles ) ) { | |
| 191 | + /* | |
| 192 | + * The viewport state can explicitly clear the position type inherited from the | |
| 193 | + * default state. | |
| 194 | + */ | |
| 195 | + $viewport_styles = array( | |
| 196 | + array( | |
| 197 | + 'selector' => $selector, | |
| 198 | + 'declarations' => array( 'position' => 'static' ), | |
| 109 | 199 | ), |
| 110 | 200 | ); |
| 201 | + } else { | |
| 202 | + continue; | |
| 203 | + } | |
| 204 | + | |
| 205 | + foreach ( $viewport_styles as $index => $rule ) { | |
| 206 | + $viewport_styles[ $index ]['rules_group'] = $media_query; | |
| 207 | + } | |
| 208 | + | |
| 209 | + $position_styles = array_merge( $position_styles, $viewport_styles ); | |
| 111 | 210 | } |
| 112 | 211 | |
| 113 | 212 | if ( ! empty( $position_styles ) ) { |
| 114 | 213 | /* |
| @@ -124,9 +223,10 @@ | ||
| 124 | 223 | |
| 125 | 224 | // Inject class name to block container markup. |
| 126 | 225 | $content = new WP_HTML_Tag_Processor( $block_content ); |
| 127 | 226 | $content->next_tag(); |
| 128 | - foreach ( $wrapper_classes as $class ) { | |
| 227 | + $content->add_class( $class_name ); | |
| 228 | + foreach ( array_unique( $wrapper_classes ) as $class ) { | |
| 129 | 229 | $content->add_class( $class ); |
| 130 | 230 | } |
| 131 | 231 | return (string) $content; |
| 132 | 232 | } |