PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / components / InputField.php
tutor / components Last commit date
Constants 23 hours ago Accordion.php 23 hours ago Alert.php 23 hours ago AttachmentCard.php 23 hours ago Avatar.php 23 hours ago Badge.php 23 hours ago BaseComponent.php 23 hours ago Button.php 23 hours ago ConfirmationModal.php 23 hours ago CourseFilter.php 23 hours ago DateFilter.php 23 hours ago DropdownFilter.php 23 hours ago EmptyState.php 23 hours ago FileUploader.php 23 hours ago InputField.php 23 hours ago Modal.php 23 hours ago Nav.php 23 hours ago Pagination.php 23 hours ago Popover.php 23 hours ago PreviewTrigger.php 23 hours ago Progress.php 23 hours ago SearchFilter.php 23 hours ago Sorting.php 23 hours ago StarRating.php 23 hours ago StarRatingInput.php 23 hours ago StatusSelect.php 23 hours ago SvgIcon.php 23 hours ago Table.php 23 hours ago Tabs.php 23 hours ago Tooltip.php 23 hours ago WPEditor.php 23 hours ago
InputField.php
2069 lines
1 <?php
2 /**
3 * Tutor Component: InputField
4 *
5 * Provides a fluent builder for rendering various input field types with
6 * labels, validation states, and helper text.
7 *
8 * @package Tutor\Components
9 * @author Themeum
10 * @link https://themeum.com
11 * @since 4.0.0
12 */
13
14 namespace Tutor\Components;
15
16 defined( 'ABSPATH' ) || exit;
17
18 use TUTOR\Icon;
19 use ReflectionClass;
20 use Tutor\Components\Constants\Size;
21 use Tutor\Components\Constants\InputType;
22
23 /**
24 * InputField Component Class.
25 *
26 * Example usage:
27 * ```
28 * // Text input with clear button
29 * InputField::make()
30 * ->type( 'text' )
31 * ->name( 'full_name' )
32 * ->label( 'Full Name' )
33 * ->placeholder( 'Enter your full name' )
34 * ->required()
35 * ->clearable()
36 * ->help_text( 'This is a helper text.' )
37 * ->attr( 'x-bind', "register('full_name', { required: 'Name is required', minLength: { value: 2, message: 'Name must be at least 2 characters' } })")
38 * ->render();
39 *
40 * // Text input with left icon
41 * InputField::make()
42 * ->type( 'text' )
43 * ->name( 'email' )
44 * ->label( 'Email' )
45 * ->left_icon( '<svg>...</svg>' )
46 * ->render();
47 *
48 * // Textarea
49 * InputField::make()
50 * ->type( 'textarea' )
51 * ->name( 'bio' )
52 * ->label( 'Bio' )
53 * ->render();
54 *
55 * // Checkbox
56 * InputField::make()
57 * ->type( 'checkbox' )
58 * ->name( 'agree' )
59 * ->label( 'I agree to terms' )
60 * ->size( 'md' )
61 * ->render();
62 *
63 * // Radio
64 * InputField::make()
65 * ->type( 'radio' )
66 * ->name( 'gender' )
67 * ->label( 'Male' )
68 * ->value( 'male' )
69 * ->render();
70 *
71 * // Switch
72 * InputField::make()
73 * ->type( 'switch' )
74 * ->name( 'notifications' )
75 * ->label( 'Enable notifications?' )
76 * ->size( 'md' )
77 * ->render();
78 *
79 * // InputField with error
80 * InputField::make()
81 * ->type( 'text' )
82 * ->name( 'username' )
83 * ->label( 'Username' )
84 * ->error( 'This field is required.' )
85 * ->render();
86 *
87 * // Select
88 * InputField::make()
89 * ->type( 'select' )
90 * ->name( 'interests' )
91 * ->label( 'Interests' )
92 * ->placeholder( 'Select your interests')
93 * ->required( 'Please select an interest')
94 * ->clearable()
95 * ->options( $interests )
96 * ->multiple()
97 * ->searchable()
98 * ->size( 'md' )
99 * ->max_selections( 2 )
100 * ->help_text( 'This is a helper next.' )
101 * ->render();
102 *
103 * ```
104 *
105 * @since 4.0.0
106 */
107 class InputField extends BaseComponent {
108
109 /**
110 * InputField type (text|email|password|number|textarea|checkbox|radio|switch|time).
111 *
112 * @since 4.0.0
113 *
114 * @var string
115 */
116 protected $type = InputType::TEXT;
117
118 /**
119 * InputField name attribute.
120 *
121 * @since 4.0.0
122 *
123 * @var string
124 */
125 protected $name = '';
126
127 /**
128 * InputField ID attribute.
129 *
130 * @since 4.0.0
131 *
132 * @var string
133 */
134 protected $id = '';
135
136 /**
137 * InputField value.
138 *
139 * @since 4.0.0
140 *
141 * @var string
142 */
143 protected $value = '';
144
145 /**
146 * InputField label text.
147 *
148 * @since 4.0.0
149 *
150 * @var string
151 */
152 protected $label = '';
153
154 /**
155 * InputField label HTML (for checkbox/radio labels that contain HTML markup).
156 * When set, this takes precedence over $label for checkbox/radio/switch types.
157 *
158 * @since 4.0.0
159 *
160 * @var string
161 */
162 protected $label_html = '';
163
164 /**
165 * InputField placeholder text.
166 *
167 * @since 4.0.0
168 *
169 * @var string
170 */
171 protected $placeholder = '';
172
173 /**
174 * Search Input placeholder text.
175 *
176 * @since 4.0.0
177 *
178 * @var string
179 */
180 protected $search_placeholder = '';
181
182 /**
183 * Help text below input.
184 *
185 * @since 4.0.0
186 *
187 * @var string
188 */
189 protected $help_text = '';
190
191 /**
192 * Error message text.
193 *
194 * @since 4.0.0
195 *
196 * @var string
197 */
198 protected $error = '';
199
200 /**
201 * Whether input is required.
202 *
203 * @since 4.0.0
204 *
205 * @var bool
206 */
207 protected $required = false;
208
209 /**
210 * Input field attr like alpine attribute for validation
211 *
212 * @since 4.0.0
213 *
214 * @var string
215 */
216 protected $attr = '';
217
218 /**
219 * Whether input is disabled.
220 *
221 * @since 4.0.0
222 *
223 * @var bool
224 */
225 protected $disabled = false;
226
227 /**
228 * Whether input is loading
229 *
230 * @since 4.0.0
231 *
232 * @var boolean
233 */
234 protected $loading = false;
235
236 /**
237 * Whether input is checked (checkbox/radio/switch).
238 *
239 * @since 4.0.0
240 *
241 * @var bool
242 */
243 protected $checked = false;
244
245 /**
246 * Whether checkbox is intermediate state.
247 *
248 * @since 4.0.0
249 *
250 * @var bool
251 */
252 protected $intermediate = false;
253
254 /**
255 * Whether to use WordPress media library instead of native file input.
256 *
257 * @since 4.0.0
258 *
259 * @var bool
260 */
261 protected $use_wp_media = false;
262
263 /**
264 * WordPress media modal title.
265 *
266 * @since 4.0.0
267 *
268 * @var string
269 */
270 protected $wp_media_title = '';
271
272 /**
273 * WordPress media modal button text.
274 *
275 * @since 4.0.0
276 *
277 * @var string
278 */
279 protected $wp_media_button_text = '';
280
281 /**
282 * WordPress media library type filter.
283 *
284 * @since 4.0.0
285 *
286 * @var string
287 */
288 protected $wp_media_library_type = '';
289
290 /**
291 * Component variant.
292 *
293 * @since 4.0.0
294 *
295 * @var string
296 */
297 protected $variant = '';
298
299 /**
300 * Whether input has clear button.
301 *
302 * @since 4.0.0
303 *
304 * @var bool
305 */
306 protected $clearable = false;
307
308 /**
309 * Whether input is searchable.
310 *
311 * @since 4.0.0
312 *
313 * @var boolean
314 */
315 protected $searchable = false;
316
317 /**
318 * Whether multiple option can be selected in input.
319 *
320 * @since 4.0.0
321 *
322 * @var boolean
323 */
324 protected $multiple = false;
325
326 /**
327 * Left icon SVG markup.
328 *
329 * @since 4.0.0
330 *
331 * @var string
332 */
333 protected $left_icon = '';
334
335 /**
336 * Right icon SVG markup.
337 *
338 * @since 4.0.0
339 *
340 * @var string
341 */
342 protected $right_icon = '';
343
344 /**
345 * InputField size (sm|md).
346 *
347 * @since 4.0.0
348 *
349 * @var string
350 */
351 protected $size = Size::MD;
352
353 /**
354 * Options for select input field.
355 *
356 * @since 4.0.0
357 *
358 * @var array
359 */
360 protected $options = array();
361
362
363
364 /**
365 * Grouped option for input field.
366 *
367 * @since 4.0.0
368 *
369 * @var array
370 */
371 protected $groups = array();
372
373 /**
374 * Max number of input option to be selected.
375 *
376 * @since 4.0.0
377 *
378 * @var integer
379 */
380 protected $max_selections = 0;
381
382 /**
383 * Close select input on selecting option.
384 *
385 * @since 4.0.0
386 *
387 * @var boolean
388 */
389 protected $close_on_select = null;
390
391 /**
392 * Raw JS onChange callback for select input.
393 *
394 * @since 4.0.0
395 *
396 * @var string|null
397 */
398 protected $on_change = null;
399
400 /**
401 * Max Height for select input.
402 *
403 * @since 4.0.0
404 *
405 * @var integer
406 */
407 protected $max_height = 280;
408
409 /**
410 * Empty state message for input.
411 *
412 * @since 4.0.0
413 *
414 * @var string
415 */
416 protected $empty_message = '';
417
418 /**
419 * Loading state message for input.
420 *
421 * @since 4.0.0
422 *
423 * @var string
424 */
425 protected $loading_message = '';
426
427 /**
428 * Selection time mode (12|24).
429 *
430 * @since 4.0.0
431 *
432 * @var int|null
433 */
434 protected $selection_time_mode = null;
435
436 /**
437 * Time input option interval in minutes.
438 *
439 * @since 4.0.0
440 *
441 * @var int
442 */
443 protected $interval = 30;
444
445 /**
446 * Whether to show password strength meter.
447 *
448 * @since 4.0.0
449 *
450 * @var bool
451 */
452 protected $show_strength = false;
453
454 /**
455 * Minimum password strength required.
456 *
457 * @since 4.0.0
458 *
459 * @var int
460 */
461 protected $min_strength = 3;
462
463 /**
464 * Set input type.
465 *
466 * @since 4.0.0
467 *
468 * @param string $type InputField type.
469 *
470 * @return $this
471 */
472 public function type( $type ) {
473 $allowed = $this->get_allowed_types();
474 if ( in_array( $type, $allowed, true ) ) {
475 $this->type = $type;
476 }
477 return $this;
478 }
479
480 /**
481 * Get allowed input types
482 *
483 * @since 4.0.0
484 *
485 * @return array
486 */
487 public function get_allowed_types() {
488 $class = new ReflectionClass( InputType::class );
489 $constants = $class->getConstants();
490
491 return array_values( $constants );
492 }
493
494 /**
495 * Set input name.
496 *
497 * @since 4.0.0
498 *
499 * @param string $name InputField name.
500 *
501 * @return $this
502 */
503 public function name( $name ) {
504 $this->name = sanitize_text_field( $name );
505 return $this;
506 }
507
508 /**
509 * Set input ID.
510 *
511 * @since 4.0.0
512 *
513 * @param string $id InputField ID.
514 *
515 * @return $this
516 */
517 public function id( $id ) {
518 $this->id = sanitize_text_field( $id );
519 return $this;
520 }
521
522 /**
523 * Set input value.
524 *
525 * @since 4.0.0
526 *
527 * @param string $value InputField value.
528 *
529 * @return $this
530 */
531 public function value( $value ) {
532 $this->value = $value;
533 return $this;
534 }
535
536 /**
537 * Set input label.
538 *
539 * @since 4.0.0
540 *
541 * @param string $label Label text.
542 *
543 * @return $this
544 */
545 public function label( $label ) {
546 $this->label = $label;
547 return $this;
548 }
549
550 /**
551 * Set input label as HTML markup (for checkbox/radio/switch labels containing links or other HTML).
552 * Content is sanitized via wp_kses with a safe set of allowed tags.
553 *
554 * @since 4.0.0
555 *
556 * @param string $html Raw HTML label content.
557 * @param array $allowed_tags Optional. Allowed HTML tags. Defaults to a, span, strong, em.
558 *
559 * @return $this
560 */
561 public function label_html( $html, $allowed_tags = array() ) {
562 if ( empty( $allowed_tags ) ) {
563 $allowed_tags = array(
564 'a' => array(
565 'href' => true,
566 'target' => true,
567 'class' => true,
568 'rel' => true,
569 ),
570 'span' => array( 'class' => true ),
571 'strong' => array(),
572 'em' => array(),
573 );
574 }
575 $this->label_html = wp_kses( $html, $allowed_tags );
576 return $this;
577 }
578
579 /**
580 * Set input placeholder.
581 *
582 * @since 4.0.0
583 *
584 * @param string $placeholder Placeholder text.
585 * @param bool $search whether placeholder is for search input.
586 *
587 * @return $this
588 */
589 public function placeholder( $placeholder, $search = false ) {
590 if ( $search ) {
591 $this->search_placeholder = $placeholder;
592 } else {
593 $this->placeholder = $placeholder;
594 }
595 return $this;
596 }
597
598 /**
599 * Set the help text with HTML sanitization.
600 *
601 * @since 4.0.0
602 *
603 * @param string $text Help text.
604 * @param array $allowed_tags Optional. Allowed HTML tags and attributes for wp_kses().
605 *
606 * @return $this
607 */
608 public function help_text( $text, $allowed_tags = array() ) {
609
610 if ( ! empty( $allowed_tags ) && is_array( $allowed_tags ) ) {
611 $this->help_text = wp_kses( $text, $allowed_tags );
612 return $this;
613 }
614
615 $this->help_text = esc_html( $text );
616 return $this;
617 }
618
619 /**
620 * Set the max height for select input
621 *
622 * @since 4.0.0
623 *
624 * @param integer $max_height the max height.
625 *
626 * @return self
627 */
628 public function max_height( $max_height = 280 ): self {
629 $this->max_height = $max_height;
630 return $this;
631 }
632
633 /**
634 * Collapse select input on selecting option.
635 *
636 * @since 4.0.0
637 *
638 * @param boolean $close_on_select close on selecting option.
639 *
640 * @return self
641 */
642 public function collapsable( $close_on_select = true ): self {
643 $this->close_on_select = $close_on_select;
644 return $this;
645 }
646
647 /**
648 * Set raw JS onChange callback for select input.
649 *
650 * Example: ->on_change( 'function(value){ console.log(value); }' )
651 *
652 * @since 4.0.0
653 *
654 * @param string $on_change Raw JS function string.
655 *
656 * @return self
657 */
658 public function on_change( $on_change ): self {
659 $this->on_change = $on_change;
660 return $this;
661 }
662
663 /**
664 * Set error message.
665 *
666 * @since 4.0.0
667 *
668 * @param string $error Error message.
669 *
670 * @return $this
671 */
672 public function error( $error ) {
673 $this->error = $error;
674 return $this;
675 }
676
677 /**
678 * Set required state.
679 *
680 * @since 4.0.0
681 *
682 * @param bool|string $required Whether input is required.
683 *
684 * @return $this
685 */
686 public function required( $required = true ) {
687 $this->required = $required;
688 return $this;
689 }
690
691 /**
692 * Set disabled state.
693 *
694 * @since 4.0.0
695 *
696 * @param bool $disabled Whether input is disabled.
697 *
698 * @return $this
699 */
700 public function disabled( $disabled = true ) {
701 $this->disabled = (bool) $disabled;
702 return $this;
703 }
704
705 /**
706 * Set loading state.
707 *
708 * @since 4.0.0
709 *
710 * @param bool $loading Whether input is loading.
711 *
712 * @return $this
713 */
714 public function loading( $loading = true ) {
715 $this->loading = (bool) $loading;
716 return $this;
717 }
718
719 /**
720 * Set checked state.
721 *
722 * @since 4.0.0
723 *
724 * @param bool $checked Whether input is checked.
725 *
726 * @return $this
727 */
728 public function checked( $checked = true ) {
729 $this->checked = (bool) $checked;
730 return $this;
731 }
732
733 /**
734 * Set intermediate state for checkbox.
735 *
736 * @since 4.0.0
737 *
738 * @param bool $intermediate Whether checkbox is intermediate.
739 *
740 * @return $this
741 */
742 public function intermediate( $intermediate = true ) {
743 $this->intermediate = $intermediate;
744
745 return $this;
746 }
747
748 /**
749 * Enable WordPress media library instead of native file input.
750 *
751 * @since 4.0.0
752 *
753 * @param bool $use_wp_media Whether to use WordPress media library.
754 *
755 * @return $this
756 */
757 public function use_wp_media( $use_wp_media = true ) {
758 $this->use_wp_media = (bool) $use_wp_media;
759
760 return $this;
761 }
762
763 /**
764 * Set WordPress media modal title.
765 *
766 * @since 4.0.0
767 *
768 * @param string $title Modal title.
769 *
770 * @return $this
771 */
772 public function wp_media_title( $title ) {
773 $this->wp_media_title = $title;
774
775 return $this;
776 }
777
778 /**
779 * Set WordPress media modal button text.
780 *
781 * @since 4.0.0
782 *
783 * @param string $text Button text.
784 *
785 * @return $this
786 */
787 public function wp_media_button_text( $text ) {
788 $this->wp_media_button_text = $text;
789
790 return $this;
791 }
792
793 /**
794 * Set WordPress media library type filter.
795 *
796 * @since 4.0.0
797 *
798 * @param string $type Library type (image, video, audio, application).
799 *
800 * @return $this
801 */
802 public function wp_media_library_type( $type ) {
803 $this->wp_media_library_type = $type;
804
805 return $this;
806 }
807
808 /**
809 * Set component variant.
810 *
811 * @since 4.0.0
812 *
813 * @param string $variant Component variant.
814 *
815 * @return $this
816 */
817 public function variant( $variant ) {
818 $this->variant = $variant;
819
820 return $this;
821 }
822
823 /**
824 * Enable clear button.
825 *
826 * @since 4.0.0
827 *
828 * @param bool $clearable Whether to show clear button.
829 *
830 * @return $this
831 */
832 public function clearable( $clearable = true ) {
833 $this->clearable = (bool) $clearable;
834 return $this;
835 }
836
837 /**
838 * Set left icon.
839 *
840 * @since 4.0.0
841 *
842 * @param string $icon SVG icon markup.
843 *
844 * @return $this
845 */
846 public function left_icon( $icon ) {
847 $this->left_icon = $icon;
848 return $this;
849 }
850
851 /**
852 * Set right icon.
853 *
854 * @since 4.0.0
855 *
856 * @param string $icon SVG icon markup.
857 *
858 * @return $this
859 */
860 public function right_icon( $icon ) {
861 $this->right_icon = $icon;
862 return $this;
863 }
864
865 /**
866 * Set input size.
867 *
868 * @since 4.0.0
869 *
870 * @param string $size Size (sm|md).
871 *
872 * @return $this
873 */
874 public function size( $size ) {
875 $allowed = array( 'sm', 'md', 'lg' );
876 if ( in_array( $size, $allowed, true ) ) {
877 $this->size = $size;
878 }
879 return $this;
880 }
881
882 /**
883 * Set whether multiple select enabled or not.
884 *
885 * @since 4.0.0
886 *
887 * @param boolean $multiple whether multiple select is enable.
888 *
889 * @return self
890 */
891 public function multiple( $multiple = true ): self {
892 $this->multiple = $multiple;
893 return $this;
894 }
895
896 /**
897 * Set if input field is searchable.
898 *
899 * @since 4.0.0
900 *
901 * @param boolean $searchable whether input is searchable.
902 *
903 * @return self
904 */
905 public function searchable( $searchable = true ): self {
906 $this->searchable = $searchable;
907 return $this;
908 }
909
910 /**
911 * Set empty message for input.
912 *
913 * @since 4.0.0
914 *
915 * @param string $empty_message the empty message.
916 *
917 * @return self
918 */
919 public function empty_message( $empty_message = '' ): self {
920 $this->empty_message = $empty_message;
921 return $this;
922 }
923
924 /**
925 * Set loading message for input.
926 *
927 * @since 4.0.0
928 *
929 * @param string $loading_message the empty message.
930 *
931 * @return self
932 */
933 public function loading_message( $loading_message = '' ): self {
934 $this->loading_message = $loading_message;
935 return $this;
936 }
937
938 /**
939 * Set selection time mode.
940 *
941 * @since 4.0.0
942 *
943 * @param int $mode Mode (12 or 24).
944 *
945 * @return self
946 */
947 public function selection_time_mode( $mode = 12 ): self {
948 $this->selection_time_mode = $mode;
949 return $this;
950 }
951
952 /**
953 * Set interval for time input options.
954 *
955 * @since 4.0.0
956 *
957 * @param int $interval Interval in minutes.
958 *
959 * @return self
960 */
961 public function interval( int $interval = 30 ): self {
962 $this->interval = max( 1, $interval );
963 return $this;
964 }
965
966 /**
967 * Set whether to show password strength meter.
968 *
969 * @since 4.0.0
970 *
971 * @param bool $show_strength Whether to show strength meter.
972 *
973 * @return self
974 */
975 public function show_strength( $show_strength = true ): self {
976 $this->show_strength = $show_strength;
977 return $this;
978 }
979
980 /**
981 * Set minimum password strength.
982 *
983 * @since 4.0.0
984 *
985 * @param int $min_strength Minimum strength score (0-5).
986 *
987 * @return self
988 */
989 public function min_strength( $min_strength = 3 ): self {
990 $this->min_strength = $min_strength;
991 return $this;
992 }
993
994
995
996 /**
997 * Options for select input field.
998 *
999 * @since 4.0.0
1000 *
1001 * @param array $options the options for input field.
1002 *
1003 * Example format for $options:
1004 * ```
1005 * $options = array(
1006 * array(
1007 * 'label' => '',
1008 * 'value' => '',
1009 * 'icon' => '',
1010 * 'description' => '',
1011 * )
1012 * );
1013 * ```.
1014 *
1015 * @return self
1016 */
1017 public function options( $options = array() ): self {
1018 $this->options = $options;
1019 return $this;
1020 }
1021
1022 /**
1023 * Set the number of max option to be selected.
1024 *
1025 * @since 4.0.0
1026 *
1027 * @param integer $max_selections the number of max selections.
1028 *
1029 * @return self
1030 */
1031 public function max_selections( $max_selections = 3 ): self {
1032 $this->max_selections = $max_selections;
1033 return $this;
1034 }
1035
1036 /**
1037 * Grouped Options for select input field.
1038 *
1039 * @since 4.0.0
1040 *
1041 * @param array $groups the options for input field.
1042 *
1043 * Example format for $groups:
1044 *
1045 * ```
1046 * $groups = array(
1047 * array(
1048 * 'label' => '',
1049 * 'options' => array(
1050 * array(
1051 * 'label' => '',
1052 * 'value' => '',
1053 * ),
1054 * array(
1055 * 'label' => '',
1056 * 'value' => '',
1057 * ),
1058 * array(
1059 * 'label' => '',
1060 * 'value' => '',
1061 * ),
1062 * ),
1063 * ),
1064 * );
1065 * ```.
1066 *
1067 * @return self
1068 */
1069 public function groups( $groups = array() ): self {
1070 $this->groups = $groups;
1071 return $this;
1072 }
1073
1074 /**
1075 * Render select input trigger button.
1076 *
1077 * @since 4.0.0
1078 *
1079 * @return string
1080 */
1081 protected function render_select_input_button(): string {
1082
1083 $single_input = '
1084 <div class="tutor-select-value">
1085 <template x-if="selectedOptions.length > 0 && selectedOptions[0].icon">
1086 <span class="tutor-select-value-icon" x-data="tutorIcon({ name: selectedOptions[0].icon })"></span>
1087 </template>
1088 <span
1089 class="tutor-select-value-text"
1090 :class="{ \'tutor-select-value-placeholder\': selectedValues.size === 0 }"
1091 x-text="displayValue"
1092 ></span>
1093 </div>';
1094
1095 $multiple_input = sprintf(
1096 '
1097 <div class="tutor-select-tags">
1098 <template x-if="selectedOptions.length === 0">
1099 <span class="tutor-select-value-placeholder" x-text="placeholder"></span>
1100 </template>
1101 <template x-for="option in selectedOptions" :key="option.value">
1102 <span class="tutor-select-tag">
1103 <span class="tutor-select-tag-label" x-text="option.label"></span>
1104 <button
1105 type="button"
1106 class="tutor-select-tag-remove"
1107 @click.stop="deselectOption(option, $event)"
1108 :aria-label="\'Remove \' + option.label"
1109 >
1110 %s
1111 </button>
1112 </span>
1113 </template>
1114 </div>
1115 ',
1116 SvgIcon::make()->name( Icon::CROSS )->size( 12 )->get()
1117 );
1118
1119 $right_icon_html = $this->right_icon ? $this->right_icon : SvgIcon::make()->name( Icon::CHEVRON_DOWN )->size( 16 )->get();
1120
1121 $input_button = $this->multiple ? $multiple_input : $single_input;
1122
1123 return sprintf(
1124 '<button
1125 type="button"
1126 class="tutor-select-trigger"
1127 data-select-trigger
1128 @click="toggle()"
1129 :aria-expanded="isOpen.toString()"
1130 :aria-haspopup="\'listbox\'"
1131 :disabled="disabled"
1132 >
1133 %s
1134 <div class="tutor-select-actions" x-cloak>
1135 <template x-if="canClear">
1136 <button
1137 type="button"
1138 class="tutor-select-clear"
1139 @click.stop="clear($event)"
1140 aria-label="%s"
1141 >
1142 %s
1143 </button>
1144 </template>
1145 <span class="tutor-select-arrow" :data-open="isOpen.toString()">%s</span>
1146 </div>
1147 </button>
1148 ',
1149 $input_button,
1150 esc_attr__( 'Clear selection', 'tutor' ),
1151 SvgIcon::make()->name( Icon::CROSS )->size( 16 )->get(),
1152 $right_icon_html
1153 );
1154 }
1155
1156 /**
1157 * Render option values for different selection.
1158 *
1159 * @since 4.0.0
1160 *
1161 * @param boolean $is_grouped whether the options are grouped.
1162 *
1163 * @return string
1164 */
1165 protected function render_selection_option( $is_grouped = false ): string {
1166 $option_type = $is_grouped ? 'group.options' : 'filteredOptions';
1167
1168 return sprintf(
1169 '
1170 <template x-for="(option, optionIndex) in %s" :key="option.value">
1171 <div
1172 class="tutor-select-option"
1173 data-select-option
1174 :data-disabled="option.disabled ? \'true\' : \'false\'"
1175 :data-selected="isSelected(option) ? \'true\' : \'false\'"
1176 :data-highlighted="isHighlighted(filteredOptions.indexOf(option)) ? \'true\' : \'false\'"
1177 @click="selectOption(option)"
1178 @mouseenter="highlightedIndex = filteredOptions.indexOf(option)"
1179 role="option"
1180 :aria-selected="isSelected(option).toString()"
1181 >
1182 <template x-if="option.icon">
1183 <span class="tutor-select-option-icon" x-data="tutorIcon({ name: option.icon })"></span>
1184 </template>
1185 <div class="tutor-select-option-content">
1186 <template x-if="option.html_label">
1187 <div class="tutor-select-option-label" x-html="option.html_label"></div>
1188 </template>
1189 <template x-if="!option.html_label">
1190 <div class="tutor-select-option-label" x-text="option.label"></div>
1191 </template>
1192 <template x-if="option.description">
1193 <div class="tutor-select-option-description" x-text="option.description"></div>
1194 </template>
1195 </div>
1196 </div>
1197 </template>
1198 ',
1199 $option_type
1200 );
1201 }
1202
1203 /**
1204 * Get grouped select option markup
1205 *
1206 * @since 4.0.0
1207 *
1208 * @return string
1209 */
1210 protected function get_grouped_option_markup(): string {
1211 return sprintf(
1212 '
1213 <template x-if="!isLoading && !loading && hasGroups">
1214 <template x-for="(group, groupIndex) in filteredGroups" :key="groupIndex">
1215 <div class="tutor-select-group">
1216 <div class="tutor-select-group-label" x-text="group.label"></div>
1217 <div class="tutor-select-group-options">
1218 %s
1219 </div>
1220 </div>
1221 </template>
1222 </template>',
1223 $this->render_selection_option( true )
1224 );
1225 }
1226
1227 /**
1228 * Get flat select option markup
1229 *
1230 * @since 4.0.0
1231 *
1232 * @return string
1233 */
1234 protected function get_flat_option_markup(): string {
1235 return sprintf(
1236 '
1237 <template x-if="!isLoading && !loading && !hasGroups">
1238 %s
1239 </template>
1240 ',
1241 $this->render_selection_option()
1242 );
1243 }
1244
1245 /**
1246 * Get select input search field markup
1247 *
1248 * @since 4.0.0
1249 *
1250 * @return string
1251 */
1252 protected function get_search_input_markup(): string {
1253
1254 $left_icon_html = $this->left_icon ? $this->left_icon : SvgIcon::make()->name( Icon::SEARCH_2 )->size( 20 )->get();
1255
1256 $search_input = sprintf(
1257 '
1258 <template x-if="searchable">
1259 <div class="tutor-select-search">
1260 <span class="tutor-select-search-icon">
1261 %s
1262 </span>
1263 <input
1264 type="text"
1265 class="tutor-select-search-input"
1266 data-select-search
1267 :placeholder="searchPlaceholder"
1268 x-model="searchQuery"
1269 @input="handleSearch($event.target.value)"
1270 @keydown.stop
1271 />
1272 </div>
1273 </div>
1274 </template>
1275 ',
1276 $left_icon_html
1277 );
1278
1279 return $search_input;
1280 }
1281
1282 /**
1283 * Render select input option list.
1284 *
1285 * @since 4.0.0
1286 *
1287 * @return string
1288 */
1289 protected function render_select_input_options(): string {
1290
1291 $loading_state = '
1292 <template x-if="isLoading || loading">
1293 <div class="tutor-select-loading">
1294 <span class="tutor-select-loading-spinner"></span>
1295 <span x-text="loadingMessage"></span>
1296 </div>
1297 </template>';
1298
1299 $empty_state = '
1300 <template x-if="!isLoading && !loading && filteredOptions.length === 0">
1301 <div class="tutor-select-empty" x-text="emptyMessage"></div>
1302 </template>';
1303
1304 $grouped_options = $this->get_grouped_option_markup();
1305 $flat_options = $this->get_flat_option_markup();
1306
1307 return sprintf(
1308 '<div
1309 x-show="isOpen"
1310 x-cloak
1311 x-transition
1312 @click.outside="close()"
1313 class="tutor-select-menu"
1314 data-select-menu
1315 :data-position="dropdownPosition"
1316 :style="{ maxHeight: maxHeight + \'px\' }"
1317 >
1318 %s
1319 <div class="tutor-select-options">
1320 %s
1321 %s
1322 %s
1323 %s
1324 </div>
1325 </div>',
1326 $this->get_search_input_markup(),
1327 $loading_state,
1328 $empty_state,
1329 $grouped_options,
1330 $flat_options
1331 );
1332 }
1333
1334 /**
1335 * Render the Select Input Component HTML.
1336 *
1337 * @since 4.0.0
1338 *
1339 * @return string
1340 */
1341 protected function render_select_input(): string {
1342 $props = array(
1343 'options' => $this->options,
1344 'groups' => $this->groups,
1345
1346 'searchable' => $this->searchable,
1347 'clearable' => $this->clearable,
1348 'disabled' => $this->disabled,
1349 'loading' => $this->loading,
1350
1351 'multiple' => $this->multiple,
1352
1353 'name' => $this->name,
1354 'required' => $this->required,
1355
1356 'placeholder' => $this->placeholder,
1357 'searchPlaceholder' => $this->search_placeholder,
1358 'emptyMessage' => $this->empty_message,
1359 'loadingMessage' => $this->loading_message,
1360 'maxHeight' => $this->max_height,
1361
1362 );
1363
1364 if ( $this->value ) {
1365 $props['value'] = $this->value;
1366 }
1367
1368 if ( $this->max_selections ) {
1369 $props['maxSelections'] = $this->max_selections;
1370 }
1371
1372 if ( null !== $this->close_on_select ) {
1373 $props['closeOnSelect'] = $this->close_on_select;
1374 }
1375
1376 $size_class = '';
1377 if ( Size::SM === $this->size ) {
1378 $size_class = 'tutor-select-sm';
1379 } elseif ( Size::LG === $this->size ) {
1380 $size_class = 'tutor-select-lg';
1381 }
1382
1383 $props_json_raw = wp_json_encode( $props );
1384 $props_json = $props_json_raw;
1385
1386 if ( null !== $this->on_change && '' !== $this->on_change ) {
1387 $props_json = sprintf( 'Object.assign(%s, { onChange: %s })', $props_json_raw, $this->on_change );
1388 }
1389
1390 $props_json = htmlspecialchars( $props_json, ENT_QUOTES, 'UTF-8' );
1391 $select_input_buttons = $this->render_select_input_button() ?? '';
1392 $select_input_options = $this->render_select_input_options() ?? '';
1393
1394 return sprintf(
1395 '<div
1396 x-data="tutorSelect(%s)"
1397 class="tutor-select %s"
1398 :data-disabled="disabled.toString()"
1399 %s
1400 >
1401 %s
1402 %s
1403 </div>',
1404 $props_json,
1405 $size_class,
1406 $this->get_attributes_string(),
1407 $select_input_buttons,
1408 $select_input_options
1409 );
1410 }
1411
1412 /**
1413 * Render password input.
1414 *
1415 * @since 4.0.0
1416 *
1417 * @return string Password HTML.
1418 */
1419 protected function render_password_input() {
1420 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1421
1422 $input_classes = 'tutor-input';
1423 if ( Size::SM === $this->size ) {
1424 $input_classes .= ' tutor-input-sm';
1425 } elseif ( Size::LG === $this->size ) {
1426 $input_classes .= ' tutor-input-lg';
1427 }
1428
1429 // Password inputs always have a right icon (toggle button).
1430 $input_classes .= ' tutor-input-content-right';
1431
1432 if ( ! empty( $this->left_icon ) ) {
1433 $input_classes .= ' tutor-input-content-left';
1434 }
1435
1436 $input_attrs = sprintf(
1437 'type="password" id="%s" name="%s" class="%s" %s',
1438 esc_attr( $input_id ),
1439 esc_attr( $this->name ),
1440 esc_attr( $input_classes ),
1441 $this->get_attributes_string()
1442 );
1443
1444 if ( ! empty( $this->placeholder ) ) {
1445 $input_attrs .= sprintf( ' placeholder="%s"', esc_attr( $this->placeholder ) );
1446 }
1447
1448 if ( ! empty( $this->value ) ) {
1449 $input_attrs .= sprintf( ' value="%s"', esc_attr( $this->value ) );
1450 }
1451
1452 if ( $this->disabled ) {
1453 $input_attrs .= ' disabled';
1454 }
1455
1456 $left_icon_html = '';
1457 if ( ! empty( $this->left_icon ) ) {
1458 $left_icon_html = sprintf(
1459 '<div class="tutor-input-content tutor-input-content-left">%s</div>',
1460 $this->left_icon
1461 );
1462 }
1463
1464 // Toggle button is always present for password fields.
1465 $toggle_button = '
1466 <button
1467 type="button"
1468 class="tutor-input-password-toggle"
1469 x-bind="getToggleBindings()"
1470 >
1471 <span x-show="!showPassword" x-cloak>' . SvgIcon::make()->name( Icon::EYE_OFF )->size( 16 )->get() . '</span>
1472 <span x-show="showPassword" x-cloak>' . SvgIcon::make()->name( Icon::EYE )->size( 16 )->get() . '</span>
1473 </button>
1474 ';
1475
1476 $right_icon_html = sprintf(
1477 '<div class="tutor-input-content tutor-input-content-right">%s</div>',
1478 $toggle_button
1479 );
1480
1481 return sprintf(
1482 '<div class="tutor-input-wrapper">
1483 <input %s>
1484 %s
1485 %s
1486 </div>',
1487 $input_attrs,
1488 $left_icon_html,
1489 $right_icon_html
1490 );
1491 }
1492
1493
1494 /**
1495 * Render text/email/password/number input.
1496 *
1497 * @since 4.0.0
1498 *
1499 * @return string InputField HTML.
1500 */
1501 protected function render_text_input() {
1502 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1503
1504 $input_classes = 'tutor-input';
1505
1506 if ( Size::SM === $this->size ) {
1507 $input_classes .= ' tutor-input-sm';
1508 } elseif ( Size::LG === $this->size ) {
1509 $input_classes .= ' tutor-input-lg';
1510 }
1511
1512 if ( ! empty( $this->left_icon ) ) {
1513 $input_classes .= ' tutor-input-content-left';
1514 }
1515 if ( ! empty( $this->right_icon ) ) {
1516 $input_classes .= ' tutor-input-content-right';
1517 }
1518 if ( ! $this->disabled && $this->clearable ) {
1519 $input_classes .= ' tutor-input-content-clear';
1520 }
1521
1522 $input_attrs = sprintf(
1523 'type="%s" id="%s" name="%s" class="%s" %s',
1524 esc_attr( $this->type ),
1525 esc_attr( $input_id ),
1526 esc_attr( $this->name ),
1527 esc_attr( $input_classes ),
1528 $this->get_attributes_string()
1529 );
1530
1531 if ( ! empty( $this->placeholder ) ) {
1532 $input_attrs .= sprintf( ' placeholder="%s"', esc_attr( $this->placeholder ) );
1533 }
1534
1535 if ( ! empty( $this->value ) ) {
1536 $input_attrs .= sprintf( ' value="%s"', esc_attr( $this->value ) );
1537 }
1538
1539 if ( $this->disabled ) {
1540 $input_attrs .= ' disabled';
1541 }
1542
1543 $left_icon_html = '';
1544 if ( ! empty( $this->left_icon ) ) {
1545 $left_icon_html = sprintf(
1546 '<div class="tutor-input-content tutor-input-content-left">%s</div>',
1547 $this->left_icon
1548 );
1549 }
1550
1551 $right_icon_html = '';
1552 if ( ! empty( $this->right_icon ) ) {
1553 $right_icon_html = sprintf(
1554 '<div class="tutor-input-content tutor-input-content-right">%s</div>',
1555 $this->right_icon
1556 );
1557 }
1558
1559 $clear_button_html = '';
1560 if ( ! $this->disabled && $this->clearable ) {
1561 $clear_icon = SvgIcon::make()->name( Icon::CROSS )->size( 16 )->get();
1562
1563 $clear_button_html = sprintf(
1564 '<button
1565 type="button"
1566 class="tutor-input-clear-button"
1567 aria-label="Clear input"
1568 x-cloak
1569 x-show="values[\'%1$s\'] && String(values[\'%1$s\']).length > 0"
1570 @click="setValue(\'%1$s\', \'\')"
1571 >%2$s</button>',
1572 esc_attr( $this->name ),
1573 $clear_icon
1574 );
1575 }
1576
1577 return sprintf(
1578 '<div class="tutor-input-wrapper">
1579 <input %s>
1580 %s
1581 %s
1582 %s
1583 </div>
1584 ',
1585 $input_attrs,
1586 $left_icon_html,
1587 $right_icon_html,
1588 $clear_button_html
1589 );
1590 }
1591
1592 /**
1593 * Render textarea input.
1594 *
1595 * @since 4.0.0
1596 *
1597 * @return string Textarea HTML.
1598 */
1599 protected function render_textarea() {
1600 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1601
1602 $input_classes = 'tutor-input tutor-text-area';
1603
1604 if ( Size::SM === $this->size ) {
1605 $input_classes .= ' tutor-input-sm';
1606 } elseif ( Size::LG === $this->size ) {
1607 $input_classes .= ' tutor-input-lg';
1608 }
1609
1610 if ( $this->clearable ) {
1611 $input_classes .= ' tutor-input-content-clear';
1612 }
1613
1614 $input_attrs = sprintf(
1615 'id="%s" name="%s" class="%s" %s',
1616 esc_attr( $input_id ),
1617 esc_attr( $this->name ),
1618 esc_attr( $input_classes ),
1619 $this->get_attributes_string()
1620 );
1621
1622 if ( ! empty( $this->placeholder ) ) {
1623 $input_attrs .= sprintf( ' placeholder="%s"', esc_attr( $this->placeholder ) );
1624 }
1625
1626 if ( $this->disabled ) {
1627 $input_attrs .= ' disabled';
1628 }
1629
1630 $clear_button_html = '';
1631 if ( $this->clearable ) {
1632 $clear_icon = SvgIcon::make()->name( Icon::CROSS )->size( 16 )->get();
1633 $clear_button_html = sprintf(
1634 '<button
1635 type="button"
1636 class="tutor-input-clear-button"
1637 aria-label="Clear input"
1638 x-cloak
1639 x-show="values[\'%1$s\'] && String(values[\'%1$s\']).length > 0"
1640 @click="setValue(\'%1$s\', \'\')"
1641 >%2$s</button>',
1642 esc_attr( $this->name ),
1643 $clear_icon
1644 );
1645 }
1646
1647 return sprintf(
1648 '<div class="tutor-input-wrapper">
1649 <textarea %s>%s</textarea>
1650 %s
1651 </div>',
1652 $input_attrs,
1653 esc_textarea( $this->value ),
1654 $clear_button_html,
1655 );
1656 }
1657
1658 /**
1659 * Render checkbox input.
1660 *
1661 * @since 4.0.0
1662 *
1663 * @return string Checkbox HTML.
1664 */
1665 protected function render_checkbox() {
1666 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1667
1668 $input_classes = 'tutor-checkbox';
1669 if ( 'md' === $this->size ) {
1670 $input_classes .= ' tutor-checkbox-md';
1671 }
1672 if ( $this->intermediate ) {
1673 $input_classes .= ' tutor-checkbox-intermediate';
1674 }
1675
1676 $input_attrs = sprintf(
1677 'type="checkbox" id="%s" name="%s" class="%s" %s',
1678 esc_attr( $input_id ),
1679 esc_attr( $this->name ),
1680 esc_attr( $input_classes ),
1681 $this->get_attributes_string()
1682 );
1683
1684 if ( ! empty( $this->value ) ) {
1685 $input_attrs .= sprintf( ' value="%s"', esc_attr( $this->value ) );
1686 }
1687
1688 if ( $this->checked ) {
1689 $input_attrs .= ' checked';
1690 }
1691
1692 if ( $this->disabled ) {
1693 $input_attrs .= ' disabled';
1694 }
1695
1696 return sprintf(
1697 '<div class="tutor-input-wrapper">
1698 <input %s>
1699 <label for="%s" class="tutor-label%s">%s</label>
1700 </div>',
1701 $input_attrs,
1702 esc_attr( $input_id ),
1703 $this->required ? ' tutor-label-required' : '',
1704 ! empty( $this->label_html ) ? $this->label_html : $this->esc( $this->label )
1705 );
1706 }
1707
1708 /**
1709 * Render radio input.
1710 *
1711 * @since 4.0.0
1712 *
1713 * @return string Radio HTML.
1714 */
1715 protected function render_radio() {
1716 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1717
1718 $input_classes = 'tutor-radio';
1719 if ( 'md' === $this->size ) {
1720 $input_classes .= ' tutor-radio-md';
1721 }
1722
1723 $input_attrs = sprintf(
1724 'type="radio" id="%s" name="%s" class="%s" %s',
1725 esc_attr( $input_id ),
1726 esc_attr( $this->name ),
1727 esc_attr( $input_classes ),
1728 $this->get_attributes_string()
1729 );
1730
1731 if ( ! empty( $this->value ) ) {
1732 $input_attrs .= sprintf( ' value="%s"', esc_attr( $this->value ) );
1733 }
1734
1735 if ( $this->checked ) {
1736 $input_attrs .= ' checked';
1737 }
1738
1739 if ( $this->disabled ) {
1740 $input_attrs .= ' disabled';
1741 }
1742
1743 return sprintf(
1744 '<div class="tutor-input-wrapper">
1745 <input %s>
1746 <label for="%s" class="tutor-label%s">%s</label>
1747 </div>',
1748 $input_attrs,
1749 esc_attr( $input_id ),
1750 $this->required ? ' tutor-label-required' : '',
1751 ! empty( $this->label_html ) ? $this->label_html : $this->esc( $this->label )
1752 );
1753 }
1754
1755 /**
1756 * Render switch input.
1757 *
1758 * @since 4.0.0
1759 *
1760 * @return string Switch HTML.
1761 */
1762 protected function render_switch() {
1763 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1764
1765 $input_classes = 'tutor-switch';
1766 if ( 'md' === $this->size ) {
1767 $input_classes .= ' tutor-switch-md';
1768 }
1769 if ( $this->intermediate ) {
1770 $input_classes .= ' tutor-switch--intermediate';
1771 }
1772
1773 $input_attrs = sprintf(
1774 'type="checkbox" id="%s" name="%s" class="%s" %s',
1775 esc_attr( $input_id ),
1776 esc_attr( $this->name ),
1777 esc_attr( $input_classes ),
1778 $this->get_attributes_string()
1779 );
1780
1781 if ( ! empty( $this->value ) ) {
1782 $input_attrs .= sprintf( ' value="%s"', esc_attr( $this->value ) );
1783 }
1784
1785 if ( $this->checked ) {
1786 $input_attrs .= ' checked';
1787 }
1788
1789 if ( $this->disabled ) {
1790 $input_attrs .= ' disabled';
1791 }
1792
1793 return sprintf(
1794 '<div class="tutor-input-wrapper">
1795 <input %s>
1796 <label for="%s" class="tutor-label%s">%s</label>
1797 </div>',
1798 $input_attrs,
1799 esc_attr( $input_id ),
1800 $this->required ? ' tutor-label-required' : '',
1801 $this->esc( $this->label )
1802 );
1803 }
1804
1805 /**
1806 * Render date/time input.
1807 *
1808 * @since 4.0.0
1809 *
1810 * @return string InputField HTML.
1811 */
1812 protected function render_date_input() {
1813 $original_type = $this->type;
1814 $original_left_icon = $this->left_icon;
1815 $this->type = 'text';
1816
1817 if ( empty( $this->left_icon ) && empty( $this->right_icon ) ) {
1818 $this->left_icon = SvgIcon::make()->name( Icon::CALENDAR_2 )->size( 20 )->get();
1819 }
1820
1821 $options = array(
1822 'inputMode' => true,
1823 );
1824
1825 if ( InputType::DATE_TIME === $original_type ) {
1826 $options['selectionTimeMode'] = $this->selection_time_mode ? $this->selection_time_mode : 12;
1827 } elseif ( ! is_null( $this->selection_time_mode ) ) {
1828 $options['selectionTimeMode'] = $this->selection_time_mode;
1829 }
1830
1831 $config = array(
1832 'options' => $options,
1833 );
1834
1835 $json_config = wp_json_encode( $config );
1836 $this->attr( 'x-data', "tutorCalendar($json_config)" );
1837 $this->attr( 'readonly', 'readonly' );
1838
1839 $html = $this->render_text_input();
1840
1841 $this->type = $original_type;
1842 $this->left_icon = $original_left_icon;
1843
1844 return $html;
1845 }
1846
1847 /**
1848 * Render time input.
1849 *
1850 * @since 4.0.0
1851 *
1852 * @return string InputField HTML.
1853 */
1854 protected function render_time_input() {
1855 $props = array(
1856 'name' => $this->name,
1857 'value' => $this->value,
1858 'placeholder' => $this->placeholder,
1859 'disabled' => $this->disabled,
1860 'clearable' => $this->clearable,
1861 'required' => $this->required,
1862 'interval' => $this->interval,
1863 );
1864
1865 $props_json = htmlspecialchars( wp_json_encode( $props ), ENT_QUOTES, 'UTF-8' );
1866 $input_icon = $this->left_icon;
1867 if ( empty( $input_icon ) ) {
1868 $input_icon = SvgIcon::make()->name( Icon::CLOCK )->size( 20 )->get();
1869 }
1870 $clear_icon = SvgIcon::make()->name( Icon::CROSS )->size( 12 )->get();
1871
1872 return sprintf(
1873 '<div
1874 x-data="tutorTimeInput(%1$s)"
1875 class="tutor-time-input"
1876 @click.outside="handleClickOutside()"
1877 %2$s
1878 >
1879 <div class="tutor-input-wrapper" x-ref="trigger">
1880 <input
1881 type="text"
1882 class="tutor-input tutor-input-content-left"
1883 :class="{ \'tutor-input-content-clear\': canClear }"
1884 :placeholder="placeholder"
1885 :disabled="disabled"
1886 :value="value"
1887 @click.stop="toggleDropdown()"
1888 @keydown="onInputKeydown($event)"
1889 @input="onInputChange($event)"
1890 autocomplete="off"
1891 aria-haspopup="listbox"
1892 :aria-expanded="open.toString()"
1893 :aria-disabled="disabled.toString()"
1894 />
1895
1896 <span class="tutor-input-content tutor-input-content-left" aria-hidden="true">
1897 %3$s
1898 </span>
1899
1900 <button
1901 type="button"
1902 class="tutor-input-clear-button"
1903 x-show="canClear"
1904 @click.stop="clearValue()"
1905 aria-label="%4$s"
1906 >
1907 %5$s
1908 </button>
1909 </div>
1910
1911 <div
1912 x-ref="content"
1913 class="tutor-time-input-menu"
1914 x-show="open"
1915 x-cloak
1916 x-transition.opacity.scale.origin.top
1917 @keydown="onListKeydown($event)"
1918 >
1919 <template x-for="(option, index) in options" :key="option">
1920 <button
1921 type="button"
1922 class="tutor-time-input-option"
1923 :data-option-index="index"
1924 :data-active="(highlightedIndex === index).toString()"
1925 :data-selected="(value === option).toString()"
1926 @click="selectOption(option)"
1927 @mousemove="highlightedIndex = index"
1928 @focus="highlightedIndex = index"
1929 x-text="option"
1930 ></button>
1931 </template>
1932 </div>
1933 </div>',
1934 $props_json,
1935 $this->get_attributes_string(),
1936 $input_icon,
1937 esc_attr__( 'Clear time', 'tutor' ),
1938 $clear_icon
1939 );
1940 }
1941
1942
1943
1944 /**
1945 * Get the input field HTML.
1946 *
1947 * @since 4.0.0
1948 *
1949 * @return string HTML output.
1950 */
1951 public function get(): string {
1952 if ( empty( $this->name ) ) {
1953 return '';
1954 }
1955
1956 $input_id = ! empty( $this->id ) ? $this->id : $this->name;
1957
1958 // Field wrapper classes.
1959 $field_classes = array( 'tutor-input-field' );
1960 if ( ! empty( $this->error ) ) {
1961 $field_classes[] = ' tutor-input-field-error';
1962 }
1963
1964 if ( isset( $this->attributes['class'] ) ) {
1965 $field_classes[] = $this->attributes['class'];
1966 }
1967
1968 // Render label for text inputs.
1969 $label_html = '';
1970 if ( ! in_array( $this->type, array( InputType::CHECKBOX, InputType::RADIO, InputType::SWITCH ), true ) && ! empty( $this->label ) ) {
1971 $label_html = sprintf(
1972 '<label for="%s" class="tutor-label%s">%s</label>',
1973 esc_attr( $input_id ),
1974 $this->required ? ' tutor-label-required' : '',
1975 $this->esc( $this->label )
1976 );
1977 }
1978
1979 // Render input based on type.
1980 $input_html = '';
1981 $root_attrs = '';
1982 switch ( $this->type ) {
1983 case InputType::PASSWORD:
1984 $input_html = $this->render_password_input();
1985
1986 $password_props = array(
1987 'showStrength' => $this->show_strength,
1988 'minStrength' => $this->min_strength,
1989 );
1990 $alpine_data = sprintf( 'tutorPasswordInput(%s)', htmlspecialchars( wp_json_encode( $password_props ), ENT_QUOTES, 'UTF-8' ) );
1991 $root_attrs = sprintf( ' x-data="%s"', $alpine_data );
1992 break;
1993 case InputType::TEXTAREA:
1994 $input_html = $this->render_textarea();
1995 break;
1996 case InputType::CHECKBOX:
1997 $input_html = $this->render_checkbox();
1998 break;
1999 case InputType::RADIO:
2000 $input_html = $this->render_radio();
2001 break;
2002 case InputType::SWITCH:
2003 $input_html = $this->render_switch();
2004 break;
2005 case InputType::DATE:
2006 case InputType::DATE_TIME:
2007 $input_html = $this->render_date_input();
2008 break;
2009 case InputType::TIME:
2010 $input_html = $this->render_time_input();
2011 break;
2012 case InputType::SELECT:
2013 $input_html = $this->render_select_input();
2014 break;
2015
2016 default:
2017 $input_html = $this->render_text_input();
2018 break;
2019 }
2020
2021 $error_html = sprintf(
2022 '<div
2023 class="tutor-error-text"
2024 x-cloak
2025 x-show="errors[\'%1$s\']"
2026 x-text="errors?.[\'%1$s\']?.message"
2027 role="alert"
2028 aria-live="polite"
2029 ></div>',
2030 esc_attr( $this->name )
2031 );
2032
2033 $help_html = sprintf(
2034 '<div
2035 class="tutor-help-text"
2036 x-show="!errors?.[\'%1$s\']?.message"
2037 >%2$s</div>',
2038 esc_attr( $this->name ),
2039 $this->help_text
2040 );
2041
2042 $strength_meter_html = '';
2043 if ( InputType::PASSWORD === $this->type && $this->show_strength ) {
2044 $strength_meter_html = sprintf(
2045 '<div
2046 class="tutor-help-text"
2047 x-show="password.length > 0"
2048 x-cloak
2049 x-bind="getStrengthTextBindings()"
2050 ></div>'
2051 );
2052 }
2053
2054 $this->component_string = sprintf(
2055 '<div class="%s" :class="{ \'tutor-input-field-error\': errors[\'%s\'] }" %s>%s%s%s%s%s</div>',
2056 esc_attr( implode( ' ', $field_classes ) ),
2057 esc_attr( $this->name ),
2058 $root_attrs,
2059 $label_html,
2060 $input_html,
2061 $error_html,
2062 $help_html,
2063 $strength_meter_html
2064 );
2065
2066 return $this->component_string;
2067 }
2068 }
2069