add_class_styles( '{{WRAPPER}}', $this->get_child_width_css( $attributes ), $this->get_child_width_css( $attributes, 'Tablet' ), $this->get_child_width_css( $attributes, 'Mobile' ), $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_child_width_css( $attributes, $device ); } ) ); // An Image block placed directly in the item follows the item's width. // The Image block prints `.ablocks-block- .ablocks-image-figure img` // (0-2-1) both for a width of its own and, when it has none, for its // natural width, so the item can't simply outrank it. Instead: // - the natural-width fallback reads this variable first (see // get_image_css() in blocks/image/block.php), so an image without a // width of its own takes the item's width, while an explicit width // is printed as a plain length and keeps winning; $css_generator->add_class_styles( '{{WRAPPER}} > .ablocks-block--image', $this->get_content_image_width_var_css( $attributes ), $this->get_content_image_width_var_css( $attributes, 'Tablet' ), $this->get_content_image_width_var_css( $attributes, 'Mobile' ), $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_content_image_width_var_css( $attributes, $device ); } ) ); // - an image saved without a natural width gets no width rule of its // own (only the HTML `width` attribute); this one covers it, kept at // 0-1-1 by `:where()` so it still loses to an explicit width. $css_generator->add_class_styles( '{{WRAPPER}} > :where(.ablocks-block--image) :where(.ablocks-image-figure) img', $this->get_content_image_width_css( $attributes ), $this->get_content_image_width_css( $attributes, 'Tablet' ), $this->get_content_image_width_css( $attributes, 'Mobile' ), $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_content_image_width_css( $attributes, $device ); } ) ); // The marquee a percentage width resolves against. if ( ! empty( $attributes['block_id'] ) ) { $css_generator->add_class_styles( '.ablocks-block-marquee:has(.ablocks-block-' . sanitize_html_class( $attributes['block_id'] ) . ')', $this->get_marquee_container_css( $attributes ), $this->get_marquee_container_css( $attributes, 'Tablet' ), $this->get_marquee_container_css( $attributes, 'Mobile' ), $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_marquee_container_css( $attributes, $device ); } ) ); } return $css_generator->generate_css(); } /** * Makes the visible marquee item take its configured width. Mirrors * getChildWidthCSS() in src/blocks/marquee-child/styling.js. * - width: re-printed after the global wrapper CSS so a percentage becomes * `cqw` (see to_marquee_width()). Printed for every sized device so a * desktop rule here can't override a tablet/mobile rule of the global one. * - flex-shrink / overflow-x: no flex shrinking, and content wider than the * item (an image's px width, nowrap text) is clipped instead of spilling * over the next item. */ public function get_child_width_css( $attributes, $device = '' ) { $width = $this->get_width_value( $attributes, $device ); // A unit-only value (custom type, empty number) prints no width. if ( '' === $width || ! preg_match( '/^-?\d*\.?\d/', $width ) ) { return []; } return [ 'width' => $this->to_marquee_width( $width ), 'flex-shrink' => '0', 'overflow-x' => 'clip', ]; } /** * Makes an Image block placed directly in a sized item follow the item's * width. Mirrors getContentImageWidthCSS() in * src/blocks/marquee-child/styling.js. * The Image block's figure is `width: fit-content` around an pinned * to the image's natural width (its saved `width` attribute), so an item * wider than the image left blank space beside it. The item's width on the * makes the figure grow with the item; the existing `max-width: 100%` * caps it to the space left inside the item, so it shrinks with it too. */ public function get_content_image_width_css( $attributes, $device = '' ) { $css = $this->get_child_width_css( $attributes, $device ); return isset( $css['width'] ) ? [ 'width' => $css['width'] ] : []; } /** * The same width, handed to a directly nested Image block's natural-width * fallback (`--ablocks-image-auto-width`, read in get_image_css() of * blocks/image/block.php). Frontend only: the editor prints no natural * width, its ResizableBox is handled in src/blocks/marquee-child/edit.js. */ public function get_content_image_width_var_css( $attributes, $device = '' ) { $css = $this->get_child_width_css( $attributes, $device ); return isset( $css['width'] ) ? [ '--ablocks-image-auto-width' => $css['width'] ] : []; } /** * Makes the parent marquee the size container `cqw` resolves against, only * where this item uses a percentage: inline-size containment drops the * marquee's content-based width, which matters in a content-sized parent. * Mirrors getMarqueeContainerCSS() in src/blocks/marquee-child/styling.js. */ public function get_marquee_container_css( $attributes, $device = '' ) { $width = $this->get_width_value( $attributes, $device ); if ( '' === $width || $this->to_marquee_width( $width ) === $width ) { return []; } return [ 'container-type' => 'inline-size' ]; } private function get_width_value( $attributes, $device ) { if ( empty( $attributes['_width'] ) || ! is_array( $attributes['_width'] ) ) { return ''; } $css = Width::get_css( $attributes['_width'], 'width', $device ); return isset( $css['width'] ) ? trim( (string) $css['width'] ) : ''; } /** * A percentage would resolve against the marquee track, which is itself * sized by its items, so it's measured against the visible marquee box * instead: `N%` becomes `Ncqw`. Other values are returned unchanged. */ private function to_marquee_width( $width ) { return preg_match( '/^-?\d*\.?\d+%$/', $width ) ? substr( $width, 0, -1 ) . 'cqw' : $width; } }