first commit
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Core\Breakpoints\Breakpoint;
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Memo;
|
||||
use Elementor\Modules\AtomicWidgets\Cache_Validity;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Styles_Manager {
|
||||
private static ?self $instance = null;
|
||||
|
||||
/**
|
||||
* @var array<string, array{styles: callable, cache_keys: array<string>}>
|
||||
*/
|
||||
private array $registered_styles_by_key = [];
|
||||
|
||||
private Cache_Validity $cache_validity;
|
||||
|
||||
private array $post_ids = [];
|
||||
|
||||
private CSS_Files_Manager $css_files_manager;
|
||||
|
||||
const DEFAULT_BREAKPOINT = 'desktop';
|
||||
|
||||
private array $fonts = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->css_files_manager = new CSS_Files_Manager();
|
||||
$this->cache_validity = new Cache_Validity();
|
||||
}
|
||||
|
||||
public static function instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_hooks() {
|
||||
add_action( 'elementor/frontend/after_enqueue_post_styles', fn() => $this->enqueue_styles() );
|
||||
add_action( 'elementor/post/render', function( $post_id ) {
|
||||
$this->post_ids[] = $post_id;
|
||||
} );
|
||||
}
|
||||
|
||||
public function register( string $key, callable $get_style_defs, array $cache_keys ) {
|
||||
$this->registered_styles_by_key[ $key ] = [
|
||||
'get_styles' => $get_style_defs,
|
||||
'cache_keys' => $cache_keys,
|
||||
];
|
||||
}
|
||||
|
||||
private function enqueue_styles() {
|
||||
if ( empty( $this->post_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_action( 'elementor/atomic-widgets/styles/register', $this, $this->post_ids );
|
||||
|
||||
$get_styles_memo = new Memo();
|
||||
|
||||
$styles_by_key = Collection::make( $this->registered_styles_by_key )
|
||||
->map_with_keys( fn ( $style_params, $style_key ) => [
|
||||
$style_key => [
|
||||
'get_styles' => $get_styles_memo->memoize( $style_key, $style_params['get_styles'] ),
|
||||
'cache_keys' => $style_params['cache_keys'],
|
||||
],
|
||||
])
|
||||
->all();
|
||||
|
||||
$this->before_render( $styles_by_key );
|
||||
|
||||
$this->render( $styles_by_key );
|
||||
|
||||
$this->after_render( $styles_by_key );
|
||||
}
|
||||
|
||||
private function before_render( array $styles_by_key ) {
|
||||
$this->fonts = [];
|
||||
|
||||
foreach ( $styles_by_key as $style_key => $style_params ) {
|
||||
$cache_keys = $style_params['cache_keys'];
|
||||
|
||||
// This cache validity check is of the general style, and used to reset dependencies that can only be evaluated
|
||||
// upon the style rendering flow (i.e. when cache is invalid).
|
||||
// (the corresponding css files cache validity includes also the file's breakpoint in the cache keys array)
|
||||
if ( ! $this->cache_validity->is_valid( $cache_keys ) ) {
|
||||
Style_Fonts::make( $style_key )->clear();
|
||||
}
|
||||
|
||||
// We should validate it after this iteration
|
||||
$this->cache_validity->validate( $cache_keys );
|
||||
}
|
||||
}
|
||||
|
||||
private function render( array $styles_by_key ) {
|
||||
$group_by_breakpoint_memo = new Memo();
|
||||
$breakpoints = $this->get_breakpoints();
|
||||
|
||||
foreach ( $breakpoints as $breakpoint_key ) {
|
||||
foreach ( $styles_by_key as $style_key => $style_params ) {
|
||||
$cache_keys = $style_params['cache_keys'];
|
||||
$render_css = fn() => $this->render_css_by_breakpoints( $style_params['get_styles'], $style_key, $breakpoint_key, $group_by_breakpoint_memo );
|
||||
|
||||
$breakpoint_media = $this->get_breakpoint_media( $breakpoint_key );
|
||||
|
||||
if ( ! $breakpoint_media ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$breakpoint_cache_keys = array_merge( $cache_keys, [ $breakpoint_key ] );
|
||||
|
||||
$style_file = $this->css_files_manager->get(
|
||||
$style_key . '-' . $breakpoint_key,
|
||||
$breakpoint_media,
|
||||
$render_css,
|
||||
$this->cache_validity->is_valid( $breakpoint_cache_keys )
|
||||
);
|
||||
|
||||
$this->cache_validity->validate( $breakpoint_cache_keys );
|
||||
|
||||
if ( ! $style_file ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
$style_file->get_handle(),
|
||||
$style_file->get_url(),
|
||||
[],
|
||||
$style_file->get_media()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function render_css( array $styles, string $style_key ) {
|
||||
$style_fonts = Style_Fonts::make( $style_key );
|
||||
|
||||
return Styles_Renderer::make(
|
||||
Plugin::$instance->breakpoints->get_breakpoints_config()
|
||||
)->on_prop_transform( function( $key, $value ) use ( $style_fonts ) {
|
||||
if ( 'font-family' !== $key ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$style_fonts->add( $value );
|
||||
} )->render( $styles );
|
||||
}
|
||||
|
||||
private function get_breakpoint_media( string $breakpoint_key ): ?string {
|
||||
$breakpoint_config = Plugin::$instance->breakpoints->get_breakpoints_config()[ $breakpoint_key ] ?? null;
|
||||
|
||||
return $breakpoint_config ? Styles_Renderer::get_media_query( $breakpoint_config ) : 'all';
|
||||
}
|
||||
|
||||
private function render_css_by_breakpoints( callable $get_styles, string $style_key, string $breakpoint_key, Memo $group_by_breakpoint_memo ) {
|
||||
$memo_key = $style_key . '-' . $breakpoint_key;
|
||||
$get_grouped_styles = $group_by_breakpoint_memo->memoize( $memo_key, fn() => $this->group_by_breakpoint( $get_styles() ) );
|
||||
$grouped_styles = $get_grouped_styles();
|
||||
|
||||
return $this->render_css( $grouped_styles[ $breakpoint_key ] ?? [], $style_key );
|
||||
}
|
||||
|
||||
private function group_by_breakpoint( $styles ) {
|
||||
return Collection::make( $styles )->reduce( function( $group, $style ) {
|
||||
Collection::make( $style['variants'] )->each( function( $variant ) use ( &$group, $style ) {
|
||||
$breakpoint = $variant['meta']['breakpoint'] ?? self::DEFAULT_BREAKPOINT;
|
||||
|
||||
if ( ! isset( $group[ $breakpoint ][ $style['id'] ] ) ) {
|
||||
$group[ $breakpoint ][ $style['id'] ] = [
|
||||
'id' => $style['id'],
|
||||
'type' => $style['type'],
|
||||
'variants' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$group[ $breakpoint ][ $style['id'] ]['variants'][] = $variant;
|
||||
} );
|
||||
|
||||
return $group;
|
||||
}, [] );
|
||||
}
|
||||
|
||||
private function get_breakpoints() {
|
||||
return Collection::make( Plugin::$instance->breakpoints->get_breakpoints() )
|
||||
->map( fn( Breakpoint $breakpoint ) => $breakpoint->get_name() )
|
||||
->reverse()
|
||||
->prepend( self::DEFAULT_BREAKPOINT )
|
||||
->all();
|
||||
}
|
||||
|
||||
private function after_render( array $styles_by_key ) {
|
||||
foreach ( $styles_by_key as $style_key => $style_params ) {
|
||||
$this->add_fonts_to_enqueue( $style_key );
|
||||
}
|
||||
|
||||
$this->enqueue_fonts();
|
||||
}
|
||||
|
||||
private function add_fonts_to_enqueue( string $style_key ) {
|
||||
$style_fonts = Style_Fonts::make( $style_key );
|
||||
|
||||
$this->fonts = array_unique(
|
||||
array_merge(
|
||||
$this->fonts,
|
||||
array_values( $style_fonts->get() )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function enqueue_fonts() {
|
||||
foreach ( $this->fonts as $font ) {
|
||||
Plugin::instance()->frontend->enqueue_font( $font );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Cache_Validity;
|
||||
use Elementor\Modules\AtomicWidgets\Utils;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Atomic_Widget_Base_Styles {
|
||||
const STYLES_KEY = 'base';
|
||||
|
||||
public function register_hooks() {
|
||||
add_action(
|
||||
'elementor/atomic-widgets/styles/register',
|
||||
fn( Atomic_Styles_Manager $styles_manager ) => $this->register_styles( $styles_manager ),
|
||||
10,
|
||||
1
|
||||
);
|
||||
|
||||
add_action(
|
||||
'elementor/core/files/clear_cache',
|
||||
fn() => $this->invalidate_cache(),
|
||||
);
|
||||
}
|
||||
|
||||
private function register_styles( Atomic_Styles_Manager $styles_manager ) {
|
||||
$styles_manager->register(
|
||||
self::STYLES_KEY,
|
||||
fn () => $this->get_all_base_styles(),
|
||||
[ self::STYLES_KEY ]
|
||||
);
|
||||
}
|
||||
|
||||
private function invalidate_cache() {
|
||||
$cache_validity = new Cache_Validity();
|
||||
|
||||
$cache_validity->invalidate( [ self::STYLES_KEY ] );
|
||||
}
|
||||
|
||||
public function get_all_base_styles(): array {
|
||||
$elements = Plugin::$instance->elements_manager->get_element_types();
|
||||
$widgets = Plugin::$instance->widgets_manager->get_widget_types();
|
||||
|
||||
return Collection::make( $elements )
|
||||
->merge( $widgets )
|
||||
->filter( fn( $element ) => Utils::is_atomic( $element ) )
|
||||
->map( fn( $element ) => $element->get_base_styles() )
|
||||
->flatten()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Modules\AtomicWidgets\Cache_Validity;
|
||||
use Elementor\Modules\AtomicWidgets\Utils;
|
||||
use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Atomic_Widget_Styles {
|
||||
const STYLES_KEY = 'local';
|
||||
|
||||
public function register_hooks() {
|
||||
add_action( 'elementor/atomic-widgets/styles/register', function( Atomic_Styles_Manager $styles_manager, array $post_ids ) {
|
||||
$this->register_styles( $styles_manager, $post_ids );
|
||||
}, 30, 2 );
|
||||
|
||||
add_action( 'elementor/document/after_save', fn( Document $document ) => $this->invalidate_cache(
|
||||
[ $document->get_main_post()->ID ]
|
||||
), 20, 2 );
|
||||
|
||||
add_action(
|
||||
'elementor/core/files/clear_cache',
|
||||
fn() => $this->invalidate_cache(),
|
||||
);
|
||||
}
|
||||
|
||||
private function register_styles( Atomic_Styles_Manager $styles_manager, array $post_ids ) {
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
$get_styles = fn() => $this->parse_post_styles( $post_id );
|
||||
|
||||
$style_key = $this->get_style_key( $post_id );
|
||||
|
||||
$styles_manager->register( $style_key, $get_styles, [ self::STYLES_KEY, $post_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
private function parse_post_styles( $post_id ) {
|
||||
$post_styles = [];
|
||||
|
||||
Utils::traverse_post_elements( $post_id, function( $element_data ) use ( &$post_styles ) {
|
||||
$post_styles = array_merge( $post_styles, $this->parse_element_style( $element_data ) );
|
||||
} );
|
||||
|
||||
return $post_styles;
|
||||
}
|
||||
|
||||
private function parse_element_style( array $element_data ) {
|
||||
$element_type = Atomic_Elements_Utils::get_element_type( $element_data );
|
||||
$element_instance = Atomic_Elements_Utils::get_element_instance( $element_type );
|
||||
|
||||
if ( ! Utils::is_atomic( $element_instance ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $element_data['styles'] ?? [];
|
||||
}
|
||||
|
||||
private function invalidate_cache( ?array $post_ids = null ) {
|
||||
$cache_validity = new Cache_Validity();
|
||||
|
||||
if ( empty( $post_ids ) ) {
|
||||
$cache_validity->invalidate( [ self::STYLES_KEY ] );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
$cache_validity->invalidate( [ self::STYLES_KEY, $post_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
private function get_style_key( $post_id ) {
|
||||
return self::STYLES_KEY . '-' . $post_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
class CSS_Files_Manager {
|
||||
const DEFAULT_CSS_DIR = 'elementor/css/';
|
||||
const FILE_EXTENSION = '.css';
|
||||
// Read and write permissions for the owner
|
||||
const PERMISSIONS = 0644;
|
||||
|
||||
public function get( string $handle, string $media, callable $get_css, bool $is_valid_cache ): ?Style_File {
|
||||
$filesystem = $this->get_filesystem();
|
||||
$path = $this->get_path( $handle );
|
||||
|
||||
if ( $is_valid_cache ) {
|
||||
if ( ! $filesystem->exists( $path ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return the existing file
|
||||
return Style_File::create(
|
||||
$this->sanitize_handle( $handle ),
|
||||
$this->get_filesystem_path( $this->get_path( $handle ) ),
|
||||
$this->get_url( $handle ),
|
||||
$media,
|
||||
);
|
||||
}
|
||||
|
||||
$css = $get_css();
|
||||
|
||||
if ( empty( $css ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$filesystem_path = $this->get_filesystem_path( $path );
|
||||
|
||||
$is_created = $filesystem->put_contents( $filesystem_path, $css, self::PERMISSIONS );
|
||||
|
||||
if ( false === $is_created ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Style_File::create(
|
||||
$this->sanitize_handle( $handle ),
|
||||
$filesystem_path,
|
||||
$this->get_url( $handle ),
|
||||
$media
|
||||
);
|
||||
}
|
||||
|
||||
private function get_filesystem(): \WP_Filesystem_Base {
|
||||
global $wp_filesystem;
|
||||
|
||||
if ( empty( $wp_filesystem ) ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/file.php';
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
return $wp_filesystem;
|
||||
}
|
||||
|
||||
private function get_filesystem_path( $path ): string {
|
||||
$filesystem = $this->get_filesystem();
|
||||
|
||||
return str_replace( ABSPATH, $filesystem->abspath(), $path );
|
||||
}
|
||||
|
||||
private function get_url( string $handle ): string {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$sanitized_handle = $this->sanitize_handle( $handle );
|
||||
$handle = $sanitized_handle . self::FILE_EXTENSION;
|
||||
|
||||
return trailingslashit( $upload_dir['baseurl'] ) . self::DEFAULT_CSS_DIR . $handle;
|
||||
}
|
||||
|
||||
private function get_path( string $handle ): string {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$sanitized_handle = $this->sanitize_handle( $handle );
|
||||
$handle = $sanitized_handle . self::FILE_EXTENSION;
|
||||
|
||||
return trailingslashit( $upload_dir['basedir'] ) . self::DEFAULT_CSS_DIR . $handle;
|
||||
}
|
||||
|
||||
private function sanitize_handle( string $handle ): string {
|
||||
return preg_replace( '/[^a-zA-Z0-9_-]/', '', $handle );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Size_Constants {
|
||||
const UNIT_PX = 'px';
|
||||
const UNIT_PERCENT = '%';
|
||||
const UNIT_EM = 'em';
|
||||
const UNIT_REM = 'rem';
|
||||
const UNIT_VW = 'vw';
|
||||
const UNIT_VH = 'vh';
|
||||
const UNIT_AUTO = 'auto';
|
||||
const UNIT_CUSTOM = 'custom';
|
||||
const UNIT_SECOND = 's';
|
||||
const UNIT_MILLI_SECOND = 'ms';
|
||||
const UNIT_ANGLE_DEG = 'deg';
|
||||
|
||||
const LENGTH_UNITS = [
|
||||
self::UNIT_PX,
|
||||
self::UNIT_EM,
|
||||
self::UNIT_REM,
|
||||
self::UNIT_VW,
|
||||
self::UNIT_VH,
|
||||
];
|
||||
|
||||
const TIME_UNITS = [ self::UNIT_SECOND, self::UNIT_MILLI_SECOND ];
|
||||
const EXTENDED_UNITS = [ self::UNIT_AUTO, self::UNIT_CUSTOM ];
|
||||
const VIEWPORT_MIN_MAX_UNITS = [ 'vmin', 'vmax' ];
|
||||
const ANGLE_UNITS = [ self::UNIT_ANGLE_DEG, 'rad', 'grad', 'turn' ];
|
||||
|
||||
public static function all_supported_units(): array {
|
||||
return array_merge(
|
||||
self::all(),
|
||||
self::ANGLE_UNITS,
|
||||
self::TIME_UNITS,
|
||||
self::EXTENDED_UNITS,
|
||||
self::VIEWPORT_MIN_MAX_UNITS,
|
||||
);
|
||||
}
|
||||
|
||||
public static function all(): array {
|
||||
return [
|
||||
...self::LENGTH_UNITS,
|
||||
self::UNIT_PERCENT,
|
||||
self::UNIT_AUTO,
|
||||
];
|
||||
}
|
||||
|
||||
private static function units_without_auto(): array {
|
||||
return [ ...self::LENGTH_UNITS, self::UNIT_PERCENT ];
|
||||
}
|
||||
|
||||
public static function layout() {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function spacing(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function position(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function anchor_offset() {
|
||||
return self::LENGTH_UNITS;
|
||||
}
|
||||
|
||||
public static function typography(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function stroke_width() {
|
||||
return [
|
||||
self::UNIT_PX,
|
||||
self::UNIT_EM,
|
||||
self::UNIT_REM,
|
||||
];
|
||||
}
|
||||
|
||||
public static function transition() {
|
||||
return self::TIME_UNITS;
|
||||
}
|
||||
|
||||
public static function border(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
|
||||
public static function opacity(): array {
|
||||
return [ self::UNIT_PERCENT ];
|
||||
}
|
||||
|
||||
public static function box_shadow(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function rotate(): array {
|
||||
return self::ANGLE_UNITS;
|
||||
}
|
||||
|
||||
public static function transform(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function drop_shadow() {
|
||||
return self::LENGTH_UNITS;
|
||||
}
|
||||
|
||||
public static function blur_filter() {
|
||||
return self::LENGTH_UNITS;
|
||||
}
|
||||
|
||||
public static function intensity_filter() {
|
||||
return [ self::UNIT_PERCENT ];
|
||||
}
|
||||
|
||||
public static function color_tone_filter() {
|
||||
return [ self::UNIT_PERCENT ];
|
||||
}
|
||||
|
||||
public static function hue_rotate_filter() {
|
||||
return self::ANGLE_UNITS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
class Style_Definition {
|
||||
private string $type = 'class';
|
||||
private string $label = '';
|
||||
|
||||
/** @var Style_Variant[] */
|
||||
private array $variants = [];
|
||||
|
||||
public static function make(): self {
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function set_type( string $type ): self {
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_label( string $label ): self {
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add_variant( Style_Variant $variant ): self {
|
||||
$this->variants[] = $variant->build();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build( string $id ): array {
|
||||
return [
|
||||
'id' => $id,
|
||||
'type' => $this->type,
|
||||
'label' => $this->label,
|
||||
'variants' => $this->variants,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Style_File {
|
||||
private string $handle;
|
||||
private string $path;
|
||||
private string $url;
|
||||
private string $media;
|
||||
|
||||
private function __construct( string $handle, string $path, string $url, string $media ) {
|
||||
$this->handle = $handle;
|
||||
$this->path = $path;
|
||||
$this->url = $url;
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
public static function create( string $handle, string $path, string $url, string $media ): self {
|
||||
return new self( $handle, $path, $url, $media );
|
||||
}
|
||||
|
||||
public function get_handle(): string {
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function get_path(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function get_url(): string {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function get_media(): string {
|
||||
return $this->media;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Core\Breakpoints\Breakpoint;
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Memo;
|
||||
use Elementor\Modules\AtomicWidgets\Cache_Validity;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
const FONTS_KEY_PREFIX = 'elementor_atomic_styles_fonts-';
|
||||
|
||||
class Style_Fonts {
|
||||
private string $style_key;
|
||||
|
||||
public function __construct( string $style_key ) {
|
||||
$this->style_key = $style_key;
|
||||
}
|
||||
|
||||
public static function make( string $style_key ) {
|
||||
return new static( $style_key );
|
||||
}
|
||||
|
||||
public function add( string $font ) {
|
||||
$style_fonts = $this->get_fonts();
|
||||
|
||||
if ( ! in_array( $font, $style_fonts, true ) ) {
|
||||
$style_fonts[] = $font;
|
||||
|
||||
$this->update_fonts( $style_fonts );
|
||||
}
|
||||
}
|
||||
|
||||
public function get(): array {
|
||||
return $this->get_fonts();
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
$this->update_fonts( [] );
|
||||
}
|
||||
|
||||
private function get_fonts(): array {
|
||||
$style_fonts_key = $this->get_key();
|
||||
return get_option( $style_fonts_key, [] );
|
||||
}
|
||||
|
||||
private function update_fonts( array $fonts ) {
|
||||
$style_fonts_key = $this->get_key();
|
||||
update_option( $style_fonts_key, $fonts );
|
||||
}
|
||||
|
||||
private function get_key(): string {
|
||||
return FONTS_KEY_PREFIX . $this->style_key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Types_Mapping;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Overlay_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Box_Shadow_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Border_Radius_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Border_Width_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Backdrop_Filter_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Filter_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Layout_Direction_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Stroke_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transition_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Flex_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Style_Schema {
|
||||
public static function get() {
|
||||
return apply_filters( 'elementor/atomic-widgets/styles/schema', static::get_style_schema() );
|
||||
}
|
||||
|
||||
public static function get_style_schema(): array {
|
||||
return array_merge(
|
||||
self::get_size_props(),
|
||||
self::get_position_props(),
|
||||
self::get_typography_props(),
|
||||
self::get_spacing_props(),
|
||||
self::get_border_props(),
|
||||
self::get_background_props(),
|
||||
self::get_effects_props(),
|
||||
self::get_layout_props(),
|
||||
self::get_alignment_props(),
|
||||
);
|
||||
}
|
||||
|
||||
private static function get_size_props() {
|
||||
return [
|
||||
'width' => Size_Prop_Type::make(),
|
||||
'height' => Size_Prop_Type::make(),
|
||||
'min-width' => Size_Prop_Type::make(),
|
||||
'min-height' => Size_Prop_Type::make(),
|
||||
'max-width' => Size_Prop_Type::make(),
|
||||
'max-height' => Size_Prop_Type::make(),
|
||||
'overflow' => String_Prop_Type::make()->enum( [
|
||||
'visible',
|
||||
'hidden',
|
||||
'auto',
|
||||
] ),
|
||||
'aspect-ratio' => String_Prop_Type::make(),
|
||||
'object-fit' => String_Prop_Type::make()->enum( [
|
||||
'fill',
|
||||
'cover',
|
||||
'contain',
|
||||
'none',
|
||||
'scale-down',
|
||||
] ),
|
||||
'object-position' => Union_Prop_Type::make()
|
||||
->add_prop_type( String_Prop_Type::make()->enum( Position_Prop_Type::get_position_enum_values() ) )
|
||||
->add_prop_type( Position_Prop_Type::make() )
|
||||
->set_dependencies(
|
||||
Dependency_Manager::make( Dependency_Manager::RELATION_AND )
|
||||
->where( [
|
||||
'operator' => 'ne',
|
||||
'path' => [ 'object-fit' ],
|
||||
'value' => 'fill',
|
||||
] )
|
||||
->where( [
|
||||
'operator' => 'exists',
|
||||
'path' => [ 'object-fit' ],
|
||||
] )
|
||||
->get()
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_position_props() {
|
||||
return [
|
||||
'position' => String_Prop_Type::make()->enum( [
|
||||
'static',
|
||||
'relative',
|
||||
'absolute',
|
||||
'fixed',
|
||||
'sticky',
|
||||
] ),
|
||||
'inset-block-start' => Size_Prop_Type::make(),
|
||||
'inset-inline-end' => Size_Prop_Type::make(),
|
||||
'inset-block-end' => Size_Prop_Type::make(),
|
||||
'inset-inline-start' => Size_Prop_Type::make(),
|
||||
'z-index' => Number_Prop_Type::make(),
|
||||
'scroll-margin-top' => Size_Prop_Type::make()->units( Size_Constants::anchor_offset() ),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_typography_props() {
|
||||
return [
|
||||
'font-family' => String_Prop_Type::make(),
|
||||
'font-weight' => String_Prop_Type::make()->enum( [
|
||||
'100',
|
||||
'200',
|
||||
'300',
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
'800',
|
||||
'900',
|
||||
'normal',
|
||||
'bold',
|
||||
'bolder',
|
||||
'lighter',
|
||||
] ),
|
||||
'font-size' => Size_Prop_Type::make()->units( Size_Constants::typography() ),
|
||||
'color' => Color_Prop_Type::make(),
|
||||
'letter-spacing' => Size_Prop_Type::make()->units( Size_Constants::typography() ),
|
||||
'word-spacing' => Size_Prop_Type::make()->units( Size_Constants::typography() ),
|
||||
'column-count' => Number_Prop_Type::make(),
|
||||
'column-gap' => Size_Prop_Type::make()
|
||||
->set_dependencies(
|
||||
Dependency_Manager::make()
|
||||
->where( [
|
||||
'operator' => 'gte',
|
||||
'path' => [ 'column-count' ],
|
||||
'value' => 1,
|
||||
] )
|
||||
->get()
|
||||
),
|
||||
'line-height' => Size_Prop_Type::make()->units( Size_Constants::typography() ),
|
||||
'text-align' => String_Prop_Type::make()->enum( [
|
||||
'start',
|
||||
'center',
|
||||
'end',
|
||||
'justify',
|
||||
] ),
|
||||
'font-style' => String_Prop_Type::make()->enum( [
|
||||
'normal',
|
||||
'italic',
|
||||
'oblique',
|
||||
] ),
|
||||
// TODO: validate text-decoration in more specific way [EDS-524]
|
||||
'text-decoration' => String_Prop_Type::make(),
|
||||
'text-transform' => String_Prop_Type::make()->enum( [
|
||||
'none',
|
||||
'capitalize',
|
||||
'uppercase',
|
||||
'lowercase',
|
||||
] ),
|
||||
'direction' => String_Prop_Type::make()->enum( [
|
||||
'ltr',
|
||||
'rtl',
|
||||
] ),
|
||||
'stroke' => Stroke_Prop_Type::make(),
|
||||
'all' => String_Prop_Type::make()->enum( [
|
||||
'initial',
|
||||
'inherit',
|
||||
'unset',
|
||||
'revert',
|
||||
'revert-layer',
|
||||
] ),
|
||||
'cursor' => String_Prop_Type::make()->enum( [
|
||||
'pointer',
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_spacing_props() {
|
||||
return [
|
||||
'padding' => Union_Prop_Type::make()
|
||||
->add_prop_type( Dimensions_Prop_Type::make_with_units( Size_Constants::spacing() ) )
|
||||
->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::spacing() ) ),
|
||||
'margin' => Union_Prop_Type::make()
|
||||
->add_prop_type( Dimensions_Prop_Type::make() )
|
||||
->add_prop_type( Size_Prop_Type::make() ),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_border_props() {
|
||||
return [
|
||||
'border-radius' => Union_Prop_Type::make()
|
||||
->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::border() ) )
|
||||
->add_prop_type( Border_Radius_Prop_Type::make() ),
|
||||
'border-width' => Union_Prop_Type::make()
|
||||
->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::border() ) )
|
||||
->add_prop_type( Border_Width_Prop_Type::make() ),
|
||||
'border-color' => Color_Prop_Type::make(),
|
||||
'border-style' => String_Prop_Type::make()->enum( [
|
||||
'none',
|
||||
'hidden',
|
||||
'dotted',
|
||||
'dashed',
|
||||
'solid',
|
||||
'double',
|
||||
'groove',
|
||||
'ridge',
|
||||
'inset',
|
||||
'outset',
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_background_props() {
|
||||
// Background image overlay as an exception
|
||||
$background_prop_type = Background_Prop_Type::make();
|
||||
$bg_overlay_prop_type = $background_prop_type->get_shape_field( Background_Overlay_Prop_Type::get_key() );
|
||||
$bg_image_overlay_prop_type = $bg_overlay_prop_type->get_item_type()->get_prop_type( Background_Image_Overlay_Prop_Type::get_key() );
|
||||
Dynamic_Prop_Types_Mapping::make()->get_modified_prop_types( $bg_image_overlay_prop_type->get_shape() );
|
||||
return [
|
||||
'background' => $background_prop_type,
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_effects_props() {
|
||||
return [
|
||||
'box-shadow' => Box_Shadow_Prop_Type::make(),
|
||||
'opacity' => Size_Prop_Type::make()
|
||||
->units( Size_Constants::opacity() )
|
||||
->default_unit( Size_Constants::UNIT_PERCENT ),
|
||||
'filter' => Filter_Prop_Type::make(),
|
||||
'backdrop-filter' => Backdrop_Filter_Prop_Type::make(),
|
||||
'transform' => Transform_Prop_Type::make(),
|
||||
'transition' => Transition_Prop_Type::make(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_layout_props() {
|
||||
return [
|
||||
'display' => String_Prop_Type::make()->enum( [
|
||||
'block',
|
||||
'inline',
|
||||
'inline-block',
|
||||
'flex',
|
||||
'inline-flex',
|
||||
'grid',
|
||||
'inline-grid',
|
||||
'flow-root',
|
||||
'none',
|
||||
'contents',
|
||||
] ),
|
||||
'flex-direction' => String_Prop_Type::make()->enum( [
|
||||
'row',
|
||||
'row-reverse',
|
||||
'column',
|
||||
'column-reverse',
|
||||
] ),
|
||||
'gap' => Union_Prop_Type::make()
|
||||
->add_prop_type( Layout_Direction_Prop_Type::make() )
|
||||
->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::layout() ) ),
|
||||
'flex-wrap' => String_Prop_Type::make()->enum( [
|
||||
'wrap',
|
||||
'nowrap',
|
||||
'wrap-reverse',
|
||||
] ),
|
||||
'flex' => Flex_Prop_Type::make(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_alignment_props() {
|
||||
return [
|
||||
'justify-content' => String_Prop_Type::make()->enum( [
|
||||
'center',
|
||||
'start',
|
||||
'end',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'left',
|
||||
'right',
|
||||
'normal',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'stretch',
|
||||
] ),
|
||||
'align-content' => String_Prop_Type::make()->enum( [
|
||||
'center',
|
||||
'start',
|
||||
'end',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
] ),
|
||||
'align-items' => String_Prop_Type::make()->enum( [
|
||||
'normal',
|
||||
'stretch',
|
||||
'center',
|
||||
'start',
|
||||
'end',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'self-start',
|
||||
'self-end',
|
||||
'anchor-center',
|
||||
] ),
|
||||
'align-self' => String_Prop_Type::make()->enum( [
|
||||
'auto',
|
||||
'normal',
|
||||
'center',
|
||||
'start',
|
||||
'end',
|
||||
'self-start',
|
||||
'self-end',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'anchor-center',
|
||||
'baseline',
|
||||
'first baseline',
|
||||
'last baseline',
|
||||
'stretch',
|
||||
] ),
|
||||
'order' => Number_Prop_Type::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
class Style_Variant {
|
||||
private ?string $breakpoint = null;
|
||||
private ?string $state = null;
|
||||
|
||||
/** @var array<string, array> */
|
||||
private array $props = [];
|
||||
|
||||
public static function make(): self {
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function set_breakpoint( string $breakpoint ): self {
|
||||
$this->breakpoint = $breakpoint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_state( string $state ): self {
|
||||
$this->state = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add_prop( string $key, $value ): self {
|
||||
$this->props[ $key ] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): array {
|
||||
return [
|
||||
'meta' => [
|
||||
'breakpoint' => $this->breakpoint,
|
||||
'state' => $this->state,
|
||||
],
|
||||
'props' => $this->props,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Module;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
|
||||
|
||||
class Styles_Renderer {
|
||||
const DEFAULT_SELECTOR_PREFIX = '.elementor';
|
||||
|
||||
/**
|
||||
* @var array<string, array{direction: 'min' | 'max', value: int, is_enabled: boolean}>
|
||||
*/
|
||||
private array $breakpoints;
|
||||
|
||||
private $on_prop_transform;
|
||||
|
||||
private string $selector_prefix;
|
||||
|
||||
/**
|
||||
* @param array<string, array{direction: 'min' | 'max', value: int, is_enabled: boolean}> $breakpoints
|
||||
* @param string $selector_prefix
|
||||
*/
|
||||
private function __construct( array $breakpoints, string $selector_prefix = self::DEFAULT_SELECTOR_PREFIX ) {
|
||||
$this->breakpoints = $breakpoints;
|
||||
$this->selector_prefix = $selector_prefix;
|
||||
}
|
||||
|
||||
public static function make( array $breakpoints, string $selector_prefix = self::DEFAULT_SELECTOR_PREFIX ): self {
|
||||
return new self( $breakpoints, $selector_prefix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the styles to a CSS string.
|
||||
*
|
||||
* Styles format:
|
||||
* array<int, array{
|
||||
* id: string,
|
||||
* type: string,
|
||||
* cssName: string | null,
|
||||
* variants: array<int, array{
|
||||
* props: array<string, mixed>,
|
||||
* meta: array<string, mixed>
|
||||
* }>
|
||||
* }>
|
||||
*
|
||||
* @param array $styles Array of style definitions.
|
||||
*
|
||||
* @return string Rendered CSS string.
|
||||
*/
|
||||
public function render( array $styles ): string {
|
||||
$css_style = [];
|
||||
|
||||
foreach ( $styles as $style_def ) {
|
||||
$style = $this->style_definition_to_css_string( $style_def );
|
||||
$css_style[] = $style;
|
||||
}
|
||||
|
||||
return implode( '', $css_style );
|
||||
}
|
||||
|
||||
public function on_prop_transform( callable $callback ): self {
|
||||
$this->on_prop_transform = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function style_definition_to_css_string( array $style ): string {
|
||||
$base_selector = $this->get_base_selector( $style );
|
||||
|
||||
if ( ! $base_selector ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$stylesheet = [];
|
||||
|
||||
foreach ( $style['variants'] as $variant ) {
|
||||
$style_declaration = $this->variant_to_css_string( $base_selector, $variant );
|
||||
|
||||
if ( $style_declaration ) {
|
||||
$stylesheet[] = $style_declaration;
|
||||
}
|
||||
}
|
||||
|
||||
return implode( '', $stylesheet );
|
||||
}
|
||||
|
||||
private function get_base_selector( array $style_def ): ?string {
|
||||
$map = [
|
||||
'class' => '.',
|
||||
];
|
||||
|
||||
if (
|
||||
isset( $style_def['type'] ) &&
|
||||
isset( $style_def['id'] ) &&
|
||||
isset( $map[ $style_def['type'] ] ) &&
|
||||
$style_def['id']
|
||||
) {
|
||||
$type = $map[ $style_def['type'] ];
|
||||
$name = $style_def['cssName'] ?? $style_def['id'];
|
||||
|
||||
$selector_parts = array_filter( [
|
||||
$this->selector_prefix,
|
||||
"{$type}{$name}",
|
||||
] );
|
||||
|
||||
return implode( ' ', $selector_parts );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function variant_to_css_string( string $base_selector, array $variant ): string {
|
||||
$css = $this->props_to_css_string( $variant['props'] ) ?? '';
|
||||
$custom_css = $this->custom_css_to_css_string( $variant['custom_css'] ?? null );
|
||||
|
||||
if ( ! $css && ! $custom_css ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$state = isset( $variant['meta']['state'] ) ? ':' . $variant['meta']['state'] : '';
|
||||
$selector = $base_selector . $state;
|
||||
|
||||
$style_declaration = $selector . '{' . $css . $custom_css . '}';
|
||||
|
||||
if ( isset( $variant['meta']['breakpoint'] ) ) {
|
||||
$style_declaration = $this->wrap_with_media_query( $variant['meta']['breakpoint'], $style_declaration );
|
||||
}
|
||||
|
||||
return $style_declaration;
|
||||
}
|
||||
|
||||
private function props_to_css_string( array $props ): string {
|
||||
$schema = Style_Schema::get();
|
||||
|
||||
return Collection::make( Render_Props_Resolver::for_styles()->resolve( $schema, $props ) )
|
||||
->filter()
|
||||
->map( function ( $value, $prop ) {
|
||||
if ( $this->on_prop_transform ) {
|
||||
call_user_func( $this->on_prop_transform, $prop, $value );
|
||||
}
|
||||
|
||||
return $prop . ':' . $value . ';';
|
||||
} )
|
||||
->implode( '' );
|
||||
}
|
||||
|
||||
private function custom_css_to_css_string( ?array $custom_css ): string {
|
||||
$is_feature_active = Plugin::$instance->experiments->is_feature_active( Module::EXPERIMENT_CUSTOM_CSS );
|
||||
|
||||
return $is_feature_active && ! empty( $custom_css['raw'] )
|
||||
? Utils::decode_string( $custom_css['raw'], '' ) . '\n'
|
||||
: '';
|
||||
}
|
||||
|
||||
private function wrap_with_media_query( string $breakpoint_id, string $css ): string {
|
||||
if ( ! isset( $this->breakpoints[ $breakpoint_id ] ) ) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
$breakpoint = $this->breakpoints[ $breakpoint_id ];
|
||||
if ( isset( $breakpoint['is_enabled'] ) && ! $breakpoint['is_enabled'] ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$query = $this->get_media_query( $this->breakpoints[ $breakpoint_id ] );
|
||||
|
||||
return $query ? $query . '{' . $css . '}' : $css;
|
||||
}
|
||||
|
||||
public static function get_media_query( $breakpoint ): ?string {
|
||||
if ( isset( $breakpoint['is_enabled'] ) && ! $breakpoint['is_enabled'] ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$size = self::get_breakpoint_size( $breakpoint );
|
||||
|
||||
return $size ? '@media(' . $size . ')' : null;
|
||||
}
|
||||
|
||||
private static function get_breakpoint_size( array $breakpoint ): ?string {
|
||||
$bound = 'min' === $breakpoint['direction'] ? 'min-width' : 'max-width';
|
||||
$width = $breakpoint['value'] . 'px';
|
||||
|
||||
return "{$bound}:{$width}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user