PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Interactive_Steps_Progress / Interactive_Steps_Progress.php

Interactive_Steps_Progress.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.49, at includes/widgets/Interactive_Steps_Progress/Interactive_Steps_Progress.php

1,703 lines 60.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Interactive Steps with Progress Widget.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Background;
12 use Elementor\Group_Control_Border;
13 use Elementor\Group_Control_Box_Shadow;
14 use Elementor\Group_Control_Typography;
15 use Elementor\Icons_Manager;
16 use Elementor\Plugin;
17 use Elementor\Repeater;
18 use Elementor\Widget_Base;
19
20 if (!defined('ABSPATH')) {
21 exit; // Exit if accessed directly.
22 }
23
24 class Interactive_Steps_Progress extends Widget_Base
25 {
26 public function get_name(): string
27 {
28 return 'king-addons-interactive-steps-progress';
29 }
30
31 public function get_title(): string
32 {
33 return esc_html__('Interactive Steps with Progress', 'king-addons');
34 }
35
36 public function get_icon(): string
37 {
38 return 'eicon-steps';
39 }
40
41 public function get_categories(): array
42 {
43 return ['king-addons'];
44 }
45
46 public function get_keywords(): array
47 {
48 return ['steps', 'progress', 'how it works', 'process', 'accordion', 'king-addons'];
49 }
50
51 public function get_style_depends(): array
52 {
53 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-interactive-steps-progress-style'];
54 }
55
56 public function get_script_depends(): array
57 {
58 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-interactive-steps-progress-script'];
59 }
60
61 protected function register_controls(): void
62 {
63 $this->register_layout_controls();
64 $this->register_steps_controls();
65 $this->register_behavior_controls();
66 $this->register_progress_controls();
67 $this->register_style_container_controls();
68 $this->register_style_step_controls();
69 $this->register_style_marker_controls();
70 $this->register_style_typography_controls();
71 $this->register_style_cta_controls();
72 $this->register_pro_notice_controls();
73 }
74
75 protected function register_layout_controls(): void
76 {
77 $this->start_controls_section(
78 'kng_isp_layout_section',
79 [
80 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
81 'tab' => Controls_Manager::TAB_CONTENT,
82 ]
83 );
84
85 $this->add_control(
86 'kng_steps_orientation',
87 [
88 'label' => esc_html__('Orientation', 'king-addons'),
89 'type' => Controls_Manager::SELECT,
90 'default' => 'vertical',
91 'options' => [
92 'vertical' => esc_html__('Vertical', 'king-addons'),
93 'horizontal' => esc_html__('Horizontal (Pro)', 'king-addons'),
94 ],
95 ]
96 );
97
98 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'interactive-steps-progress', 'kng_steps_orientation', ['horizontal']);
99
100 $this->add_control(
101 'kng_steps_alignment',
102 [
103 'label' => esc_html__('Content Alignment', 'king-addons'),
104 'type' => Controls_Manager::CHOOSE,
105 'default' => 'left',
106 'options' => [
107 'left' => [
108 'title' => esc_html__('Left', 'king-addons'),
109 'icon' => 'eicon-text-align-left',
110 ],
111 'center' => [
112 'title' => esc_html__('Center', 'king-addons'),
113 'icon' => 'eicon-text-align-center',
114 ],
115 ],
116 ]
117 );
118
119 $this->add_responsive_control(
120 'kng_steps_gap',
121 [
122 'label' => esc_html__('Step Gap', 'king-addons'),
123 'type' => Controls_Manager::SLIDER,
124 'size_units' => ['px', 'em', 'rem'],
125 'range' => [
126 'px' => ['min' => 0, 'max' => 80],
127 'em' => ['min' => 0, 'max' => 6],
128 'rem' => ['min' => 0, 'max' => 6],
129 ],
130 'selectors' => [
131 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-gap: {{SIZE}}{{UNIT}};',
132 ],
133 ]
134 );
135
136 $this->add_responsive_control(
137 'kng_steps_content_padding',
138 [
139 'label' => esc_html__('Content Padding', 'king-addons'),
140 'type' => Controls_Manager::DIMENSIONS,
141 'size_units' => ['px', 'em', 'rem'],
142 'selectors' => [
143 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-content-padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
144 ],
145 ]
146 );
147
148 $this->add_control(
149 'kng_steps_connector',
150 [
151 'label' => esc_html__('Connector Line', 'king-addons'),
152 'type' => Controls_Manager::SELECT,
153 'default' => 'show',
154 'options' => [
155 'show' => esc_html__('Show', 'king-addons'),
156 'hide' => esc_html__('Hide', 'king-addons'),
157 ],
158 ]
159 );
160
161 $this->add_control(
162 'kng_steps_line_thickness',
163 [
164 'label' => esc_html__('Line Thickness', 'king-addons'),
165 'type' => Controls_Manager::SLIDER,
166 'size_units' => ['px'],
167 'range' => [
168 'px' => ['min' => 1, 'max' => 10],
169 ],
170 'selectors' => [
171 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-line-thickness: {{SIZE}}{{UNIT}};',
172 ],
173 ]
174 );
175
176 $this->add_control(
177 'kng_steps_line_offset',
178 [
179 'label' => esc_html__('Line Offset', 'king-addons'),
180 'type' => Controls_Manager::SLIDER,
181 'size_units' => ['px'],
182 'range' => [
183 'px' => ['min' => 0, 'max' => 40],
184 ],
185 'selectors' => [
186 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-line-offset: {{SIZE}}{{UNIT}};',
187 ],
188 ]
189 );
190
191 $this->end_controls_section();
192 }
193
194 protected function register_steps_controls(): void
195 {
196 $this->start_controls_section(
197 'kng_isp_steps_section',
198 [
199 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Steps', 'king-addons'),
200 'tab' => Controls_Manager::TAB_CONTENT,
201 ]
202 );
203
204 $this->add_control(
205 'kng_steps_numbering_mode',
206 [
207 'label' => esc_html__('Step Numbering', 'king-addons'),
208 'type' => Controls_Manager::SELECT,
209 'default' => 'auto',
210 'options' => [
211 'auto' => esc_html__('Auto', 'king-addons'),
212 'manual' => esc_html__('Manual', 'king-addons'),
213 ],
214 ]
215 );
216
217 $repeater = new Repeater();
218
219 $repeater->add_control(
220 'step_title',
221 [
222 'label' => esc_html__('Title', 'king-addons'),
223 'type' => Controls_Manager::TEXT,
224 'default' => esc_html__('Step Title', 'king-addons'),
225 'label_block' => true,
226 ]
227 );
228
229 $repeater->add_control(
230 'step_description',
231 [
232 'label' => esc_html__('Short Description', 'king-addons'),
233 'type' => Controls_Manager::TEXTAREA,
234 'rows' => 3,
235 'default' => esc_html__('Explain the step in a single, clear sentence.', 'king-addons'),
236 ]
237 );
238
239 $repeater->add_control(
240 'step_details',
241 [
242 'label' => esc_html__('Details', 'king-addons'),
243 'type' => Controls_Manager::WYSIWYG,
244 'default' => esc_html__('Add the expanded details for this step here.', 'king-addons'),
245 ]
246 );
247
248 $repeater->add_control(
249 'step_points',
250 [
251 'label' => esc_html__('Details List (Optional)', 'king-addons'),
252 'type' => Controls_Manager::TEXTAREA,
253 'rows' => 4,
254 'default' => '',
255 'description' => esc_html__('Add one list item per line.', 'king-addons'),
256 ]
257 );
258
259 $repeater->add_control(
260 'step_icon',
261 [
262 'label' => esc_html__('Icon', 'king-addons'),
263 'type' => Controls_Manager::ICONS,
264 'skin' => 'inline',
265 'label_block' => true,
266 ]
267 );
268
269 $repeater->add_control(
270 'step_icon_position',
271 [
272 'label' => esc_html__('Icon Position', 'king-addons'),
273 'type' => Controls_Manager::SELECT,
274 'default' => 'left',
275 'options' => [
276 'left' => esc_html__('Left', 'king-addons'),
277 'top' => esc_html__('Top', 'king-addons'),
278 ],
279 ]
280 );
281
282 $repeater->add_control(
283 'step_number',
284 [
285 'label' => esc_html__('Custom Number', 'king-addons'),
286 'type' => Controls_Manager::TEXT,
287 'condition' => [
288 'kng_steps_numbering_mode' => 'manual',
289 ],
290 ]
291 );
292
293 $repeater->add_control(
294 'step_cta_text',
295 [
296 'label' => esc_html__('CTA Label', 'king-addons'),
297 'type' => Controls_Manager::TEXT,
298 'default' => '',
299 'separator' => 'before',
300 ]
301 );
302
303 $repeater->add_control(
304 'step_cta_link',
305 [
306 'label' => esc_html__('CTA Link', 'king-addons'),
307 'type' => Controls_Manager::URL,
308 'placeholder' => esc_html__('https://your-link.com', 'king-addons'),
309 ]
310 );
311
312 $repeater->add_control(
313 'step_anchor_id',
314 [
315 'label' => esc_html__('Anchor ID (Pro)', 'king-addons'),
316 'type' => Controls_Manager::TEXT,
317 'classes' => $this->get_pro_control_class(),
318 'description' => esc_html__('Use lowercase letters, numbers, dashes, or underscores only.', 'king-addons'),
319 ]
320 );
321
322 $this->add_control(
323 'kng_steps_list',
324 [
325 'label' => esc_html__('Steps', 'king-addons'),
326 'type' => Controls_Manager::REPEATER,
327 'fields' => $repeater->get_controls(),
328 'default' => $this->get_default_steps(),
329 'title_field' => '{{{ step_title }}}',
330 ]
331 );
332
333 $this->end_controls_section();
334 }
335
336 protected function register_behavior_controls(): void
337 {
338 $this->start_controls_section(
339 'kng_isp_behavior_section',
340 [
341 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Behavior', 'king-addons'),
342 'tab' => Controls_Manager::TAB_CONTENT,
343 ]
344 );
345
346 $this->add_control(
347 'kng_steps_expand_behavior',
348 [
349 'label' => esc_html__('Expand Behavior', 'king-addons'),
350 'type' => Controls_Manager::SELECT,
351 'default' => 'click',
352 'options' => [
353 'none' => esc_html__('Always Open', 'king-addons'),
354 'click' => esc_html__('Click to Expand', 'king-addons'),
355 ],
356 ]
357 );
358
359 $this->add_control(
360 'kng_steps_default_open',
361 [
362 'label' => esc_html__('Default Open', 'king-addons'),
363 'type' => Controls_Manager::SELECT,
364 'default' => 'first',
365 'options' => [
366 'first' => esc_html__('First Step', 'king-addons'),
367 'closed' => esc_html__('All Closed', 'king-addons'),
368 ],
369 'condition' => [
370 'kng_steps_expand_behavior' => 'click',
371 ],
372 ]
373 );
374
375 $this->add_control(
376 'kng_steps_single_open',
377 [
378 'label' => esc_html__('Single Open', 'king-addons'),
379 'type' => Controls_Manager::SWITCHER,
380 'return_value' => 'yes',
381 'default' => 'yes',
382 'condition' => [
383 'kng_steps_expand_behavior' => 'click',
384 ],
385 ]
386 );
387
388 $this->add_control(
389 'kng_steps_scroll_to_step',
390 [
391 'label' => esc_html__('Scroll to Step on Click', 'king-addons'),
392 'type' => Controls_Manager::SWITCHER,
393 'return_value' => 'yes',
394 'default' => '',
395 ]
396 );
397
398 $this->end_controls_section();
399 }
400
401 protected function register_progress_controls(): void
402 {
403 $this->start_controls_section(
404 'kng_isp_progress_section',
405 [
406 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Progress', 'king-addons'),
407 'tab' => Controls_Manager::TAB_CONTENT,
408 ]
409 );
410
411 $this->add_control(
412 'kng_steps_progress_style',
413 [
414 'label' => esc_html__('Progress Style', 'king-addons'),
415 'type' => Controls_Manager::SELECT,
416 'default' => 'dots',
417 'options' => [
418 'dots' => esc_html__('Dots + Line', 'king-addons'),
419 'numbers' => esc_html__('Numbered Badges', 'king-addons'),
420 ],
421 ]
422 );
423
424 $this->add_control(
425 'kng_steps_show_completed',
426 [
427 'label' => esc_html__('Show Completed State', 'king-addons'),
428 'type' => Controls_Manager::SELECT,
429 'default' => 'yes',
430 'options' => [
431 'yes' => esc_html__('Yes', 'king-addons'),
432 'no' => esc_html__('No', 'king-addons'),
433 ],
434 ]
435 );
436
437 $this->end_controls_section();
438 }
439
440 protected function register_style_container_controls(): void
441 {
442 $this->start_controls_section(
443 'kng_isp_style_container',
444 [
445 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Container', 'king-addons'),
446 'tab' => Controls_Manager::TAB_STYLE,
447 ]
448 );
449
450 $this->add_group_control(
451 Group_Control_Background::get_type(),
452 [
453 'name' => 'kng_isp_container_bg',
454 'selector' => '{{WRAPPER}} .king-addons-interactive-steps',
455 ]
456 );
457
458 $this->add_responsive_control(
459 'kng_isp_container_padding',
460 [
461 'label' => esc_html__('Padding', 'king-addons'),
462 'type' => Controls_Manager::DIMENSIONS,
463 'size_units' => ['px', 'em', 'rem'],
464 'selectors' => [
465 '{{WRAPPER}} .king-addons-interactive-steps' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
466 ],
467 ]
468 );
469
470 $this->add_responsive_control(
471 'kng_isp_container_radius',
472 [
473 'label' => esc_html__('Border Radius', 'king-addons'),
474 'type' => Controls_Manager::DIMENSIONS,
475 'size_units' => ['px', '%'],
476 'selectors' => [
477 '{{WRAPPER}} .king-addons-interactive-steps' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
478 ],
479 ]
480 );
481
482 $this->add_group_control(
483 Group_Control_Box_Shadow::get_type(),
484 [
485 'name' => 'kng_isp_container_shadow',
486 'selector' => '{{WRAPPER}} .king-addons-interactive-steps',
487 ]
488 );
489
490 $this->end_controls_section();
491 }
492
493 protected function register_style_step_controls(): void
494 {
495 $this->start_controls_section(
496 'kng_isp_style_steps',
497 [
498 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Step Item', 'king-addons'),
499 'tab' => Controls_Manager::TAB_STYLE,
500 ]
501 );
502
503 $this->add_control(
504 'kng_steps_card',
505 [
506 'label' => esc_html__('Card Mode', 'king-addons'),
507 'type' => Controls_Manager::SWITCHER,
508 'return_value' => 'yes',
509 'default' => 'yes',
510 ]
511 );
512
513 $this->add_control(
514 'kng_steps_item_bg',
515 [
516 'label' => esc_html__('Background', 'king-addons'),
517 'type' => Controls_Manager::COLOR,
518 'selectors' => [
519 '{{WRAPPER}} .king-addons-isp-step__body' => 'background-color: {{VALUE}};',
520 ],
521 ]
522 );
523
524 $this->add_group_control(
525 Group_Control_Border::get_type(),
526 [
527 'name' => 'kng_steps_item_border',
528 'selector' => '{{WRAPPER}} .king-addons-isp-step__body',
529 ]
530 );
531
532 $this->add_responsive_control(
533 'kng_steps_item_radius',
534 [
535 'label' => esc_html__('Border Radius', 'king-addons'),
536 'type' => Controls_Manager::DIMENSIONS,
537 'size_units' => ['px', '%'],
538 'selectors' => [
539 '{{WRAPPER}} .king-addons-isp-step__body' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
540 ],
541 ]
542 );
543
544 $this->add_group_control(
545 Group_Control_Box_Shadow::get_type(),
546 [
547 'name' => 'kng_steps_item_shadow',
548 'selector' => '{{WRAPPER}} .king-addons-isp-step__body',
549 ]
550 );
551
552 $this->add_control(
553 'kng_steps_item_bg_hover',
554 [
555 'label' => esc_html__('Hover Background', 'king-addons'),
556 'type' => Controls_Manager::COLOR,
557 'selectors' => [
558 '{{WRAPPER}} .king-addons-isp-step:hover .king-addons-isp-step__body' => 'background-color: {{VALUE}};',
559 ],
560 ]
561 );
562
563 $this->add_control(
564 'kng_steps_item_border_hover',
565 [
566 'label' => esc_html__('Hover Border Color', 'king-addons'),
567 'type' => Controls_Manager::COLOR,
568 'selectors' => [
569 '{{WRAPPER}} .king-addons-isp-step:hover .king-addons-isp-step__body' => 'border-color: {{VALUE}};',
570 ],
571 ]
572 );
573
574 $this->add_group_control(
575 Group_Control_Box_Shadow::get_type(),
576 [
577 'name' => 'kng_steps_item_shadow_hover',
578 'selector' => '{{WRAPPER}} .king-addons-isp-step:hover .king-addons-isp-step__body',
579 ]
580 );
581
582 $this->add_control(
583 'kng_steps_item_bg_active',
584 [
585 'label' => esc_html__('Active Background', 'king-addons'),
586 'type' => Controls_Manager::COLOR,
587 'selectors' => [
588 '{{WRAPPER}} .king-addons-isp-step.is-active .king-addons-isp-step__body' => 'background-color: {{VALUE}};',
589 ],
590 ]
591 );
592
593 $this->add_control(
594 'kng_steps_item_border_active',
595 [
596 'label' => esc_html__('Active Border Color', 'king-addons'),
597 'type' => Controls_Manager::COLOR,
598 'selectors' => [
599 '{{WRAPPER}} .king-addons-isp-step.is-active .king-addons-isp-step__body' => 'border-color: {{VALUE}};',
600 ],
601 ]
602 );
603
604 $this->add_group_control(
605 Group_Control_Box_Shadow::get_type(),
606 [
607 'name' => 'kng_steps_item_shadow_active',
608 'selector' => '{{WRAPPER}} .king-addons-isp-step.is-active .king-addons-isp-step__body',
609 ]
610 );
611
612 $this->end_controls_section();
613 }
614
615 protected function register_style_marker_controls(): void
616 {
617 $this->start_controls_section(
618 'kng_isp_style_marker',
619 [
620 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Marker & Progress', 'king-addons'),
621 'tab' => Controls_Manager::TAB_STYLE,
622 ]
623 );
624
625 $this->add_responsive_control(
626 'kng_steps_marker_size',
627 [
628 'label' => esc_html__('Marker Size', 'king-addons'),
629 'type' => Controls_Manager::SLIDER,
630 'size_units' => ['px'],
631 'range' => [
632 'px' => ['min' => 16, 'max' => 80],
633 ],
634 'selectors' => [
635 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-size: {{SIZE}}{{UNIT}};',
636 ],
637 ]
638 );
639
640 $this->add_responsive_control(
641 'kng_steps_dot_size',
642 [
643 'label' => esc_html__('Dot Size', 'king-addons'),
644 'type' => Controls_Manager::SLIDER,
645 'size_units' => ['px'],
646 'range' => [
647 'px' => ['min' => 6, 'max' => 30],
648 ],
649 'selectors' => [
650 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-dot-size: {{SIZE}}{{UNIT}};',
651 ],
652 ]
653 );
654
655 $this->add_responsive_control(
656 'kng_steps_icon_size',
657 [
658 'label' => esc_html__('Icon Size', 'king-addons'),
659 'type' => Controls_Manager::SLIDER,
660 'size_units' => ['px'],
661 'range' => [
662 'px' => ['min' => 10, 'max' => 48],
663 ],
664 'selectors' => [
665 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-icon-size: {{SIZE}}{{UNIT}};',
666 ],
667 ]
668 );
669
670 $this->add_responsive_control(
671 'kng_steps_icon_spacing',
672 [
673 'label' => esc_html__('Icon Spacing', 'king-addons'),
674 'type' => Controls_Manager::SLIDER,
675 'size_units' => ['px'],
676 'range' => [
677 'px' => ['min' => 0, 'max' => 40],
678 ],
679 'selectors' => [
680 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-icon-gap: {{SIZE}}{{UNIT}};',
681 ],
682 ]
683 );
684
685 $this->add_group_control(
686 Group_Control_Typography::get_type(),
687 [
688 'name' => 'kng_steps_number_typography',
689 'selector' => '{{WRAPPER}} .king-addons-isp-step__number',
690 ]
691 );
692
693 $this->add_group_control(
694 Group_Control_Typography::get_type(),
695 [
696 'name' => 'kng_steps_label_typography',
697 'selector' => '{{WRAPPER}} .king-addons-isp-step__label',
698 ]
699 );
700
701 $this->add_control(
702 'kng_steps_label_color',
703 [
704 'label' => esc_html__('Label Color', 'king-addons'),
705 'type' => Controls_Manager::COLOR,
706 'selectors' => [
707 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-label-color: {{VALUE}};',
708 ],
709 ]
710 );
711
712 $this->add_control(
713 'kng_steps_marker_color',
714 [
715 'label' => esc_html__('Marker Text Color', 'king-addons'),
716 'type' => Controls_Manager::COLOR,
717 'selectors' => [
718 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-color: {{VALUE}};',
719 ],
720 ]
721 );
722
723 $this->add_control(
724 'kng_steps_marker_bg',
725 [
726 'label' => esc_html__('Marker Background', 'king-addons'),
727 'type' => Controls_Manager::COLOR,
728 'selectors' => [
729 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-bg: {{VALUE}};',
730 ],
731 ]
732 );
733
734 $this->add_control(
735 'kng_steps_marker_border',
736 [
737 'label' => esc_html__('Marker Border Color', 'king-addons'),
738 'type' => Controls_Manager::COLOR,
739 'selectors' => [
740 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-border-color: {{VALUE}};',
741 ],
742 ]
743 );
744
745 $this->add_control(
746 'kng_steps_marker_color_active',
747 [
748 'label' => esc_html__('Active Marker Text', 'king-addons'),
749 'type' => Controls_Manager::COLOR,
750 'selectors' => [
751 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-color-active: {{VALUE}};',
752 ],
753 ]
754 );
755
756 $this->add_control(
757 'kng_steps_marker_bg_active',
758 [
759 'label' => esc_html__('Active Marker Background', 'king-addons'),
760 'type' => Controls_Manager::COLOR,
761 'selectors' => [
762 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-bg-active: {{VALUE}};',
763 ],
764 ]
765 );
766
767 $this->add_control(
768 'kng_steps_marker_border_active',
769 [
770 'label' => esc_html__('Active Marker Border', 'king-addons'),
771 'type' => Controls_Manager::COLOR,
772 'selectors' => [
773 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-border-active: {{VALUE}};',
774 ],
775 ]
776 );
777
778 $this->add_control(
779 'kng_steps_marker_color_completed',
780 [
781 'label' => esc_html__('Completed Marker Text', 'king-addons'),
782 'type' => Controls_Manager::COLOR,
783 'selectors' => [
784 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-color-completed: {{VALUE}};',
785 ],
786 ]
787 );
788
789 $this->add_control(
790 'kng_steps_marker_bg_completed',
791 [
792 'label' => esc_html__('Completed Marker Background', 'king-addons'),
793 'type' => Controls_Manager::COLOR,
794 'selectors' => [
795 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-bg-completed: {{VALUE}};',
796 ],
797 ]
798 );
799
800 $this->add_control(
801 'kng_steps_marker_border_completed',
802 [
803 'label' => esc_html__('Completed Marker Border', 'king-addons'),
804 'type' => Controls_Manager::COLOR,
805 'selectors' => [
806 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-marker-border-completed: {{VALUE}};',
807 ],
808 ]
809 );
810
811 $this->add_control(
812 'kng_steps_icon_color',
813 [
814 'label' => esc_html__('Icon Color', 'king-addons'),
815 'type' => Controls_Manager::COLOR,
816 'selectors' => [
817 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-icon-color: {{VALUE}};',
818 ],
819 ]
820 );
821
822 $this->add_control(
823 'kng_steps_icon_color_active',
824 [
825 'label' => esc_html__('Active Icon Color', 'king-addons'),
826 'type' => Controls_Manager::COLOR,
827 'selectors' => [
828 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-icon-color-active: {{VALUE}};',
829 ],
830 ]
831 );
832
833 $this->add_control(
834 'kng_steps_icon_color_completed',
835 [
836 'label' => esc_html__('Completed Icon Color', 'king-addons'),
837 'type' => Controls_Manager::COLOR,
838 'selectors' => [
839 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-icon-color-completed: {{VALUE}};',
840 ],
841 ]
842 );
843
844 $this->add_control(
845 'kng_steps_line_color',
846 [
847 'label' => esc_html__('Line Color', 'king-addons'),
848 'type' => Controls_Manager::COLOR,
849 'selectors' => [
850 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-line-color: {{VALUE}};',
851 ],
852 ]
853 );
854
855 $this->add_control(
856 'kng_steps_line_color_active',
857 [
858 'label' => esc_html__('Active Line Color', 'king-addons'),
859 'type' => Controls_Manager::COLOR,
860 'selectors' => [
861 '{{WRAPPER}} .king-addons-interactive-steps' => '--kng-isp-line-active-color: {{VALUE}};',
862 ],
863 ]
864 );
865
866 $this->end_controls_section();
867 }
868
869 protected function register_style_typography_controls(): void
870 {
871 $this->start_controls_section(
872 'kng_isp_style_typography',
873 [
874 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Typography', 'king-addons'),
875 'tab' => Controls_Manager::TAB_STYLE,
876 ]
877 );
878
879 $this->add_group_control(
880 Group_Control_Typography::get_type(),
881 [
882 'name' => 'kng_steps_title_typography',
883 'selector' => '{{WRAPPER}} .king-addons-isp-step__title',
884 ]
885 );
886
887 $this->add_control(
888 'kng_steps_title_color',
889 [
890 'label' => esc_html__('Title Color', 'king-addons'),
891 'type' => Controls_Manager::COLOR,
892 'selectors' => [
893 '{{WRAPPER}} .king-addons-isp-step__title' => 'color: {{VALUE}};',
894 ],
895 ]
896 );
897
898 $this->add_group_control(
899 Group_Control_Typography::get_type(),
900 [
901 'name' => 'kng_steps_description_typography',
902 'selector' => '{{WRAPPER}} .king-addons-isp-step__description',
903 ]
904 );
905
906 $this->add_control(
907 'kng_steps_description_color',
908 [
909 'label' => esc_html__('Description Color', 'king-addons'),
910 'type' => Controls_Manager::COLOR,
911 'selectors' => [
912 '{{WRAPPER}} .king-addons-isp-step__description' => 'color: {{VALUE}};',
913 ],
914 ]
915 );
916
917 $this->add_group_control(
918 Group_Control_Typography::get_type(),
919 [
920 'name' => 'kng_steps_details_typography',
921 'selector' => '{{WRAPPER}} .king-addons-isp-step__details',
922 ]
923 );
924
925 $this->add_control(
926 'kng_steps_details_color',
927 [
928 'label' => esc_html__('Details Color', 'king-addons'),
929 'type' => Controls_Manager::COLOR,
930 'selectors' => [
931 '{{WRAPPER}} .king-addons-isp-step__details' => 'color: {{VALUE}};',
932 ],
933 ]
934 );
935
936 $this->add_control(
937 'kng_steps_title_color_active',
938 [
939 'label' => esc_html__('Active Title Color', 'king-addons'),
940 'type' => Controls_Manager::COLOR,
941 'selectors' => [
942 '{{WRAPPER}} .king-addons-isp-step.is-active .king-addons-isp-step__title' => 'color: {{VALUE}};',
943 ],
944 ]
945 );
946
947 $this->add_control(
948 'kng_steps_description_color_active',
949 [
950 'label' => esc_html__('Active Description Color', 'king-addons'),
951 'type' => Controls_Manager::COLOR,
952 'selectors' => [
953 '{{WRAPPER}} .king-addons-isp-step.is-active .king-addons-isp-step__description' => 'color: {{VALUE}};',
954 ],
955 ]
956 );
957
958 $this->end_controls_section();
959 }
960
961 protected function register_style_cta_controls(): void
962 {
963 $this->start_controls_section(
964 'kng_isp_style_cta',
965 [
966 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('CTA', 'king-addons'),
967 'tab' => Controls_Manager::TAB_STYLE,
968 ]
969 );
970
971 $this->add_group_control(
972 Group_Control_Typography::get_type(),
973 [
974 'name' => 'kng_steps_cta_typography',
975 'selector' => '{{WRAPPER}} .king-addons-isp-step__cta',
976 ]
977 );
978
979 $this->add_control(
980 'kng_steps_cta_color',
981 [
982 'label' => esc_html__('CTA Color', 'king-addons'),
983 'type' => Controls_Manager::COLOR,
984 'selectors' => [
985 '{{WRAPPER}} .king-addons-isp-step__cta' => 'color: {{VALUE}};',
986 ],
987 ]
988 );
989
990 $this->add_control(
991 'kng_steps_cta_hover_color',
992 [
993 'label' => esc_html__('CTA Hover Color', 'king-addons'),
994 'type' => Controls_Manager::COLOR,
995 'selectors' => [
996 '{{WRAPPER}} .king-addons-isp-step__cta:hover' => 'color: {{VALUE}};',
997 ],
998 ]
999 );
1000
1001 $this->add_control(
1002 'kng_steps_cta_bg',
1003 [
1004 'label' => esc_html__('CTA Background', 'king-addons'),
1005 'type' => Controls_Manager::COLOR,
1006 'selectors' => [
1007 '{{WRAPPER}} .king-addons-isp-step__cta' => 'background-color: {{VALUE}};',
1008 ],
1009 ]
1010 );
1011
1012 $this->add_control(
1013 'kng_steps_cta_hover_bg',
1014 [
1015 'label' => esc_html__('CTA Hover Background', 'king-addons'),
1016 'type' => Controls_Manager::COLOR,
1017 'selectors' => [
1018 '{{WRAPPER}} .king-addons-isp-step__cta:hover' => 'background-color: {{VALUE}};',
1019 ],
1020 ]
1021 );
1022
1023 $this->add_group_control(
1024 Group_Control_Border::get_type(),
1025 [
1026 'name' => 'kng_steps_cta_border',
1027 'selector' => '{{WRAPPER}} .king-addons-isp-step__cta',
1028 ]
1029 );
1030
1031 $this->add_control(
1032 'kng_steps_cta_hover_border_color',
1033 [
1034 'label' => esc_html__('CTA Hover Border Color', 'king-addons'),
1035 'type' => Controls_Manager::COLOR,
1036 'selectors' => [
1037 '{{WRAPPER}} .king-addons-isp-step__cta:hover' => 'border-color: {{VALUE}};',
1038 ],
1039 ]
1040 );
1041
1042 $this->add_responsive_control(
1043 'kng_steps_cta_radius',
1044 [
1045 'label' => esc_html__('CTA Border Radius', 'king-addons'),
1046 'type' => Controls_Manager::DIMENSIONS,
1047 'size_units' => ['px', '%'],
1048 'selectors' => [
1049 '{{WRAPPER}} .king-addons-isp-step__cta' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1050 ],
1051 ]
1052 );
1053
1054 $this->add_responsive_control(
1055 'kng_steps_cta_padding',
1056 [
1057 'label' => esc_html__('CTA Padding', 'king-addons'),
1058 'type' => Controls_Manager::DIMENSIONS,
1059 'size_units' => ['px', 'em'],
1060 'selectors' => [
1061 '{{WRAPPER}} .king-addons-isp-step__cta' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1062 ],
1063 ]
1064 );
1065
1066 $this->end_controls_section();
1067 }
1068
1069 protected function register_pro_notice_controls(): void
1070 {
1071 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
1072 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'interactive-steps-progress', [
1073 'Horizontal layout with wrap and sticky progress',
1074 'Deep links with hash sync and anchor navigation',
1075 'Scroll reveal and active-on-scroll interactions',
1076 'Advanced animations with reduced motion options',
1077 ]);
1078 }
1079 }
1080
1081 protected function should_render_progress_header(array $settings): bool
1082 {
1083 if (!$this->is_pro_enabled()) {
1084 return false;
1085 }
1086
1087 $show_count = ('yes' === ($settings['kng_steps_show_step_count'] ?? ''));
1088 $show_percent = ('yes' === ($settings['kng_steps_show_percent'] ?? ''));
1089 $sticky = ('yes' === ($settings['kng_steps_sticky_progress'] ?? ''));
1090
1091 return $show_count || $show_percent || $sticky;
1092 }
1093
1094 protected function render_progress_header(array $settings, int $total_steps): void
1095 {
1096 $show_count = ('yes' === ($settings['kng_steps_show_step_count'] ?? ''));
1097 $show_percent = ('yes' === ($settings['kng_steps_show_percent'] ?? ''));
1098
1099 $sticky_enabled = ('yes' === ($settings['kng_steps_sticky_progress'] ?? ''));
1100 $sticky_type = $this->sanitize_sticky_type((string) ($settings['kng_steps_sticky_type'] ?? 'bar'));
1101 $markers_clickable = ('yes' === ($settings['kng_steps_progress_clickable'] ?? ''));
1102 $sticky_steps_interactive = $sticky_enabled && ('header' === $sticky_type) && $markers_clickable;
1103
1104 $count_text = sprintf(
1105 esc_html__('Step %1$s of %2$s', 'king-addons'),
1106 1,
1107 max($total_steps, 1)
1108 );
1109
1110 echo '<div class="king-addons-isp__sticky">';
1111 echo '<div class="king-addons-isp__progress">';
1112 echo '<div class="king-addons-isp-progress__bar" aria-hidden="true">';
1113 echo '<span class="king-addons-isp-progress__fill" style="width: 0%;"></span>';
1114 echo '</div>';
1115 if ($sticky_steps_interactive) {
1116 echo '<div class="king-addons-isp-progress__steps" role="navigation" aria-label="' . esc_attr__('Steps', 'king-addons') . '"></div>';
1117 } else {
1118 echo '<div class="king-addons-isp-progress__steps" aria-hidden="true"></div>';
1119 }
1120
1121 if ($show_count || $show_percent) {
1122 echo '<div class="king-addons-isp-progress__meta">';
1123
1124 if ($show_count) {
1125 echo '<span class="king-addons-isp-progress__count" aria-live="polite">' . esc_html($count_text) . '</span>';
1126 }
1127
1128 if ($show_percent) {
1129 echo '<span class="king-addons-isp-progress__percent">0%</span>';
1130 }
1131
1132 echo '</div>';
1133 }
1134
1135 echo '</div>';
1136 echo '</div>';
1137 }
1138
1139 protected function render(): void
1140 {
1141 $settings = $this->get_settings_for_display();
1142 $steps = $settings['kng_steps_list'] ?? [];
1143
1144 if (empty($steps)) {
1145 if ($this->is_editor()) {
1146 echo '<div class="king-addons-isp-empty">' . esc_html__('Add steps to get started.', 'king-addons') . '</div>';
1147 }
1148 return;
1149 }
1150
1151 $wrapper_classes = $this->get_wrapper_classes($settings);
1152 $data_settings = $this->get_data_settings($settings);
1153 $total_steps = count($steps);
1154 $unique_anchors = $this->build_unique_step_anchors($settings, $steps);
1155
1156 $this->add_render_attribute('kng-isp-wrapper', [
1157 'class' => implode(' ', $wrapper_classes),
1158 'data-settings' => wp_json_encode($data_settings),
1159 ]);
1160
1161 echo '<div ' . $this->get_render_attribute_string('kng-isp-wrapper') . '>';
1162
1163 if ($this->should_render_progress_header($settings)) {
1164 $this->render_progress_header($settings, $total_steps);
1165 }
1166
1167 echo '<ul class="king-addons-isp__list">';
1168
1169 foreach ($steps as $index => $step) {
1170 if (!empty($unique_anchors)) {
1171 $step['_kng_isp_anchor'] = $unique_anchors[$index] ?? '';
1172 }
1173 $this->render_step($settings, $step, $index);
1174 }
1175
1176 echo '</ul>';
1177 echo '</div>';
1178 }
1179
1180 protected function render_step(array $settings, array $step, int $index): void
1181 {
1182 $title = trim((string) ($step['step_title'] ?? ''));
1183 $description = trim((string) ($step['step_description'] ?? ''));
1184 $details = (string) ($step['step_details'] ?? '');
1185 $points_raw = (string) ($step['step_points'] ?? '');
1186 $cta_label = trim((string) ($step['step_cta_text'] ?? ''));
1187 $cta_link = $step['step_cta_link'] ?? [];
1188
1189 $points = $this->parse_step_points($points_raw);
1190
1191 $number_text = $this->get_step_number($settings, $step, $index);
1192 $label_text = '' !== $number_text
1193 ? sprintf(esc_html__('Step %s', 'king-addons'), $number_text)
1194 : '';
1195
1196 $expand_behavior = $this->sanitize_expand_behavior($settings['kng_steps_expand_behavior'] ?? 'click');
1197 $default_open = $this->sanitize_default_open($settings['kng_steps_default_open'] ?? 'first');
1198 $has_panel = ('' !== trim($details)) || !empty($points) || (!empty($cta_link['url']) && '' !== $cta_label);
1199 $is_open = ('none' === $expand_behavior) || ('click' === $expand_behavior && 'first' === $default_open && 0 === $index && $has_panel);
1200 $anchor_id = (string) ($step['_kng_isp_anchor'] ?? '');
1201 if ('' === $anchor_id) {
1202 $anchor_id = $this->get_step_anchor($settings, $step, $index);
1203 }
1204
1205 $step_classes = ['king-addons-isp-step'];
1206 if (0 === $index) {
1207 $step_classes[] = 'is-active';
1208 } else {
1209 $step_classes[] = 'is-upcoming';
1210 }
1211
1212 if ($is_open) {
1213 $step_classes[] = 'is-open';
1214 }
1215
1216 $attributes = [
1217 'class' => implode(' ', $step_classes),
1218 'data-step-index' => (string) $index,
1219 ];
1220
1221 if ('' !== $anchor_id) {
1222 $attributes['id'] = $anchor_id;
1223 $attributes['data-anchor'] = $anchor_id;
1224 }
1225
1226 if (0 === $index) {
1227 $attributes['aria-current'] = 'step';
1228 }
1229
1230 $attr_output = [];
1231 foreach ($attributes as $key => $value) {
1232 $attr_output[] = esc_attr($key) . '="' . esc_attr((string) $value) . '"';
1233 }
1234
1235 $panel_id = '';
1236 $button_id = $this->get_button_id($index);
1237 if ($has_panel) {
1238 $panel_id = $this->get_panel_id($index);
1239 }
1240
1241 $icon_position = $this->sanitize_icon_position((string) ($step['step_icon_position'] ?? 'left'));
1242 $has_icon = !empty($step['step_icon']['value']);
1243
1244 echo '<li ' . implode(' ', $attr_output) . '>';
1245 echo '<span class="king-addons-isp-step__marker" aria-hidden="true">';
1246
1247 if ('' !== $number_text) {
1248 echo '<span class="king-addons-isp-step__number">' . esc_html($number_text) . '</span>';
1249 }
1250
1251 echo '</span>';
1252 echo '<div class="king-addons-isp-step__body">';
1253 echo '<button class="king-addons-isp-step__button" type="button" id="' . esc_attr($button_id) . '"';
1254
1255 if ($has_panel) {
1256 echo ' aria-expanded="' . ($is_open ? 'true' : 'false') . '" aria-controls="' . esc_attr($panel_id) . '"';
1257 }
1258
1259 echo '>';
1260 echo '<span class="king-addons-isp-step__heading king-addons-isp-icon-' . esc_attr($icon_position) . '">';
1261
1262 if ($has_icon) {
1263 echo '<span class="king-addons-isp-step__icon">';
1264 Icons_Manager::render_icon($step['step_icon'], ['aria-hidden' => 'true']);
1265 echo '</span>';
1266 }
1267
1268 echo '<span class="king-addons-isp-step__text">';
1269
1270 if ('' !== $label_text) {
1271 echo '<span class="king-addons-isp-step__label">' . esc_html($label_text) . '</span>';
1272 }
1273
1274 if ('' !== $title) {
1275 echo '<span class="king-addons-isp-step__title">' . esc_html($title) . '</span>';
1276 }
1277
1278 if ('' !== $description) {
1279 echo '<span class="king-addons-isp-step__description">' . wp_kses_post($description) . '</span>';
1280 }
1281
1282 echo '</span>';
1283 echo '</span>';
1284 echo '</button>';
1285
1286 if ($has_panel) {
1287 $panel_hidden = $is_open ? '' : ' hidden="hidden"';
1288 echo '<div id="' . esc_attr($panel_id) . '" class="king-addons-isp-step__panel" role="region" aria-labelledby="' . esc_attr($button_id) . '" aria-hidden="' . ($is_open ? 'false' : 'true') . '"' . $panel_hidden . '>';
1289
1290 if ('' !== trim($details)) {
1291 echo '<div class="king-addons-isp-step__details">' . wp_kses_post($details) . '</div>';
1292 }
1293
1294 if (!empty($points)) {
1295 echo '<ul class="king-addons-isp-step__points">';
1296 foreach ($points as $point) {
1297 echo '<li>' . esc_html($point) . '</li>';
1298 }
1299 echo '</ul>';
1300 }
1301
1302 if (!empty($cta_link['url']) && '' !== $cta_label) {
1303 $link_attrs = $this->get_link_attributes($cta_link, 'king-addons-isp-step__cta');
1304 if ('' !== $link_attrs) {
1305 echo '<a ' . $link_attrs . '>' . esc_html($cta_label) . '</a>';
1306 }
1307 }
1308
1309 echo '</div>';
1310 }
1311
1312 echo '</div>';
1313 echo '</li>';
1314 }
1315
1316 /**
1317 * @param string $raw
1318 * @return string[]
1319 */
1320 protected function parse_step_points(string $raw): array
1321 {
1322 $raw = str_replace(["\r\n", "\r"], "\n", $raw);
1323 $lines = array_filter(array_map('trim', explode("\n", $raw)), static function ($value) {
1324 return '' !== $value;
1325 });
1326
1327 // Normalize and cap to a reasonable number for performance.
1328 $lines = array_slice(array_values($lines), 0, 50);
1329
1330 return array_map('sanitize_text_field', $lines);
1331 }
1332
1333 protected function get_step_number(array $settings, array $step, int $index): string
1334 {
1335 $mode = $settings['kng_steps_numbering_mode'] ?? 'auto';
1336 $custom = trim((string) ($step['step_number'] ?? ''));
1337
1338 if ('manual' === $mode && '' !== $custom) {
1339 return sanitize_text_field($custom);
1340 }
1341
1342 return (string) ($index + 1);
1343 }
1344
1345 protected function get_wrapper_classes(array $settings): array
1346 {
1347 $alignment = $this->sanitize_alignment($settings['kng_steps_alignment'] ?? 'left');
1348 $progress_style = $this->sanitize_progress_style($settings['kng_steps_progress_style'] ?? 'dots');
1349 $line_display = $this->sanitize_line_display($settings['kng_steps_connector'] ?? 'show');
1350 $show_completed = $this->sanitize_show_completed($settings['kng_steps_show_completed'] ?? 'yes');
1351 $expand_behavior = $this->sanitize_expand_behavior($settings['kng_steps_expand_behavior'] ?? 'click');
1352 $card_mode = ('yes' === ($settings['kng_steps_card'] ?? 'yes')) ? 'yes' : 'no';
1353 $orientation = $this->get_orientation($settings);
1354 $classes = [
1355 'king-addons-interactive-steps',
1356 'king-addons-isp-layout-' . $orientation,
1357 'king-addons-isp-align-' . $alignment,
1358 'king-addons-isp-progress-' . $progress_style,
1359 'king-addons-isp-line-' . $line_display,
1360 'king-addons-isp-completed-' . $show_completed,
1361 'king-addons-isp-expand-' . $expand_behavior,
1362 'king-addons-isp-card-' . $card_mode,
1363 ];
1364
1365 if ($this->is_pro_enabled()) {
1366 $wrap_mode = $this->sanitize_wrap_mode($settings['kng_steps_horizontal_wrap'] ?? 'wrap');
1367 $wrap_class = ('scroll' === $wrap_mode) ? 'nowrap' : $wrap_mode;
1368 $scroll_class = ('scroll' === $wrap_mode) ? 'yes' : 'no';
1369
1370 $classes[] = 'king-addons-isp--pro';
1371 $classes[] = 'king-addons-isp-wrap-' . $wrap_class;
1372 $classes[] = 'king-addons-isp-scroll-' . $scroll_class;
1373 $classes[] = 'king-addons-isp-equal-' . $this->sanitize_toggle($settings['kng_steps_equal_width'] ?? '');
1374 $classes[] = 'king-addons-isp-mobile-' . $this->sanitize_mobile_fallback($settings['kng_steps_mobile_fallback'] ?? 'vertical');
1375
1376 if ('yes' === ($settings['kng_steps_sticky_progress'] ?? '')) {
1377 $classes[] = 'king-addons-isp--sticky';
1378 $classes[] = 'king-addons-isp-sticky-' . $this->sanitize_sticky_type($settings['kng_steps_sticky_type'] ?? 'bar');
1379
1380 if ('yes' === ($settings['kng_steps_sticky_compact_mobile'] ?? '')) {
1381 $classes[] = 'king-addons-isp-sticky-compact';
1382 }
1383 }
1384
1385 if ('yes' === ($settings['kng_steps_progress_animate'] ?? '')) {
1386 $classes[] = 'king-addons-isp-progress-animated';
1387 }
1388
1389 if ('yes' === ($settings['kng_steps_active_animation'] ?? '')) {
1390 $classes[] = 'king-addons-isp-animate-active';
1391 }
1392
1393 if ('yes' === ($settings['kng_steps_reveal'] ?? '')) {
1394 $classes[] = 'king-addons-isp-reveal';
1395 $classes[] = 'king-addons-isp-reveal-' . $this->sanitize_reveal_type($settings['kng_steps_reveal_type'] ?? 'fade');
1396 }
1397
1398 $reduced_motion = $this->sanitize_reduced_motion($settings['kng_steps_reduced_motion'] ?? 'auto');
1399 if ('full' === $reduced_motion) {
1400 $classes[] = 'king-addons-isp-reduced-full';
1401 } elseif ('minimal' === $reduced_motion) {
1402 $classes[] = 'king-addons-isp-reduced-minimal';
1403 } elseif ('off' === $reduced_motion) {
1404 $classes[] = 'king-addons-isp-reduced-off';
1405 }
1406
1407 if ('yes' === ($settings['kng_steps_progress_clickable'] ?? '')) {
1408 $classes[] = 'king-addons-isp-markers-clickable';
1409 }
1410
1411 if ($this->should_render_progress_header($settings)) {
1412 $classes[] = 'king-addons-isp-has-progress';
1413 }
1414 } else {
1415 $classes[] = 'king-addons-isp-markers-clickable';
1416 }
1417
1418 return $classes;
1419 }
1420
1421 protected function get_data_settings(array $settings): array
1422 {
1423 $data = [
1424 'expandBehavior' => $this->sanitize_expand_behavior($settings['kng_steps_expand_behavior'] ?? 'click'),
1425 'defaultOpen' => $this->sanitize_default_open($settings['kng_steps_default_open'] ?? 'first'),
1426 'singleOpen' => ('yes' === ($settings['kng_steps_single_open'] ?? 'yes')) ? 'yes' : 'no',
1427 'scrollToStep' => ('yes' === ($settings['kng_steps_scroll_to_step'] ?? '')) ? 'yes' : 'no',
1428 ];
1429
1430 if ($this->is_pro_enabled()) {
1431 $data['orientation'] = $this->get_orientation($settings);
1432 $data['activateOnScroll'] = ('yes' === ($settings['kng_steps_activate_on_scroll'] ?? '')) ? 'yes' : 'no';
1433 $data['activationOffset'] = $this->sanitize_activation_offset($settings['kng_steps_activation_offset'] ?? 40);
1434 $data['anchorLinking'] = ('yes' === ($settings['kng_steps_enable_anchors'] ?? '')) ? 'yes' : 'no';
1435 $data['updateHash'] = ('yes' === ($settings['kng_steps_update_hash'] ?? '')) ? 'yes' : 'no';
1436 $data['scrollToHash'] = ('yes' === ($settings['kng_steps_scroll_to_hash'] ?? '')) ? 'yes' : 'no';
1437 $data['keyboardNav'] = ('yes' === ($settings['kng_steps_keyboard_nav'] ?? '')) ? 'yes' : 'no';
1438 $data['reveal'] = ('yes' === ($settings['kng_steps_reveal'] ?? '')) ? 'yes' : 'no';
1439 $data['revealType'] = $this->sanitize_reveal_type($settings['kng_steps_reveal_type'] ?? 'fade');
1440 $data['revealStagger'] = absint($settings['kng_steps_reveal_stagger'] ?? 0);
1441 $data['activeAnimation'] = ('yes' === ($settings['kng_steps_active_animation'] ?? '')) ? 'yes' : 'no';
1442 $data['activeDuration'] = absint($settings['kng_steps_active_duration'] ?? 250);
1443 $data['activeEasing'] = $this->sanitize_easing($settings['kng_steps_active_easing'] ?? 'ease');
1444 $data['progressAnimate'] = ('yes' === ($settings['kng_steps_progress_animate'] ?? '')) ? 'yes' : 'no';
1445 $data['progressDuration'] = absint($settings['kng_steps_progress_duration'] ?? 300);
1446 $data['progressEasing'] = $this->sanitize_easing($settings['kng_steps_progress_easing'] ?? 'ease');
1447 $data['showStepCount'] = ('yes' === ($settings['kng_steps_show_step_count'] ?? '')) ? 'yes' : 'no';
1448 $data['showPercent'] = ('yes' === ($settings['kng_steps_show_percent'] ?? '')) ? 'yes' : 'no';
1449 $data['markersClickable'] = ('yes' === ($settings['kng_steps_progress_clickable'] ?? '')) ? 'yes' : 'no';
1450 $data['reducedMotion'] = $this->sanitize_reduced_motion($settings['kng_steps_reduced_motion'] ?? 'auto');
1451 $data['stepLabel'] = esc_html__('Step %1$s of %2$s', 'king-addons');
1452 }
1453
1454 return $data;
1455 }
1456
1457 protected function get_default_steps(): array
1458 {
1459 return [
1460 [
1461 'step_title' => esc_html__('Share your goals', 'king-addons'),
1462 'step_description' => esc_html__('Tell us what success looks like so we can tailor the plan.', 'king-addons'),
1463 'step_details' => esc_html__('Include your timeline, target audience, and success metrics to align the team.', 'king-addons'),
1464 ],
1465 [
1466 'step_title' => esc_html__('We build the roadmap', 'king-addons'),
1467 'step_description' => esc_html__('A clear sequence of tasks keeps everyone on track.', 'king-addons'),
1468 'step_details' => esc_html__('We break the project into milestones with owners and deadlines.', 'king-addons'),
1469 ],
1470 [
1471 'step_title' => esc_html__('Launch and improve', 'king-addons'),
1472 'step_description' => esc_html__('Ship fast and iterate with real data.', 'king-addons'),
1473 'step_details' => esc_html__('Measure results, collect feedback, and keep refining.', 'king-addons'),
1474 ],
1475 ];
1476 }
1477
1478 protected function get_panel_id(int $index): string
1479 {
1480 $raw = 'kng-isp-panel-' . $this->get_id() . '-' . ($index + 1);
1481 return sanitize_key($raw);
1482 }
1483
1484 protected function get_button_id(int $index): string
1485 {
1486 $raw = 'kng-isp-button-' . $this->get_id() . '-' . ($index + 1);
1487 return sanitize_key($raw);
1488 }
1489
1490 /**
1491 * Ensure step anchors are unique within a single widget instance.
1492 *
1493 * @param array $settings
1494 * @param array $steps
1495 * @return array<int, string>
1496 */
1497 protected function build_unique_step_anchors(array $settings, array $steps): array
1498 {
1499 if (!$this->is_pro_enabled()) {
1500 return [];
1501 }
1502
1503 if ('yes' !== ($settings['kng_steps_enable_anchors'] ?? '')) {
1504 return [];
1505 }
1506
1507 $used = [];
1508 $anchors = [];
1509
1510 foreach ($steps as $index => $step) {
1511 $raw = trim((string) ($step['step_anchor_id'] ?? ''));
1512 if ('' === $raw) {
1513 $raw = trim((string) ($step['step_title'] ?? ''));
1514 }
1515
1516 $slug = $this->sanitize_anchor_id($raw);
1517 if ('' === $slug) {
1518 $slug = 'kng-isp-' . $this->get_id() . '-' . ($index + 1);
1519 }
1520
1521 $base = $slug;
1522 $suffix = 2;
1523 while (isset($used[$slug])) {
1524 $slug = $base . '-' . $suffix;
1525 $suffix++;
1526 }
1527
1528 $used[$slug] = true;
1529 $anchors[(int) $index] = $slug;
1530 }
1531
1532 return $anchors;
1533 }
1534
1535 protected function get_step_anchor(array $settings, array $step, int $index): string
1536 {
1537 if (!$this->is_pro_enabled()) {
1538 return '';
1539 }
1540
1541 if ('yes' !== ($settings['kng_steps_enable_anchors'] ?? '')) {
1542 return '';
1543 }
1544
1545 $raw = trim((string) ($step['step_anchor_id'] ?? ''));
1546 if ('' === $raw) {
1547 $raw = trim((string) ($step['step_title'] ?? ''));
1548 }
1549
1550 $slug = $this->sanitize_anchor_id($raw);
1551 if ('' === $slug) {
1552 $slug = 'kng-isp-' . $this->get_id() . '-' . ($index + 1);
1553 }
1554
1555 return $slug;
1556 }
1557
1558 protected function sanitize_anchor_id(string $raw): string
1559 {
1560 $slug = strtolower($raw);
1561 $slug = preg_replace('/[^a-z0-9\\-_]+/', '-', $slug);
1562 return trim((string) $slug, '-_');
1563 }
1564
1565 protected function get_orientation(array $settings): string
1566 {
1567 $orientation = $settings['kng_steps_orientation'] ?? 'vertical';
1568 if (!$this->is_pro_enabled()) {
1569 return 'vertical';
1570 }
1571
1572 return in_array($orientation, ['vertical', 'horizontal'], true) ? $orientation : 'vertical';
1573 }
1574
1575 protected function sanitize_alignment(string $alignment): string
1576 {
1577 return in_array($alignment, ['left', 'center'], true) ? $alignment : 'left';
1578 }
1579
1580 protected function sanitize_progress_style(string $style): string
1581 {
1582 return in_array($style, ['dots', 'numbers'], true) ? $style : 'dots';
1583 }
1584
1585 protected function sanitize_line_display(string $display): string
1586 {
1587 return in_array($display, ['show', 'hide'], true) ? $display : 'show';
1588 }
1589
1590 protected function sanitize_show_completed(string $value): string
1591 {
1592 return in_array($value, ['yes', 'no'], true) ? $value : 'yes';
1593 }
1594
1595 protected function sanitize_expand_behavior(string $value): string
1596 {
1597 return in_array($value, ['none', 'click'], true) ? $value : 'click';
1598 }
1599
1600 protected function sanitize_default_open(string $value): string
1601 {
1602 return in_array($value, ['first', 'closed'], true) ? $value : 'first';
1603 }
1604
1605 protected function sanitize_icon_position(string $value): string
1606 {
1607 return in_array($value, ['left', 'top'], true) ? $value : 'left';
1608 }
1609
1610 protected function sanitize_toggle(string $value): string
1611 {
1612 return 'yes' === $value ? 'yes' : 'no';
1613 }
1614
1615 protected function sanitize_wrap_mode(string $value): string
1616 {
1617 return in_array($value, ['wrap', 'nowrap', 'scroll'], true) ? $value : 'wrap';
1618 }
1619
1620 protected function sanitize_mobile_fallback(string $value): string
1621 {
1622 return in_array($value, ['vertical', 'scroll'], true) ? $value : 'vertical';
1623 }
1624
1625 protected function sanitize_sticky_type(string $value): string
1626 {
1627 return in_array($value, ['bar', 'header'], true) ? $value : 'bar';
1628 }
1629
1630 protected function sanitize_reveal_type(string $value): string
1631 {
1632 return in_array($value, ['fade', 'slide', 'scale'], true) ? $value : 'fade';
1633 }
1634
1635 protected function sanitize_reduced_motion(string $value): string
1636 {
1637 return in_array($value, ['auto', 'off', 'minimal', 'full'], true) ? $value : 'auto';
1638 }
1639
1640 protected function sanitize_easing(string $value): string
1641 {
1642 $allowed = ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'];
1643 return in_array($value, $allowed, true) ? $value : 'ease';
1644 }
1645
1646 protected function sanitize_activation_offset($value): int
1647 {
1648 $offset = is_numeric($value) ? (int) $value : 40;
1649 $offset = max(10, min(80, $offset));
1650 return $offset;
1651 }
1652
1653 protected function get_link_attributes(array $link, string $class = ''): string
1654 {
1655 if (empty($link['url'])) {
1656 return '';
1657 }
1658
1659 $attributes = [
1660 'href' => esc_url($link['url']),
1661 ];
1662
1663 if ('' !== $class) {
1664 $attributes['class'] = $class;
1665 }
1666
1667 if (!empty($link['is_external'])) {
1668 $attributes['target'] = '_blank';
1669 $attributes['rel'] = 'noopener noreferrer';
1670 }
1671
1672 if (!empty($link['nofollow'])) {
1673 $attributes['rel'] = isset($attributes['rel']) ? $attributes['rel'] . ' nofollow' : 'nofollow';
1674 }
1675
1676 $output = [];
1677 foreach ($attributes as $key => $value) {
1678 $output[] = esc_attr($key) . '="' . esc_attr((string) $value) . '"';
1679 }
1680
1681 return implode(' ', $output);
1682 }
1683
1684 protected function is_pro_enabled(): bool
1685 {
1686 return function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only();
1687 }
1688
1689 protected function get_pro_control_class(string $extra = ''): string
1690 {
1691 if ($this->is_pro_enabled()) {
1692 return $extra;
1693 }
1694
1695 return trim('king-addons-pro-control ' . $extra);
1696 }
1697
1698 protected function is_editor(): bool
1699 {
1700 return class_exists(Plugin::class) && Plugin::$instance->editor->is_edit_mode();
1701 }
1702 }
1703