PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.82
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.82
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.82, at includes/widgets/Interactive_Steps_Progress/Interactive_Steps_Progress.php

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