PluginProbe
Elementor Website Builder – more than just a page builder / 3.28.0-dev1
Elementor Website Builder – more than just a page builder v3.28.0-dev1
4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
elementor / modules / nested-accordion / widgets / nested-accordion.php

nested-accordion.php in Elementor Website Builder – more than just a page builder 3.28.0-dev1, at modules/nested-accordion/widgets/nested-accordion.php

1,037 lines 29.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Modules\NestedAccordion\Widgets;
3
4 use Elementor\Controls_Manager;
5 use Elementor\Group_Control_Background;
6 use Elementor\Group_Control_Border;
7 use Elementor\Group_Control_Text_Shadow;
8 use Elementor\Group_Control_Typography;
9 use Elementor\Icons_Manager;
10 use Elementor\Modules\NestedElements\Base\Widget_Nested_Base;
11 use Elementor\Modules\NestedElements\Controls\Control_Nested_Repeater;
12 use Elementor\Plugin;
13 use Elementor\Repeater;
14 use Elementor\Utils;
15 use Elementor\Group_Control_Text_Stroke;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Elementor Nested Accordion widget.
23 *
24 * Elementor widget that displays a collapsible display of content in an
25 * accordion style.
26 *
27 * @since 3.15.0
28 */
29 class Nested_Accordion extends Widget_Nested_Base {
30
31 private $optimized_markup = null;
32 private $widget_container_selector = '';
33
34 public function get_name() {
35 return 'nested-accordion';
36 }
37
38 public function get_title() {
39 return esc_html__( 'Accordion', 'elementor' );
40 }
41
42 public function get_icon() {
43 return 'eicon-accordion';
44 }
45
46 public function get_keywords() {
47 return [ 'nested', 'tabs', 'accordion', 'toggle' ];
48 }
49
50 public function get_style_depends(): array {
51 return [ 'widget-nested-accordion' ];
52 }
53
54 public function show_in_panel(): bool {
55 return Plugin::$instance->experiments->is_feature_active( 'nested-elements', true );
56 }
57
58 public function has_widget_inner_wrapper(): bool {
59 return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
60 }
61
62 protected function item_content_container( int $index ) {
63 return [
64 'elType' => 'container',
65 'settings' => [
66 '_title' => sprintf( __( 'item #%s', 'elementor' ), $index ),
67 'content_width' => 'full',
68 ],
69 ];
70 }
71
72 protected function get_default_children_elements() {
73 return [
74 $this->item_content_container( 1 ),
75 $this->item_content_container( 2 ),
76 $this->item_content_container( 3 ),
77 ];
78 }
79
80 protected function get_default_repeater_title_setting_key() {
81 return 'item_title';
82 }
83
84 protected function get_default_children_title() {
85 return esc_html__( 'Item #%d', 'elementor' );
86 }
87
88 protected function get_default_children_placeholder_selector() {
89 return '.e-n-accordion';
90 }
91
92 protected function get_default_children_container_placeholder_selector() {
93 return '.e-n-accordion-item';
94 }
95
96 protected function get_html_wrapper_class() {
97 return 'elementor-widget-n-accordion';
98 }
99
100 protected function register_controls() {
101 if ( null === $this->optimized_markup ) {
102 $this->optimized_markup = Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ) && ! $this->has_widget_inner_wrapper();
103 $this->widget_container_selector = $this->optimized_markup ? '' : ' > .elementor-widget-container';
104 }
105
106 $this->start_controls_section( 'section_items', [
107 'label' => esc_html__( 'Layout', 'elementor' ),
108 ] );
109
110 $repeater = new Repeater();
111
112 $repeater->add_control(
113 'item_title',
114 [
115 'label' => esc_html__( 'Title', 'elementor' ),
116 'type' => Controls_Manager::TEXT,
117 'default' => esc_html__( 'Item Title', 'elementor' ),
118 'placeholder' => esc_html__( 'Item Title', 'elementor' ),
119 'label_block' => true,
120 'dynamic' => [
121 'active' => true,
122 ],
123 ]
124 );
125
126 $repeater->add_control(
127 'element_css_id',
128 [
129 'label' => esc_html__( 'CSS ID', 'elementor' ),
130 'type' => Controls_Manager::TEXT,
131 'default' => '',
132 'dynamic' => [
133 'active' => true,
134 ],
135 'ai' => [
136 'active' => false,
137 ],
138 'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor' ),
139 'style_transfer' => false,
140 ]
141 );
142
143 $this->add_control(
144 'items',
145 [
146 'label' => esc_html__( 'Items', 'elementor' ),
147 'type' => Control_Nested_Repeater::CONTROL_TYPE,
148 'fields' => $repeater->get_controls(),
149 'default' => [
150 [
151 'item_title' => esc_html__( 'Item #1', 'elementor' ),
152 ],
153 [
154 'item_title' => esc_html__( 'Item #2', 'elementor' ),
155 ],
156 [
157 'item_title' => esc_html__( 'Item #3', 'elementor' ),
158 ],
159 ],
160 'title_field' => '{{{ item_title }}}',
161 'button_text' => esc_html__( 'Add Item', 'elementor' ),
162 ]
163 );
164
165 $this->add_responsive_control(
166 'accordion_item_title_position_horizontal',
167 [
168 'label' => esc_html__( 'Item Position', 'elementor' ),
169 'type' => Controls_Manager::CHOOSE,
170 'separator' => 'before',
171 'options' => [
172 'start' => [
173 'title' => esc_html__( 'Start', 'elementor' ),
174 'icon' => 'eicon-flex eicon-align-start-h',
175 ],
176 'center' => [
177 'title' => esc_html__( 'Center', 'elementor' ),
178 'icon' => 'eicon-h-align-center',
179 ],
180 'end' => [
181 'title' => esc_html__( 'End', 'elementor' ),
182 'icon' => 'eicon-flex eicon-align-end-h',
183 ],
184 'stretch' => [
185 'title' => esc_html__( 'Stretch', 'elementor' ),
186 'icon' => 'eicon-h-align-stretch',
187 ],
188 ],
189 'selectors_dictionary' => [
190 'start' => '--n-accordion-title-justify-content: initial; --n-accordion-title-flex-grow: initial;',
191 'center' => '--n-accordion-title-justify-content: center; --n-accordion-title-flex-grow: initial;',
192 'end' => '--n-accordion-title-justify-content: flex-end; --n-accordion-title-flex-grow: initial;',
193 'stretch' => '--n-accordion-title-justify-content: space-between; --n-accordion-title-flex-grow: 1;',
194 ],
195 'selectors' => [
196 '{{WRAPPER}}' => '{{VALUE}}',
197 ],
198 ]
199 );
200
201 $this->add_control(
202 'heading_accordion_item_title_icon',
203 [
204 'type' => Controls_Manager::HEADING,
205 'label' => esc_html__( 'Icon', 'elementor' ),
206 'separator' => 'before',
207 ]
208 );
209
210 $this->add_responsive_control(
211 'accordion_item_title_icon_position',
212 [
213 'label' => esc_html__( 'Position', 'elementor' ),
214 'type' => Controls_Manager::CHOOSE,
215 'options' => [
216 'start' => [
217 'title' => esc_html__( 'Start', 'elementor' ),
218 'icon' => 'eicon-h-align-left',
219 ],
220 'end' => [
221 'title' => esc_html__( 'End', 'elementor' ),
222 'icon' => 'eicon-h-align-right',
223 ],
224 ],
225 'selectors_dictionary' => [
226 'start' => '--n-accordion-title-icon-order: -1;',
227 'end' => '--n-accordion-title-icon-order: initial;',
228 ],
229 'selectors' => [
230 '{{WRAPPER}}' => '{{VALUE}}',
231 ],
232 ]
233 );
234
235 $this->add_control(
236 'accordion_item_title_icon',
237 [
238 'label' => esc_html__( 'Expand', 'elementor' ),
239 'type' => Controls_Manager::ICONS,
240 'default' => [
241 'value' => 'fas fa-plus',
242 'library' => 'fa-solid',
243 ],
244 'skin' => 'inline',
245 'label_block' => false,
246 ]
247 );
248
249 $this->add_control(
250 'accordion_item_title_icon_active',
251 [
252 'label' => esc_html__( 'Collapse', 'elementor' ),
253 'type' => Controls_Manager::ICONS,
254 'fa4compatibility' => 'icon_active',
255 'default' => [
256 'value' => 'fas fa-minus',
257 'library' => 'fa-solid',
258 ],
259 'condition' => [
260 'accordion_item_title_icon[value]!' => '',
261 ],
262 'skin' => 'inline',
263 'label_block' => false,
264 ]
265 );
266
267 $this->add_control(
268 'title_tag',
269 [
270 'label' => esc_html__( 'Title HTML Tag', 'elementor' ),
271 'type' => Controls_Manager::SELECT,
272 'options' => [
273 'h1' => 'H1',
274 'h2' => 'H2',
275 'h3' => 'H3',
276 'h4' => 'H4',
277 'h5' => 'H5',
278 'h6' => 'H6',
279 'div' => 'div',
280 'span' => 'span',
281 'p' => 'p',
282 ],
283 'selectors_dictionary' => [
284 'h1' => '--n-accordion-title-font-size: 2.5rem;',
285 'h2' => '--n-accordion-title-font-size: 2rem;',
286 'h3' => '--n-accordion-title-font-size: 1,75rem;',
287 'h4' => '--n-accordion-title-font-size: 1.5rem;',
288 'h5' => '--n-accordion-title-font-size: 1rem;',
289 'h6' => '--n-accordion-title-font-size: 1rem; ',
290 'div' => '--n-accordion-title-font-size: 1rem;',
291 'span' => '--n-accordion-title-font-size: 1rem; ',
292 'p' => '--n-accordion-title-font-size: 1rem;',
293 ],
294 'selectors' => [
295 '{{WRAPPER}}' => '{{VALUE}}',
296 ],
297 'default' => 'div',
298 'separator' => 'before',
299 'render_type' => 'template',
300
301 ]
302 );
303
304 $this->add_control(
305 'faq_schema',
306 [
307 'label' => esc_html__( 'FAQ Schema', 'elementor' ),
308 'type' => Controls_Manager::SWITCHER,
309 'label_on' => esc_html__( 'Yes', 'elementor' ),
310 'label_off' => esc_html__( 'No', 'elementor' ),
311 'default' => 'no',
312 ]
313 );
314
315 $this->add_control(
316 'faq_schema_message',
317 [
318 'type' => Controls_Manager::ALERT,
319 'alert_type' => 'info',
320 'content' => esc_html__( 'Let Google know that this section contains an FAQ. Make sure to only use it only once per page', 'elementor' ),
321 'condition' => [
322 'faq_schema[value]' => 'yes',
323 ],
324 ]
325 );
326
327 $this->end_controls_section();
328
329 $this->start_controls_section(
330 'section_interactions',
331 [
332 'label' => esc_html__( 'Interactions', 'elementor' ),
333 ]
334 );
335
336 $this->add_control(
337 'default_state',
338 [
339 'label' => esc_html__( 'Default State', 'elementor' ),
340 'type' => Controls_Manager::SELECT,
341 'options' => [
342 'expanded' => esc_html__( 'First expanded', 'elementor' ),
343 'all_collapsed' => esc_html__( 'All collapsed', 'elementor' ),
344 ],
345 'default' => 'expanded',
346 'frontend_available' => true,
347 ]
348 );
349
350 $this->add_control(
351 'max_items_expended',
352 [
353 'label' => esc_html__( 'Max Items Expanded', 'elementor' ),
354 'type' => Controls_Manager::SELECT,
355 'options' => [
356 'one' => esc_html__( 'One', 'elementor' ),
357 'multiple' => esc_html__( 'Multiple', 'elementor' ),
358 ],
359 'default' => 'one',
360 'frontend_available' => true,
361 ]
362 );
363
364 $this->add_control(
365 'n_accordion_animation_duration',
366 [
367 'label' => esc_html__( 'Animation Duration', 'elementor' ),
368 'type' => Controls_Manager::SLIDER,
369 'size_units' => [ 's', 'ms' ],
370 'default' => [
371 'unit' => 'ms',
372 'size' => 400,
373 ],
374 'frontend_available' => true,
375 ]
376 );
377
378 $this->end_controls_section();
379
380 $this->add_style_tab();
381 }
382
383 private function add_style_tab() {
384 $this->add_accordion_style_section();
385 $this->add_header_style_section();
386 $this->add_content_style_section();
387 }
388
389 private function add_accordion_style_section() {
390 $this->start_controls_section(
391 'section_accordion_style',
392 [
393 'label' => esc_html__( 'Accordion', 'elementor' ),
394 'tab' => Controls_Manager::TAB_STYLE,
395 ]
396 );
397
398 $this->add_responsive_control(
399 'accordion_item_title_space_between',
400 [
401 'label' => esc_html__( 'Space between Items', 'elementor' ),
402 'type' => Controls_Manager::SLIDER,
403 'size_units' => [ 'px', 'em', 'rem', 'custom' ],
404 'range' => [
405 'px' => [
406 'max' => 200,
407 ],
408 'em' => [
409 'max' => 20,
410 ],
411 'rem' => [
412 'max' => 20,
413 ],
414 ],
415 'default' => [
416 'size' => 0,
417 ],
418 'selectors' => [
419 '{{WRAPPER}}' => '--n-accordion-item-title-space-between: {{SIZE}}{{UNIT}}',
420 ],
421 ]
422 );
423
424 $this->add_responsive_control(
425 'accordion_item_title_distance_from_content',
426 [
427 'label' => esc_html__( 'Distance from content', 'elementor' ),
428 'type' => Controls_Manager::SLIDER,
429 'size_units' => [ 'px', 'em', 'rem', 'custom' ],
430 'range' => [
431 'px' => [
432 'max' => 200,
433 ],
434 'em' => [
435 'max' => 20,
436 ],
437 'rem' => [
438 'max' => 20,
439 ],
440 ],
441 'default' => [
442 'size' => 0,
443 ],
444 'selectors' => [
445 '{{WRAPPER}}' => '--n-accordion-item-title-distance-from-content: {{SIZE}}{{UNIT}}',
446 ],
447 ]
448 );
449
450 $this->start_controls_tabs( 'accordion_border_and_background' );
451
452 foreach ( [ 'normal', 'hover', 'active' ] as $state ) {
453 $this->add_border_and_radius_style( $state );
454 }
455
456 $this->end_controls_tabs();
457
458 $this->add_responsive_control(
459 'accordion_border_radius',
460 [
461 'label' => esc_html__( 'Border Radius', 'elementor' ),
462 'type' => Controls_Manager::DIMENSIONS,
463 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
464 'selectors' => [
465 '{{WRAPPER}}' => '--n-accordion-border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
466 ],
467 'separator' => 'before',
468 ]
469 );
470
471 $this->add_responsive_control(
472 'accordion_padding',
473 [
474 'label' => esc_html__( 'Padding', 'elementor' ),
475 'type' => Controls_Manager::DIMENSIONS,
476 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
477 'selectors' => [
478 '{{WRAPPER}} ' => '--n-accordion-padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
479 ],
480 ]
481 );
482
483 $this->end_controls_section();
484 }
485
486 private function add_content_style_section() {
487 $low_specificity_accordion_item_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item ) > .e-con";
488
489 $this->start_controls_section(
490 'section_content_style',
491 [
492 'label' => esc_html__( 'Content', 'elementor' ),
493 'tab' => Controls_Manager::TAB_STYLE,
494 ]
495 );
496
497 $this->add_group_control(
498 Group_Control_Background::get_type(),
499 [
500 'name' => 'content_background',
501 'types' => [ 'classic', 'gradient' ],
502 'exclude' => [ 'image' ],
503 'selector' => $low_specificity_accordion_item_selector,
504 ]
505 );
506
507 $this->add_group_control(
508 Group_Control_Border::get_type(),
509 [
510 'name' => 'content_border',
511 'selector' => $low_specificity_accordion_item_selector,
512 'fields_options' => [
513 'color' => [
514 'label' => esc_html__( 'Border Color', 'elementor' ),
515 ],
516 'width' => [
517 'label' => esc_html__( 'Border Width', 'elementor' ),
518 ],
519 ],
520 ]
521 );
522
523 $this->add_responsive_control(
524 'content_border_radius',
525 [
526 'label' => esc_html__( 'Border Radius', 'elementor' ),
527 'type' => Controls_Manager::DIMENSIONS,
528 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
529 'selectors' => [
530 $low_specificity_accordion_item_selector => '--border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
531 ],
532 ]
533 );
534
535 $this->add_responsive_control(
536 'content_padding',
537 [
538 'label' => esc_html__( 'Padding', 'elementor' ),
539 'type' => Controls_Manager::DIMENSIONS,
540 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
541 'selectors' => [
542 $low_specificity_accordion_item_selector => '--padding-top: {{TOP}}{{UNIT}}; --padding-right: {{RIGHT}}{{UNIT}}; --padding-bottom: {{BOTTOM}}{{UNIT}}; --padding-left: {{LEFT}}{{UNIT}};',
543 ],
544 ]
545 );
546
547 $this->end_controls_section();
548 }
549
550 private function add_header_style_section() {
551 $this->start_controls_section(
552 'section_header_style',
553 [
554 'label' => esc_html__( 'Header', 'elementor' ),
555 'tab' => Controls_Manager::TAB_STYLE,
556 ]
557 );
558
559 $this->add_control(
560 'heading_header_style_title',
561 [
562 'type' => Controls_Manager::HEADING,
563 'label' => esc_html__( 'Title', 'elementor' ),
564
565 ]
566 );
567
568 $this->add_group_control(
569 Group_Control_Typography::get_type(),
570 [
571 'name' => 'title_typography',
572 'selector' => ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item > .e-n-accordion-item-title > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text",
573 'fields_options' => [
574 'font_size' => [
575 'selectors' => [
576 '{{WRAPPER}}' => '--n-accordion-title-font-size: {{SIZE}}{{UNIT}}',
577 ],
578 ],
579 ],
580 ]
581 );
582
583 $this->start_controls_tabs( 'header_title_color_style' );
584
585 foreach ( [ 'normal', 'hover', 'active' ] as $state ) {
586 $this->add_header_style( $state, 'title' );
587 }
588
589 $this->end_controls_tabs();
590
591 $this->add_control(
592 'heading_icon_style_title',
593 [
594 'type' => Controls_Manager::HEADING,
595 'label' => esc_html__( 'Icon', 'elementor' ),
596 'separator' => 'before',
597 ]
598 );
599
600 $this->add_responsive_control(
601 'icon_size',
602 [
603 'label' => esc_html__( 'Size', 'elementor' ),
604 'type' => Controls_Manager::SLIDER,
605 'range' => [
606 'em' => [
607 'max' => 10,
608 ],
609 'rem' => [
610 'max' => 10,
611 ],
612 ],
613 'default' => [
614 'unit' => 'px',
615 'size' => 15,
616 ],
617 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
618 'selectors' => [
619 '{{WRAPPER}}' => '--n-accordion-icon-size: {{SIZE}}{{UNIT}}',
620 ],
621 ]
622 );
623
624 $this->add_responsive_control(
625 'icon_spacing',
626 [
627 'label' => esc_html__( 'Spacing', 'elementor' ),
628 'type' => Controls_Manager::SLIDER,
629 'range' => [
630 'px' => [
631 'max' => 400,
632 ],
633 'vw' => [
634 'max' => 50,
635 'step' => 0.1,
636 ],
637 ],
638 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
639 'selectors' => [
640 '{{WRAPPER}}' => '--n-accordion-icon-gap: {{SIZE}}{{UNIT}}',
641 ],
642 'condition' => [
643 'accordion_item_title_position_horizontal!' => 'stretch',
644 ],
645 ]
646 );
647
648 $this->start_controls_tabs( 'header_icon_color_style' );
649
650 foreach ( [ 'normal', 'hover', 'active' ] as $state ) {
651 $this->add_header_style( $state, 'icon' );
652 }
653
654 $this->end_controls_tabs();
655 $this->end_controls_section();
656 }
657
658 private function add_header_style( $state, $context ) {
659 $variable = '--n-accordion-' . $context . '-' . $state . '-color';
660
661 switch ( $state ) {
662 case 'hover':
663 $translated_tab_text = esc_html__( 'Hover', 'elementor' );
664 $translated_tab_css_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item:not([open]) > .e-n-accordion-item-title:hover > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text";
665 break;
666 case 'active':
667 $translated_tab_text = esc_html__( 'Active', 'elementor' );
668 $translated_tab_css_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item[open] > .e-n-accordion-item-title > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text";
669 break;
670 default:
671 $translated_tab_text = esc_html__( 'Normal', 'elementor' );
672 $translated_tab_css_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item:not([open]) > .e-n-accordion-item-title:not(hover) > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text";
673 break;
674 }
675
676 $this->start_controls_tab(
677 'header_' . $state . '_' . $context,
678 [
679 'label' => $translated_tab_text,
680 ]
681 );
682
683 $this->add_control(
684 $state . '_' . $context . '_color',
685 [
686 'label' => esc_html__( 'Color', 'elementor' ),
687 'type' => Controls_Manager::COLOR,
688 'selectors' => [
689 '{{WRAPPER}}' => $variable . ': {{VALUE}};',
690 ],
691 ]
692 );
693
694 if ( 'title' === $context ) {
695 $this->add_group_control(
696 Group_Control_Text_Shadow::get_type(),
697 [
698 'name' => $context . '_' . $state . '_text_shadow',
699 'selector' => '{{WRAPPER}} ' . $translated_tab_css_selector,
700 ]
701 );
702
703 $this->add_group_control(
704 Group_Control_Text_Stroke::get_type(),
705 [
706 'name' => $context . '_' . $state . '_stroke',
707 'selector' => '{{WRAPPER}} ' . $translated_tab_css_selector,
708 ]
709 );
710 }
711
712 $this->end_controls_tab();
713 }
714
715 /**
716 * @string $state
717 */
718 private function add_border_and_radius_style( $state ) {
719 $selector = "{{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item > .e-n-accordion-item-title";
720
721 $translated_tab_text = esc_html__( 'Normal', 'elementor' );
722
723 switch ( $state ) {
724 case 'hover':
725 $selector .= ':hover';
726 $translated_tab_text = esc_html__( 'Hover', 'elementor' );
727 break;
728 case 'active':
729 $selector = "{{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item[open] > .e-n-accordion-item-title";
730 $translated_tab_text = esc_html__( 'Active', 'elementor' );
731 break;
732 }
733
734 $this->start_controls_tab(
735 'accordion_' . $state . '_border_and_background',
736 [
737 'label' => $translated_tab_text,
738 ]
739 );
740
741 $this->add_group_control(
742 Group_Control_Background::get_type(),
743 [
744 'name' => 'accordion_background_' . $state,
745 'types' => [ 'classic', 'gradient' ],
746 'exclude' => [ 'image' ],
747 'fields_options' => [
748 'color' => [
749 'label' => esc_html__( 'Color', 'elementor' ),
750 ],
751 ],
752 'selector' => $selector,
753 ]
754 );
755
756 $this->add_group_control(
757 Group_Control_Border::get_type(),
758 [
759 'name' => 'accordion_border_' . $state,
760 'selector' => $selector,
761 ]
762 );
763
764 $this->end_controls_tab();
765 }
766
767 private function is_active_icon_exist( $settings ): bool {
768 return array_key_exists( 'accordion_item_title_icon_active', $settings ) && ! empty( $settings['accordion_item_title_icon_active'] ) && ! empty( $settings['accordion_item_title_icon_active']['value'] );
769 }
770
771 private function render_accordion_icons( $settings ) {
772 $icon_html = Icons_Manager::try_get_icon_html( $settings['accordion_item_title_icon'], [ 'aria-hidden' => 'true' ] );
773 $icon_active_html = $this->is_active_icon_exist( $settings )
774 ? Icons_Manager::try_get_icon_html( $settings['accordion_item_title_icon_active'], [ 'aria-hidden' => 'true' ] )
775 : $icon_html;
776
777 ob_start();
778 ?>
779 <span class='e-n-accordion-item-title-icon'>
780 <span class='e-opened' ><?php echo $icon_active_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
781 <span class='e-closed'><?php echo $icon_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
782 </span>
783
784 <?php
785 return ob_get_clean();
786 }
787
788 protected function render() {
789 $settings = $this->get_settings_for_display();
790 $items = $settings['items'];
791 $id_int = substr( $this->get_id_int(), 0, 3 );
792 $items_title_html = '';
793 $icons_content = $this->render_accordion_icons( $settings );
794 $this->add_render_attribute( 'elementor-accordion', 'class', 'e-n-accordion' );
795 $this->add_render_attribute( 'elementor-accordion', 'aria-label', 'Accordion. Open links with Enter or Space, close with Escape, and navigate with Arrow Keys' );
796 $default_state = $settings['default_state'];
797 $title_html_tag = Utils::validate_html_tag( $settings['title_tag'] );
798
799 $faq_schema = [];
800
801 foreach ( $items as $index => $item ) {
802 $accordion_count = $index + 1;
803 $item_setting_key = $this->get_repeater_setting_key( 'item_title', 'items', $index );
804 $item_summary_key = $this->get_repeater_setting_key( 'item_summary', 'items', $index );
805 $item_classes = [ 'e-n-accordion-item' ];
806 $item_id = empty( $item['element_css_id'] ) ? 'e-n-accordion-item-' . $id_int . $index : $item['element_css_id'];
807 $item_title = $item['item_title'];
808 $is_open = 'expanded' === $default_state && 0 === $index ? 'open' : '';
809 $aria_expanded = 'expanded' === $default_state && 0 === $index;
810
811 $this->add_render_attribute( $item_setting_key, [
812 'id' => $item_id,
813 'class' => $item_classes,
814 ] );
815
816 $this->add_render_attribute( $item_summary_key, [
817 'class' => [ 'e-n-accordion-item-title' ],
818 'data-accordion-index' => $accordion_count,
819 'tabindex' => 0 === $index ? 0 : -1,
820 'aria-expanded' => $aria_expanded ? 'true' : 'false',
821 'aria-controls' => $item_id,
822 ] );
823
824 $title_render_attributes = $this->get_render_attribute_string( $item_setting_key );
825 $title_render_attributes = $title_render_attributes . ' ' . $is_open;
826
827 $summary_render_attributes = $this->get_render_attribute_string( $item_summary_key );
828
829 // items content.
830 ob_start();
831 $this->print_child( $index, $item_id );
832 $item_content = ob_get_clean();
833
834 $faq_schema[ $item_title ] = $item_content;
835
836 ob_start();
837 ?>
838 <details <?php echo wp_kses_post( $title_render_attributes ); ?>>
839 <summary <?php echo wp_kses_post( $summary_render_attributes ); ?> >
840 <span class='e-n-accordion-item-title-header'><?php echo wp_kses_post( "<$title_html_tag class=\"e-n-accordion-item-title-text\"> $item_title </$title_html_tag>" ); ?></span>
841 <?php if ( ! empty( $settings['accordion_item_title_icon']['value'] ) ) {
842 echo $icons_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
843 } ?>
844 </summary>
845 <?php echo $item_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
846 </details>
847 <?php
848 $items_title_html .= ob_get_clean();
849 }
850
851 ?>
852 <div <?php $this->print_render_attribute_string( 'elementor-accordion' ); ?>>
853 <?php echo $items_title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
854 </div>
855 <?php
856 if ( isset( $settings['faq_schema'] ) && 'yes' === $settings['faq_schema'] ) {
857 $json = [
858 '@context' => 'https://schema.org',
859 '@type' => 'FAQPage',
860 'mainEntity' => [],
861 ];
862
863 foreach ( $faq_schema as $name => $text ) {
864 $json['mainEntity'][] = [
865 '@type' => 'Question',
866 'name' => wp_strip_all_tags( $name ),
867 'acceptedAnswer' => [
868 '@type' => 'Answer',
869 'text' => wp_strip_all_tags( $text ),
870 ],
871 ];
872 }
873 ?>
874 <script type="application/ld+json"><?php echo wp_json_encode( $json ); ?></script>
875 <?php
876 }
877 }
878
879 public function print_child( $index, $item_id = null ) {
880 $children = $this->get_children();
881
882 if ( ! empty( $children[ $index ] ) ) {
883 // Add data-tab-index attribute to the content area.
884 $add_attribute_to_container = function ( $should_render, $container ) use ( $item_id ) {
885 $this->add_attributes_to_container( $container, $item_id );
886
887 return $should_render;
888 };
889
890 add_filter( 'elementor/frontend/container/should_render', $add_attribute_to_container, 10, 3 );
891 $children[ $index ]->print_element();
892 remove_filter( 'elementor/frontend/container/should_render', $add_attribute_to_container );
893 }
894 }
895
896 protected function add_attributes_to_container( $container, $item_id ) {
897 $container->add_render_attribute( '_wrapper', [
898 'role' => 'region',
899 'aria-labelledby' => $item_id,
900 ] );
901 }
902
903 protected function get_initial_config(): array {
904 return array_merge( parent::get_initial_config(), [
905 'support_improved_repeaters' => true,
906 'target_container' => [ '.e-n-accordion' ],
907 'node' => 'details',
908 'is_interlaced' => true,
909 ] );
910 }
911
912 protected function content_template_single_repeater_item() {
913 ?>
914 <#
915 const elementUid = view.getIDInt().toString().substring( 0, 3 ) + view.collection.length;
916
917 const itemWrapperAttributes = {
918 'id': 'e-n-accordion-item-' + elementUid,
919 'class': [ 'e-n-accordion-item', 'e-normal' ],
920 };
921
922 const itemTitleAttributes = {
923 'class': [ 'e-n-accordion-item-title' ],
924 'data-accordion-index': view.collection.length + 1,
925 'tabindex': -1,
926 'aria-expanded': 'false',
927 'aria-controls': 'e-n-accordion-item-' + elementUid,
928 };
929
930 const itemTitleTextAttributes = {
931 'class': [ 'e-n-accordion-item-title-text' ],
932 'data-binding-type': 'repeater-item',
933 'data-binding-repeater-name': 'items',
934 'data-binding-setting': ['item_title'],
935 'data-binding-index': view.collection.length + 1,
936 'data-binding-dynamic': 'true',
937 };
938
939 view.addRenderAttribute( 'details-container', itemWrapperAttributes, null, true );
940 view.addRenderAttribute( 'summary-container', itemTitleAttributes, null, true );
941 view.addRenderAttribute( 'text-container', itemTitleTextAttributes, null, true );
942 #>
943
944 <details {{{ view.getRenderAttributeString( 'details-container' ) }}}>
945 <summary {{{ view.getRenderAttributeString( 'summary-container' ) }}}>
946 <span class="e-n-accordion-item-title-header">
947 <div {{{ view.getRenderAttributeString( 'text-container' ) }}}>{{{ data.item_title }}}</div>
948 </span>
949 <span class="e-n-accordion-item-title-icon">
950 <span class="e-opened"><i aria-hidden="true" class="fas fa-minus"></i></span>
951 <span class="e-closed"><i aria-hidden="true" class="fas fa-plus"></i></span>
952 </span>
953 </summary>
954 </details>
955 <?php
956 }
957
958 protected function content_template() {
959 ?>
960 <div class="e-n-accordion" aria-label="Accordion. Open links with Enter or Space, close with Escape, and navigate with Arrow Keys">
961 <# if ( settings['items'] ) {
962 const elementUid = view.getIDInt().toString().substring( 0, 3 ),
963 titleHTMLTag = elementor.helpers.validateHTMLTag( settings.title_tag ),
964 defaultState = settings.default_state,
965 itemTitleIcon = elementor.helpers.renderIcon( view, settings['accordion_item_title_icon'], { 'aria-hidden': true }, 'i', 'object' ) ?? '',
966 itemTitleIconActive = '' === settings.accordion_item_title_icon_active.value
967 ? itemTitleIcon
968 : elementor.helpers.renderIcon( view, settings['accordion_item_title_icon_active'], { 'aria-hidden': true }, 'i', 'object' );
969 #>
970
971 <# _.each( settings['items'], function( item, index ) {
972 const itemCount = index + 1,
973 itemUid = elementUid + index,
974 itemTitleTextKey = 'item-title-text-' + itemUid,
975 itemWrapperKey = itemUid,
976 itemTitleKey = 'item-' + itemUid,
977 ariaExpanded = 'expanded' === defaultState && 0 === index ? 'true' : 'false';
978
979 if ( '' !== item.element_css_id ) {
980 itemId = item.element_css_id;
981 } else {
982 itemId = 'e-n-accordion-item-' + itemUid;
983 }
984
985 const itemWrapperAttributes = {
986 'id': itemId,
987 'class': [ 'e-n-accordion-item', 'e-normal' ],
988 };
989
990 if ( defaultState === 'expanded' && index === 0) {
991 itemWrapperAttributes['open'] = true;
992 }
993
994 view.addRenderAttribute( itemWrapperKey, itemWrapperAttributes );
995
996 view.addRenderAttribute( itemTitleKey, {
997 'class': ['e-n-accordion-item-title'],
998 'data-accordion-index': itemCount,
999 'tabindex': 0 === index ? 0 : -1,
1000 'aria-expanded': ariaExpanded,
1001 'aria-controls': itemId,
1002 });
1003
1004 view.addRenderAttribute( itemTitleTextKey, {
1005 'class': ['e-n-accordion-item-title-text'],
1006 'data-binding-type': 'repeater-item',
1007 'data-binding-repeater-name': 'items',
1008 'data-binding-setting': ['item_title'],
1009 'data-binding-index': itemCount,
1010 'data-binding-dynamic': 'true',
1011 'data-binding-dynamic-css-id': 'element_css_id',
1012 'data-binding-single-item-html-wrapper-tag': 'details',
1013 });
1014 #>
1015
1016 <details {{{ view.getRenderAttributeString( itemWrapperKey ) }}}>
1017 <summary {{{ view.getRenderAttributeString( itemTitleKey ) }}}>
1018 <span class="e-n-accordion-item-title-header">
1019 <{{{ titleHTMLTag }}} {{{ view.getRenderAttributeString( itemTitleTextKey ) }}}>
1020 {{{ item.item_title }}}
1021 </{{{ titleHTMLTag }}}>
1022 </span>
1023 <# if (settings.accordion_item_title_icon.value) { #>
1024 <span class="e-n-accordion-item-title-icon">
1025 <span class="e-opened">{{{ itemTitleIconActive.value }}}</span>
1026 <span class="e-closed">{{{ itemTitleIcon.value }}}</span>
1027 </span>
1028 <# } #>
1029 </summary>
1030 </details>
1031 <# } ); #>
1032 <# } #>
1033 </div>
1034 <?php
1035 }
1036 }
1037