first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
export default class extends elementorModules.Module {
constructor() {
super();
elementorFrontend.elementsHandler.attachHandler( 'text-path', () => import( /* webpackChunkName: 'text-path' */ './handlers/text-path' ) );
}
}

View File

@@ -0,0 +1,224 @@
import { escapeHTML } from 'elementor-frontend/utils/utils';
export default class TextPathHandler extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
pathContainer: '.e-text-path',
svg: '.e-text-path > svg',
},
};
}
getDefaultElements() {
const { selectors } = this.getSettings();
const element = this.$element[ 0 ];
return {
widgetWrapper: element,
pathContainer: element.querySelector( selectors.pathContainer ),
svg: element.querySelector( selectors.svg ),
textPath: element.querySelector( selectors.textPath ),
};
}
/**
* Initialize the object.
*
* @returns {void}
*/
onInit() {
this.elements = this.getDefaultElements();
// Generate unique IDs using the wrapper's `data-id`.
this.pathId = `e-path-${ this.elements.widgetWrapper.dataset.id }`;
this.textPathId = `e-text-path-${ this.elements.widgetWrapper.dataset.id }`;
if ( ! this.elements.svg ) {
return;
}
this.initTextPath();
}
/**
* Set the start offset for the text.
*
* @param offset {string|int} The text start offset.
*
* @returns {void}
*/
setOffset( offset ) {
if ( ! this.elements.textPath ) {
return;
}
if ( this.isRTL() ) {
offset = 100 - parseInt( offset );
}
this.elements.textPath.setAttribute( 'startOffset', offset + '%' );
}
/**
* Handle element settings changes.
*
* @param setting {Object} The settings object from the editor.
*
* @returns {void}
*/
onElementChange( setting ) {
const {
start_point: startPoint,
text,
} = this.getElementSettings();
switch ( setting ) {
case 'start_point':
this.setOffset( startPoint.size );
break;
case 'text':
this.setText( text );
break;
case 'text_path_direction':
this.setOffset( startPoint.size );
this.setText( text );
break;
default:
break;
}
}
/**
* Attach a unique id to the path.
*
* @returns {void}
*/
attachIdToPath() {
// Prioritize the custom `data` attribute over the `path` element, and fallback to the first `path`.
const path = this.elements.svg.querySelector( '[data-path-anchor]' ) || this.elements.svg.querySelector( 'path' );
path.id = this.pathId;
}
/**
* Initialize the text path element.
*
* @returns {void}
*/
initTextPath() {
const {
start_point: startPoint,
text,
} = this.getElementSettings();
this.attachIdToPath();
// Generate the `textPath` element with its settings.
this.elements.svg.innerHTML += `
<text>
<textPath id="${ this.textPathId }" href="#${ this.pathId }"></textPath>
</text>
`;
// Regenerate the elements object to have access to `this.elements.textPath`.
this.elements.textPath = this.elements.svg.querySelector( `#${ this.textPathId }` );
this.setOffset( startPoint.size );
this.setText( text );
}
/**
* Set the new text into the path.
*
* @param newText {string} The new text to put in the text path.
*
* @returns {void}
*/
setText( newText ) {
const {
url,
is_external: isExternal,
nofollow,
} = this.getElementSettings()?.link;
const target = isExternal ? '_blank' : '',
rel = nofollow ? 'nofollow' : '';
// Add link attributes.
if ( url ) {
newText = `<a href="${ escapeHTML( url ) }" rel="${ rel }" target="${ target }">${ escapeHTML( newText ) }</a>`;
}
// Set the text.
this.elements.textPath.innerHTML = newText;
// Remove the cloned element if exists.
const existingClone = this.elements.svg.querySelector( `#${ this.textPathId }-clone` );
if ( existingClone ) {
existingClone.remove();
}
// Reverse the text if needed.
if ( this.shouldReverseText() ) {
// Keep an invisible selectable copy of original element for better a11y.
const clone = this.elements.textPath.cloneNode();
clone.id += '-clone';
clone.classList.add( 'elementor-hidden' );
clone.textContent = newText;
this.elements.textPath.parentNode.appendChild( clone );
this.reverseToRTL();
}
}
/**
* Determine if the current layout should be RTL.
*
* @returns {boolean}
*/
isRTL() {
const { text_path_direction: direction } = this.getElementSettings();
let isRTL = elementorFrontend.config.is_rtl;
if ( direction ) {
isRTL = ( 'rtl' === direction );
}
return isRTL;
}
/**
* Determine if it should RTL the text (reversing it, etc.).
*
* @returns {boolean}
*/
shouldReverseText() {
return ( this.isRTL() && -1 === navigator.userAgent.indexOf( 'Firefox' ) );
}
/**
* Reverse the text path to support RTL.
*
* @returns {void}
*/
reverseToRTL() {
// Make sure to use the inner `a` tag if exists.
let parentElement = this.elements.textPath;
parentElement = parentElement.querySelector( 'a' ) || parentElement;
// Catch all RTL chars and reverse their order.
const pattern = /([\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC\s$&+,:;=?@#|'<>.^*()%!-]+)/ig;
// Reverse the text.
parentElement.textContent = parentElement.textContent.replace( pattern, ( word ) => {
return word.split( '' ).reverse().join( '' );
} );
// Add a11y attributes.
parentElement.setAttribute( 'aria-hidden', true );
}
}

View File

@@ -0,0 +1 @@
@import "widgets/text-path.scss";

View File

@@ -0,0 +1,43 @@
.elementor-widget-text-path {
font-size: 20px;
text-align: var( --alignment, #{$start} );
svg {
width: var( --width );
max-width: 100%;
height: auto;
overflow: visible;
word-spacing: var( --word-spacing );
transform: rotate( var( --rotate, 0 ) ) scaleX( var( --scale-x, 1 ) ) scaleY( var( --scale-y, 1 ) );
path {
vector-effect: non-scaling-stroke; /* Prevent stroke size scaling when resizing the SVG. */
fill: var( --path-fill, transparent );
stroke: var( --stroke-color, transparent );
stroke-width: var( --stroke-width, 1px );
transition: var( --stroke-transition ) stroke, var( --stroke-transition ) fill;
}
&:hover {
path {
--path-fill: var( --path-fill-hover );
--stroke-color: var( --stroke-color-hover );
--stroke-width: var( --stroke-width-hover );
}
}
text {
--fill: var( --text-color );
fill: var( --fill );
direction: var( --direction, $direction );
transition: var( --transition ) stroke,
var( --transition ) stroke-width,
var( --transition ) fill;
&:hover {
--text-color: var( --text-color-hover );
}
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Elementor\Modules\Shapes;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends \Elementor\Core\Base\Module {
/**
* @param bool $add_custom Determine if the output should include the `Custom` option.
*
* @return array List of paths.
*/
public static function get_paths( $add_custom = true ) {
$paths = [
'wave' => __( 'Wave', 'elementor' ),
'arc' => __( 'Arc', 'elementor' ),
'circle' => __( 'Circle', 'elementor' ),
'line' => __( 'Line', 'elementor' ),
'oval' => __( 'Oval', 'elementor' ),
'spiral' => __( 'Spiral', 'elementor' ),
];
if ( $add_custom ) {
$paths['custom'] = __( 'Custom', 'elementor' );
}
return $paths;
}
/**
* @param $path string Path name.
*
* @return string The path SVG markup.
*/
public static function get_path_svg( $path ) {
$file_name = ELEMENTOR_ASSETS_PATH . 'svg-paths/' . $path . '.svg';
if ( ! is_file( $file_name ) ) {
return '';
}
return file_get_contents( $file_name );
}
/**
* Get the module's associated widgets.
*
* @return string[]
*/
protected function get_widgets() {
return [
'TextPath',
];
}
/**
* Retrieve the module name.
*
* @return string
*/
public function get_name() {
return 'shapes';
}
}

View File

@@ -0,0 +1,679 @@
<?php
namespace Elementor\Modules\Shapes\Widgets;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Group_Control_Typography;
use Elementor\Modules\Shapes\Module as Shapes_Module;
use Elementor\Utils;
use Elementor\Widget_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor WordArt widget.
*
* Elementor widget that displays text along SVG path.
*
*/
class TextPath extends Widget_Base {
const DEFAULT_PATH_FILL = '#E8178A';
/**
* Get widget name.
*
* Retrieve Text Path widget name.
*
* @return string Widget name.
* @access public
*
*/
public function get_name() {
return 'text-path';
}
/**
* Get widget title.
*
* Retrieve Text Path widget title.
*
* @return string Widget title.
* @access public
*
*/
public function get_title() {
return __( 'Text Path', 'elementor' );
}
/**
* Get widget icon.
*
* Retrieve Text Path widget icon.
*
* @return string Widget icon.
* @access public
*
*/
public function get_icon() {
return 'eicon-wordart';
}
/**
* Get widget keywords.
*
* Retrieve the list of keywords the widget belongs to.
*
* @return array Widget keywords.
* @access public
*
*/
public function get_keywords() {
return [ 'text path', 'word path', 'text on path', 'wordart', 'word art' ];
}
/**
* Register content controls under content tab.
*/
protected function register_content_tab() {
$this->start_controls_section(
'section_content_text_path',
[
'label' => __( 'Text Path', 'elementor' ),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'text',
[
'label' => __( 'Text', 'elementor' ),
'type' => Controls_Manager::TEXT,
'label_block' => true,
'default' => __( 'Add Your Curvy Text Here', 'elementor' ),
'frontend_available' => true,
'render_type' => 'none',
'dynamic' => [
'active' => true,
],
]
);
$this->add_control(
'path',
[
'label' => __( 'Path Type', 'elementor' ),
'type' => Controls_Manager::SELECT,
'options' => Shapes_Module::get_paths(),
'default' => 'wave',
]
);
$this->add_control(
'custom_path',
[
'label' => __( 'SVG', 'elementor' ),
'type' => Controls_Manager::MEDIA,
'media_types' => [
'svg',
],
'condition' => [
'path' => 'custom',
],
'dynamic' => [
'active' => true,
],
'description' => sprintf( __( 'Want to create custom text paths with SVG? <a target="_blank" href="%s">Learn More</a>', 'elementor' ), 'https://go.elementor.com/text-path-create-paths' ),
]
);
$this->add_control(
'link',
[
'label' => __( 'Link', 'elementor' ),
'type' => Controls_Manager::URL,
'label_block' => true,
'dynamic' => [
'active' => true,
],
'placeholder' => __( 'Paste URL or type', 'elementor' ),
'frontend_available' => true,
]
);
$this->add_responsive_control(
'align',
[
'label' => __( 'Alignment', 'elementor' ),
'type' => Controls_Manager::CHOOSE,
'default' => '',
'options' => [
'left' => [
'title' => __( 'Left', 'elementor' ),
'icon' => 'eicon-text-align-left',
],
'center' => [
'title' => __( 'Center', 'elementor' ),
'icon' => 'eicon-text-align-center',
],
'right' => [
'title' => __( 'Right', 'elementor' ),
'icon' => 'eicon-text-align-right',
],
],
'selectors' => [
'{{WRAPPER}}' => '--alignment: {{VALUE}}',
],
'frontend_available' => true,
]
);
$this->add_control(
'text_path_direction',
[
'label' => __( 'Text Direction', 'elementor' ),
'type' => Controls_Manager::SELECT,
'default' => '',
'options' => [
'' => __( 'Default', 'elementor' ),
'rtl' => __( 'RTL', 'elementor' ),
'ltr' => __( 'LTR', 'elementor' ),
],
'selectors' => [
'{{WRAPPER}}' => '--direction: {{VALUE}}',
],
'frontend_available' => true,
]
);
$this->add_control(
'show_path',
[
'label' => __( 'Show Path', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => __( 'On', 'elementor' ),
'label_off' => __( 'Off', 'elementor' ),
'return_value' => self::DEFAULT_PATH_FILL,
'separator' => 'before',
'default' => '',
'selectors' => [
'{{WRAPPER}}' => '--path-stroke: {{VALUE}}; --path-fill: transparent;',
],
]
);
$this->end_controls_section();
}
/**
* Register style controls under style tab.
*/
protected function register_style_tab() {
/**
* Text Path styling section.
*/
$this->start_controls_section(
'section_style_text_path',
[
'label' => __( 'Text Path', 'elementor' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_responsive_control(
'size',
[
'label' => __( 'Size', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ '%', 'px' ],
'range' => [
'%' => [
'min' => 0,
'max' => 100,
'step' => 10,
],
'px' => [
'min' => 0,
'max' => 800,
'step' => 50,
],
],
'default' => [
'unit' => 'px',
'size' => 500,
],
'tablet_default' => [
'unit' => 'px',
'size' => 500,
],
'mobile_default' => [
'unit' => 'px',
'size' => 500,
],
'selectors' => [
'{{WRAPPER}}' => '--width: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_responsive_control(
'rotation',
[
'label' => __( 'Rotate', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'deg' ],
'range' => [
'deg' => [
'min' => 0,
'max' => 360,
'step' => 1,
],
],
'default' => [
'unit' => 'deg',
'size' => '',
],
'tablet_default' => [
'unit' => 'deg',
'size' => '',
],
'mobile_default' => [
'unit' => 'deg',
'size' => '',
],
'selectors' => [
'{{WRAPPER}}' => '--rotate: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_control(
'text_heading',
[
'label' => __( 'Text', 'elementor' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'text_typography',
'selector' => '{{WRAPPER}}',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_TEXT,
],
'fields_options' => [
'font_size' => [
'default' => [
'size' => '20',
'unit' => 'px',
],
'size_units' => [ 'px' ],
],
],
]
);
$this->add_responsive_control(
'word_spacing',
[
'label' => __( 'Word Spacing', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px' ],
'range' => [
'px' => [
'min' => -20,
'max' => 20,
'step' => 1,
],
],
'default' => [
'unit' => 'px',
'size' => '',
],
'tablet_default' => [
'unit' => 'px',
'size' => '',
],
'mobile_default' => [
'unit' => 'px',
'size' => '',
],
'selectors' => [
'{{WRAPPER}}' => '--word-spacing: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_control(
'start_point',
[
'label' => __( 'Starting Point', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ '%' ],
'range' => [
'px' => [
'min' => -100,
'max' => 100,
'step' => 1,
],
],
'default' => [
'unit' => '%',
'size' => 0,
],
'frontend_available' => true,
'render_type' => 'none',
]
);
$this->start_controls_tabs( 'text_style' );
/**
* Normal tab.
*/
$this->start_controls_tab(
'text_normal',
[
'label' => __( 'Normal', 'elementor' ),
]
);
$this->add_control(
'text_color_normal',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}}' => '--text-color: {{VALUE}};',
],
]
);
$this->end_controls_tab();
/**
* Hover tab.
*/
$this->start_controls_tab(
'text_hover',
[
'label' => __( 'Hover', 'elementor' ),
]
);
$this->add_control(
'text_color_hover',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}}' => '--text-color-hover: {{VALUE}};',
],
]
);
$this->add_control(
'hover_animation',
[
'label' => __( 'Hover Animation', 'elementor' ),
'type' => Controls_Manager::HOVER_ANIMATION,
]
);
$this->add_control(
'hover_transition',
[
'label' => __( 'Transition Duration', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'default' => [
'size' => 0.3,
'unit' => 's',
],
'range' => [
's' => [
'min' => 0,
'max' => 3,
'step' => 0.1,
],
],
'selectors' => [
'{{WRAPPER}}' => '--transition: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->end_controls_section();
/**
* Path styling section.
*/
$this->start_controls_section(
'section_style_path',
[
'label' => __( 'Path', 'elementor' ),
'tab' => Controls_Manager::TAB_STYLE,
'condition' => [
'show_path!' => '',
],
]
);
$this->start_controls_tabs( 'path_style' );
/**
* Normal tab.
*/
$this->start_controls_tab(
'path_normal',
[
'label' => __( 'Normal', 'elementor' ),
]
);
$this->add_control(
'path_fill_normal',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}}' => '--path-fill: {{VALUE}};',
],
]
);
$this->add_control(
'stroke_heading_normal',
[
'label' => __( 'Stroke', 'elementor' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_control(
'stroke_color_normal',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => self::DEFAULT_PATH_FILL,
'selectors' => [
'{{WRAPPER}}' => '--stroke-color: {{VALUE}};',
],
]
);
$this->add_control(
'stroke_width_normal',
[
'label' => __( 'Width', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'default' => [
'size' => 1,
'unit' => 'px',
],
'range' => [
'px' => [
'min' => 0,
'max' => 20,
'step' => 1,
],
],
'selectors' => [
'{{WRAPPER}}' => '--stroke-width: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_controls_tab();
/**
* Hover tab.
*/
$this->start_controls_tab(
'path_hover',
[
'label' => __( 'Hover', 'elementor' ),
]
);
$this->add_control(
'path_fill_hover',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}}' => '--path-fill-hover: {{VALUE}};',
],
]
);
$this->add_control(
'stroke_heading_hover',
[
'label' => __( 'Stroke', 'elementor' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_control(
'stroke_color_hover',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}}' => '--stroke-color-hover: {{VALUE}};',
],
]
);
$this->add_control(
'stroke_width_hover',
[
'label' => __( 'Width', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'default' => [
'size' => '',
'unit' => 'px',
],
'range' => [
'px' => [
'min' => 0,
'max' => 20,
'step' => 1,
],
],
'selectors' => [
'{{WRAPPER}}' => '--stroke-width-hover: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'stroke_transition',
[
'label' => __( 'Transition Duration', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'default' => [
'size' => 0.3,
'unit' => 's',
],
'range' => [
's' => [
'min' => 0,
'max' => 3,
'step' => 0.1,
],
],
'selectors' => [
'{{WRAPPER}}' => '--stroke-transition: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->end_controls_section();
}
/**
* Register Text Path widget controls.
*
* Adds different input fields to allow the user to change and customize the widget settings.
*
* @access protected
*/
protected function register_controls() {
$this->register_content_tab();
$this->register_style_tab();
}
/**
* Render Text Path widget output on the frontend.
*
* Written in PHP and used to generate the final HTML.
*
* @access protected
*/
protected function render() {
$settings = $this->get_settings_for_display();
// Get the shape SVG markup.
if ( 'custom' !== $settings['path'] ) {
$path_svg = Shapes_Module::get_path_svg( $settings['path'] );
} else {
$url = $settings['custom_path']['url'];
// Get the file contents only if it's svg.
$path_svg = ( 'svg' === pathinfo( $url, PATHINFO_EXTENSION ) ) ? file_get_contents( $url ) : '';
}
// Add Text Path text.
$this->add_render_attribute( 'text_path', 'class', 'e-text-path' );
// Add hover animation.
if ( ! empty( $settings['hover_animation'] ) ) {
$this->add_render_attribute( 'text_path', 'class', 'elementor-animation-' . $settings['hover_animation'] );
}
// Render.
?>
<div <?php echo $this->get_render_attribute_string( 'text_path' ); ?>>
<?php echo $path_svg; ?>
</div>
<?php
}
}