Constants
20 hours ago
Accordion.php
20 hours ago
Alert.php
20 hours ago
AttachmentCard.php
20 hours ago
Avatar.php
20 hours ago
Badge.php
20 hours ago
BaseComponent.php
20 hours ago
Button.php
20 hours ago
ConfirmationModal.php
20 hours ago
CourseFilter.php
20 hours ago
DateFilter.php
20 hours ago
DropdownFilter.php
20 hours ago
EmptyState.php
20 hours ago
FileUploader.php
20 hours ago
InputField.php
20 hours ago
Modal.php
20 hours ago
Nav.php
20 hours ago
Pagination.php
20 hours ago
Popover.php
20 hours ago
PreviewTrigger.php
20 hours ago
Progress.php
20 hours ago
SearchFilter.php
20 hours ago
Sorting.php
20 hours ago
StarRating.php
20 hours ago
StarRatingInput.php
20 hours ago
StatusSelect.php
20 hours ago
SvgIcon.php
20 hours ago
Table.php
20 hours ago
Tabs.php
20 hours ago
Tooltip.php
20 hours ago
WPEditor.php
20 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 |