first commit
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
<?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\Utils\Memo;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\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, path: 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;
|
||||
} );
|
||||
|
||||
add_action( 'elementor/atomic-widgets/styles/clear', fn( array $path ) => $this->clear_styles( $path ) );
|
||||
}
|
||||
|
||||
public function register( array $path, callable $get_style_defs ) {
|
||||
$key = $this->convert_path_to_handle( $path );
|
||||
|
||||
$this->registered_styles_by_key[ $key ] = [
|
||||
'get_styles' => $get_style_defs,
|
||||
'path' => $path,
|
||||
];
|
||||
}
|
||||
|
||||
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'] ),
|
||||
'path' => $style_params['path'],
|
||||
],
|
||||
])
|
||||
->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 ) {
|
||||
$path = $style_params['path'];
|
||||
|
||||
// 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( $path ) ) {
|
||||
Style_Fonts::make( $style_key )->clear();
|
||||
|
||||
// We should validate it after this iteration
|
||||
$this->cache_validity->validate( $path, uniqid() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ) {
|
||||
$path = $style_params['path'];
|
||||
$render_css = fn() => $this->render_css_by_breakpoints( $style_params['get_styles'], $style_key, $breakpoint_key, $group_by_breakpoint_memo );
|
||||
|
||||
$version = $this->cache_validity->get_meta( $path );
|
||||
|
||||
$breakpoint_media = $this->get_breakpoint_media( $breakpoint_key );
|
||||
|
||||
if ( ! $breakpoint_media ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$breakpoint_path = array_merge( $path, [ $breakpoint_key ] );
|
||||
|
||||
$style_file = $this->css_files_manager->get(
|
||||
$this->convert_path_to_handle( $breakpoint_path ),
|
||||
$breakpoint_media,
|
||||
$render_css,
|
||||
$this->cache_validity->is_valid( $breakpoint_path )
|
||||
);
|
||||
|
||||
$this->cache_validity->validate( $breakpoint_path );
|
||||
|
||||
if ( ! $style_file ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
$style_file->get_handle(),
|
||||
$style_file->get_url(),
|
||||
[],
|
||||
$version,
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
|
||||
private function clear_styles( array $path ) {
|
||||
$node = $this->cache_validity->get_node( $path );
|
||||
|
||||
$this->clear_styles_by_node( $path, $node );
|
||||
|
||||
$this->cache_validity->invalidate( $path );
|
||||
}
|
||||
|
||||
private function clear_styles_by_node( array $path, $node ) {
|
||||
if ( ! $node ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( true === $node || $node['state'] ) {
|
||||
$this->css_files_manager->delete( $this->convert_path_to_handle( $path ) );
|
||||
}
|
||||
|
||||
if ( is_bool( $node ) || empty( $node['children'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $node['children'] as $child_key => $child_node ) {
|
||||
$this->clear_styles_by_node( array_merge( $path, [ $child_key ] ), $child_node );
|
||||
}
|
||||
}
|
||||
|
||||
private function convert_path_to_handle( array $path ) {
|
||||
return implode( '-', $path );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\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(),
|
||||
);
|
||||
}
|
||||
|
||||
private function invalidate_cache() {
|
||||
do_action( 'elementor/atomic-widgets/styles/clear', [ 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,121 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Utils;
|
||||
use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils;
|
||||
use Elementor\Utils as ElementorUtils;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Atomic_Widget_Styles {
|
||||
const STYLES_KEY = 'local';
|
||||
const CONTEXT_FRONTEND = 'frontend';
|
||||
const CONTEXT_PREVIEW = 'preview';
|
||||
|
||||
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 ],
|
||||
$this->get_context( ! Utils::is_post_published( $document ) )
|
||||
), 20, 2 );
|
||||
|
||||
add_action(
|
||||
'elementor/core/files/clear_cache',
|
||||
fn() => $this->invalidate_cache(),
|
||||
);
|
||||
|
||||
add_action(
|
||||
'deleted_post',
|
||||
fn( $post_id ) => $this->invalidate_cache( [ $post_id ] )
|
||||
);
|
||||
|
||||
add_action( 'update_option__elementor_pro_license_v2_data', fn() => Plugin::$instance->files_manager->clear_cache() );
|
||||
add_action( 'delete_option__elementor_pro_license_v2_data', fn() => Plugin::$instance->files_manager->clear_cache() );
|
||||
}
|
||||
|
||||
private function register_styles( Atomic_Styles_Manager $styles_manager, array $post_ids ) {
|
||||
$context = $this->get_context( is_preview() );
|
||||
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
$get_styles = fn() => $this->parse_post_styles( $post_id );
|
||||
|
||||
$styles_manager->register( [ self::STYLES_KEY, $post_id, $context ], $get_styles );
|
||||
}
|
||||
}
|
||||
|
||||
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 self::get_license_based_filtered_styles( $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, ?string $context = null ) {
|
||||
if ( empty( $post_ids ) ) {
|
||||
do_action( 'elementor/atomic-widgets/styles/clear', [ self::STYLES_KEY ] );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$is_post_status_publish = self::CONTEXT_FRONTEND === $context;
|
||||
|
||||
// When a user publishes a post, we should invalidate the styles of the draft too
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
do_action( 'elementor/atomic-widgets/styles/clear',
|
||||
empty( $context ) || $is_post_status_publish
|
||||
? [ self::STYLES_KEY, $post_id ]
|
||||
: [ self::STYLES_KEY, $post_id, $context ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function get_context( bool $is_preview ) {
|
||||
return $is_preview ? self::CONTEXT_PREVIEW : self::CONTEXT_FRONTEND;
|
||||
}
|
||||
|
||||
public static function get_license_based_filtered_styles( $styles ) {
|
||||
if ( ElementorUtils::has_pro() && version_compare( ELEMENTOR_PRO_VERSION, '3.35', '<' ) ) {
|
||||
return $styles;
|
||||
}
|
||||
|
||||
return apply_filters(
|
||||
'elementor/atomic_widgets/editor_data/element_styles',
|
||||
self::remove_custom_css_from_styles( $styles ),
|
||||
$styles
|
||||
);
|
||||
}
|
||||
|
||||
public static function remove_custom_css_from_styles( array $styles ) {
|
||||
if ( empty( $styles ) ) {
|
||||
return $styles;
|
||||
}
|
||||
|
||||
foreach ( $styles as $style_id => $style ) {
|
||||
if ( isset( $style['variants'] ) && is_array( $style['variants'] ) ) {
|
||||
foreach ( $style['variants'] as $variant_index => $variant ) {
|
||||
unset( $styles[ $style_id ]['variants'][ $variant_index ]['custom_css'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $styles;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles\CacheValidity;
|
||||
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
|
||||
class Cache_Validity_Item {
|
||||
const CACHE_KEY_PREFIX = 'elementor_atomic_cache_validity__';
|
||||
|
||||
private string $root;
|
||||
|
||||
public function __construct( string $root ) {
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
public function get( array $keys ): ?array {
|
||||
return $this->wrap_exception( function() use ( $keys ) {
|
||||
$data = $this->get_stored_data();
|
||||
|
||||
$node = $this->get_node( $data, $keys );
|
||||
|
||||
if ( null === $node ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_bool( $node ) ? [ 'state' => $node ] : $node;
|
||||
} );
|
||||
}
|
||||
|
||||
public function validate( array $keys, $meta = null ) {
|
||||
return $this->wrap_exception( function() use ( $keys, $meta ) {
|
||||
$data = $this->get_stored_data();
|
||||
|
||||
if ( empty( $keys ) ) {
|
||||
$data['state'] = true;
|
||||
$data['meta'] = $meta;
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate_nested_node( $data, $keys, $meta );
|
||||
} );
|
||||
}
|
||||
|
||||
public function invalidate( array $keys ) {
|
||||
return $this->wrap_exception( function() use ( $keys ) {
|
||||
if ( empty( $keys ) ) {
|
||||
$this->delete_stored_data();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->get_stored_data();
|
||||
|
||||
$this->invalidate_nested_node( $data, $keys );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @param mixed | null $meta
|
||||
*/
|
||||
private function validate_nested_node( array $data, array $keys, $meta = null ) {
|
||||
$data = $this->ensure_path( $data, $keys );
|
||||
|
||||
$last_key = array_pop( $keys );
|
||||
|
||||
// parent is guaranteed to be an array as we send the full $keys array to ensure_path
|
||||
$parent = &$this->get_node( $data, $keys );
|
||||
|
||||
$old_node = &$parent['children'][ $last_key ];
|
||||
|
||||
$has_children = is_array( $old_node ) && ! empty( $old_node['children'] );
|
||||
|
||||
if ( null === $meta && ! $has_children ) {
|
||||
$parent['children'][ $last_key ] = true;
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
return;
|
||||
}
|
||||
|
||||
$new_node = [
|
||||
'state' => true,
|
||||
];
|
||||
|
||||
if ( $has_children ) {
|
||||
$new_node['children'] = $old_node['children'];
|
||||
}
|
||||
|
||||
if ( null !== $meta ) {
|
||||
$new_node['meta'] = $meta;
|
||||
}
|
||||
|
||||
$parent['children'][ $last_key ] = $new_node;
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
*/
|
||||
private function invalidate_nested_node( array $data, array $keys ) {
|
||||
$last_key = array_pop( $keys );
|
||||
$parent = &$this->get_node( $data, $keys );
|
||||
|
||||
if ( ! is_array( $parent ) || ! isset( $parent['children'][ $last_key ] ) ) {
|
||||
// node doesn't exist - no need to do anything
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count( $parent['children'] ) === 1 ) {
|
||||
// if the invalidated node is the parent's o nly child - normalize the data
|
||||
$data = $this->get_normalized_data( $data, $keys, $last_key );
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
return;
|
||||
}
|
||||
|
||||
unset( $parent['children'][ $last_key ] );
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} $data
|
||||
* @param array<string> $keys
|
||||
* @param string $last_key
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>}
|
||||
*/
|
||||
private function get_normalized_data( array $data, array $keys, string $last_key ) {
|
||||
$obsolete_root_params = &$this->find_empty_parents_path_root( $data, $keys, $last_key );
|
||||
$parent = &$this->get_node( $data, $keys );
|
||||
|
||||
if ( $obsolete_root_params['node'] && $obsolete_root_params['key'] ) {
|
||||
unset( $obsolete_root_params['node']['children'][ $obsolete_root_params['key'] ] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $obsolete_root_params['node'] ) {
|
||||
unset( $data['children'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $parent ) {
|
||||
unset( $parent['children'][ $last_key ] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @return array{key: string | null, node: array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | null}
|
||||
*/
|
||||
private function &find_empty_parents_path_root( array &$data, array $keys ) {
|
||||
$root_node = [
|
||||
'key' => null,
|
||||
'node' => null,
|
||||
];
|
||||
|
||||
$current = &$data;
|
||||
$parent = &$current;
|
||||
|
||||
while ( ! empty( $keys ) ) {
|
||||
$key = array_shift( $keys );
|
||||
$parent = &$current;
|
||||
$current = &$current['children'][ $key ];
|
||||
|
||||
if ( $this->is_empty_parent( $current ) && empty( $root_node['node'] ) ) {
|
||||
$root_node = [
|
||||
'key' => $key,
|
||||
'node' => &$parent,
|
||||
];
|
||||
} elseif ( is_array( $current ) && ! $this->is_empty_parent( $current ) ) {
|
||||
$root_node = [
|
||||
'key' => null,
|
||||
'node' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $root_node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stored tree, guaranteed to have a path representation based on $keys
|
||||
*
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>}
|
||||
*/
|
||||
private function ensure_path( array $data, array $keys ): ?array {
|
||||
$current = &$data;
|
||||
|
||||
while ( ! empty( $keys ) ) {
|
||||
$key = array_shift( $keys );
|
||||
|
||||
if ( is_bool( $current ) ) {
|
||||
$current = [ 'state' => $current ];
|
||||
}
|
||||
|
||||
if ( ! isset( $current['children'] ) ) {
|
||||
$current['children'] = [];
|
||||
}
|
||||
|
||||
if ( ! isset( $current['children'][ $key ] ) ) {
|
||||
$current['children'][ $key ] = [ 'state' => false ];
|
||||
}
|
||||
|
||||
$current = &$current['children'][ $key ];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @return array<array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean | null> | null | boolean
|
||||
*/
|
||||
private function &get_node( array &$data, array $keys ) {
|
||||
$current = &$data;
|
||||
|
||||
while ( ! empty( $keys ) ) {
|
||||
$key = array_shift( $keys );
|
||||
|
||||
if ( isset( $current['children'][ $key ] ) ) {
|
||||
$current = &$current['children'][ $key ];
|
||||
} else {
|
||||
$current = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
private function is_empty_parent( $data ): bool {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( ! isset( $data['children'] ) || 1 === count( $data['children'] ) ) && ! $data['state'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>}
|
||||
*/
|
||||
private function get_stored_data() {
|
||||
return get_option( self::CACHE_KEY_PREFIX . $this->root, [ 'state' => false ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} $data
|
||||
*/
|
||||
private function update_stored_data( $data ) {
|
||||
// setting autoload with false to avoid unnecessary memory usage
|
||||
update_option( self::CACHE_KEY_PREFIX . $this->root, $data, false );
|
||||
}
|
||||
|
||||
private function delete_stored_data() {
|
||||
delete_option( self::CACHE_KEY_PREFIX . $this->root );
|
||||
}
|
||||
|
||||
private function wrap_exception( callable $callback ) {
|
||||
try {
|
||||
return $callback();
|
||||
} catch ( \Exception $e ) {
|
||||
$this->delete_stored_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles\CacheValidity;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity_Item;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
|
||||
class Cache_Validity {
|
||||
/**
|
||||
* @param array<string> $keys
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid( $keys ): bool {
|
||||
$root = array_shift( $keys );
|
||||
$cache_item = new Cache_Validity_Item( $root );
|
||||
|
||||
$item = $cache_item->get( $keys );
|
||||
|
||||
if ( ! $item ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $item['state'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $keys
|
||||
* @return mixed | null
|
||||
*/
|
||||
public function get_meta( array $keys ) {
|
||||
$root = array_shift( $keys );
|
||||
$cache_item = new Cache_Validity_Item( $root );
|
||||
|
||||
$item = $cache_item->get( $keys );
|
||||
|
||||
if ( ! $item || is_bool( $item ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $item['meta'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $keys
|
||||
* @param mixed | null $meta
|
||||
* @return void
|
||||
*/
|
||||
public function validate( $keys, $meta = null ): void {
|
||||
$root = array_shift( $keys );
|
||||
$cache_item = new Cache_Validity_Item( $root );
|
||||
|
||||
$cache_item->validate( $keys, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $keys
|
||||
* @return void
|
||||
*/
|
||||
public function invalidate( array $keys ): void {
|
||||
$root = array_shift( $keys );
|
||||
$cache_item = new Cache_Validity_Item( $root );
|
||||
|
||||
$cache_item->invalidate( $keys );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $keys
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | null
|
||||
*/
|
||||
public function get_node( array $keys ): ?array {
|
||||
$root = array_shift( $keys );
|
||||
$cache_item = new Cache_Validity_Item( $root );
|
||||
|
||||
return $cache_item->get( $keys );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
|
||||
public function delete( string $handle ): void {
|
||||
$filesystem = $this->get_filesystem();
|
||||
$path = $this->get_path( $handle );
|
||||
|
||||
if ( ! $filesystem->exists( $path ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filesystem->delete( $path );
|
||||
}
|
||||
|
||||
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,139 @@
|
||||
<?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_CH = 'ch';
|
||||
const UNIT_AUTO = 'auto';
|
||||
const UNIT_CUSTOM = 'custom';
|
||||
const UNIT_SECOND = 's';
|
||||
const UNIT_MILLI_SECOND = 'ms';
|
||||
const UNIT_ANGLE_DEG = 'deg';
|
||||
|
||||
const DEFAULT_UNIT = self::UNIT_PX;
|
||||
|
||||
const LENGTH_UNITS = [
|
||||
self::UNIT_PX,
|
||||
self::UNIT_EM,
|
||||
self::UNIT_REM,
|
||||
self::UNIT_VW,
|
||||
self::UNIT_VH,
|
||||
self::UNIT_CH,
|
||||
];
|
||||
|
||||
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,
|
||||
self::UNIT_CUSTOM,
|
||||
];
|
||||
}
|
||||
|
||||
private static function units_without_auto(): array {
|
||||
return [ ...self::LENGTH_UNITS, self::UNIT_PERCENT, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function layout(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function spacing_margin() {
|
||||
return self::all();
|
||||
}
|
||||
|
||||
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(): array {
|
||||
return [ ...self::LENGTH_UNITS, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function typography(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function stroke_width(): array {
|
||||
return [
|
||||
self::UNIT_PX,
|
||||
self::UNIT_EM,
|
||||
self::UNIT_REM,
|
||||
self::UNIT_CUSTOM,
|
||||
];
|
||||
}
|
||||
|
||||
public static function transition(): array {
|
||||
return [ ...self::TIME_UNITS, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function border(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
|
||||
public static function opacity(): array {
|
||||
return [ self::UNIT_PERCENT, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function box_shadow(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function rotate(): array {
|
||||
return [ ...self::ANGLE_UNITS, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function transform(): array {
|
||||
return self::units_without_auto();
|
||||
}
|
||||
|
||||
public static function drop_shadow(): array {
|
||||
return [ ...self::LENGTH_UNITS, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function blur_filter(): array {
|
||||
return [ ...self::LENGTH_UNITS, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function intensity_filter(): array {
|
||||
return [ self::UNIT_PERCENT, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function color_tone_filter(): array {
|
||||
return [ self::UNIT_PERCENT, self::UNIT_CUSTOM ];
|
||||
}
|
||||
|
||||
public static function hue_rotate_filter(): array {
|
||||
return [ ...self::ANGLE_UNITS, self::UNIT_CUSTOM ];
|
||||
}
|
||||
}
|
||||
@@ -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,45 @@
|
||||
<?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 {
|
||||
if ( str_starts_with( $this->media, '@media' ) ) {
|
||||
return substr( $this->media, 6 );
|
||||
}
|
||||
|
||||
return $this->media;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
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();
|
||||
|
||||
if ( empty( $fonts ) ) {
|
||||
delete_option( $style_fonts_key );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( $style_fonts_key, $fonts, false );
|
||||
}
|
||||
|
||||
private function get_key(): string {
|
||||
return FONTS_KEY_PREFIX . $this->style_key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
<?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()->description( 'The width of the element' ),
|
||||
'height' => Size_Prop_Type::make()->description( 'The height of the element' ),
|
||||
'min-width' => Size_Prop_Type::make()->description( 'The minimum width of the element' ),
|
||||
'min-height' => Size_Prop_Type::make()->description( 'The minimum height of the element' ),
|
||||
'max-width' => Size_Prop_Type::make()->description( 'The maximum width of the element' ),
|
||||
'max-height' => Size_Prop_Type::make()->description( 'The maximum height of the element' ),
|
||||
'overflow' => String_Prop_Type::make()->enum( [
|
||||
'visible',
|
||||
'hidden',
|
||||
'auto',
|
||||
] )->description( 'The overflow CSS property. CSS values: visible, hidden, auto' ),
|
||||
'aspect-ratio' => String_Prop_Type::make()->description( 'Equivalent to CSS aspect-ration property' ),
|
||||
'object-fit' => String_Prop_Type::make()->enum( [
|
||||
'fill',
|
||||
'cover',
|
||||
'contain',
|
||||
'none',
|
||||
'scale-down',
|
||||
] )->description( 'The object-fit CSS. CSS values: 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',
|
||||
] )->description( 'The CSS position property specifies the type of positioning method used for an element (static, relative, absolute, fixed, or sticky).' ),
|
||||
'inset-block-start' => Size_Prop_Type::make()->description( 'Size PropType for the inset-block-start CSS property' ),
|
||||
'inset-inline-end' => Size_Prop_Type::make()->description( 'Size PropType for the inset-inline-end CSS property' ),
|
||||
'inset-block-end' => Size_Prop_Type::make()->description( 'Size PropType for the inset-block-end CSS property' ),
|
||||
'inset-inline-start' => Size_Prop_Type::make()->description( 'Size PropType for the inset-inline-start CSS property' ),
|
||||
'z-index' => Number_Prop_Type::make()
|
||||
->description( 'The z-index CSS property sets the z-order of a positioned element and its descendants or flex items. It specifies the stack order of elements.' ),
|
||||
'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()->description( 'The font family of the text content.' ),
|
||||
'font-weight' => String_Prop_Type::make()->enum( [
|
||||
'100',
|
||||
'200',
|
||||
'300',
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
'800',
|
||||
'900',
|
||||
'normal',
|
||||
'bold',
|
||||
'bolder',
|
||||
'lighter',
|
||||
] )
|
||||
->description( 'The weight (or boldness) of the font. Values should match css font-weight specifications.' ),
|
||||
'font-size' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The font size in Size PropType Format' ),
|
||||
'color' => Color_Prop_Type::make()
|
||||
->description( 'The text color, specified as a hex code, rgb(a), hsl(a), or a standard css color name.' ),
|
||||
'letter-spacing' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The spacing between letters in Size PropType format' ),
|
||||
'word-spacing' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The spacing between words in Size PropType format' ),
|
||||
'column-count' => Number_Prop_Type::make()->description( 'The number of columns the text content should be divided into.' ),
|
||||
'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() )->description( 'The line height of the text content in Size PropType format' ),
|
||||
'text-align' => String_Prop_Type::make()->enum( [
|
||||
'start',
|
||||
'center',
|
||||
'end',
|
||||
'justify',
|
||||
] )
|
||||
->description( 'The horizontal alignment of the text content. Allowed values: start, center, end, justify.' ),
|
||||
'font-style' => String_Prop_Type::make()->enum( [
|
||||
'normal',
|
||||
'italic',
|
||||
'oblique',
|
||||
] )
|
||||
->description( 'The font style of the text content. CSS values: normal, italic, oblique' ),
|
||||
// TODO: validate text-decoration in more specific way [EDS-524]
|
||||
'text-decoration' => String_Prop_Type::make()
|
||||
->description( 'The text decoration style. CSS values like: none, underline, overline, line-through, blink, etc.' ),
|
||||
'text-transform' => String_Prop_Type::make()->enum( [
|
||||
'none',
|
||||
'capitalize',
|
||||
'uppercase',
|
||||
'lowercase',
|
||||
] )
|
||||
->description( 'Controls the capitalization of text. CSS values: none, capitalize, uppercase, lowercase' ),
|
||||
'direction' => String_Prop_Type::make()->enum( [
|
||||
'ltr',
|
||||
'rtl',
|
||||
] )->description( 'The text direction. CSS values: ltr (left to right), rtl (right to left)' ),
|
||||
'stroke' => Stroke_Prop_Type::make(),
|
||||
'all' => String_Prop_Type::make()->enum( [
|
||||
'initial',
|
||||
'inherit',
|
||||
'unset',
|
||||
'revert',
|
||||
'revert-layer',
|
||||
] )->description( 'The all CSS property. CSS values: initial, inherit, unset, revert, revert-layer' ),
|
||||
'cursor' => String_Prop_Type::make()->enum( [
|
||||
'pointer',
|
||||
] )
|
||||
->description( 'The type of cursor to be displayed when pointing over the element. E.g., 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() )
|
||||
->description( 'Padding css in Size PropType format' )
|
||||
),
|
||||
'margin' => Union_Prop_Type::make()
|
||||
->add_prop_type( Dimensions_Prop_Type::make_with_units( Size_Constants::spacing_margin() ) )
|
||||
->add_prop_type(
|
||||
Size_Prop_Type::make()
|
||||
->units( Size_Constants::spacing_margin() )
|
||||
->description( 'Margin css in Size PropType format' )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_border_props() {
|
||||
return [
|
||||
'border-radius' => Union_Prop_Type::make()
|
||||
->add_prop_type( Border_Radius_Prop_Type::make() )
|
||||
->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::border() ) ),
|
||||
'border-width' => Union_Prop_Type::make()
|
||||
->add_prop_type( Border_Width_Prop_Type::make() )
|
||||
->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::border() ) ),
|
||||
'border-color' => Color_Prop_Type::make()->description( 'The border color, specified as a hex code, rgb(a), hsl(a), or a standard css color name.' ),
|
||||
'border-style' => String_Prop_Type::make()->enum( [
|
||||
'none',
|
||||
'hidden',
|
||||
'dotted',
|
||||
'dashed',
|
||||
'solid',
|
||||
'double',
|
||||
'groove',
|
||||
'ridge',
|
||||
'inset',
|
||||
'outset',
|
||||
] )
|
||||
->description( 'The border style in CSS values' ),
|
||||
'outline-width' => Size_Prop_Type::make()
|
||||
->units( Size_Constants::border() )
|
||||
->description( 'The width of the outline in Size PropType format' ),
|
||||
];
|
||||
}
|
||||
|
||||
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_extended_schema( $bg_image_overlay_prop_type->get_shape() );
|
||||
return [
|
||||
'background' => $background_prop_type,
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_effects_props() {
|
||||
return [
|
||||
'mix-blend-mode' => String_Prop_Type::make()->enum( [
|
||||
'normal',
|
||||
'multiply',
|
||||
'screen',
|
||||
'overlay',
|
||||
'darken',
|
||||
'lighten',
|
||||
'color-dodge',
|
||||
'saturation',
|
||||
'color',
|
||||
'difference',
|
||||
'exclusion',
|
||||
'hue',
|
||||
'luminosity',
|
||||
'soft-light',
|
||||
'hard-light',
|
||||
'color-burn',
|
||||
] )->description( 'Applied as mix-blend mode css effect.' ),
|
||||
'box-shadow' => Box_Shadow_Prop_Type::make(),
|
||||
'opacity' => Size_Prop_Type::make()
|
||||
->description( 'The opacity of the element, specified as a percentage between 0 (fully transparent) and 100 (fully opaque).' )
|
||||
->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',
|
||||
] )->description( 'The CSS display property defines the display behavior (the type of rendering box) of an element.' ),
|
||||
'flex-direction' => String_Prop_Type::make()
|
||||
->description( 'The direction of the contained items.' )
|
||||
->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',
|
||||
] )->description( 'Specifies whether the flex items should wrap or not. CSS values: 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',
|
||||
] )
|
||||
->description( 'Defines how the browser distributes space between and around content items along the main-axis of a flex container. CSS values: 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',
|
||||
] )
|
||||
->description( 'Aligns a flex container\'s lines within when there is extra space in the cross-axis. CSS values: 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',
|
||||
] )->description( 'Defines the default behavior for how flex items are laid out along the cross axis on the current line. CSS values: 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',
|
||||
] )->description( 'Allows the default alignment (or the one specified by align-items) to be overridden for individual flex items. CSS values: 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()->description( 'Specifies the order of the flex items. Items with lower order values are displayed first.' ),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles;
|
||||
|
||||
class Style_States {
|
||||
const HOVER = 'hover';
|
||||
const ACTIVE = 'active';
|
||||
const FOCUS = 'focus';
|
||||
const FOCUS_VISIBLE = 'focus-visible';
|
||||
|
||||
const SELECTED = 'e--selected';
|
||||
|
||||
private static function get_pseudo_states(): array {
|
||||
return [
|
||||
self::HOVER,
|
||||
self::ACTIVE,
|
||||
self::FOCUS,
|
||||
self::FOCUS_VISIBLE,
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_class_states(): array {
|
||||
return [
|
||||
self::SELECTED,
|
||||
];
|
||||
}
|
||||
|
||||
private static function get_additional_states_map(): array {
|
||||
return [
|
||||
self::HOVER => [ self::FOCUS_VISIBLE ],
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_selector_with_state( string $base_selector, string $state ): string {
|
||||
$additional_states = self::get_additional_states( $state );
|
||||
$all_states = [ $state, ...$additional_states ];
|
||||
|
||||
foreach ( $all_states as $current_state ) {
|
||||
$selector_strings[] = $base_selector . self::get_state_selector( $current_state );
|
||||
}
|
||||
|
||||
return implode( ',', $selector_strings );
|
||||
}
|
||||
|
||||
public static function get_additional_states( string $state ): array {
|
||||
return self::get_additional_states_map()[ $state ] ?? [];
|
||||
}
|
||||
|
||||
public static function get_state_selector( string $state ): string {
|
||||
if ( self::is_class_state( $state ) ) {
|
||||
return '.' . $state;
|
||||
}
|
||||
|
||||
if ( self::is_pseudo_state( $state ) ) {
|
||||
return ':' . $state;
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
|
||||
public static function get_valid_states(): array {
|
||||
return [
|
||||
...array_filter( self::get_pseudo_states(), function ( $state ) {
|
||||
return ! in_array( $state, self::get_additional_states_map()[ $state ] ?? [], true );
|
||||
} ),
|
||||
...self::get_class_states(),
|
||||
null,
|
||||
];
|
||||
}
|
||||
|
||||
public static function is_pseudo_state( string $state ): bool {
|
||||
return in_array( $state, self::get_pseudo_states(), true );
|
||||
}
|
||||
|
||||
public static function is_class_state( string $state ): bool {
|
||||
return in_array( $state, self::get_class_states(), true );
|
||||
}
|
||||
|
||||
public static function is_valid_state( $state ): bool {
|
||||
if ( null === $state ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return is_string( $state ) && in_array( $state, self::get_valid_states(), true );
|
||||
}
|
||||
|
||||
public static function get_class_states_map(): array {
|
||||
return [
|
||||
'selected' => [
|
||||
'name' => 'selected',
|
||||
'value' => self::SELECTED,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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 add_props( array $props ): self {
|
||||
$this->props = array_merge( $this->props, $props );
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): array {
|
||||
return [
|
||||
'meta' => [
|
||||
'breakpoint' => $this->breakpoint,
|
||||
'state' => $this->state,
|
||||
],
|
||||
'props' => $this->props,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?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 '';
|
||||
}
|
||||
|
||||
if ( isset( $variant['meta']['state'] ) ) {
|
||||
$selector = Style_States::get_selector_with_state( $base_selector, $variant['meta']['state'] );
|
||||
} else {
|
||||
$selector = $base_selector;
|
||||
}
|
||||
|
||||
$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 {
|
||||
return ! 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