diff --git a/classes/controllers/FrmFieldsController.php b/classes/controllers/FrmFieldsController.php index b4f173eabb..568826db60 100644 --- a/classes/controllers/FrmFieldsController.php +++ b/classes/controllers/FrmFieldsController.php @@ -218,6 +218,12 @@ private static function get_classes_for_builder_field( $field, $display, $field_ $li_classes = $field_info->form_builder_classes( $display['type'] ); $li_classes .= ' frm_form_field frmstart '; + $style_align_class = self::get_builder_field_style_align_class( $field, $field_info ); + + if ( $style_align_class ) { + $li_classes .= $style_align_class . ' '; + } + if ( isset( $field['classes'] ) ) { $li_classes .= trim( $field['classes'] ) . ' '; } @@ -231,6 +237,37 @@ private static function get_classes_for_builder_field( $field, $display, $field_ return $li_classes; } + /** + * Apply the active style alignment to a radio or checkbox builder preview on load. + * + * The per-field alignment setting only exists in Pro, so on load the style setting is + * used. When the field has an explicit alignment (Pro), the frm_build_field_class filter + * applies it instead. When Pro is not installed, any saved alignment is treated as empty. + * + * @since x.x + * + * @param array $field + * @param FrmFieldType $field_info + * + * @return string + */ + private static function get_builder_field_style_align_class( $field, $field_info ) { + if ( ! $field || ! is_array( $field ) || ( ! FrmField::is_radio( $field ) && ! FrmField::is_checkbox( $field ) ) ) { + return ''; + } + + $align = FrmAppHelper::pro_is_installed() ? FrmField::get_option( $field, 'align' ) : ''; + + if ( $align ) { + return ''; + } + + $align = FrmStylesHelper::get_align_from_active_style( $field ); + $field_info->prepare_align_class( $align ); + + return $align; + } + public static function destroy() { FrmAppHelper::permission_check( 'frm_edit_forms' ); check_ajax_referer( 'frm_ajax', 'nonce' ); diff --git a/classes/helpers/FrmStylesHelper.php b/classes/helpers/FrmStylesHelper.php index 1bddb67ac8..b1e1646d81 100644 --- a/classes/helpers/FrmStylesHelper.php +++ b/classes/helpers/FrmStylesHelper.php @@ -926,6 +926,22 @@ private static function description_margin_for_screensize( $width ) { return $change_margin; } + /** + * Get the raw alignment value stored in the active style for a radio or checkbox field. + * + * @since x.x + * + * @param array|int $field The 'field' array. + * + * @return string + */ + public static function get_align_from_active_style( $field ) { + $field_type = FrmField::is_checkbox( $field ) ? 'checkbox' : 'radio'; + $key = FrmStylesController::get_align_key_for_style_settings( $field_type ); + + return FrmStylesController::get_active_style( $field )->post_content[ $key ] ?? ''; + } + /** * @since 2.3 * diff --git a/classes/helpers/FrmStylesPreviewHelper.php b/classes/helpers/FrmStylesPreviewHelper.php index 48dcb80baf..72913fe0c0 100644 --- a/classes/helpers/FrmStylesPreviewHelper.php +++ b/classes/helpers/FrmStylesPreviewHelper.php @@ -57,6 +57,47 @@ public function adjust_form_for_preview() { $this->hide_captcha_fields(); $this->disable_javascript_validation(); $this->add_a_div_class_for_default_label_positions(); + $this->add_a_div_class_for_style_driven_alignment(); + } + + /** + * Add a marker class to radio and checkbox fields that use the style's option + * alignment setting rather than a field level override. + * + * Only fields with this class change when the radio or checkbox alignment style + * setting is updated in the styler preview. Fields with their own alignment + * override are left untouched. + * + * @since x.x + * + * @return void + */ + private function add_a_div_class_for_style_driven_alignment() { + add_filter( + 'frm_field_div_classes', + /** + * @param string $classes + * @param array|object $field + * + * @return string + */ + function ( $classes, $field ) { + if ( ! FrmField::is_radio( $field ) && ! FrmField::is_checkbox( $field ) ) { + return $classes; + } + + // Mirror FrmFieldType::get_container_class(): an override only exists in Pro. + $align = FrmAppHelper::pro_is_installed() ? FrmField::get_option( $field, 'align' ) : ''; + + if ( ! $align ) { + $classes .= ' frm-default-option-align'; + } + + return $classes; + }, + 10, + 2 + ); } /** diff --git a/classes/models/fields/FrmFieldType.php b/classes/models/fields/FrmFieldType.php index 87f3cbdfdc..df7f28b30a 100644 --- a/classes/models/fields/FrmFieldType.php +++ b/classes/models/fields/FrmFieldType.php @@ -1085,7 +1085,7 @@ public function get_container_class() { $align = FrmAppHelper::pro_is_installed() ? FrmField::get_option( $this->field, 'align' ) : ''; if ( ! $align ) { - $align = $this->get_align_class_from_style_settings(); + $align = FrmStylesHelper::get_align_from_active_style( $this->field ); } $this->prepare_align_class( $align ); @@ -1093,23 +1093,6 @@ public function get_container_class() { return $align ? ' ' . $align : ''; } - /** - * Get the alignment value from the active style settings. - * - * Used as the fallback when the field has no specific alignment option set. - * - * @since x.x - * - * @return string - */ - protected function get_align_class_from_style_settings() { - $field_type = FrmField::is_checkbox( $this->field ) ? 'checkbox' : 'radio'; - $key = FrmStylesController::get_align_key_for_style_settings( $field_type ); - $active_style = FrmStylesController::get_active_style( $this->field ); - - return $active_style->post_content[ $key ] ?? ''; - } - /** * @since 4.0 * diff --git a/js/admin/style.js b/js/admin/style.js index 6a4aa0e7c7..b653ef129e 100644 --- a/js/admin/style.js +++ b/js/admin/style.js @@ -61,6 +61,95 @@ document.getElementById( 'frm_style_sidebar' ).classList.add( 'wp-core-ui' ); jQuery( document ).on( 'input change', 'input[data-frmrange]', initSliderPreview ); + + initOptionLayoutPreview(); + } + + /** + * Sync the radio/checkbox option layout (alignment) style settings to the preview form in real time. + * + * The layout is applied to the front end through a container class, so changing the style setting + * does not re-render the preview form. This swaps the container class on preview fields that use the + * style setting. The styler preview marks those fields with .frm-default-option-align, so fields with + * their own alignment override are left untouched. + * + * @return {void} + */ + function initOptionLayoutPreview() { + bindOptionLayoutSelect( document.getElementById( 'frm_radio_align' ), '.frm_radio' ); + bindOptionLayoutSelect( document.getElementById( 'frm_check_align' ), '.frm_checkbox' ); + } + + /** + * Listen for changes on an option layout style setting and update the preview to match. + * + * @param {HTMLSelectElement|null} select The style setting dropdown. + * @param {string} optionSelector The single option selector ('.frm_radio' or '.frm_checkbox'). + * @return {void} + */ + function bindOptionLayoutSelect( select, optionSelector ) { + if ( ! select ) { + return; + } + + select.addEventListener( 'change', () => { + updatePreviewOptionLayout( optionSelector, select.value ); + } ); + } + + /** + * Replace the alignment container class on preview fields that are using the style setting. + * + * @param {string} optionSelector The single option selector ('.frm_radio' or '.frm_checkbox'). + * @param {string} newAlign The newly selected style alignment value. + * @return {void} + */ + function updatePreviewOptionLayout( optionSelector, newAlign ) { + const newClass = optionLayoutAlignToClass( newAlign ); + + if ( ! newClass ) { + return; + } + + const activeForm = document.getElementById( 'frm_active_style_form' ); + if ( ! activeForm ) { + return; + } + + const alignClasses = [ 'vertical_radio', 'horizontal_radio', 'frm_two_col', 'frm_three_col', 'frm_four_col' ]; + const containers = new Set(); + + activeForm.querySelectorAll( optionSelector ).forEach( option => { + const container = option.closest( '.frm_form_field' ); + + // Only fields using the style setting (no override) are marked in the styler preview. + if ( container && container.classList.contains( 'frm-default-option-align' ) ) { + containers.add( container ); + } + } ); + + containers.forEach( container => { + container.classList.remove( ...alignClasses ); + container.classList.add( newClass ); + } ); + } + + /** + * Map an option layout style value to its front-end container class. + * + * @param {string} align The style alignment value. + * @return {string} The matching container class. + */ + function optionLayoutAlignToClass( align ) { + if ( 'inline' === align ) { + return 'horizontal_radio'; + } + + if ( 'block' === align ) { + return 'vertical_radio'; + } + + return align; } /**