first commit

This commit is contained in:
Roman Pyrih
2026-05-21 15:33:11 +02:00
commit acb036dbd9
8059 changed files with 2885104 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
{% if settings.text is not empty %}
{%- set allowed_tags = '<b><strong><sup><sub><s><em><i><u><del><span><br>' %}
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% if settings.link and settings.link.attributes is not empty %}
<{{ settings.link.tag | e('html_tag') }}
{{ settings.link.attributes | raw }}
class="{{ classes }}"
data-interaction-id="{{ interaction_id }}"
{{ id_attribute }} {{ settings.attributes | raw }}
>
{{ settings.text | striptags(allowed_tags) | raw }}
</{{ settings.link.tag | e('html_tag') }}>
{% else %}
<button class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }}>
{{ settings.text | striptags(allowed_tags) | raw }}
</button>
{% endif %}
{% endif %}

View File

@@ -0,0 +1,150 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Button;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Atomic_Button extends Atomic_Widget_Base {
use Has_Template;
public static function get_element_type(): string {
return 'e-button';
}
public function get_title() {
return esc_html__( 'Button', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-e-button';
}
protected static function define_props_schema(): array {
$props = [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'text' => Html_V3_Prop_Type::make()
->default( [
'content' => String_Prop_Type::generate( __( 'Click here', 'elementor' ) ),
'children' => [],
] )
->description( 'The text displayed on the button.' ),
'link' => Link_Prop_Type::make(),
'tag' => String_Prop_Type::make()
->default( 'button' )
->description( 'The HTML tag for the button element.' ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
return $props;
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_items( [
Inline_Editing_Control::bind_to( 'text' )
->set_placeholder( __( 'Type your button text here', 'elementor' ) )
->set_label( __( 'Button text', 'elementor' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
$background_color_value = Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( '#375EFB' ),
] );
$display_value = String_Prop_Type::generate( 'inline-block' );
$padding_value = Dimensions_Prop_Type::generate( [
'block-start' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
]),
'inline-end' => Size_Prop_Type::generate( [
'size' => 24,
'unit' => 'px',
]),
'block-end' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
]),
'inline-start' => Size_Prop_Type::generate( [
'size' => 24,
'unit' => 'px',
]),
]);
$border_radius_value = Size_Prop_Type::generate( [
'size' => 2,
'unit' => 'px',
] );
$border_width_value = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
$align_text_value = String_Prop_Type::generate( 'center' );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'background', $background_color_value )
->add_prop( 'display', $display_value )
->add_prop( 'padding', $padding_value )
->add_prop( 'border-radius', $border_radius_value )
->add_prop( 'border-width', $border_width_value )
->add_prop( 'text-align', $align_text_value )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-button' => __DIR__ . '/atomic-button.html.twig',
];
}
}

View File

@@ -0,0 +1,4 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
<hr class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }} />

View File

@@ -0,0 +1,108 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Divider;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Atomic_Divider extends Atomic_Widget_Base {
use Has_Template;
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
public static function get_element_type(): string {
return 'e-divider';
}
public function get_title() {
return esc_html__( 'Divider', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic', 'divider', 'hr', 'line', 'border', 'separator' ];
}
public function get_icon() {
return 'eicon-e-divider';
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
$border_width_value = Size_Prop_Type::generate([
'size' => 0,
'unit' => 'px',
]);
$height_value = Size_Prop_Type::generate([
'size' => 1,
'unit' => 'px',
]);
$background_value = Background_Prop_Type::generate([
'color' => Color_Prop_Type::generate( '#000' ),
]);
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'border-width', $border_width_value )
->add_prop( 'border-color', 'transparent' )
->add_prop( 'border-style', 'none' )
->add_prop( 'background', $background_value )
->add_prop( 'height', $height_value )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-divider' => __DIR__ . '/atomic-divider.html.twig',
];
}
}

View File

@@ -0,0 +1,20 @@
{%- set form_state = form_state | default(settings['form-state']) | default('default') -%}
{%- set classes = ['e-con', 'e-atomic-element', base_styles.base, 'form-state-' ~ form_state] | merge(settings.classes | default([])) | join(' ') -%}
<form class="{{ classes }} {{ editor_classes | default('') }}"
data-id="{{ id }}"
data-element_type="{{ type }}"
data-e-type="{{ type }}"
data-interaction-id="{{ interaction_id }}"
data-interactions="{{ interactions | json_encode | e('html_attr') }}"
x-data="eForm{{ id }}"
x-on:submit="submit"
{%- if settings['form-name'] is not empty %}
aria-label="{{ settings['form-name'] | e('html_attr') }}"
data-form-name="{{ settings['form-name'] | e('html_attr') }}"
{% endif -%}
{%- if settings['_cssid'] is not empty %}
id="{{ settings['_cssid'] | e('html_attr') }}"
{% endif -%}
{{ editor_attributes | default('') | raw }}>
<!-- elementor-children-placeholder -->
</form>

View File

@@ -0,0 +1,363 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Chips_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Email_Form_Action_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Toggle_Control;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message\Form_Success_Message;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message\Form_Error_Message;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Element_Builder;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\Elements\Base\Widget_Builder;
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Email_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Array_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Form extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
public const ACTION_COLLECT_SUBMISSIONS = 'collect-submissions';
public const METADATA_REMOTE_IP = 'remote_ip';
public const METADATA_USER_AGENT = 'user_agent';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'is_container', true );
}
public static function get_type() {
return 'e-form';
}
public static function get_element_type(): string {
return self::get_type();
}
public function get_title() {
return esc_html__( 'Atomic form', 'elementor' );
}
public function get_keywords() {
return [ 'atomic', 'form' ];
}
public function get_icon() {
return 'eicon-atomic-form';
}
protected static function define_props_schema(): array {
$email_dependencies = Dependency_Manager::make()
->where( [
'operator' => 'contains',
'path' => [ 'actions-after-submit' ],
'value' => 'email',
'effect' => 'hide',
] )
->get();
$submissions_metadata_dependencies = Dependency_Manager::make()
->where( [
'operator' => 'contains',
'path' => [ 'actions-after-submit' ],
'value' => self::ACTION_COLLECT_SUBMISSIONS,
'effect' => 'hide',
] )
->get();
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'form-name' => String_Prop_Type::make()
->default( __( 'Form', 'elementor' ) ),
'form-state' => String_Prop_Type::make()
->enum( [ 'default', 'success', 'error' ] )
->default( 'default' )
->meta( 'generates_class', 'form-state-{value}' ),
'actions-after-submit' => String_Array_Prop_Type::make()
->default( [ String_Prop_Type::generate( 'email' ) ] ),
'submissions_metadata' => String_Array_Prop_Type::make()
->set_dependencies( $submissions_metadata_dependencies )
->default( [
String_Prop_Type::generate( self::METADATA_REMOTE_IP ),
String_Prop_Type::generate( self::METADATA_USER_AGENT ),
] ),
'email' => Email_Prop_Type::make()
->set_dependencies( $email_dependencies )
->meta( Overridable_Prop_Type::ignore() )
->default( [] ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
$state_control = Toggle_Control::bind_to( 'form-state' )
->set_label( __( 'States', 'elementor' ) )
->set_meta( [ 'topDivider' => true ] );
if ( $state_control instanceof Toggle_Control ) {
$state_control
->add_options( [
'default' => [
'title' => __( 'Normal', 'elementor' ),
],
'success' => [
'title' => __( 'Success', 'elementor' ),
],
'error' => [
'title' => __( 'Error', 'elementor' ),
],
] )
->set_exclusive( true )
->set_convert_options( true )
->set_size( 'tiny' )
->set_full_width( true );
}
return [
Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_items( [
Text_Control::bind_to( 'form-name' )
->set_label( __( 'Form name', 'elementor' ) ),
$state_control,
Chips_Control::bind_to( 'actions-after-submit' )
->set_label( __( 'Actions after submit', 'elementor' ) )
->set_meta( [ 'topDivider' => true ] )
->set_options( [
[
'label' => __( 'Collect submissions', 'elementor' ),
'value' => self::ACTION_COLLECT_SUBMISSIONS,
],
[
'label' => __( 'Email', 'elementor' ),
'value' => 'email',
],
] ),
Chips_Control::bind_to( 'submissions_metadata' )
->set_label( __( 'Include metadata', 'elementor' ) )
->set_meta( [ 'topDivider' => true ] )
->set_options( [
[
'label' => __( 'User IP', 'elementor' ),
'value' => self::METADATA_REMOTE_IP,
],
[
'label' => __( 'User Agent', 'elementor' ),
'value' => self::METADATA_USER_AGENT,
],
] ),
Email_Form_Action_Control::bind_to( 'email' )
->set_meta( [
'topDivider' => true,
] ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
] ),
];
}
protected function define_base_styles(): array {
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->set_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP )
->add_prop( 'display', String_Prop_Type::generate( 'flex' ) )
->add_prop( 'flex', String_Prop_Type::generate( '1' ) )
->add_prop( 'flex-direction', String_Prop_Type::generate( 'row' ) )
->add_prop( 'flex-wrap', String_Prop_Type::generate( 'wrap' ) )
->add_prop( 'align-items', String_Prop_Type::generate( 'flex-start' ) )
->add_prop( 'align-content', String_Prop_Type::generate( 'start' ) )
->add_prop( 'gap', Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
] ) )
->add_prop( 'padding', Size_Prop_Type::generate( [
'size' => 20,
'unit' => 'px',
] ) )
),
static::BASE_STYLE_KEY . ' .e-form-checkbox-row' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'align-items', String_Prop_Type::generate( 'center' ) )
->add_prop( 'gap', Size_Prop_Type::generate( [
'size' => 8,
'unit' => 'px',
] ) )
->add_prop( 'padding', Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] ) )
),
];
}
protected function define_panel_categories(): array {
return [ 'atomic-form' ];
}
protected function define_default_html_tag() {
return 'form';
}
protected function define_default_children() {
$prefix = 'e-form-';
return [
$this->build_label( __( 'First name', 'elementor' ), $prefix . 'first-name' ),
$this->build_input( __( 'First name', 'elementor' ), 'text', $prefix . 'first-name' ),
$this->build_label( __( 'Last name', 'elementor' ), $prefix . 'last-name' ),
$this->build_input( __( 'Last name', 'elementor' ), 'text', $prefix . 'last-name' ),
$this->build_label( __( 'Email', 'elementor' ), $prefix . 'email' ),
$this->build_input( __( 'your@mail.com', 'elementor' ), 'email', $prefix . 'email' ),
$this->build_label( __( 'Message', 'elementor' ), $prefix . 'message' ),
$this->build_input( __( 'Your message', 'elementor' ), 'textarea', $prefix . 'message' ),
$this->build_checkbox_row( __( 'Checkbox', 'elementor' ), $prefix . 'checkbox' ),
Widget_Builder::make( 'e-form-submit-button' )
->settings( [
'text' => Html_V3_Prop_Type::generate( [
'content' => String_Prop_Type::generate( __( 'Submit', 'elementor' ) ),
'children' => [],
] ),
] )
->build(),
$this->build_status_message(
__( 'Great! Weve received your information.', 'elementor' ),
'success',
__( 'Success message', 'elementor' )
),
$this->build_status_message(
__( 'We couldnt process your submission. Please retry', 'elementor' ),
'error',
__( 'Error message', 'elementor' )
),
];
}
private function build_checkbox_row( string $label_text, string $checkbox_id ): array {
$checkbox = Widget_Builder::make( 'e-form-checkbox' )
->settings( [
'_cssid' => String_Prop_Type::generate( $checkbox_id ),
] )
->build();
$label = $this->build_label( $label_text, $checkbox_id );
return Element_Builder::make( 'e-flexbox' )
->children( [ $checkbox, $label ] )
->settings( [
'classes' => Classes_Prop_Type::generate( [ 'e-form-checkbox-row' ] ),
] )
->build();
}
private function build_label( string $text, string $input_id ): array {
return Widget_Builder::make( 'e-form-label' )
->settings( [
'text' => Html_V3_Prop_Type::generate( [
'content' => String_Prop_Type::generate( $text ),
'children' => [],
] ),
'input-id' => String_Prop_Type::generate( $input_id ),
] )
->build();
}
private function build_input( string $placeholder, string $type = 'text', $input_id = '' ): array {
if ( 'textarea' === $type ) {
return Widget_Builder::make( 'e-form-textarea' )
->settings( [
'placeholder' => String_Prop_Type::generate( $placeholder ),
'rows' => Number_Prop_Type::generate( 4 ),
'_cssid' => String_Prop_Type::generate( $input_id ),
] )
->build();
}
return Widget_Builder::make( 'e-form-input' )
->settings( [
'placeholder' => String_Prop_Type::generate( $placeholder ),
'type' => String_Prop_Type::generate( $type ),
'_cssid' => String_Prop_Type::generate( $input_id ),
] )
->build();
}
private function build_status_message( string $message, string $state, string $title ): array {
$paragraph_value = Html_V3_Prop_Type::generate( [
'content' => String_Prop_Type::generate( $message ),
'children' => [],
] );
$element_type = 'success' === $state
? Form_Success_Message::get_element_type()
: Form_Error_Message::get_element_type();
return Element_Builder::make( $element_type )
->settings( [
'attributes' => Attributes_Prop_Type::generate( [
Key_Value_Prop_Type::generate( [] ),
] ),
] )
->editor_settings( [
'title' => $title,
] )
->children( [
Widget_Builder::make( Atomic_Paragraph::get_element_type() )
->settings( [
'paragraph' => $paragraph_value,
] )
->build(),
] )
->is_locked( true )
->build();
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-form' => __DIR__ . '/atomic-form.html.twig',
];
}
protected function build_template_context(): array {
$context = $this->build_base_template_context();
$context['form_state'] = 'default';
return $context;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message\Form_Message;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Form_Error_Message extends Form_Message {
public static function get_type() {
return 'e-form-error-message';
}
public static function get_element_type(): string {
return 'e-form-error-message';
}
public function get_title() {
return esc_html__( 'Error message', 'elementor' );
}
protected static function get_background_color(): string {
return '#ffdede';
}
protected static function get_text_color(): string {
return '#870000';
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
}

View File

@@ -0,0 +1,10 @@
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
{% set message_type = type == 'e-form-success-message' ? 'message-success' : 'message-error' %}
<div class="{{ classes }} {{ message_type }} {{ editor_classes | default('') }}"
data-id="{{ id }}"
data-element_type="{{ type }}"
data-e-type="{{ type }}"
data-interaction-id="{{ interaction_id }}"
{{ editor_attributes | default('') | raw }}>
<!-- elementor-children-placeholder -->
</div>

View File

@@ -0,0 +1,99 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
abstract class Form_Message extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
abstract protected static function get_background_color(): string;
abstract protected static function get_text_color(): string;
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'is_container', true );
}
public function get_icon() {
return 'eicon-div-block';
}
public function should_show_in_panel() {
return false;
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
] ),
];
}
protected function define_base_styles(): array {
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'display' => String_Prop_Type::generate( 'none' ),
'background' => Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( static::get_background_color() ),
] ),
'color' => Color_Prop_Type::generate( static::get_text_color() ),
'padding' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
] ),
'text-align' => String_Prop_Type::generate( 'center' ),
'font-size' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
] ),
] )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/form-message' => __DIR__ . '/form-message.html.twig',
];
}
protected function build_template_context(): array {
return $this->build_base_template_context();
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message\Form_Message;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Form_Success_Message extends Form_Message {
public static function get_type() {
return 'e-form-success-message';
}
public static function get_element_type(): string {
return 'e-form-success-message';
}
public function get_title() {
return esc_html__( 'Success message', 'elementor' );
}
protected static function get_background_color(): string {
return '#D4E9D6';
}
protected static function get_text_color(): string {
return '#2F532E';
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
}

View File

@@ -0,0 +1,21 @@
{% if settings.title is not empty %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
<{{ settings.tag | e('html_tag') }}
data-interaction-id="{{ interaction_id }}"
class="{{ settings.classes | merge( [ base_styles.base ] ) | join(' ') }}"
{{ id_attribute }}
{{ settings.attributes | raw }}
>
{% set allowed_tags = '<b><strong><sup><sub><s><em><i><u><a><del><span><br>' %}
{% if settings.link and settings.link.attributes is not empty %}
<{{ settings.link.tag | e('html_tag') }}
{{ settings.link.attributes | raw }}
class="{{ base_styles['link-base'] }}">
{{ settings.title | striptags(allowed_tags) | raw }}
</{{ settings.link.tag | e('html_tag') }}>
{% else %}
{{ settings.title | striptags(allowed_tags) | raw }}
{% endif %}
</{{ settings.tag | e('html_tag') }}>
{% endif %}

View File

@@ -0,0 +1,157 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Heading;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Heading extends Atomic_Widget_Base {
use Has_Template;
const LINK_BASE_STYLE_KEY = 'link-base';
public static $widget_description = 'Display a heading with customizable tag, styles, and link options.';
public static function get_element_type(): string {
return 'e-heading';
}
public function get_title() {
return esc_html__( 'Heading', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-e-heading';
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'tag' => String_Prop_Type::make()
->enum( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ] )
->default( 'h2' )
->description( 'The HTML tag for the heading element. Could be h1, h2, up to h6' ),
'title' => Html_V3_Prop_Type::make()
->default( [
'content' => String_Prop_Type::generate( __( 'This is a title', 'elementor' ) ),
'children' => [],
] )
->description( 'The text content of the heading.' ),
'link' => Link_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
$content_section = Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_items( [
Inline_Editing_Control::bind_to( 'title' )
->set_placeholder( __( 'Type your title here', 'elementor' ) )
->set_label( __( 'Title', 'elementor' ) ),
] );
return [
$content_section,
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Select_Control::bind_to( 'tag' )
->set_options([
[
'value' => 'h1',
'label' => 'H1',
],
[
'value' => 'h2',
'label' => 'H2',
],
[
'value' => 'h3',
'label' => 'H3',
],
[
'value' => 'h4',
'label' => 'H4',
],
[
'value' => 'h5',
'label' => 'H5',
],
[
'value' => 'h6',
'label' => 'H6',
],
])
->set_label( __( 'Tag', 'elementor' ) ),
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) )
->set_meta( [
'topDivider' => true,
] ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
$margin_value = Size_Prop_Type::generate( [
'unit' => 'px',
'size' => 0 ,
] );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'margin', $margin_value )
),
self::LINK_BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'all', 'unset' )
->add_prop( 'cursor', 'pointer' )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-heading' => __DIR__ . '/atomic-heading.html.twig',
];
}
}

View File

@@ -0,0 +1,27 @@
{% if settings.image.src is not empty %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set has_link = settings.link and settings.link.attributes is not empty %}
{% if has_link %}
<{{ settings.link.tag | e('html_tag') }}
{{ settings.link.attributes | raw }}
class="{{ base_styles['link-base'] }}"
data-interaction-id="{{ interaction_id }}"
>
{% endif %}
<img class="{{ base_styles['base'] }} {{ settings.classes | join(' ') }}"
{% if not settings.link.href %}
data-interaction-id="{{ interaction_id }}"
{% endif %}
{{ id_attribute }} {{ settings.attributes | raw }}
{% for attr, value in settings.image %}
{% if attr == 'src' %}
src="{{ value | e('full_url') }}"
{% else %}
{{ attr | e('html_attr') }}="{{ value }}"
{% endif %}
{% endfor %}
/>
{% if has_link %}
</{{ settings.link.tag | e('html_tag') }}>
{% endif %}
{% endif %}

View File

@@ -0,0 +1,111 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Image;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control;
use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Image extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display an image with customizable styles and link options.';
const LINK_BASE_STYLE_KEY = 'link-base';
const BASE_STYLE_KEY = 'base';
public static function get_element_type(): string {
return 'e-image';
}
public function get_title() {
return esc_html__( 'Image', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-e-image';
}
protected static function define_props_schema(): array {
$props = [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'image' => Image_Prop_Type::make()
->default_url( Placeholder_Image::get_placeholder_image() )
->default_size( 'full' ),
'link' => Link_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
return $props;
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( esc_html__( 'Content', 'elementor' ) )
->set_items( [
Image_Control::bind_to( 'image' )
->set_label( __( 'Image', 'elementor' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
return [
self::LINK_BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'display', 'inherit' )
->add_prop( 'width', 'fit-content' )
),
self::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'display', 'block' )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-image' => __DIR__ . '/atomic-image.html.twig',
];
}
}

View File

@@ -0,0 +1,15 @@
{% if settings.paragraph is not empty %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
<{{ settings.tag | e('html_tag') }} class="{{ settings.classes | merge( [ base_styles.base ] ) | join(' ') }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }}>
{% if settings.link and settings.link.attributes is not empty %}
<{{ settings.link.tag | e('html_tag') }}
{{ settings.link.attributes | raw }}
class="{{ base_styles['link-base'] }}"
>
{{ settings.paragraph | striptags(allowed_tags) | raw }}
</{{ settings.link.tag | e('html_tag') }}>
{% else %}
{{ settings.paragraph | striptags('<b><strong><sup><sub><s><em><u><ul><ol><li><blockquote><a><del><span><br>') | raw }}
{% endif %}
</{{ settings.tag | e('html_tag') }}>
{% endif %}

View File

@@ -0,0 +1,139 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Atomic_Paragraph extends Atomic_Widget_Base {
use Has_Template;
const LINK_BASE_STYLE_KEY = 'link-base';
public static $widget_description = 'Display a paragraph with customizable tag, styles, and link options.';
public static function get_element_type(): string {
return 'e-paragraph';
}
public function get_title() {
return esc_html__( 'Paragraph', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-paragraph';
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'paragraph' => Html_V3_Prop_Type::make()
->default( [
'content' => String_Prop_Type::generate( __( 'Type your paragraph here', 'elementor' ) ),
'children' => [],
] )
->description( 'The text content of the paragraph.' ),
'tag' => String_Prop_Type::make()
->enum( [ 'p', 'span' ] )
->default( 'p' ),
'link' => Link_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_items( [
Inline_Editing_Control::bind_to( 'paragraph' )
->set_placeholder( __( 'Type your paragraph here', 'elementor' ) )
->set_label( __( 'Paragraph', 'elementor' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Select_Control::bind_to( 'tag' )
->set_options([
[
'value' => 'p',
'label' => 'p',
],
[
'value' => 'span',
'label' => 'span',
],
])
->set_label( __( 'Tag', 'elementor' ) ),
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) )
->set_meta( [
'topDivider' => true,
] ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
$margin_value = Size_Prop_Type::generate( [
'unit' => 'px',
'size' => 0 ,
] );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'margin', $margin_value )
),
self::LINK_BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'all', 'unset' )
->add_prop( 'cursor', 'pointer' )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-paragraph' => __DIR__ . '/atomic-paragraph.html.twig',
];
}
}

View File

@@ -0,0 +1,44 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set video_classes = base_styles.base %}
{% set video_start_time = settings.start_time %}
{% set video_end_time = settings.end_time %}
{% if video_start_time is not empty %}
{% set video_timings = '#t=' ~ video_start_time %}
{% else %}
{% set video_timings = '#t=0' %}
{% endif %}
{% if video_end_time is not empty and video_end_time > video_start_time %}
{% set video_timings = video_timings ~ ',' ~ video_end_time %}
{% endif %}
{% if settings.source.url is not empty %}
{% set video_url = settings.source.url | e('full_url') %}
<video
data-id="{{ id }}" data-interaction-id="{{ interaction_id }}"
data-e-type="{{ type }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}"
{{ id_attribute }}
class="{{ classes }}"
{{ settings.attributes | raw }}
class="{{ video_classes }}"
{% if settings.poster_enabled %}poster="{{ settings.poster.src | e('full_url') }}"{% endif %}
{% if settings.autoplay %}autoplay{% endif %}
{% if settings.mute %}muted{% endif %}
{% if settings.loop %}loop{% endif %}
{% if settings.controls %}controls{% endif %}
{% if settings.playsinline %}playsinline{% endif %}
{% if not settings.download %}controlslist="nodownload"{% endif %}
preload="{{ settings.preload }}"
>
<source src="{{ video_url ~ video_timings }}">
</video>
{% else %}
{% if settings.poster_enabled %}
<div style="width:100%; aspect-ratio: 16/9; background-image: url({{ settings.poster.src | e('full_url') }}); background-size: contain; background-position: center center; background-repeat: no-repeat; min-height: 100px;"></div>
{% else %}
<div style="width:100%; background-color: rgb(245, 245, 245); aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;">
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.6902 8.92189C18.3227 8.56844 19.0991 8.58381 19.7162 8.96355L54.3829 30.2969C54.9738 30.661 55.3334 31.3059 55.3334 32C55.3334 32.6941 54.9738 33.339 54.3829 33.7031L19.7162 55.0365C19.0991 55.4162 18.3227 55.4316 17.6902 55.0781C17.0584 54.7245 16.6667 54.0574 16.6667 53.3333V10.6667L16.685 10.3984C16.7685 9.7807 17.1373 9.23132 17.6902 8.92189ZM20.6667 49.7526L49.5157 32L20.6667 14.2448V49.7526Z" fill="black" fill-opacity="0.54"/>
</svg>
</div>
{% endif %}
{% endif %}

View File

@@ -0,0 +1,208 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Self_Hosted_Video;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Video_Control;
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Video_Src_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Atomic_Self_Hosted_Video extends Atomic_Widget_Base {
use Has_Template;
protected static function get_preload_options() {
return [
'auto' => esc_html__( 'Auto', 'elementor' ),
'metadata' => esc_html__( 'Metadata', 'elementor' ),
'none' => esc_html__( 'None (Lazy Load)', 'elementor' ),
]; }
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
public static function get_element_type(): string {
return 'e-self-hosted-video';
}
public function get_title() {
return esc_html__( 'Video', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic', 'video', 'player', 'media', 'hosted' ];
}
public function get_icon() {
return 'eicon-video';
}
protected static function define_props_schema(): array {
$playsinline_dependencies = Dependency_Manager::make()
->where([
'operator' => 'eq',
'path' => [ 'autoplay' ],
'value' => true,
'effect' => 'hide',
])
->get();
// NOTE: restore the dependency when dependencies works in overridables
$poster_dependencies = Dependency_Manager::make()
->where([
'operator' => 'eq',
'path' => [ 'poster_enabled' ],
'value' => true,
'effect' => 'hide',
])
->get();
$allow_download_dependencies = Dependency_Manager::make()
->where([
'operator' => 'eq',
'path' => [ 'controls' ],
'value' => true,
'effect' => 'hide',
])
->get();
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'source' => Video_Src_Prop_Type::make(),
'autoplay' => Boolean_Prop_Type::make()->default( false ),
'playsinline' => Boolean_Prop_Type::make()
->default( false )
->set_dependencies( $playsinline_dependencies )
->meta( Overridable_Prop_Type::ignore() ),
'mute' => Boolean_Prop_Type::make()->default( false ),
'loop' => Boolean_Prop_Type::make()->default( false ),
'controls' => Boolean_Prop_Type::make()->default( true ),
'preload' => String_Prop_Type::make()
->default( 'metadata' )
->enum( array_keys( self::get_preload_options() ) ),
'download' => Boolean_Prop_Type::make()->default( false )
->set_dependencies( $allow_download_dependencies ),
'start_time' => Number_Prop_Type::make()
->default( null )
->meta( Dynamic_Prop_Type::ignore() )
->meta( 'suffix', 'SEC' ),
'end_time' => Number_Prop_Type::make()
->default( null )
->meta( 'suffix', 'SEC' )
->meta( Dynamic_Prop_Type::ignore() ),
'poster_enabled' => Boolean_Prop_Type::make()->default( false ),
'poster' => Image_Prop_Type::make()
->default_size( 'medium_large' )
->default_url( Placeholder_Image::get_placeholder_image() ),
// TODO: restore the dependency when dependencies works in overridables
// ->set_dependencies( $poster_dependencies ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_items([
Video_Control::bind_to( 'source' )
->set_label( esc_html__( 'Video', 'elementor' ) ),
Number_Control::bind_to( 'start_time' )
->set_label( esc_html__( 'Start Time', 'elementor' ) )
->set_min( 0 )
->set_max( 10000 ),
Number_Control::bind_to( 'end_time' )
->set_label( esc_html__( 'End Time', 'elementor' ) )
->set_min( 0 )
->set_max( 10000 ),
Switch_Control::bind_to( 'autoplay' )->set_label( esc_html__( 'Autoplay', 'elementor' ) ),
Switch_Control::bind_to( 'playsinline' )
->set_label( esc_html__( 'Play on mobile', 'elementor' ) ),
Switch_Control::bind_to( 'mute' )->set_label( esc_html__( 'Mute', 'elementor' ) ),
Switch_Control::bind_to( 'loop' )->set_label( esc_html__( 'Loop', 'elementor' ) ),
Switch_Control::bind_to( 'controls' )->set_label( esc_html__( 'Player Controls', 'elementor' ) ),
Switch_Control::bind_to( 'download' )->set_label( esc_html__( 'Allow Download', 'elementor' ) ),
Select_Control::bind_to( 'preload' )
->set_label( esc_html__( 'Preload', 'elementor' ) )
->set_options( self::format_options( self::get_preload_options() ) ),
Switch_Control::bind_to( 'poster_enabled' )
->set_label( esc_html__( 'Poster Image', 'elementor' ) ),
Image_Control::bind_to( 'poster' )
->set_label( esc_html__( 'Image', 'elementor' ) ),
]),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
$max_width = Size_Prop_Type::generate([
'unit' => 'vw',
'size' => 100,
]);
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'max-width', $max_width )
->add_prop( 'display', String_Prop_Type::generate( 'inline-block' ) )
->add_prop( 'aspect-ratio', String_Prop_Type::generate( '16/9' ) )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-self-hosted-video' => __DIR__ . '/atomic-self-hosted-video.html.twig',
];
}
private static function format_options( array $options ): array {
return array_map(
fn( $value, $key ) => [
'value' => $key,
'label' => $value,
],
$options,
array_keys( $options )
);
}
}

View File

@@ -0,0 +1,15 @@
{%- if settings.svg.html is defined and settings.svg.html is not empty -%}
{%- set classes = settings.classes | merge([base_styles.base]) | join(' ') -%}
{%- set id_attribute = settings._cssid is not empty ? 'id="' ~ settings._cssid | e('html_attr') ~ '"' : '' -%}
{%- if settings.link is defined and settings.link.attributes is not empty -%}
<{{ settings.link.tag | e('html_tag') }} {{ settings.link.attributes | raw }} class="{{ classes }}" data-interaction-id="{{ interaction_id }}"
{%- if id_attribute is not empty %} {{ id_attribute }}{% endif -%}
{%- if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }}{% endif -%}
>{{ settings.svg.html | raw }}</{{ settings.link.tag | e('html_tag') }}>
{%- else -%}
<div class="{{ classes }}" data-interaction-id="{{ interaction_id }}"
{%- if id_attribute is not empty %} {{ id_attribute }}{% endif -%}
{%- if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }}{% endif -%}
>{{ settings.svg.html | raw }}</div>
{%- endif -%}
{%- endif -%}

View File

@@ -0,0 +1,109 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Svg;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Svg_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Svg extends Atomic_Widget_Base {
use Has_Template;
const BASE_STYLE_KEY = 'base';
const DEFAULT_SVG = 'images/default-svg.svg';
const DEFAULT_SVG_PATH = ELEMENTOR_ASSETS_PATH . self::DEFAULT_SVG;
const DEFAULT_SVG_URL = ELEMENTOR_ASSETS_URL . self::DEFAULT_SVG;
public static $widget_description = 'Display an SVG image with customizable styles and link options.';
public static function get_element_type(): string {
return 'e-svg';
}
public function get_title() {
return esc_html__( 'SVG', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-svg';
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()->default( [] ),
'svg' => Svg_Src_Prop_Type::make()->default_url( static::DEFAULT_SVG_URL ),
'link' => Link_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( esc_html__( 'Content', 'elementor' ) )
->set_items( [
Svg_Control::bind_to( 'svg' )
->set_label( __( 'SVG', 'elementor' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
$display_value = String_Prop_Type::generate( 'inline-block' );
$size = Size_Prop_Type::generate( [
'size' => 65,
'unit' => 'px',
] );
return [
self::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'display', $display_value )
->add_prop( 'width', $size )
->add_prop( 'height', $size )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-svg' => __DIR__ . '/atomic-svg.html.twig',
];
}
}

View File

@@ -0,0 +1,2 @@
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
<div class="{{ classes }} {{ editor_classes | default('') }}{{ is_active ? ' e--selected' : '' }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tabpanel" x-bind="tabContent" id="{{ tab_content_id }}" aria-labelledby="{{ tab_id }}"{% if not is_active %} hidden="true" style="display: none;"{% endif %} {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div>

View File

@@ -0,0 +1,130 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Tab_Content extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'llm_support', false );
}
public static function get_type() {
return 'e-tab-content';
}
public static function get_element_type(): string {
return 'e-tab-content';
}
public function get_title() {
return esc_html__( 'Tab content', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic', 'tab', 'content', 'tabs' ];
}
public function get_icon() {
return 'eicon-layout';
}
public function should_show_in_panel() {
return false;
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'tab-id' => String_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [] ),
];
}
protected function define_atomic_style_states(): array {
$selected_state = Style_States::get_class_states_map()['selected'];
return [ $selected_state ];
}
protected function define_base_styles(): array {
$styles = [
'display' => String_Prop_Type::generate( 'block' ),
'padding' => Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
] ),
'min-width' => Size_Prop_Type::generate( [
'size' => 30,
'unit' => 'px',
] ),
];
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( $styles )
),
];
}
protected function define_initial_attributes() {
return [
'role' => 'tabpanel',
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-tab-content' => __DIR__ . '/atomic-tab-content.html.twig',
];
}
protected function build_template_context(): array {
$tabs_context = Render_Context::get( Atomic_Tabs::class );
$default_active_tab = $tabs_context['default-active-tab'];
$get_tab_content_index = $tabs_context['get-tab-content-index'];
$tabs_id = $tabs_context['tabs-id'];
$index = $get_tab_content_index( $this->get_id() );
$is_active = $default_active_tab === $index;
return array_merge( $this->build_base_template_context(), [
'is_active' => $is_active,
'tab_id' => Atomic_Tabs::get_tab_id( $tabs_id, $index ),
'tab_content_id' => Atomic_Tabs::get_tab_content_id( $tabs_id, $index ),
] );
}
}

View File

@@ -0,0 +1,2 @@
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
<button class="{{ classes }} {{ editor_classes | default('') }}{{ is_active ? ' e--selected' : '' }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tab" tabindex="{{ is_active ? '0' : '-1' }}" aria-selected="{{ is_active ? 'true' : 'false' }}" x-bind="tab" x-ref="{{ id }}" id="{{ tab_id }}" aria-controls="{{ tab_content_id }}" {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></button>

View File

@@ -0,0 +1,191 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context;
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Tab extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'llm_support', false );
}
public static function get_type() {
return 'e-tab';
}
public static function get_element_type(): string {
return 'e-tab';
}
public function get_title() {
return esc_html__( 'Tab trigger', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-layout';
}
public function should_show_in_panel() {
return false;
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [] ),
];
}
protected function define_atomic_style_states(): array {
$selected_state = Style_States::get_class_states_map()['selected'];
return [ $selected_state ];
}
protected function define_base_styles(): array {
$styles = [
'display' => String_Prop_Type::generate( 'block' ),
'cursor' => String_Prop_Type::generate( 'pointer' ),
'color' => Color_Prop_Type::generate( '#0C0D0E' ),
'border-style' => String_Prop_Type::generate( 'solid' ),
'border-color' => Color_Prop_Type::generate( '#E0E0E0' ),
'border-width' => Size_Prop_Type::generate( [
'size' => 2,
'unit' => 'px',
]),
'padding' => Size_Prop_Type::generate( [
'size' => 8,
'unit' => 'px',
]),
'width' => Size_Prop_Type::generate( [
'size' => 160,
'unit' => 'px',
]),
'background' => Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( '#FFFFFF' ),
]),
];
$selected_styles = [
'outline-width' => Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
]),
'border-color' => Color_Prop_Type::generate( '#0C0D0E' ),
];
$hover_styles = [
'background' => Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( '#E0E0E0' ),
]),
];
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( $styles )
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::SELECTED )
->add_props( $selected_styles )
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::FOCUS )
->add_props( $selected_styles )
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::HOVER )
->add_props( $hover_styles )
),
];
}
protected function define_initial_attributes() {
return [
'role' => 'tab',
'tabindex' => '-1',
];
}
protected function define_default_html_tag() {
return 'button';
}
protected function define_default_children() {
return [
Atomic_Paragraph::generate()
->settings( [
'paragraph' => Html_V3_Prop_Type::generate( [
'content' => String_Prop_Type::generate( 'Tab' ),
'children' => [],
] ),
'tag' => String_Prop_Type::generate( 'span' ),
] )
->build(),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-tab' => __DIR__ . '/atomic-tab.html.twig',
];
}
protected function build_template_context(): array {
$tabs_context = Render_Context::get( Atomic_Tabs::class );
$default_active_tab = $tabs_context['default-active-tab'];
$get_tab_index = $tabs_context['get-tab-index'];
$tabs_id = $tabs_context['tabs-id'];
$index = $get_tab_index( $this->get_id() );
$is_active = $default_active_tab === $index;
return array_merge( $this->build_base_template_context(), [
'is_active' => $is_active,
'tab_id' => Atomic_Tabs::get_tab_id( $tabs_id, $index ),
'tab_content_id' => Atomic_Tabs::get_tab_content_id( $tabs_id, $index ),
] );
}
}

View File

@@ -0,0 +1,2 @@
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
<div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div>

View File

@@ -0,0 +1,101 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Tabs_Content_Area extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'llm_support', false );
}
public static function get_type() {
return 'e-tabs-content-area';
}
public static function get_element_type(): string {
return 'e-tabs-content-area';
}
public function get_title() {
return esc_html__( 'Tabs content area', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-tab-content';
}
public function should_show_in_panel() {
return false;
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
] ),
];
}
protected function define_base_styles(): array {
$styles = [
'display' => String_Prop_Type::generate( 'block' ),
];
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( $styles )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-tabs-content-area' => __DIR__ . '/atomic-tabs-content-area.html.twig',
];
}
protected function define_allowed_child_types() {
return [ 'e-tab-content', 'container' ];
}
}

View File

@@ -0,0 +1,2 @@
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
<div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tablist" {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div>

View File

@@ -0,0 +1,106 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Tabs_Menu extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'llm_support', false );
}
public static function get_type() {
return 'e-tabs-menu';
}
public static function get_element_type(): string {
return 'e-tabs-menu';
}
public function get_title() {
return esc_html__( 'Tabs menu', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-tab-menu';
}
public function should_show_in_panel() {
return false;
}
public function define_initial_attributes(): array {
return [
'role' => 'tablist',
];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
] ),
];
}
protected function define_base_styles(): array {
$styles = [
'display' => String_Prop_Type::generate( 'flex' ),
'justify-content' => String_Prop_Type::generate( 'center' ),
];
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( $styles )
),
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-tabs-menu' => __DIR__ . '/atomic-tabs-menu.html.twig',
];
}
protected function define_allowed_child_types() {
return [ 'e-tab', 'container' ];
}
}

View File

@@ -0,0 +1,10 @@
{% set default_tab_index = settings['default-active-tab'] | default(0) %}
{% set default_active_tab_id = id ~ '-tab-' ~ default_tab_index %}
{% set e_settings = {
'default-active-tab': default_active_tab_id,
} %}
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
<div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" x-data="eTabs{{ id }}" data-e-settings="{{ e_settings | json_encode | e('html_attr') }}" {{ editor_attributes | default('') | raw }}>
<!-- elementor-children-placeholder -->
</div>

View File

@@ -0,0 +1,256 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
use Elementor\Modules\AtomicWidgets\Controls\Types\Elements\Tabs_Control;
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab\Atomic_Tab;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content\Atomic_Tab_Content;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu\Atomic_Tabs_Menu;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area\Atomic_Tabs_Content_Area;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
use Elementor\Core\Utils\Collection;
use Elementor\Utils;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Tabs extends Atomic_Element_Base {
use Has_Element_Template;
const BASE_STYLE_KEY = 'base';
const ELEMENT_TYPE_TABS_MENU = 'e-tabs-menu';
const ELEMENT_TYPE_TABS_CONTENT_AREA = 'e-tabs-content-area';
const ELEMENT_TYPE_TAB = 'e-tab';
const ELEMENT_TYPE_TAB_CONTENT = 'e-tab-content';
public static $widget_description = 'Create a tabbed interface with customizable tabs and content areas. LLM support: Each child element will be represented as a tab, the menu auto-generates based on the children';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'is_container', true );
}
public static function get_type() {
return 'e-tabs';
}
public static function get_element_type(): string {
return 'e-tabs';
}
public function get_title() {
return esc_html__( 'Tabs', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-tabs';
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'default-active-tab' => Number_Prop_Type::make()
->default( 0 )
->meta( Overridable_Prop_Type::ignore() ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_id( 'content' )
->set_items( [
Tabs_Control::make()
->set_label( __( 'Menu items', 'elementor' ) )
->set_meta( [
'layout' => 'custom',
] ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
] ),
];
}
protected function define_base_styles(): array {
$styles = [
'display' => String_Prop_Type::generate( 'flex' ),
'flex-direction' => String_Prop_Type::generate( 'column' ),
'gap' => Size_Prop_Type::generate( [
'size' => 30,
'unit' => 'px',
]),
'padding' => Dimensions_Prop_Type::generate( [
'block-start' => Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
]),
] ),
];
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( $styles )
),
];
}
protected function define_default_children() {
$default_tab_count = 3;
$tab_elements = [];
$tab_content_elements = [];
foreach ( range( 1, $default_tab_count ) as $i ) {
$tab_elements[] = Atomic_Tab::generate()
->editor_settings( [
'title' => "Tab {$i} trigger",
'initial_position' => $i,
] )
->is_locked( true )
->build();
$tab_content_elements[] = Atomic_Tab_Content::generate()
->is_locked( true )
->editor_settings( [
'title' => "Tab {$i} content",
'initial_position' => $i,
] )
->build();
}
$tabs_menu = Atomic_Tabs_Menu::generate()
->children( $tab_elements )
->is_locked( true )
->build();
$tabs_content_area = Atomic_Tabs_Content_Area::generate()
->children( $tab_content_elements )
->is_locked( true )
->build();
return [
$tabs_menu,
$tabs_content_area,
];
}
public function get_script_depends() {
$global_depends = parent::get_script_depends();
if ( Plugin::$instance->preview->is_preview_mode() ) {
return array_merge( $global_depends, [ 'elementor-tabs-handler', 'elementor-tabs-preview-handler' ] );
}
return array_merge( $global_depends, [ 'elementor-tabs-handler' ] );
}
public function register_frontend_handlers() {
$assets_url = ELEMENTOR_ASSETS_URL;
$min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
wp_register_script(
'elementor-tabs-handler',
"{$assets_url}js/tabs-handler{$min_suffix}.js",
[ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE, Frontend_Assets_Loader::ALPINEJS_HANDLE ],
ELEMENTOR_VERSION,
true
);
wp_register_script(
'elementor-tabs-preview-handler',
"{$assets_url}js/tabs-preview-handler{$min_suffix}.js",
[ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE, Frontend_Assets_Loader::ALPINEJS_HANDLE ],
ELEMENTOR_VERSION,
true
);
}
private function get_filtered_children_ids( $parent_element, $child_type ) {
if ( ! $parent_element ) {
return [];
}
return Collection::make( $parent_element->get_children() )
->filter( fn( $element ) => $element->get_type() === $child_type )
->map( fn( $element ) => $element->get_id() )
->flip()
->all();
}
private function get_tab_index( $tab_id ) {
$direct_children = Collection::make( $this->get_children() );
$tabs_menu = $direct_children->filter( fn( $child ) => $child->get_type() === self::ELEMENT_TYPE_TABS_MENU )->first();
$tab_ids = $this->get_filtered_children_ids( $tabs_menu, self::ELEMENT_TYPE_TAB );
return $tab_ids[ $tab_id ];
}
private function get_tab_content_index( $tab_content_id ) {
$direct_children = Collection::make( $this->get_children() );
$tabs_content_area = $direct_children->filter( fn( $child ) => $child->get_type() === self::ELEMENT_TYPE_TABS_CONTENT_AREA )->first();
$tab_content_ids = $this->get_filtered_children_ids( $tabs_content_area, self::ELEMENT_TYPE_TAB_CONTENT );
return $tab_content_ids[ $tab_content_id ];
}
protected function define_render_context(): array {
$default_active_tab = $this->get_atomic_setting( 'default-active-tab' );
return [
[
'context' => [
'default-active-tab' => $default_active_tab,
'get-tab-index' => fn( $tab_id ) => $this->get_tab_index( $tab_id ),
'get-tab-content-index' => fn( $tab_content_id ) => $this->get_tab_content_index( $tab_content_id ),
'tabs-id' => $this->get_id(),
],
],
];
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-tabs' => __DIR__ . '/atomic-tabs.html.twig',
];
}
public static function get_tab_id( $tabs_id, $index ) {
return "{$tabs_id}-tab-{$index}";
}
public static function get_tab_content_id( $tabs_id, $index ) {
return "{$tabs_id}-tab-content-{$index}";
}
}

View File

@@ -0,0 +1,87 @@
import { register } from '@elementor/frontend-handlers';
import { Alpine } from '@elementor/alpinejs';
import { TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE, getTabId, getTabContentId, getIndex, getNextTab } from './utils';
const SELECTED_CLASS = 'e--selected';
register( {
elementType: 'e-tabs',
id: 'e-tabs-handler',
callback: ( { element, settings } ) => {
const tabsId = element.dataset.id;
Alpine.data( `eTabs${ tabsId }`, () => ( {
activeTab: settings[ 'default-active-tab' ],
navigateTabs( { key, target: tab } ) {
const nextTab = getNextTab( key, tab );
nextTab.focus();
},
tab: {
':id'() {
const index = getIndex( this.$el, TAB_ELEMENT_TYPE );
return getTabId( tabsId, index );
},
'@click'() {
const id = this.$el.id;
this.activeTab = id;
},
'@keydown.arrow-right.prevent'( event ) {
this.navigateTabs( event );
},
'@keydown.arrow-left.prevent'( event ) {
this.navigateTabs( event );
},
':class'() {
const id = this.$el.id;
return { [ SELECTED_CLASS ]: this.activeTab === id };
},
':aria-selected'() {
const id = this.$el.id;
return this.activeTab === id ? 'true' : 'false';
},
':tabindex'() {
const id = this.$el.id;
return this.activeTab === id ? '0' : '-1';
},
':aria-controls'() {
const index = getIndex( this.$el, TAB_ELEMENT_TYPE );
return getTabContentId( tabsId, index );
},
},
tabContent: {
':aria-labelledby'() {
const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE );
return getTabId( tabsId, index );
},
'x-show'() {
const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE );
const tabId = getTabId( tabsId, index );
const isActive = this.activeTab === tabId;
this.$nextTick( () => {
this.$el.classList.toggle( SELECTED_CLASS, isActive );
} );
return isActive;
},
':id'() {
const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE );
return getTabContentId( tabsId, index );
},
},
} ) );
},
} );

View File

@@ -0,0 +1,30 @@
import { register } from '@elementor/frontend-handlers';
import { Alpine, refreshTree } from '@elementor/alpinejs';
import { TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE, getTabId, getIndex } from './utils';
register( {
elementType: 'e-tabs',
id: 'e-tabs-preview-handler',
callback: ( { element, signal, listenToChildren } ) => {
window?.parent.addEventListener( 'elementor/navigator/item/click', ( event ) => {
const { id, type } = event.detail;
if ( type !== TAB_ELEMENT_TYPE && type !== TAB_CONTENT_ELEMENT_TYPE ) {
return;
}
const targetElement = Alpine.$data( element ).$refs[ id ];
if ( ! targetElement ) {
return;
}
const targetIndex = getIndex( targetElement, type );
Alpine.$data( element ).activeTab = getTabId( element.dataset.id, targetIndex );
}, { signal } );
// Re-initialize Alpine to sync with editor DOM manipulations that bypass Alpine's reactivity.
listenToChildren( [ TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE ] )
.render( () => refreshTree( element ) );
},
} );

View File

@@ -0,0 +1,44 @@
export const TAB_ELEMENT_TYPE = 'e-tab';
export const TAB_CONTENT_ELEMENT_TYPE = 'e-tab-content';
export const TABS_CONTENT_AREA_ELEMENT_TYPE = 'e-tabs-content-area';
export const TABS_MENU_ELEMENT_TYPE = 'e-tabs-menu';
const NAVIGATE_UP_KEYS = [ 'ArrowUp', 'ArrowLeft' ];
const NAVIGATE_DOWN_KEYS = [ 'ArrowDown', 'ArrowRight' ];
export const getTabId = ( tabsId, tabIndex ) => {
return `${ tabsId }-tab-${ tabIndex }`;
};
export const getTabContentId = ( tabsId, tabIndex ) => {
return `${ tabsId }-tab-content-${ tabIndex }`;
};
export const getChildren = ( el, elementType ) => {
const parent = el.parentElement;
return Array.from( parent.children ).filter( ( child ) => {
return child.dataset.element_type === elementType;
} );
};
export const getIndex = ( el, elementType ) => {
const children = getChildren( el, elementType );
return children.indexOf( el );
};
export const getNextTab = ( key, tab ) => {
const tabs = getChildren( tab, TAB_ELEMENT_TYPE );
const tabsLength = tabs.length;
const currentIndex = getIndex( tab, TAB_ELEMENT_TYPE );
if ( NAVIGATE_DOWN_KEYS.includes( key ) ) {
return tabs[ ( currentIndex + 1 ) % tabsLength ];
}
if ( NAVIGATE_UP_KEYS.includes( key ) ) {
return tabs[ ( currentIndex - 1 + tabsLength ) % tabsLength ];
}
};

View File

@@ -0,0 +1,18 @@
{% if settings.source is not empty %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
{% set data_settings = {
'source': settings.source,
'autoplay': settings.autoplay,
'mute': settings.mute,
'controls': settings.player_controls,
'cc_load_policy': settings.captions,
'loop': settings.loop,
'rel': settings.rel,
'start': settings.start,
'end': settings.end,
'privacy': settings.privacy_mode,
'lazyload': settings.lazyload,
} %}
<div data-id="{{ id }}" data-interaction-id="{{ interaction_id }}" data-e-type="{{ type }}" {{ id_attribute }} class="{{ classes }}" {{ settings.attributes | raw }} data-settings="{{ data_settings|json_encode|e('html_attr') }}"></div>
{% endif %}

View File

@@ -0,0 +1,144 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Youtube;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Atomic_Youtube extends Atomic_Widget_Base {
use Has_Template;
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
public static function get_element_type(): string {
return 'e-youtube';
}
public function get_title() {
return esc_html__( 'YouTube', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-e-youtube';
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'source' => String_Prop_Type::make()
->default( 'https://www.youtube.com/watch?v=XHOmBV4js_E' ),
'start' => String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() ),
'end' => String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() ),
'autoplay' => Boolean_Prop_Type::make()->default( false ),
'mute' => Boolean_Prop_Type::make()->default( false ),
'loop' => Boolean_Prop_Type::make()->default( false ),
'lazyload' => Boolean_Prop_Type::make()->default( false ),
'player_controls' => Boolean_Prop_Type::make()->default( true ),
'captions' => Boolean_Prop_Type::make()->default( false ),
'privacy_mode' => Boolean_Prop_Type::make()->default( false ),
'rel' => Boolean_Prop_Type::make()->default( true ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor' ) )
->set_items( [
Text_Control::bind_to( 'source' )
->set_placeholder( esc_html__( 'Type or paste your URL', 'elementor' ) )
->set_label( esc_html__( 'YouTube URL', 'elementor' ) ),
Text_Control::bind_to( 'start' )->set_label( esc_html__( 'Start time', 'elementor' ) ),
Text_Control::bind_to( 'end' )->set_label( esc_html__( 'End time', 'elementor' ) ),
Switch_Control::bind_to( 'autoplay' )->set_label( esc_html__( 'Autoplay', 'elementor' ) ),
Switch_Control::bind_to( 'mute' )->set_label( esc_html__( 'Mute', 'elementor' ) ),
Switch_Control::bind_to( 'loop' )->set_label( esc_html__( 'Loop', 'elementor' ) ),
Switch_Control::bind_to( 'lazyload' )->set_label( esc_html__( 'Lazy load', 'elementor' ) ),
Switch_Control::bind_to( 'player_controls' )->set_label( esc_html__( 'Player controls', 'elementor' ) ),
Switch_Control::bind_to( 'captions' )->set_label( esc_html__( 'Captions', 'elementor' ) ),
Switch_Control::bind_to( 'privacy_mode' )->set_label( esc_html__( 'Privacy mode', 'elementor' ) ),
Switch_Control::bind_to( 'rel' )->set_label( esc_html__( 'Related videos', 'elementor' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function define_base_styles(): array {
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'aspect-ratio', String_Prop_Type::generate( '16/9' ) )
->add_prop( 'overflow', String_Prop_Type::generate( 'hidden' ) )
),
];
}
public function get_script_depends() {
return array_merge(
parent::get_script_depends(),
[ 'elementor-youtube-handler' ],
);
}
public function register_frontend_handlers() {
$assets_url = ELEMENTOR_ASSETS_URL;
$min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
wp_register_script(
'elementor-youtube-handler',
"{$assets_url}js/youtube-handler{$min_suffix}.js",
[ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE ],
ELEMENTOR_VERSION,
true
);
}
protected function get_templates(): array {
return [
'elementor/elements/atomic-youtube' => __DIR__ . '/atomic-youtube.html.twig',
];
}
}

View File

@@ -0,0 +1,126 @@
import { register } from '@elementor/frontend-handlers';
const getYoutubeVideoIdFromUrl = ( url ) => {
const regex = /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?vi?=|(?:embed|v|vi|user|shorts)\/))([^?&"'>]+)/;
const match = url.match( regex );
return match ? match[ 1 ] : null;
};
const loadYouTubeAPI = () => {
return new Promise( ( resolve ) => {
if ( window.YT && window.YT.loaded ) {
resolve( window.YT );
return;
}
const YOUTUBE_IFRAME_API_URL = 'https://www.youtube.com/iframe_api';
if ( ! document.querySelector( `script[src="${ YOUTUBE_IFRAME_API_URL }"]` ) ) {
const tag = document.createElement( 'script' );
tag.src = YOUTUBE_IFRAME_API_URL;
const firstScriptTag = document.getElementsByTagName( 'script' )[ 0 ];
firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
}
const checkYT = () => {
if ( window.YT && window.YT.loaded ) {
resolve( window.YT );
} else {
setTimeout( checkYT, 350 );
}
};
checkYT();
} );
};
register( {
elementType: 'e-youtube',
id: 'e-youtube-handler',
callback: ( { element } ) => {
const youtubeElement = document.createElement( 'div' );
youtubeElement.style.height = '100%';
element.appendChild( youtubeElement );
const settingsAttr = element.getAttribute( 'data-settings' );
const parsedSettings = settingsAttr ? JSON.parse( settingsAttr ) : {};
const videoId = getYoutubeVideoIdFromUrl( parsedSettings.source );
if ( ! videoId ) {
return;
}
let player;
let observer;
const prepareYTVideo = ( YT ) => {
const playerOptions = {
videoId,
events: {
onReady: () => {
if ( parsedSettings.mute ) {
player.mute();
}
if ( parsedSettings.autoplay ) {
player.playVideo();
}
},
onStateChange: ( event ) => {
if ( event.data === YT.PlayerState.ENDED && parsedSettings.loop ) {
player.seekTo( parsedSettings.start || 0 );
}
},
},
playerVars: {
controls: parsedSettings.controls ? 1 : 0,
rel: parsedSettings.rel ? 0 : 1,
cc_load_policy: parsedSettings.cc_load_policy ? 1 : 0,
autoplay: parsedSettings.autoplay ? 1 : 0,
start: parsedSettings.start,
end: parsedSettings.end,
},
};
// To handle CORS issues, when the default host is changed, the origin parameter has to be set.
if ( parsedSettings.privacy ) {
playerOptions.host = 'https://www.youtube-nocookie.com';
playerOptions.origin = window.location.hostname;
}
player = new YT.Player( youtubeElement, playerOptions );
return player;
};
if ( parsedSettings.lazyload ) {
observer = new IntersectionObserver(
( entries ) => {
if ( entries[ 0 ].isIntersecting ) {
loadYouTubeAPI().then( ( apiObject ) => prepareYTVideo( apiObject ) );
observer.unobserve( element );
}
},
);
observer.observe( element );
} else {
loadYouTubeAPI().then( ( apiObject ) => prepareYTVideo( apiObject ) );
}
return () => {
if ( player && 'function' === typeof player.destroy ) {
player.destroy();
player = null;
}
if ( element.contains( youtubeElement ) ) {
element.removeChild( youtubeElement );
}
if ( observer && 'function' === typeof observer.disconnect ) {
observer.disconnect();
observer = null;
}
};
},
} );

View File

@@ -0,0 +1,258 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
use Elementor\Element_Base;
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Atomic_Element_Base extends Element_Base {
use Has_Atomic_Base;
use Has_Meta;
protected $version = '0.0';
protected $styles = [];
protected $interactions = [];
protected $editor_settings = [];
protected $origin_id = null;
public static $widget_description = null;
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->version = $data['version'] ?? '0.0';
$this->styles = $data['styles'] ?? [];
$this->interactions = $this->parse_atomic_interactions( $data['interactions'] ?? [] );
$this->editor_settings = $data['editor_settings'] ?? [];
$this->add_script_depends( Frontend_Assets_Loader::ATOMIC_WIDGETS_HANDLER );
if ( static::$widget_description ) {
$this->description( static::$widget_description );
}
$this->origin_id = $data['origin_id'] ?? null;
}
private function parse_atomic_interactions( $interactions ) {
if ( empty( $interactions ) ) {
return [];
}
if ( is_string( $interactions ) ) {
$decoded = json_decode( $interactions, true );
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
$interactions = $decoded;
}
}
if ( ! is_array( $interactions ) ) {
return [];
}
return $interactions;
}
abstract protected function define_atomic_controls(): array;
protected function define_atomic_style_states(): array {
return [];
}
protected function define_atomic_pseudo_states(): array {
return [];
}
public function get_global_scripts() {
return [];
}
protected function get_initial_config() {
$config = parent::get_initial_config();
$props_schema = static::get_props_schema();
$config['atomic'] = true;
$config['atomic_controls'] = $this->get_atomic_controls();
$config['atomic_props_schema'] = $props_schema;
$config['atomic_style_states'] = $this->define_atomic_style_states();
$config['atomic_pseudo_states'] = $this->define_atomic_pseudo_states();
$config['dependencies_per_target_mapping'] = Dependency_Manager::get_source_to_dependents( $props_schema );
$config['base_styles'] = $this->get_base_styles();
$config['version'] = $this->version;
$config['show_in_panel'] = $this->should_show_in_panel();
$config['categories'] = $this->define_panel_categories();
$config['hide_on_search'] = false;
$config['controls'] = [];
$config['keywords'] = $this->get_keywords();
$config['default_children'] = $this->define_default_children();
$config['initial_attributes'] = $this->define_initial_attributes();
$config['include_in_widgets_config'] = true;
$config['default_html_tag'] = $this->define_default_html_tag();
$config['meta'] = $this->get_meta();
$config['allowed_child_types'] = $this->define_allowed_child_types();
return $config;
}
protected function should_show_in_panel() {
return true;
}
protected function define_panel_categories(): array {
return [ 'v4-elements' ];
}
protected function define_default_children() {
return [];
}
protected function define_default_html_tag() {
return 'div';
}
protected function define_initial_attributes() {
return [];
}
protected function define_allowed_child_types() {
return [];
}
protected function get_interaction_id() {
return $this->origin_id ?? $this->get_id();
}
protected function add_render_attributes() {
parent::add_render_attributes();
$this->add_render_attribute( '_wrapper', 'data-interaction-id', $this->get_interaction_id() );
}
/**
* Get Element keywords.
*
* Retrieve the element keywords.
*
* @since 3.29
* @access public
*
* @return array Element keywords.
*/
public function get_keywords() {
return [];
}
/**
* @return array<string, Prop_Type>
*/
abstract protected static function define_props_schema(): array;
/**
* Get the HTML tag for rendering.
*
* @return string
*/
protected function get_html_tag(): string {
$settings = $this->get_atomic_settings();
$default_html_tag = $this->define_default_html_tag();
if ( ! empty( $settings['link']['tag'] ) ) {
return $settings['link']['tag'];
}
return $settings['tag'] ?? $default_html_tag;
}
/**
* Print safe HTML tag for the element based on the element settings.
*
* @return void
*/
protected function print_html_tag() {
$html_tag = $this->get_html_tag();
Utils::print_validated_html_tag( $html_tag );
}
/**
* Print custom attributes if they exist.
*
* @return void
*/
protected function print_custom_attributes() {
$settings = $this->get_atomic_settings();
$attributes = $settings['attributes'] ?? '';
if ( isset( $settings['link']['attributes'] ) ) {
$attributes .= ' ' . ( $settings['link']['attributes'] ?? '' );
}
if ( ! empty( $attributes ) && is_string( $attributes ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo ' ' . $attributes;
}
}
/**
* Get default child type for container elements.
*
* @param array $element_data
* @return mixed
*/
protected function _get_default_child_type( array $element_data ) {
$el_types = array_keys( Plugin::$instance->elements_manager->get_element_types() );
if ( in_array( $element_data['elType'], $el_types, true ) ) {
return Plugin::$instance->elements_manager->get_element_types( $element_data['elType'] );
}
if ( ! isset( $element_data['widgetType'] ) ) {
return null;
}
return Plugin::$instance->widgets_manager->get_widget_types( $element_data['widgetType'] );
}
/**
* Default before render for container elements.
*
* @return void
*/
public function before_render() {
?>
<<?php $this->print_html_tag(); ?> <?php $this->print_render_attribute_string( '_wrapper' );
$this->print_custom_attributes(); ?>>
<?php
}
/**
* Default after render for container elements.
*
* @return void
*/
public function after_render() {
?>
</<?php $this->print_html_tag(); ?>>
<?php
}
/**
* Default content template - can be overridden by elements that need custom templates.
*
* @return void
*/
protected function content_template() {
?>
<?php
}
public static function generate() {
return Element_Builder::make( static::get_type() );
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta;
use Elementor\Widget_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
abstract class Atomic_Widget_Base extends Widget_Base {
use Has_Atomic_Base;
use Has_Meta;
public static $widget_description = null;
protected $version = '0.0';
protected $styles = [];
protected $interactions = [];
protected $editor_settings = [];
protected $origin_id = null;
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->version = $data['version'] ?? '0.0';
$this->styles = $data['styles'] ?? [];
$this->interactions = $this->parse_atomic_interactions( $data['interactions'] ?? [] );
$this->editor_settings = $data['editor_settings'] ?? [];
if ( static::$widget_description ) {
$this->description( static::$widget_description );
}
$this->origin_id = $data['origin_id'] ?? null;
}
private function parse_atomic_interactions( $interactions ) {
if ( empty( $interactions ) ) {
return [];
}
if ( is_string( $interactions ) ) {
$decoded = json_decode( $interactions, true );
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
$interactions = $decoded;
}
}
if ( ! is_array( $interactions ) ) {
return [];
}
return $interactions;
}
abstract protected function define_atomic_controls(): array;
protected function define_atomic_pseudo_states(): array {
return [];
}
public function get_global_scripts() {
return [];
}
public function get_initial_config() {
$config = parent::get_initial_config();
$props_schema = static::get_props_schema();
$config['atomic'] = true;
$config['atomic_controls'] = $this->get_atomic_controls();
$config['base_styles'] = $this->get_base_styles();
$config['base_styles_dictionary'] = $this->get_base_styles_dictionary();
$config['atomic_props_schema'] = $props_schema;
$config['atomic_pseudo_states'] = $this->define_atomic_pseudo_states();
$config['dependencies_per_target_mapping'] = Dependency_Manager::get_source_to_dependents( $props_schema );
$config['version'] = $this->version;
$config['meta'] = $this->get_meta();
return $config;
}
public function get_categories(): array {
return [ 'v4-elements' ];
}
public function before_render() {}
public function after_render() {}
abstract protected static function define_props_schema(): array;
public static function generate() {
return Widget_Builder::make( static::get_element_type() );
}
public function get_script_depends() {
return [ Frontend_Assets_Loader::ATOMIC_WIDGETS_HANDLER ];
}
public function get_interaction_id() {
return $this->origin_id ?? $this->get_id();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
class Element_Builder {
protected $element_type;
protected $settings = [];
protected $is_locked = false;
protected $children = [];
protected $editor_settings = [];
public static function make( string $element_type ) {
return new self( $element_type );
}
private function __construct( string $element_type ) {
$this->element_type = $element_type;
}
public function settings( array $settings ) {
$this->settings = $settings;
return $this;
}
public function is_locked( $is_locked ) {
$this->is_locked = $is_locked;
return $this;
}
public function editor_settings( array $editor_settings ) {
$this->editor_settings = $editor_settings;
return $this;
}
public function children( array $children ) {
$this->children = $children;
return $this;
}
public function build() {
$element_data = [
'elType' => $this->element_type,
'settings' => $this->settings,
'isLocked' => $this->is_locked,
'editor_settings' => $this->editor_settings,
'elements' => $this->children,
];
return $element_data;
}
}

View File

@@ -0,0 +1,400 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
use Elementor\Element_Base;
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser;
use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Utils;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @mixin Element_Base
*/
trait Has_Atomic_Base {
use Has_Base_Styles;
public function has_widget_inner_wrapper(): bool {
return false;
}
abstract public static function get_element_type(): string;
final public function get_name() {
return static::get_element_type();
}
private function get_valid_controls( array $schema, array $controls ): array {
$valid_controls = [];
foreach ( $controls as $control ) {
if ( $control instanceof Section ) {
$cloned_section = clone $control;
$cloned_section->set_items(
$this->get_valid_controls( $schema, $control->get_items() )
);
$valid_controls[] = $cloned_section;
continue;
}
if ( ( $control instanceof Atomic_Control_Base ) ) {
$prop_name = $control->get_bind();
if ( ! $prop_name ) {
Utils::safe_throw( 'Control is missing a bound prop from the schema.' );
continue;
}
if ( ! array_key_exists( $prop_name, $schema ) ) {
Utils::safe_throw( "Prop `{$prop_name}` is not defined in the schema of `{$this->get_name()}`." );
continue;
}
}
$valid_controls[] = $control;
}
return $valid_controls;
}
private static function validate_schema( array $schema ) {
$widget_name = static::class;
foreach ( $schema as $key => $prop ) {
if ( ! ( $prop instanceof Prop_Type ) ) {
Utils::safe_throw( "Prop `$key` must be an instance of `Prop_Type` in `{$widget_name}`." );
}
}
}
private function parse_atomic_styles( array $data ): array {
$styles = $data['styles'] ?? [];
$style_parser = Style_Parser::make( Style_Schema::get() );
foreach ( $styles as $style_id => $style ) {
$result = $style_parser->parse( $style );
if ( ! $result->is_valid() ) {
$widget_id = $data['id'] ?? 'unknown';
throw new \Exception( esc_html( "Styles validation failed for style `$style_id`. Widget ID: `$widget_id`. " . $result->errors()->to_string() ) );
}
$styles[ $style_id ] = $result->unwrap();
}
return $styles;
}
private function parse_atomic_settings( array $settings ): array {
$schema = static::get_props_schema();
$props_parser = Props_Parser::make( $schema );
$result = $props_parser->parse( $settings );
if ( ! $result->is_valid() ) {
throw new \Exception( esc_html( 'Settings validation failed. ' . $result->errors()->to_string() ) );
}
return $result->unwrap();
}
private function parse_atomic_interactions( $interactions ) {
if ( empty( $interactions ) ) {
return [];
}
if ( is_string( $interactions ) ) {
$decoded = json_decode( $interactions, true );
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
return $decoded;
}
}
if ( is_array( $interactions ) ) {
return $interactions;
}
return [];
}
private function extract_prop_value( $data, $key, $default = '' ) {
if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) {
return $default;
}
$value = $data[ $key ];
if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) {
return $value['value'];
}
return null !== $value ? $value : $default;
}
public function get_atomic_controls() {
$controls = apply_filters(
'elementor/atomic-widgets/controls',
$this->define_atomic_controls(),
$this
);
$schema = static::get_props_schema();
// Validate the schema only in the Editor.
static::validate_schema( $schema );
return $this->get_valid_controls( $schema, $controls );
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => true,
];
}
final public function get_controls( $control_id = null ) {
if ( ! empty( $control_id ) ) {
return null;
}
return [];
}
final public function get_data_for_save() {
$data = parent::get_data_for_save();
$data['version'] = $this->version;
$data['settings'] = $this->parse_atomic_settings( $data['settings'] );
$data['styles'] = $this->parse_atomic_styles( $data );
$data['editor_settings'] = $this->parse_editor_settings( $data['editor_settings'] );
if ( isset( $data['interactions'] ) && ! empty( $data['interactions'] ) ) {
$data['interactions'] = $this->transform_interactions_for_save( $data['interactions'] );
} else {
$data['interactions'] = [];
}
return $data;
}
private function transform_interactions_for_save( $interactions ) {
$decoded = $this->decode_interactions_data( $interactions );
if ( empty( $decoded['items'] ) ) {
return [];
}
return $decoded;
}
private function decode_interactions_data( $interactions ) {
if ( is_array( $interactions ) ) {
return $interactions;
}
if ( is_string( $interactions ) ) {
$decoded = json_decode( $interactions, true );
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
return $decoded;
}
}
return [
'items' => [],
'version' => 1,
];
}
final public function get_raw_data( $with_html_content = false ) {
$raw_data = parent::get_raw_data( $with_html_content );
$raw_data['styles'] = Atomic_Widget_Styles::get_license_based_filtered_styles( $this->styles ?? [] );
$raw_data['interactions'] = $this->interactions ?? [];
$raw_data['editor_settings'] = $this->editor_settings;
return $raw_data;
}
final public function get_stack( $with_common_controls = true ) {
return [
'controls' => [],
'tabs' => [],
];
}
public function get_atomic_settings(): array {
$schema = static::get_props_schema();
$props = $this->get_settings();
$merged_attribute_values = array_merge(
$this->get_initial_attributes()['value'] ?? [],
$props['attributes']['value'] ?? []
);
$props['attributes'] = Attributes_Prop_Type::generate( $merged_attribute_values );
$parsed = Render_Props_Resolver::for_settings()->resolve( $schema, $props );
$link_attributes = isset( $parsed['link'] ) ? $this->get_link_attributes_string( $parsed['link'] ) : '';
$parsed['link'] = ! empty( $link_attributes ) ? [
'tag' => $parsed['link']['tag'],
'attributes' => $link_attributes,
] : null;
return $parsed;
}
protected function get_initial_attributes() {
return Attributes_Prop_Type::generate( [
Key_Value_Prop_Type::generate( [
'key' => String_Prop_Type::generate( 'data-e-type' ),
'value' => $this->get_type(),
] ),
Key_Value_Prop_Type::generate( [
'key' => String_Prop_Type::generate( 'data-id' ),
'value' => $this->get_id(),
] ),
] );
}
public function get_atomic_setting( string $key ) {
$schema = static::get_props_schema();
if ( ! isset( $schema[ $key ] ) ) {
return null;
}
$props = $this->get_settings();
$prop_value = $props[ $key ] ?? null;
$single_schema = [ $key => $schema[ $key ] ];
$single_props = [ $key => $prop_value ];
$resolved = Render_Props_Resolver::for_settings()->resolve( $single_schema, $single_props );
return $resolved[ $key ] ?? null;
}
protected function parse_editor_settings( array $data ): array {
$editor_data = [];
if ( isset( $data['title'] ) && is_string( $data['title'] ) ) {
$editor_data['title'] = sanitize_text_field( $data['title'] );
}
return $editor_data;
}
public static function get_props_schema(): array {
$schema = static::define_props_schema();
$schema['_cssid'] = String_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() );
return apply_filters(
'elementor/atomic-widgets/props-schema',
$schema
);
}
protected function set_render_context( array $context_pairs ): void {
foreach ( $context_pairs as $context_pair ) {
$context_key = $context_pair['context_key'] ?? static::class;
$context = $context_pair['context'];
Render_Context::push( $context_key, $context );
}
}
protected function clear_render_context( array $context_pairs ): void {
foreach ( $context_pairs as $context_pair ) {
$context_key = $context_pair['context_key'] ?? static::class;
Render_Context::pop( $context_key );
}
}
public function print_content() {
$defined_context = $this->define_render_context();
if ( empty( $defined_context ) ) {
return parent::print_content();
}
$this->set_render_context( $defined_context );
parent::print_content();
$this->clear_render_context( $defined_context );
}
/**
* Define the context for element's Render_Context.
*
* @return array Array of context pairs. Each pair is an associative array with:
* - 'context_key' (optional): The context key. Defaults to static::class if not provided.
* - 'context' (required): The context value (can be any type).
*
* @example
* [
* [
* 'context_key' => 'custom-key',
* 'context' => ['some' => 'data'],
* ],
* [
* 'context' => ['instance_id' => $this->get_id()],
* ],
* ]
*/
protected function define_render_context(): array {
return [];
}
protected function get_link_attributes( $link_settings ) {
if ( empty( $link_settings['href'] ) ) {
return [];
}
$tag = $link_settings['tag'] ?? Link_Prop_Type::DEFAULT_TAG;
$url = $link_settings['href'];
$target = $link_settings['target'] ?? '_self';
$is_action_link = 'button' === $tag;
$url_attr_key = $is_action_link ? 'data-action-link' : 'href';
return [
$url_attr_key => $url,
'target' => $target,
];
}
private function get_link_attributes_string( $link_settings ) {
$link_attributes = $this->get_link_attributes( $link_settings );
if ( empty( $link_attributes ) ) {
return '';
}
$parts = [];
foreach ( $link_attributes as $key => $value ) {
if ( 'tag' === $key ) {
continue;
}
$parts[] = sprintf( '%s="%s"', $key, esc_attr( $value ) );
}
return implode( ' ', $parts );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @mixin Has_Atomic_Base
*/
trait Has_Base_Styles {
public function get_base_styles() {
$base_styles = $this->define_base_styles();
$style_definitions = [];
foreach ( $base_styles as $key => $style ) {
$id = $this->generate_base_style_id( $key );
$style_definitions[ $id ] = $style->build( $id );
}
return $style_definitions;
}
public function get_base_styles_dictionary() {
$result = [];
$base_styles = array_keys( $this->define_base_styles() );
foreach ( $base_styles as $key ) {
$result[ $key ] = $this->generate_base_style_id( $key );
}
return $result;
}
private function generate_base_style_id( string $key ): string {
return static::get_element_type() . '-' . $key;
}
/**
* @return array<string, Style_Definition>
*/
protected function define_base_styles(): array {
return [];
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
use Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer\Template_Renderer;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Trait for nested elements that render using Twig templates.
* Provides Twig-based rendering with children support for nested elements.
*
* @mixin Has_Atomic_Base
* @mixin Atomic_Element_Base
*/
trait Has_Element_Template {
public function get_initial_config() {
$config = parent::get_initial_config();
$config['support_nesting'] = true;
$config['twig_main_template'] = $this->get_main_template();
$config['twig_templates'] = $this->get_templates_contents();
$config['base_styles_dictionary'] = $this->get_base_styles_dictionary();
return $config;
}
protected function get_templates_contents() {
return array_map(
fn ( $path ) => Utils::file_get_contents( $path ),
$this->get_templates()
);
}
protected function render() {
try {
$renderer = Template_Renderer::instance();
foreach ( $this->get_templates() as $name => $path ) {
if ( $renderer->is_registered( $name ) ) {
continue;
}
$renderer->register( $name, $path );
}
$context = $this->build_template_context();
$template_html = $renderer->render( $this->get_main_template(), $context );
$children_html = $this->render_children_to_html();
$output = str_replace( $this->get_children_placeholder(), $children_html, $template_html );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $output;
} catch ( \Exception $e ) {
if ( Utils::is_elementor_debug() ) {
throw $e;
}
}
}
protected function render_children_to_html(): string {
$html = '';
foreach ( $this->get_children() as $child ) {
ob_start();
$child->print_element();
$html .= ob_get_clean();
}
return $html;
}
protected function get_children_placeholder(): string {
return '<!-- elementor-children-placeholder -->';
}
protected function build_base_template_context(): array {
return [
'id' => $this->get_id(),
'interaction_id' => $this->get_interaction_id(),
'type' => $this->get_name(),
'settings' => $this->get_atomic_settings(),
'base_styles' => $this->get_base_styles_dictionary(),
'children_placeholder' => $this->get_children_placeholder(),
];
}
public function before_render() {
// Intentionally empty - Twig template handles full rendering
}
public function after_render() {
// Intentionally empty - Twig template handles full rendering
}
public function print_content() {
$defined_context = $this->define_render_context();
if ( empty( $defined_context ) ) {
return $this->render();
}
$this->set_render_context( $defined_context );
$this->render();
$this->clear_render_context( $defined_context );
}
protected function get_main_template(): string {
$templates = $this->get_templates();
foreach ( $templates as $key => $path ) {
return $key;
}
return '';
}
abstract protected function get_templates(): array;
protected function build_template_context(): array {
return $this->build_base_template_context();
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
use Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer\Template_Renderer;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @mixin Has_Atomic_Base
*/
trait Has_Template {
public function get_initial_config() {
$config = parent::get_initial_config();
$config['twig_main_template'] = $this->get_main_template();
$config['twig_templates'] = $this->get_templates_contents();
return $config;
}
protected function render() {
try {
$renderer = Template_Renderer::instance();
foreach ( $this->get_templates() as $name => $path ) {
if ( $renderer->is_registered( $name ) ) {
continue;
}
$renderer->register( $name, $path );
}
$context = [
'id' => $this->get_id(),
'interaction_id' => $this->get_interaction_id(),
'type' => $this->get_name(),
'settings' => $this->get_atomic_settings(),
'base_styles' => $this->get_base_styles_dictionary(),
];
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $renderer->render( $this->get_main_template(), $context );
} catch ( \Exception $e ) {
if ( Utils::is_elementor_debug() ) {
throw $e;
}
}
}
protected function get_templates_contents() {
return array_map(
fn ( $path ) => Utils::file_get_contents( $path ),
$this->get_templates()
);
}
protected function get_main_template() {
$templates = $this->get_templates();
if ( count( $templates ) > 1 ) {
Utils::safe_throw( 'When having more than one template, you should override this method to return the main template.' );
return null;
}
foreach ( $templates as $key => $path ) {
// Returns first key in the array.
return $key;
}
return null;
}
abstract protected function get_templates(): array;
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Render_Context {
private static $context_stack = [];
public static function push( string $key, array $context ): void {
if ( ! self::get( $key ) ) {
self::$context_stack[ $key ] = [];
}
self::$context_stack[ $key ][] = $context;
}
public static function pop( string $key ): void {
if ( isset( self::$context_stack[ $key ] ) && ! empty( self::$context_stack[ $key ] ) ) {
array_pop( self::$context_stack[ $key ] );
}
}
public static function get( string $key ): array {
if ( ! isset( self::$context_stack[ $key ] ) || empty( self::$context_stack[ $key ] ) ) {
return [];
}
$last_key = array_key_last( self::$context_stack[ $key ] );
return self::$context_stack[ $key ][ $last_key ];
}
public static function clear(): void {
self::$context_stack = [];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
class Widget_Builder {
protected $widget_type;
protected $settings = [];
protected $is_locked = false;
protected $editor_settings = [];
public static function make( string $widget_type ) {
return new self( $widget_type );
}
private function __construct( string $widget_type ) {
$this->widget_type = $widget_type;
}
public function settings( array $settings ) {
$this->settings = $settings;
return $this;
}
public function is_locked( $is_locked ) {
$this->is_locked = $is_locked;
return $this;
}
public function editor_settings( array $editor_settings ) {
$this->editor_settings = $editor_settings;
return $this;
}
public function build() {
$widget_data = [
'elType' => 'widget',
'widgetType' => $this->widget_type,
'settings' => $this->settings,
'isLocked' => $this->is_locked,
'editor_settings' => $this->editor_settings,
];
return $widget_data;
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Div_Block;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Div_Block extends Atomic_Element_Base {
const BASE_STYLE_KEY = 'base';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'is_container', true );
}
public static function get_type() {
return 'e-div-block';
}
public static function get_element_type(): string {
return 'e-div-block';
}
public function get_title() {
return esc_html__( 'Div block', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-div-block';
}
protected static function define_props_schema(): array {
$tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND )
->where( [
'operator' => 'ne',
'path' => [ 'link', 'destination' ],
'nestedPath' => [ 'group' ],
'value' => 'action',
'newValue' => [
'$$type' => 'string',
'value' => 'button',
],
] )->where( [
'operator' => 'not_exist',
'path' => [ 'link', 'destination' ],
'newValue' => [
'$$type' => 'string',
'value' => 'a',
],
] )->get();
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'tag' => String_Prop_Type::make()
->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] )
->default( 'div' )
->set_dependencies( $tag_dependencies ),
'link' => Link_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Html_Tag_Control::bind_to( 'tag' )
->set_options( [
[
'value' => 'div',
'label' => 'Div',
],
[
'value' => 'header',
'label' => 'Header',
],
[
'value' => 'section',
'label' => 'Section',
],
[
'value' => 'article',
'label' => 'Article',
],
[
'value' => 'aside',
'label' => 'Aside',
],
[
'value' => 'footer',
'label' => 'Footer',
],
])
->set_fallback_labels( [
'a' => 'a (link)',
] )
->set_label( esc_html__( 'HTML Tag', 'elementor' ) ),
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) )
->set_meta( [
'topDivider' => true,
] ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
] ),
];
}
protected function define_base_styles(): array {
$display = String_Prop_Type::generate( 'block' );
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'display', $display )
->add_prop( 'padding', $this->get_base_padding() )
->add_prop( 'min-width', $this->get_base_min_width() )
),
];
}
protected function get_base_padding(): array {
return Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
] );
}
protected function get_base_min_width(): array {
return Size_Prop_Type::generate( [
'size' => 30,
'unit' => 'px',
] );
}
protected function add_render_attributes() {
parent::add_render_attributes();
$settings = $this->get_atomic_settings();
$base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ];
$initial_attributes = $this->define_initial_attributes();
$attributes = [
'class' => [
'e-con',
'e-atomic-element',
$base_style_class,
...( $settings['classes'] ?? [] ),
],
];
if ( ! empty( $settings['_cssid'] ) ) {
$attributes['id'] = esc_attr( $settings['_cssid'] );
}
if ( ! empty( $settings['link']['href'] ) ) {
$link_attributes = $this->get_link_attributes( $settings['link'] );
$attributes = array_merge( $attributes, $link_attributes );
}
$this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) );
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Flexbox;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Flexbox extends Atomic_Element_Base {
const BASE_STYLE_KEY = 'base';
public function __construct( $data = [], $args = null ) {
parent::__construct( $data, $args );
$this->meta( 'is_container', true );
}
public static function get_type() {
return 'e-flexbox';
}
public static function get_element_type(): string {
return 'e-flexbox';
}
public function get_title() {
return esc_html__( 'Flexbox', 'elementor' );
}
public function get_keywords() {
return [ 'ato', 'atom', 'atoms', 'atomic' ];
}
public function get_icon() {
return 'eicon-flexbox';
}
protected static function define_props_schema(): array {
$tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND )
->where( [
'operator' => 'ne',
'path' => [ 'link', 'destination' ],
'nestedPath' => [ 'group' ],
'value' => 'action',
'newValue' => [
'$$type' => 'string',
'value' => 'button',
],
] )->where( [
'operator' => 'not_exist',
'path' => [ 'link', 'destination' ],
'newValue' => [
'$$type' => 'string',
'value' => 'a',
],
] )->get();
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'tag' => String_Prop_Type::make()
->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] )
->default( 'div' )
->description( 'The HTML tag for the flexbox container. Could be div, header, section, article, aside, footer, or a (link).' )
->set_dependencies( $tag_dependencies ),
'link' => Link_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
return $schema;
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Settings', 'elementor' ) )
->set_id( 'settings' )
->set_items( [
Html_Tag_Control::bind_to( 'tag' )
->set_options( [
[
'value' => 'div',
'label' => 'Div',
],
[
'value' => 'header',
'label' => 'Header',
],
[
'value' => 'section',
'label' => 'Section',
],
[
'value' => 'article',
'label' => 'Article',
],
[
'value' => 'aside',
'label' => 'Aside',
],
[
'value' => 'footer',
'label' => 'Footer',
],
])
->set_label( esc_html__( 'HTML Tag', 'elementor' ) )
->set_fallback_labels( [
'a' => 'a (link)',
] ),
Link_Control::bind_to( 'link' )
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
->set_label( __( 'Link', 'elementor' ) )
->set_meta( [
'topDivider' => true,
] ),
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor' ) )
->set_meta( $this->get_css_id_control_meta() ),
] ),
];
}
protected function define_base_styles(): array {
$display = String_Prop_Type::generate( 'flex' );
$flex_direction = String_Prop_Type::generate( 'row' );
return [
static::BASE_STYLE_KEY => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'display', $display )
->add_prop( 'flex-direction', $flex_direction )
->add_prop( 'padding', $this->get_base_padding() )
),
];
}
protected function get_base_padding(): array {
return Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
] );
}
protected function add_render_attributes() {
parent::add_render_attributes();
$settings = $this->get_atomic_settings();
$base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ];
$initial_attributes = $this->define_initial_attributes();
$attributes = [
'class' => [
'e-con',
'e-atomic-element',
$base_style_class,
...( $settings['classes'] ?? [] ),
],
];
if ( ! empty( $settings['_cssid'] ) ) {
$attributes['id'] = esc_attr( $settings['_cssid'] );
}
if ( ! empty( $settings['link']['href'] ) ) {
$link_attributes = $this->get_link_attributes( $settings['link'] );
$attributes = array_merge( $attributes, $link_attributes );
}
$this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) );
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\Loader;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Frontend_Assets_Loader {
const ALPINEJS_HANDLE = 'elementor-v2-alpinejs';
const FRONTEND_HANDLERS_HANDLE = 'elementor-v2-frontend-handlers';
const ATOMIC_WIDGETS_HANDLER = 'elementor-v2-widgets-frontend';
/**
* @return void
*/
public function register_scripts() {
$this->register_package_scripts();
do_action( 'elementor/atomic-widgets/frontend/loader/scripts/register', $this );
}
private function register_package_scripts() {
$assets_url = ELEMENTOR_ASSETS_URL;
$min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
wp_register_script(
self::FRONTEND_HANDLERS_HANDLE,
"{$assets_url}js/packages/frontend-handlers/frontend-handlers{$min_suffix}.js",
[],
ELEMENTOR_VERSION,
true
);
wp_register_script(
self::ALPINEJS_HANDLE,
"{$assets_url}js/packages/alpinejs/alpinejs{$min_suffix}.js",
[],
ELEMENTOR_VERSION,
true
);
wp_register_script(
self::ATOMIC_WIDGETS_HANDLER,
"{$assets_url}js/atomic-widgets-frontend-handler{$min_suffix}.js",
[ self::FRONTEND_HANDLERS_HANDLE, self::ALPINEJS_HANDLE ],
ELEMENTOR_VERSION,
true
);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer;
use ElementorDeps\Twig\Error\LoaderError;
use ElementorDeps\Twig\Loader\LoaderInterface;
use ElementorDeps\Twig\Source;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Single_File_Loader implements LoaderInterface {
private $templates = [];
private $validity_cache = [];
public function getSourceContext( string $name ): Source {
$path = $this->get_template_path( $name );
return new Source(
// This is safe to use because we're validating the file path inside `get_template_path`.
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
file_get_contents( $path ),
$name,
$path
);
}
public function getCacheKey( string $name ): string {
return $this->get_template_path( $name );
}
public function isFresh( string $name, int $time ): bool {
$path = $this->get_template_path( $name );
return filemtime( $path ) < $time;
}
public function exists( string $name ) {
$path = $this->templates[ $name ] ?? null;
return $this->is_valid_file( $path );
}
public function is_registered( string $name ): bool {
return isset( $this->templates[ $name ] );
}
public function register( string $name, string $path ): self {
if ( ! $this->is_valid_file( $path ) ) {
throw new LoaderError( esc_html( "Invalid template '{$name}': {$path}" ) );
}
$this->templates[ $name ] = $path;
return $this;
}
private function get_template_path( string $name ): string {
$path = $this->templates[ $name ] ?? null;
if ( ! $this->is_valid_file( $path ) ) {
throw new LoaderError( esc_html( "Invalid template '{$name}': {$path}" ) );
}
return $path;
}
private function is_valid_file( $path ): bool {
if ( ! $path ) {
return false;
}
if ( isset( $this->validity_cache[ $path ] ) ) {
return $this->validity_cache[ $path ];
}
// Ref: https://github.com/twigphp/Twig/blob/8432946eeeca009d75fc7fc568f3c3f4650f5a0f/src/Loader/FilesystemLoader.php#L260
if ( str_contains( $path, "\0" ) ) {
throw new LoaderError( 'A template name cannot contain NULL bytes.' );
}
$is_valid = is_file( $path ) && is_readable( $path );
$this->validity_cache[ $path ] = $is_valid;
return $is_valid;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer;
use Elementor\Utils;
use ElementorDeps\Twig\Environment;
use ElementorDeps\Twig\Runtime\EscaperRuntime;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Template_Renderer {
private static ?self $instance = null;
private Single_File_Loader $loader;
private Environment $env;
private function __construct() {
$this->loader = new Single_File_Loader();
$this->env = new Environment(
$this->loader,
[
'debug' => Utils::is_elementor_debug(),
'autoescape' => 'name',
]
);
$escaper = $this->env->getRuntime( EscaperRuntime::class );
$escaper->setEscaper( 'full_url', 'esc_url' );
$escaper->setEscaper( 'html_tag', [ Utils::class, 'validate_html_tag' ] );
}
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public static function reset() {
self::$instance = null;
}
public function is_registered( string $name ): bool {
return $this->loader->is_registered( $name );
}
public function register( string $name, string $path ): self {
$this->loader->register( $name, $path );
return $this;
}
public function render( string $name, array $context = [] ): string {
return $this->env->render( $name, $context );
}
}