PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.0-dev1
Elementor Website Builder – more than just a page builder v3.35.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.35.0-dev1, at modules/nested-accordion/widgets/nested-accordion.php

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