diff --git a/includes/admin/views/acf-field-group/location-rule.php b/includes/admin/views/acf-field-group/location-rule.php
index a0caae22..44413909 100644
--- a/includes/admin/views/acf-field-group/location-rule.php
+++ b/includes/admin/views/acf-field-group/location-rule.php
@@ -2,7 +2,6 @@
// vars
$prefix = 'acf_field_group[location][' . $rule['group'] . '][' . $rule['id'] . ']';
-
?>
diff --git a/includes/admin/views/acf-field-group/locations.php b/includes/admin/views/acf-field-group/locations.php
index fe00fb95..25df9992 100644
--- a/includes/admin/views/acf-field-group/locations.php
+++ b/includes/admin/views/acf-field-group/locations.php
@@ -2,7 +2,6 @@
// global
global $field_group;
-
?>
diff --git a/includes/admin/views/acf-field-group/options.php b/includes/admin/views/acf-field-group/options.php
index 77c61360..a816c7ab 100644
--- a/includes/admin/views/acf-field-group/options.php
+++ b/includes/admin/views/acf-field-group/options.php
@@ -72,6 +72,8 @@
case 'location_rules':
echo '
';
acf_get_view( 'acf-field-group/locations' );
+
+ do_action( 'acf/field_group/render_additional_location_settings', $field_group );
echo '
';
break;
case 'presentation':
@@ -164,6 +166,8 @@
'field'
);
+ do_action( 'acf/field_group/render_additional_presentation_settings', $field_group );
+
echo '
';
echo '
';
@@ -221,8 +225,6 @@
'prefix' => 'acf_field_group',
'value' => $field_group['active'],
'ui' => 1,
- // 'ui_on_text' => __('Active', 'secure-custom-fields'),
- // 'ui_off_text' => __('Inactive', 'secure-custom-fields'),
)
);
@@ -237,8 +239,6 @@
'prefix' => 'acf_field_group',
'value' => $field_group['show_in_rest'],
'ui' => 1,
- // 'ui_on_text' => __('Active', 'secure-custom-fields'),
- // 'ui_off_text' => __('Inactive', 'secure-custom-fields'),
)
);
}
@@ -257,6 +257,21 @@
'field'
);
+ acf_render_field_wrap(
+ array(
+ 'label' => __( 'Display Title', 'secure-custom-fields' ),
+ 'instructions' => __( 'Title shown on the edit screen for the field group meta box to use instead of the field group title', 'secure-custom-fields' ),
+ 'type' => 'text',
+ 'name' => 'display_title',
+ 'prefix' => 'acf_field_group',
+ 'value' => $field_group['display_title'],
+ ),
+ 'div',
+ 'field'
+ );
+
+ do_action( 'acf/field_group/render_additional_group_settings', $field_group );
+
/* translators: 1: Post creation date 2: Post creation time */
$acf_created_on = sprintf( __( 'Created on %1$s at %2$s', 'secure-custom-fields' ), get_the_date(), get_the_time() );
?>
diff --git a/includes/admin/views/acf-post-type/list-empty.php b/includes/admin/views/acf-post-type/list-empty.php
index f12219fd..0bfc2cca 100644
--- a/includes/admin/views/acf-post-type/list-empty.php
+++ b/includes/admin/views/acf-post-type/list-empty.php
@@ -5,7 +5,8 @@
* @package wordpress/secure-custom-fields
*/
-?>
+?>
+
diff --git a/includes/admin/views/upgrade/network.php b/includes/admin/views/upgrade/network.php
index 49ab0457..643d4b1f 100644
--- a/includes/admin/views/upgrade/network.php
+++ b/includes/admin/views/upgrade/network.php
@@ -60,7 +60,7 @@
?>
class="alternate">
@@ -73,8 +73,16 @@ class="alternate">
-
-
+
+
+
diff --git a/includes/admin/views/upgrade/notice.php b/includes/admin/views/upgrade/notice.php
index bb14df96..603d8f2e 100644
--- a/includes/admin/views/upgrade/notice.php
+++ b/includes/admin/views/upgrade/notice.php
@@ -19,7 +19,7 @@
}
?>
-
+
@@ -33,7 +33,7 @@
diff --git a/includes/ajax/class-acf-ajax-check-screen.php b/includes/ajax/class-acf-ajax-check-screen.php
index 6ee8cba8..0fe6ef7d 100644
--- a/includes/ajax/class-acf-ajax-check-screen.php
+++ b/includes/ajax/class-acf-ajax-check-screen.php
@@ -53,7 +53,7 @@ public function get_response( $request ) {
$item = array(
'id' => esc_attr( 'acf-' . $field_group['key'] ),
'key' => esc_attr( $field_group['key'] ),
- 'title' => esc_html( $field_group['title'] ),
+ 'title' => acf_esc_html( acf_get_field_group_title( $field_group ) ),
'position' => esc_attr( $field_group['position'] ),
'classes' => postbox_classes( 'acf-' . $field_group['key'], $args['screen'] ),
'style' => esc_attr( $field_group['style'] ),
diff --git a/includes/blocks.php b/includes/blocks.php
index c8765dd0..4d792ba3 100644
--- a/includes/blocks.php
+++ b/includes/blocks.php
@@ -51,6 +51,17 @@ function acf_handle_json_block_registration( $settings, $metadata ) {
return $settings;
}
+ /**
+ * Filters the default ACF block version for blocks registered via block.json.
+ *
+ * @since 6.6.0
+ *
+ * @param integer $default_acf_block_version The default ACF block version.
+ * @param array $settings An array of block settings.
+ * @return integer
+ */
+ $default_acf_block_version = apply_filters( 'acf/blocks/default_block_version', 2, $settings );
+
// Setup SCF defaults.
$settings = wp_parse_args(
$settings,
@@ -64,8 +75,7 @@ function acf_handle_json_block_registration( $settings, $metadata ) {
'uses_context' => array(),
'supports' => array(),
'attributes' => array(),
- 'acf_block_version' => 2,
- 'api_version' => 2,
+ 'acf_block_version' => $default_acf_block_version,
'validate' => true,
'validate_on_load' => true,
'use_post_meta' => false,
@@ -127,6 +137,17 @@ function acf_handle_json_block_registration( $settings, $metadata ) {
}
}
+ if ( isset( $metadata['apiVersion'] ) ) {
+ // Use the apiVersion defined in block.json if it exists.
+ $settings['api_version'] = $metadata['apiVersion'];
+ } elseif ( $settings['acf_block_version'] >= 3 && version_compare( get_bloginfo( 'version' ), '6.3', '>=' ) ) {
+ // Otherwise, if we're on WP 6.3+ and the block is ACF block version 3 or greater, use apiVersion 3.
+ $settings['api_version'] = 3;
+ } else {
+ // Otherwise, default to apiVersion 2.
+ $settings['api_version'] = 2;
+ }
+
// Add the block name and registration path to settings.
$settings['name'] = $metadata['name'];
$settings['path'] = dirname( $metadata['file'] );
@@ -198,11 +219,28 @@ function acf_register_block_type( $block ) {
// Set ACF required attributes.
$block['attributes'] = acf_get_block_type_default_attributes( $block );
- if ( ! isset( $block['api_version'] ) ) {
- $block['api_version'] = 2;
- }
+
+ /**
+ * Filters the default ACF block version for blocks registered via acf_register_block_type().
+ *
+ * @since 6.6.0
+ *
+ * @param integer $default_acf_block_version The default ACF block version.
+ * @param array $block An array of block settings.
+ * @return integer
+ */
+ $default_acf_block_version = apply_filters( 'acf/blocks/default_block_version', 1, $block );
+
if ( ! isset( $block['acf_block_version'] ) ) {
- $block['acf_block_version'] = 1;
+ $block['acf_block_version'] = $default_acf_block_version;
+ }
+
+ if ( ! isset( $block['api_version'] ) ) {
+ if ( $block['acf_block_version'] >= 3 && version_compare( get_bloginfo( 'version' ), '6.3', '>=' ) ) {
+ $block['api_version'] = 3;
+ } else {
+ $block['api_version'] = 2;
+ }
}
// Add to storage.
@@ -559,8 +597,13 @@ function acf_render_block_callback( $attributes, $content = '', $wp_block = null
* @return string The block HTML.
*/
function acf_rendered_block( $attributes, $content = '', $is_preview = false, $post_id = 0, $wp_block = null, $context = false, $is_ajax_render = false ) {
- $mode = isset( $attributes['mode'] ) ? $attributes['mode'] : 'auto';
- $form = ( 'edit' === $mode && $is_preview );
+ if ( isset( $wp_block->block_type->acf_block_version ) && $wp_block->block_type->acf_block_version >= 3 ) {
+ $mode = 'preview';
+ $form = false;
+ } else {
+ $mode = isset( $attributes['mode'] ) ? $attributes['mode'] : 'auto';
+ $form = ( 'edit' === $mode && $is_preview );
+ }
// If context is available from the WP_Block class object and we have no context of our own, use that.
if ( empty( $context ) && ! empty( $wp_block->context ) ) {
@@ -754,12 +797,16 @@ function acf_block_render_template( $block, $content, $is_preview, $post_id, $wp
$path = locate_template( $block['render_template'] );
}
+ do_action( 'acf/blocks/pre_block_template_render', $block, $content, $is_preview, $post_id, $wp_block, $context );
+
// Include template.
if ( file_exists( $path ) ) {
include $path;
} elseif ( $is_preview ) {
echo acf_esc_html( apply_filters( 'acf/blocks/template_not_found_message', '
' . __( 'The render template for this ACF Block was not found', 'secure-custom-fields' ) . '
' ) );
}
+
+ do_action( 'acf/blocks/post_block_template_render', $block, $content, $is_preview, $post_id, $wp_block, $context );
}
/**
@@ -813,7 +860,7 @@ function acf_enqueue_block_assets() {
'Change content alignment' => __( 'Change content alignment', 'secure-custom-fields' ),
'Error previewing block' => __( 'An error occurred when loading the preview for this block.', 'secure-custom-fields' ),
'Error loading block form' => __( 'An error occurred when loading the block in edit mode.', 'secure-custom-fields' ),
-
+ 'Edit Block' => __( 'Edit Block', 'secure-custom-fields' ),
/* translators: %s: Block type title */
'%s settings' => __( '%s settings', 'secure-custom-fields' ),
)
@@ -1036,8 +1083,15 @@ function acf_ajax_fetch_block() {
$content = '';
$is_preview = true;
+ $registry = WP_Block_Type_Registry::get_instance();
+ $wp_block_type = $registry->get_registered( $block['name'] );
+
+ // We need to match what gets automatically passed to acf_rendered_block by WP core.
+ $wp_block = new stdClass();
+ $wp_block->block_type = $wp_block_type;
+
// Render and store HTML.
- $response['preview'] = acf_rendered_block( $block, $content, $is_preview, $post_id, null, $context, true );
+ $response['preview'] = acf_rendered_block( $block, $content, $is_preview, $post_id, $wp_block, $context, true );
}
// Send response.
diff --git a/includes/class-acf-internal-post-type.php b/includes/class-acf-internal-post-type.php
index 8808511d..67925f8c 100644
--- a/includes/class-acf-internal-post-type.php
+++ b/includes/class-acf-internal-post-type.php
@@ -166,7 +166,7 @@ public function get_raw_post( $id = 0 ) {
* @since ACF 6.1
*
* @param integer|string $id The post ID, key, or name.
- * @return WP_Post|bool The post object, or false on failure.
+ * @return WP_Post|boolean The post object, or false on failure.
*/
public function get_post_object( $id = 0 ) {
if ( is_numeric( $id ) ) {
diff --git a/includes/fields/class-acf-field-button-group.php b/includes/fields/class-acf-field-button-group.php
index d1000551..36e480fc 100644
--- a/includes/fields/class-acf-field-button-group.php
+++ b/includes/fields/class-acf-field-button-group.php
@@ -67,10 +67,11 @@ public function render_field( $field ) {
// append
$buttons[] = array(
- 'name' => $field['name'],
- 'value' => $_value,
- 'label' => $_label,
- 'checked' => $checked,
+ 'name' => $field['name'],
+ 'value' => $_value,
+ 'label' => $_label,
+ 'checked' => $checked,
+ 'button_group' => true,
);
}
@@ -79,16 +80,29 @@ public function render_field( $field ) {
$buttons[0]['checked'] = true;
}
+ // Ensure roving tabindex when allow_null is enabled and no selection yet.
+ if ( $field['allow_null'] && null === $selected && ! empty( $buttons ) ) {
+ $buttons[0]['tabindex'] = '0';
+ }
+
// div
- $div = array( 'class' => 'acf-button-group' );
+ $div = array(
+ 'class' => 'acf-button-group',
+ 'role' => 'radiogroup',
+ );
+
+ // Add aria-labelledby if field has an ID for proper screen reader announcement
+ if ( ! empty( $field['id'] ) ) {
+ $div['aria-labelledby'] = $field['id'] . '-label';
+ }
- if ( 'vertical' === acf_maybe_get( $field, 'layout' ) ) {
+ if ( 'vertical' === $field['layout'] ) {
$div['class'] .= ' -vertical';
}
- if ( acf_maybe_get( $field, 'class' ) ) {
- $div['class'] .= ' ' . acf_maybe_get( $field, 'class' );
+ if ( $field['class'] ) {
+ $div['class'] .= ' ' . $field['class'];
}
- if ( acf_maybe_get( $field, 'allow_null' ) ) {
+ if ( $field['allow_null'] ) {
$div['data-allow_null'] = 1;
}
diff --git a/includes/fields/class-acf-field-checkbox.php b/includes/fields/class-acf-field-checkbox.php
index 582672be..5935a041 100644
--- a/includes/fields/class-acf-field-checkbox.php
+++ b/includes/fields/class-acf-field-checkbox.php
@@ -80,8 +80,14 @@ function render_field( $field ) {
$li = '';
$ul = array(
'class' => 'acf-checkbox-list',
+ 'role' => 'group',
);
+ // Add aria-labelledby if field has an ID for proper screen reader announcement
+ if ( ! empty( $field['id'] ) ) {
+ $ul['aria-labelledby'] = $field['id'] . '-label';
+ }
+
// append to class
$ul['class'] .= ' ' . ( 'horizontal' === acf_maybe_get( $field, 'layout' ) ? 'acf-hl' : 'acf-bl' );
$ul['class'] .= ' ' . acf_maybe_get( $field, 'class', '' );
diff --git a/includes/fields/class-acf-field-color_picker.php b/includes/fields/class-acf-field-color_picker.php
index 98fd1e0f..fd3d6ed9 100644
--- a/includes/fields/class-acf-field-color_picker.php
+++ b/includes/fields/class-acf-field-color_picker.php
@@ -26,9 +26,12 @@ function initialize() {
$this->doc_url = 'https://developer.wordpress.org/secure-custom-fields/features/fields/color-picker/';
$this->tutorial_url = 'https://developer.wordpress.org/secure-custom-fields/features/fields/color-picker/color-picker-tutorial/';
$this->defaults = array(
- 'default_value' => '',
- 'enable_opacity' => false,
- 'return_format' => 'string', // 'string'|'array'
+ 'default_value' => '',
+ 'enable_opacity' => false,
+ 'custom_palette_source' => '',
+ 'palette_colors' => '',
+ 'show_color_wheel' => true,
+ 'return_format' => 'string', // Possible values: 'string' or 'array'.
);
}
@@ -106,7 +109,7 @@ function input_admin_enqueue_scripts() {
* @since ACF 3.6
* @date 23/01/13
*/
- function render_field( $field ) {
+ public function render_field( $field ) {
$text_input = acf_get_sub_array( $field, array( 'id', 'class', 'name', 'value' ) );
$hidden_input = acf_get_sub_array( $field, array( 'name', 'value' ) );
$text_input['data-alpha-skip-debounce'] = true;
@@ -116,9 +119,55 @@ function render_field( $field ) {
$text_input['data-alpha-enabled'] = true;
}
+ // Handle color palette when the theme supports theme.json.
+ if ( wp_theme_has_theme_json() ) {
+ // If the field was set to use themejson.
+ if ( 'themejson' === $field['custom_palette_source'] ) {
+ $text_input['data-acf-palette-type'] = 'custom';
+
+ // Get the palette (theme + custom).
+ $global_settings = wp_get_global_settings();
+ $palette = $global_settings['color']['palette']['theme'] ?? array();
+
+ // Extract only the color values.
+ $color_values = array_map(
+ fn( $c ) => $c['color'] ?? null,
+ $palette
+ );
+
+ // Remove nulls (in case any entries are missing 'color')
+ $color_values = array_filter( $color_values );
+
+ $hex_string = implode( ',', $color_values );
+
+ $text_input['data-acf-palette-colors'] = $hex_string;
+ } elseif ( 'custom' === $field['custom_palette_source'] && ! empty( $field['palette_colors'] ) ) {
+ // If the field was set to use a custom palette.
+ $text_input['data-acf-palette-type'] = 'custom';
+ $text_input['data-acf-palette-colors'] = $field['palette_colors'];
+ } elseif ( '' === $field['custom_palette_source'] && ! empty( $field['palette_colors'] ) ) {
+ // This state can happen if they switched from a classic theme to a themejson theme without resaving the field.
+ $text_input['data-acf-palette-type'] = 'custom';
+ $text_input['data-acf-palette-colors'] = $field['palette_colors'];
+ } else {
+ // Fallback to use the default color palette for the iris color picker.
+ $text_input['data-acf-palette-type'] = 'default';
+ }
+ // phpcs:disable Universal.ControlStructures.DisallowLonelyIf.Found
+ } else {
+ // Handle color palette for themes that do not support themejson.
+ if ( ! empty( $field['palette_colors'] ) ) {
+ $text_input['data-acf-palette-type'] = 'custom';
+ $text_input['data-acf-palette-colors'] = $field['palette_colors'];
+ } else {
+ // Fallback to use the default color palette for the iris color picker.
+ $text_input['data-acf-palette-type'] = 'default';
+ }
+ }
+
// html
?>
-
+
@@ -179,6 +228,86 @@ function render_field_settings( $field ) {
);
}
+
+ /**
+ * Renders the field settings used in the "Presentation" tab.
+ *
+ * @since 6.0
+ *
+ * @param array $field The field settings array.
+ * @return void
+ */
+ public function render_field_presentation_settings( $field ) {
+ acf_render_field_setting(
+ $field,
+ array(
+ 'label' => __( 'Show Custom Palette', 'secure-custom-fields' ),
+ 'instructions' => '',
+ 'type' => 'true_false',
+ 'name' => 'show_custom_palette',
+ 'ui' => 1,
+ )
+ );
+
+ $custom_palette_conditions = array(
+ 'field' => 'show_custom_palette',
+ 'operator' => '==',
+ 'value' => 1,
+ );
+
+ if ( wp_theme_has_theme_json() ) {
+ acf_render_field_setting(
+ $field,
+ array(
+ 'label' => __( 'Custom Palette Source', 'secure-custom-fields' ),
+ 'instructions' => '',
+ 'type' => 'radio',
+ 'name' => 'custom_palette_source',
+ 'layout' => 'vertical',
+ 'choices' => array(
+ 'custom' => __( 'Specify custom colors', 'secure-custom-fields' ),
+ 'themejson' => __( 'Use colors from theme.json', 'secure-custom-fields' ),
+ ),
+ 'conditions' => array(
+ 'field' => 'show_custom_palette',
+ 'operator' => '==',
+ 'value' => 1,
+ ),
+ )
+ );
+
+ $custom_palette_conditions = array(
+ 'field' => 'custom_palette_source',
+ 'operator' => '==',
+ 'value' => 'custom',
+ );
+ }
+
+ acf_render_field_setting(
+ $field,
+ array(
+ 'label' => __( 'Custom Palette', 'secure-custom-fields' ),
+ 'instructions' => __( 'Use a custom color palette by entering comma separated hex or rgba values', 'secure-custom-fields' ),
+ 'type' => 'text',
+ 'name' => 'palette_colors',
+ 'conditions' => $custom_palette_conditions,
+ )
+ );
+
+ acf_render_field_setting(
+ $field,
+ array(
+ 'label' => __( 'Show Color Wheel', 'secure-custom-fields' ),
+ 'instructions' => '',
+ 'type' => 'true_false',
+ 'name' => 'show_color_wheel',
+ 'default_value' => 1,
+ 'ui' => 1,
+ )
+ );
+ }
+
+
/**
* Format the value for use in templates. At this stage, the value has been loaded from the
* database and is being returned by an API function such as get_field(), the_field(), etc.
diff --git a/includes/fields/class-acf-field-file.php b/includes/fields/class-acf-field-file.php
index 37fe0dad..59c40187 100644
--- a/includes/fields/class-acf-field-file.php
+++ b/includes/fields/class-acf-field-file.php
@@ -130,7 +130,7 @@ function render_field( $field ) {
)
);
?>
-
+
@@ -148,14 +148,14 @@ function render_field( $field ) {
-
+
diff --git a/includes/fields/class-acf-field-icon_picker.php b/includes/fields/class-acf-field-icon_picker.php
index d7746ad2..31b4bb17 100644
--- a/includes/fields/class-acf-field-icon_picker.php
+++ b/includes/fields/class-acf-field-icon_picker.php
@@ -334,11 +334,16 @@ public function input_admin_enqueue_scripts() {
* @return boolean true If the value is valid, false if not.
*/
public function validate_value( $valid, $value, $field, $input ) {
- // If the value is empty, return true. You're allowed to save nothing.
+ // If the value is empty and it's not required, return true. You're allowed to save nothing.
if ( empty( $value ) && empty( $field['required'] ) ) {
return true;
}
+ // Validate required.
+ if ( $field['required'] && ( empty( $value ) || empty( $value['value'] ) ) ) {
+ return false;
+ }
+
// If the value is not an array, return $valid status.
if ( ! is_array( $value ) ) {
return $valid;
diff --git a/includes/fields/class-acf-field-image.php b/includes/fields/class-acf-field-image.php
index d437965c..560fe737 100644
--- a/includes/fields/class-acf-field-image.php
+++ b/includes/fields/class-acf-field-image.php
@@ -127,17 +127,17 @@ function render_field( $field ) {
)
);
?>
-
+
/>
-
+
diff --git a/includes/fields/class-acf-field-radio.php b/includes/fields/class-acf-field-radio.php
index f0100200..28d33385 100644
--- a/includes/fields/class-acf-field-radio.php
+++ b/includes/fields/class-acf-field-radio.php
@@ -57,10 +57,16 @@ function render_field( $field ) {
'class' => 'acf-radio-list',
'data-allow_null' => $field['allow_null'],
'data-other_choice' => $field['other_choice'],
+ 'role' => 'radiogroup',
);
+ // Add aria-labelledby if field has an ID for proper screen reader announcement
+ if ( ! empty( $field['id'] ) ) {
+ $ul['aria-labelledby'] = $field['id'] . '-label';
+ }
+
// append to class
- $ul['class'] .= ' ' . ( $field['layout'] == 'horizontal' ? 'acf-hl' : 'acf-bl' );
+ $ul['class'] .= ' ' . ( 'horizontal' === $field['layout'] ? 'acf-hl' : 'acf-bl' );
$ul['class'] .= ' ' . $field['class'];
// Determine selected value.
diff --git a/includes/fields/class-acf-field-repeater.php b/includes/fields/class-acf-field-repeater.php
index 31b1743b..443dcfff 100644
--- a/includes/fields/class-acf-field-repeater.php
+++ b/includes/fields/class-acf-field-repeater.php
@@ -1016,24 +1016,55 @@ public function get_field_name_from_input_name( $input_name ) {
$name_parts = array();
foreach ( $field_keys as $field_key ) {
- if ( ! acf_is_field_key( $field_key ) ) {
- if ( 'acfcloneindex' === $field_key ) {
- $name_parts[] = 'acfcloneindex';
- continue;
- }
+ // Preserve acfcloneindex
+ if ( 'acfcloneindex' === $field_key ) {
+ $name_parts[] = 'acfcloneindex';
+ continue;
+ }
- $row_num = str_replace( 'row-', '', $field_key );
+ // Handle row numbers (row-0, row-1, etc.)
+ if ( strpos( $field_key, 'row-' ) === 0 ) {
+ $row_num = substr( $field_key, 4 );
if ( is_numeric( $row_num ) ) {
$name_parts[] = (int) $row_num;
continue;
}
}
- $field = acf_get_field( $field_key );
+ // Handle compound keys (field_..._field_...)
+ $compound_keys = preg_split( '/_field_/', $field_key );
+ if ( count( $compound_keys ) > 1 ) {
+ foreach ( $compound_keys as $i => $sub_key ) {
+ if ( $i > 0 ) {
+ $sub_key = 'field_' . $sub_key;
+ }
+
+ // Seamless clone fields use compound keys which can be skipped.
+ $field = acf_get_field( $sub_key );
+ if ( $field && 'clone' === $field['type'] && 'seamless' === $field['display'] ) {
+ continue;
+ }
+
+ $name_parts[] = $field && ! empty( $field['name'] ) ? $field['name'] : $sub_key;
+ }
+ continue;
+ }
+
+ // Handle standard field keys
+ if ( strpos( $field_key, 'field_' ) === 0 ) {
+
+ // Skip clone fields with prefix_name disabled.
+ $field = acf_get_field( $field_key );
+ if ( $field && 'clone' === $field['type'] && empty( $field['prefix_name'] ) ) {
+ continue;
+ }
- if ( $field ) {
- $name_parts[] = $field['name'];
+ $name_parts[] = $field && ! empty( $field['name'] ) ? $field['name'] : $field_key;
+ continue;
}
+
+ // Fallback: just add as is
+ $name_parts[] = $field_key;
}
return implode( '_', $name_parts );
@@ -1093,17 +1124,19 @@ public function ajax_get_rows() {
* We have to swap out the field name with the one sent via JS,
* as the repeater could be inside a subfield.
*/
- $field['name'] = $args['field_name'];
+ $field['name'] = $args['field_name'];
+ $field['prefix'] = $args['field_prefix'];
+ $field['value'] = acf_get_value( $post_id, $field );
- $field['value'] = acf_get_value( $post_id, $field );
+ if ( $args['refresh'] ) {
+ $response['total_rows'] = (int) acf_get_metadata_by_field( $post_id, $field );
+ }
+
+ // Render the rows to be sent back via AJAX.
$field = acf_prepare_field( $field );
$repeater_table = new ACF_Repeater_Table( $field );
$response['rows'] = $repeater_table->rows( true );
- if ( $args['refresh'] ) {
- $response['total_rows'] = (int) acf_get_metadata( $post_id, $args['field_name'] );
- }
-
wp_send_json_success( $response );
}
}
diff --git a/includes/fields/class-acf-field-taxonomy.php b/includes/fields/class-acf-field-taxonomy.php
index 84eeb94c..5f990f9d 100644
--- a/includes/fields/class-acf-field-taxonomy.php
+++ b/includes/fields/class-acf-field-taxonomy.php
@@ -576,7 +576,7 @@ public function render_field_checkbox( $field ) {
);
// checkbox saves an array.
- if ( $field['field_type'] == 'checkbox' ) {
+ if ( 'checkbox' === $field['field_type'] ) {
$field['name'] .= '[]';
}
@@ -601,9 +601,18 @@ public function render_field_checkbox( $field ) {
$args = apply_filters( 'acf/fields/taxonomy/wp_list_categories/name=' . $field['_name'], $args, $field );
$args = apply_filters( 'acf/fields/taxonomy/wp_list_categories/key=' . $field['key'], $args, $field );
+ // Build UL attributes for accessibility and consistency.
+ $ul = array(
+ 'class' => 'acf-checkbox-list acf-bl',
+ 'role' => 'radio' === $field['field_type'] ? 'radiogroup' : 'group',
+ );
+
+ if ( ! empty( $field['id'] ) ) {
+ $ul['aria-labelledby'] = $field['id'] . '-label';
+ }
?>
diff --git a/includes/forms/WC_Order.php b/includes/forms/WC_Order.php
index 9eb2a6c9..77161428 100644
--- a/includes/forms/WC_Order.php
+++ b/includes/forms/WC_Order.php
@@ -73,7 +73,6 @@ public function add_meta_boxes( $post_type, $post ) {
if ( $field_groups ) {
foreach ( $field_groups as $field_group ) {
$id = "acf-{$field_group['key']}"; // acf-group_123
- $title = $field_group['title']; // Group 1
$context = $field_group['position']; // normal, side, acf_after_title
$priority = 'core'; // high, core, default, low
@@ -104,7 +103,7 @@ public function add_meta_boxes( $post_type, $post ) {
// Add the meta box.
add_meta_box(
$id,
- esc_html( $title ),
+ acf_esc_html( acf_get_field_group_title( $field_group ) ),
array( $this, 'render_meta_box' ),
$screen,
$context,
diff --git a/includes/forms/form-comment.php b/includes/forms/form-comment.php
index 923c72d9..63dd3321 100644
--- a/includes/forms/form-comment.php
+++ b/includes/forms/form-comment.php
@@ -148,7 +148,7 @@ function edit_comment( $comment ) {
?>
-
+
`);
+ }
+
+ componentDidUpdate() {
+ this.setHTML(this.props.children);
+ }
+
+ componentDidMount() {
+ this.setHTML(this.props.children);
+ }
+}
+
+/**
+ * Gets the component type for a given node name
+ * Handles special cases like InnerBlocks, script tags, and comments
+ *
+ * @param {string} nodeName - Lowercase node name
+ * @returns {string|Function|null} - Component type or null
+ */
+function getComponentType(nodeName) {
+ switch (nodeName) {
+ case 'innerblocks':
+ return 'ACFInnerBlocks';
+ case 'script':
+ return ScriptComponent;
+ case '#comment':
+ return null;
+ default:
+ return getJSXNameReplacement(nodeName);
+ }
+}
+
+/**
+ * ACF InnerBlocks wrapper component
+ * Provides a container for WordPress InnerBlocks with proper props
+ *
+ * @param {Object} props - Component props
+ * @returns {JSX.Element} - Wrapped InnerBlocks component
+ */
+function ACFInnerBlocksComponent(props) {
+ const { className = 'acf-innerblocks-container' } = props;
+ const innerBlocksProps = useInnerBlocksProps({ className }, props);
+
+ return jsx('div', {
+ ...innerBlocksProps,
+ children: innerBlocksProps.children,
+ });
+}
+
+/**
+ * Parses and transforms a DOM attribute to React props format
+ * Handles special cases: class -> className, style string -> style object, JSON values, booleans
+ *
+ * @param {Attr} attribute - DOM attribute object with name and value
+ * @returns {Object} - Transformed attribute {name, value}
+ */
+function parseAttribute(attribute) {
+ let attrName = attribute.name;
+ let attrValue = attribute.value;
+
+ // Allow custom filtering via ACF hooks
+ const customParsed = acf.applyFilters(
+ 'acf_blocks_parse_node_attr',
+ false,
+ attribute
+ );
+ if (customParsed) return customParsed;
+
+ switch (attrName) {
+ case 'class':
+ // Convert HTML class to React className
+ attrName = 'className';
+ break;
+
+ case 'style':
+ // Parse inline CSS string to JavaScript style object
+ const styleObject = {};
+ attrValue.split(';').forEach((declaration) => {
+ const colonIndex = declaration.indexOf(':');
+ if (colonIndex > 0) {
+ let property = declaration.substr(0, colonIndex).trim();
+ const value = declaration.substr(colonIndex + 1).trim();
+
+ // Convert kebab-case to camelCase (except CSS variables starting with -)
+ if (property.charAt(0) !== '-') {
+ property = acf.strCamelCase(property);
+ }
+
+ styleObject[property] = value;
+ }
+ });
+ attrValue = styleObject;
+ break;
+
+ default:
+ // Preserve data- attributes as-is
+ if (attrName.indexOf('data-') === 0) break;
+
+ // Apply JSX name transformations (e.g., onclick -> onClick)
+ attrName = getJSXNameReplacement(attrName);
+
+ // Parse JSON array/object values
+ const firstChar = attrValue.charAt(0);
+ if (firstChar === '[' || firstChar === '{') {
+ attrValue = JSON.parse(attrValue);
+ }
+
+ // Convert string booleans to actual booleans
+ if (attrValue === 'true' || attrValue === 'false') {
+ attrValue = attrValue === 'true';
+ }
+ }
+
+ return { name: attrName, value: attrValue };
+}
+
+/**
+ * Recursively parses a DOM node and converts it to React/JSX elements
+ *
+ * @param {Node} node - The DOM node to parse
+ * @param {number} depth - Current recursion depth (0-based)
+ * @returns {JSX.Element|null} - React element or null if node should be skipped
+ */
+function parseNodeToJSX(node, depth = 0) {
+ // Determine the component type for this node
+ const componentType = getComponentType(node.nodeName.toLowerCase());
+
+ if (!componentType) return null;
+
+ const props = {};
+
+ // Add ref to first-level elements (except ACFInnerBlocks)
+ if (depth === 1 && componentType !== 'ACFInnerBlocks') {
+ props.ref = createRef();
+ }
+
+ // Parse all attributes and add to props
+ acf.arrayArgs(node.attributes)
+ .map(parseAttribute)
+ .forEach(({ name, value }) => {
+ props[name] = value;
+ });
+
+ // Handle special ACFInnerBlocks component
+ if (componentType === 'ACFInnerBlocks') {
+ return jsx(ACFInnerBlocksComponent, { ...props });
+ }
+
+ // Build element array: [type, props, ...children]
+ const elementArray = [componentType, props];
+
+ // Recursively process child nodes
+ acf.arrayArgs(node.childNodes).forEach((childNode) => {
+ if (childNode instanceof Text) {
+ const textContent = childNode.textContent;
+ if (textContent) {
+ elementArray.push(textContent);
+ }
+ } else {
+ elementArray.push(parseNodeToJSX(childNode, depth + 1));
+ }
+ });
+
+ // Create and return React element
+ return createElement.apply(this, elementArray);
+}
+
+/**
+ * Main parseJSX function exposed on the acf global object
+ * Converts HTML string to React elements for use in ACF blocks
+ *
+ * @param {string} htmlString - HTML markup to parse
+ * @returns {Array|JSX.Element} - React children from parsed HTML
+ */
+export function parseJSX(htmlString) {
+ // Wrap in div to ensure valid HTML structure
+ htmlString = '
' + htmlString + '
';
+
+ // Handle self-closing InnerBlocks tags (not valid HTML, but used in ACF)
+ htmlString = htmlString.replace(
+ /
]+)?\/>/,
+ ' '
+ );
+
+ // Parse with jQuery, convert to React, and extract children from wrapper div
+ const parsedElement = parseNodeToJSX(jQuery(htmlString)[0], 0);
+ return parsedElement.props.children;
+}
+
+// Expose parseJSX function on acf global object for backward compatibility
+acf.parseJSX = parseJSX;
diff --git a/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js b/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js
new file mode 100644
index 00000000..a91dfe67
--- /dev/null
+++ b/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js
@@ -0,0 +1,116 @@
+/**
+ * withAlignContent Higher-Order Component
+ * Adds content alignment toolbar controls to ACF blocks
+ * Supports both vertical alignment and matrix alignment (horizontal + vertical)
+ */
+
+const { Fragment, Component } = wp.element;
+const { BlockControls, BlockVerticalAlignmentToolbar } = wp.blockEditor;
+
+// Matrix alignment control (experimental)
+const BlockAlignmentMatrixControl =
+ wp.blockEditor.__experimentalBlockAlignmentMatrixControl ||
+ wp.blockEditor.BlockAlignmentMatrixControl;
+
+const BlockAlignmentMatrixToolbar =
+ wp.blockEditor.__experimentalBlockAlignmentMatrixToolbar ||
+ wp.blockEditor.BlockAlignmentMatrixToolbar;
+
+/**
+ * Normalizes vertical alignment value
+ *
+ * @param {string} alignment - Alignment value
+ * @returns {string} - Normalized alignment (top, center, or bottom)
+ */
+const normalizeVerticalAlignment = (alignment) => {
+ return ['top', 'center', 'bottom'].includes(alignment) ? alignment : 'top';
+};
+
+/**
+ * Gets the default horizontal alignment based on RTL setting
+ *
+ * @param {string} alignment - Current alignment value
+ * @returns {string} - Normalized alignment value (left, center, or right)
+ */
+const getDefaultHorizontalAlignment = (alignment) => {
+ const defaultAlign = acf.get('rtl') ? 'right' : 'left';
+ return ['left', 'center', 'right'].includes(alignment)
+ ? alignment
+ : defaultAlign;
+};
+
+/**
+ * Normalizes matrix alignment value (vertical + horizontal)
+ * Format: "top left", "center center", etc.
+ *
+ * @param {string} alignment - Alignment value
+ * @returns {string} - Normalized matrix alignment
+ */
+const normalizeMatrixAlignment = (alignment) => {
+ if (alignment) {
+ const [vertical, horizontal] = alignment.split(' ');
+ return `${normalizeVerticalAlignment(vertical)} ${getDefaultHorizontalAlignment(horizontal)}`;
+ }
+ return 'center center';
+};
+
+/**
+ * Higher-order component that adds content alignment controls
+ * Supports either vertical-only or matrix (2D) alignment based on block config
+ *
+ * @param {React.Component} BlockComponent - The component to wrap
+ * @param {Object} blockConfig - ACF block configuration
+ * @returns {React.Component} - Enhanced component with content alignment controls
+ */
+export const withAlignContent = (BlockComponent, blockConfig) => {
+ let AlignmentControl;
+ let normalizeAlignment;
+
+ // Determine which alignment control to use based on block supports
+ if (
+ blockConfig.supports.align_content === 'matrix' ||
+ blockConfig.supports.alignContent === 'matrix'
+ ) {
+ // Use matrix control (horizontal + vertical)
+ AlignmentControl =
+ BlockAlignmentMatrixControl || BlockAlignmentMatrixToolbar;
+ normalizeAlignment = normalizeMatrixAlignment;
+ } else {
+ // Use vertical-only control
+ AlignmentControl = BlockVerticalAlignmentToolbar;
+ normalizeAlignment = normalizeVerticalAlignment;
+ }
+
+ // If alignment control is not available, return original component
+ if (AlignmentControl === undefined) {
+ return BlockComponent;
+ }
+
+ // Set default alignment on block config
+ blockConfig.alignContent = normalizeAlignment(blockConfig.alignContent);
+
+ return class extends Component {
+ render() {
+ const { attributes, setAttributes } = this.props;
+ const { alignContent } = attributes;
+
+ return (
+
+
+
+
+
+
+ );
+ }
+ };
+};
diff --git a/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js b/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js
new file mode 100644
index 00000000..f54f5fba
--- /dev/null
+++ b/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js
@@ -0,0 +1,58 @@
+/**
+ * withAlignText Higher-Order Component
+ * Adds text alignment toolbar controls to ACF blocks
+ */
+
+const { Fragment, Component } = wp.element;
+const { BlockControls, AlignmentToolbar } = wp.blockEditor;
+
+/**
+ * Gets the default text alignment based on RTL setting
+ *
+ * @param {string} alignment - Current alignment value
+ * @returns {string} - Normalized alignment value (left, center, or right)
+ */
+const getDefaultAlignment = (alignment) => {
+ const defaultAlign = acf.get('rtl') ? 'right' : 'left';
+ return ['left', 'center', 'right'].includes(alignment)
+ ? alignment
+ : defaultAlign;
+};
+
+/**
+ * Higher-order component that adds text alignment controls
+ * Wraps a block component and adds AlignmentToolbar to BlockControls
+ *
+ * @param {React.Component} BlockComponent - The component to wrap
+ * @param {Object} blockConfig - ACF block configuration
+ * @returns {React.Component} - Enhanced component with text alignment controls
+ */
+export const withAlignText = (BlockComponent, blockConfig) => {
+ const normalizeAlignment = getDefaultAlignment;
+
+ // Set default alignment on block config
+ blockConfig.alignText = normalizeAlignment(blockConfig.alignText);
+
+ return class extends Component {
+ render() {
+ const { attributes, setAttributes } = this.props;
+ const { alignText } = attributes;
+
+ return (
+
+
+
+
+
+
+ );
+ }
+ };
+};
diff --git a/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js b/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js
new file mode 100644
index 00000000..7bc539b1
--- /dev/null
+++ b/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js
@@ -0,0 +1,48 @@
+/**
+ * withFullHeight Higher-Order Component
+ * Adds full height toggle control to ACF blocks
+ */
+
+const { Fragment, Component } = wp.element;
+const { BlockControls } = wp.blockEditor;
+
+// Full height control (experimental)
+const BlockFullHeightAlignmentControl =
+ wp.blockEditor.__experimentalBlockFullHeightAligmentControl ||
+ wp.blockEditor.__experimentalBlockFullHeightAlignmentControl ||
+ wp.blockEditor.BlockFullHeightAlignmentControl;
+
+/**
+ * Higher-order component that adds full height toggle controls
+ * Allows blocks to expand to full available height
+ *
+ * @param {React.Component} BlockComponent - The component to wrap
+ * @returns {React.Component} - Enhanced component with full height controls
+ */
+export const withFullHeight = (BlockComponent) => {
+ // If control is not available, return original component
+ if (!BlockFullHeightAlignmentControl) {
+ return BlockComponent;
+ }
+
+ return class extends Component {
+ render() {
+ const { attributes, setAttributes } = this.props;
+ const { fullHeight } = attributes;
+
+ return (
+
+
+
+
+
+
+ );
+ }
+ };
+};
diff --git a/assets/src/js/pro/blocks-v3/register-block-type-v3.js b/assets/src/js/pro/blocks-v3/register-block-type-v3.js
new file mode 100644
index 00000000..d310efc1
--- /dev/null
+++ b/assets/src/js/pro/blocks-v3/register-block-type-v3.js
@@ -0,0 +1,358 @@
+/**
+ * ACF Block Type Registration - Version 3
+ * Handles registration of ACF blocks (version 3) with WordPress Gutenberg
+ * Includes attribute setup, higher-order component composition, and block filtering
+ */
+
+import jQuery from 'jquery';
+import { BlockEdit } from './components/block-edit';
+import { withAlignText } from './high-order-components/with-align-text';
+import { withAlignContent } from './high-order-components/with-align-content';
+import { withFullHeight } from './high-order-components/with-full-height';
+
+const { InnerBlocks } = wp.blockEditor;
+const { Component } = wp.element;
+const { createHigherOrderComponent } = wp.compose;
+
+// Registry to store registered block configurations
+const registeredBlocks = {};
+
+/**
+ * Adds an attribute to the block configuration
+ *
+ * @param {Object} attributes - Existing attributes object
+ * @param {string} attributeName - Name of the attribute to add
+ * @param {string} attributeType - Type of the attribute (string, boolean, etc.)
+ * @returns {Object} - Updated attributes object
+ */
+const addAttribute = (attributes, attributeName, attributeType) => {
+ attributes[attributeName] = { type: attributeType };
+ return attributes;
+};
+
+/**
+ * Checks if block should be registered for current post type
+ *
+ * @param {Object} blockConfig - Block configuration
+ * @returns {boolean} - True if block should be registered
+ */
+function shouldRegisterBlock(blockConfig) {
+ const allowedPostTypes = blockConfig.post_types || [];
+
+ if (allowedPostTypes.length) {
+ // Always allow in reusable blocks
+ allowedPostTypes.push('wp_block');
+
+ const currentPostType = acf.get('postType');
+ if (!allowedPostTypes.includes(currentPostType)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Processes and normalizes block icon
+ *
+ * @param {Object} blockConfig - Block configuration
+ */
+function processBlockIcon(blockConfig) {
+ // Convert SVG string to JSX element
+ if (
+ typeof blockConfig.icon === 'string' &&
+ blockConfig.icon.substr(0, 4) === '
+ );
+ }
+
+ // Remove icon if empty/invalid
+ if (!blockConfig.icon) {
+ delete blockConfig.icon;
+ }
+}
+
+/**
+ * Validates and normalizes block category
+ * Falls back to 'common' if category doesn't exist
+ *
+ * @param {Object} blockConfig - Block configuration
+ */
+function validateBlockCategory(blockConfig) {
+ const categoryExists = wp.blocks
+ .getCategories()
+ .filter(({ slug }) => slug === blockConfig.category)
+ .pop();
+
+ if (!categoryExists) {
+ blockConfig.category = 'common';
+ }
+}
+
+/**
+ * Sets default values for block configuration
+ *
+ * @param {Object} blockConfig - Block configuration
+ * @returns {Object} - Block configuration with defaults applied
+ */
+function applyBlockDefaults(blockConfig) {
+ return acf.parseArgs(blockConfig, {
+ title: '',
+ name: '',
+ category: '',
+ api_version: 2,
+ acf_block_version: 3,
+ attributes: {},
+ supports: {},
+ });
+}
+
+/**
+ * Cleans up block attributes
+ * Removes empty default values
+ *
+ * @param {Object} blockConfig - Block configuration
+ */
+function cleanBlockAttributes(blockConfig) {
+ for (const attributeName in blockConfig.attributes) {
+ if (
+ 'default' in blockConfig.attributes[attributeName] &&
+ blockConfig.attributes[attributeName].default.length === 0
+ ) {
+ delete blockConfig.attributes[attributeName].default;
+ }
+ }
+}
+
+/**
+ * Configures anchor support if enabled
+ *
+ * @param {Object} blockConfig - Block configuration
+ */
+function configureAnchorSupport(blockConfig) {
+ if (blockConfig.supports && blockConfig.supports.anchor) {
+ blockConfig.attributes.anchor = { type: 'string' };
+ }
+}
+
+/**
+ * Applies higher-order components based on block supports
+ *
+ * @param {React.Component} EditComponent - Base edit component
+ * @param {Object} blockConfig - Block configuration
+ * @returns {React.Component} - Enhanced edit component
+ */
+function applyHigherOrderComponents(EditComponent, blockConfig) {
+ let enhancedComponent = EditComponent;
+
+ // Add text alignment support
+ if (blockConfig.supports.alignText || blockConfig.supports.align_text) {
+ blockConfig.attributes = addAttribute(
+ blockConfig.attributes,
+ 'align_text',
+ 'string'
+ );
+ enhancedComponent = withAlignText(enhancedComponent, blockConfig);
+ }
+
+ // Add content alignment support
+ if (
+ blockConfig.supports.alignContent ||
+ blockConfig.supports.align_content
+ ) {
+ blockConfig.attributes = addAttribute(
+ blockConfig.attributes,
+ 'align_content',
+ 'string'
+ );
+ enhancedComponent = withAlignContent(enhancedComponent, blockConfig);
+ }
+
+ // Add full height support
+ if (blockConfig.supports.fullHeight || blockConfig.supports.full_height) {
+ blockConfig.attributes = addAttribute(
+ blockConfig.attributes,
+ 'full_height',
+ 'boolean'
+ );
+ enhancedComponent = withFullHeight(enhancedComponent);
+ }
+
+ return enhancedComponent;
+}
+
+/**
+ * Registers an ACF block type (version 3) with WordPress
+ *
+ * @param {Object} blockConfig - ACF block configuration object
+ * @returns {Object|boolean} - Registered block type or false if not registered
+ */
+function registerACFBlockType(blockConfig) {
+ // Check if block should be registered for current post type
+ if (!shouldRegisterBlock(blockConfig)) {
+ return false;
+ }
+
+ // Process icon
+ processBlockIcon(blockConfig);
+
+ // Validate category
+ validateBlockCategory(blockConfig);
+
+ // Apply default values
+ blockConfig = applyBlockDefaults(blockConfig);
+
+ // Clean up attributes
+ cleanBlockAttributes(blockConfig);
+
+ // Configure anchor support
+ configureAnchorSupport(blockConfig);
+
+ // Start with base BlockEdit component
+ let EditComponent = BlockEdit;
+
+ // Apply higher-order components based on supports
+ EditComponent = applyHigherOrderComponents(EditComponent, blockConfig);
+
+ // Create edit function that passes blockConfig and jQuery
+ blockConfig.edit = function (props) {
+ return ;
+ };
+
+ // Create save function (ACF blocks save to post content as HTML comments)
+ blockConfig.save = () => ;
+
+ // Store in registry
+ registeredBlocks[blockConfig.name] = blockConfig;
+
+ // Register with WordPress
+ const registeredBlockType = wp.blocks.registerBlockType(
+ blockConfig.name,
+ blockConfig
+ );
+
+ // Ensure anchor attribute is properly configured
+ if (
+ registeredBlockType &&
+ registeredBlockType.attributes &&
+ registeredBlockType.attributes.anchor
+ ) {
+ registeredBlockType.attributes.anchor = { type: 'string' };
+ }
+
+ return registeredBlockType;
+}
+
+/**
+ * Retrieves a registered block configuration by name
+ *
+ * @param {string} blockName - Name of the block
+ * @returns {Object|boolean} - Block configuration or false
+ */
+function getRegisteredBlock(blockName) {
+ return registeredBlocks[blockName] || false;
+}
+
+/**
+ * Higher-order component to migrate legacy attribute names to new format
+ * Handles backward compatibility for align_text -> alignText, etc.
+ */
+const withDefaultAttributes = createHigherOrderComponent(
+ (BlockListBlock) =>
+ class extends Component {
+ constructor(props) {
+ super(props);
+
+ const { name, attributes } = this.props;
+ const blockConfig = getRegisteredBlock(name);
+
+ if (!blockConfig) return;
+
+ // Remove empty string attributes
+ Object.keys(attributes).forEach((key) => {
+ if (attributes[key] === '') {
+ delete attributes[key];
+ }
+ });
+
+ // Map old attribute names to new camelCase names
+ const attributeMap = {
+ full_height: 'fullHeight',
+ align_content: 'alignContent',
+ align_text: 'alignText',
+ };
+
+ Object.keys(attributeMap).forEach((oldKey) => {
+ const newKey = attributeMap[oldKey];
+
+ if (attributes[oldKey] !== undefined) {
+ // Migrate old key to new key
+ attributes[newKey] = attributes[oldKey];
+ } else if (
+ attributes[newKey] === undefined &&
+ blockConfig[oldKey] !== undefined
+ ) {
+ // Set default from block config if not present
+ attributes[newKey] = blockConfig[oldKey];
+ }
+
+ // Clean up old attribute names
+ delete blockConfig[oldKey];
+ delete attributes[oldKey];
+ });
+
+ // Apply default values from block config for missing attributes
+ for (let key in blockConfig.attributes) {
+ if (
+ attributes[key] === undefined &&
+ blockConfig[key] !== undefined
+ ) {
+ attributes[key] = blockConfig[key];
+ }
+ }
+ }
+
+ render() {
+ return ;
+ }
+ },
+ 'withDefaultAttributes'
+);
+
+/**
+ * Initialize ACF blocks on the 'prepare' action
+ * Registers all ACF blocks with version 3 or higher
+ */
+acf.addAction('prepare', function () {
+ // Ensure wp.blockEditor exists (backward compatibility)
+ if (!wp.blockEditor) {
+ wp.blockEditor = wp.editor;
+ }
+
+ const blockTypes = acf.get('blockTypes');
+
+ if (blockTypes) {
+ blockTypes.forEach((blockType) => {
+ // Only register blocks with version 3 or higher
+ if (parseInt(blockType.acf_block_version) >= 3) {
+ registerACFBlockType(blockType);
+ }
+ });
+ }
+});
+
+/**
+ * Register WordPress filter for attribute migration
+ * Ensures backward compatibility with legacy attribute names
+ */
+wp.hooks.addFilter(
+ 'editor.BlockListBlock',
+ 'acf/with-default-attributes',
+ withDefaultAttributes
+);
+
+// Export for testing/external use
+export { registerACFBlockType, getRegisteredBlock };
diff --git a/assets/src/js/pro/blocks-v3/utils/post-locking.js b/assets/src/js/pro/blocks-v3/utils/post-locking.js
new file mode 100644
index 00000000..eae004df
--- /dev/null
+++ b/assets/src/js/pro/blocks-v3/utils/post-locking.js
@@ -0,0 +1,45 @@
+/**
+ * WordPress post locking utilities for ACF blocks
+ * Handles locking/unlocking post saving during block operations
+ */
+
+/**
+ * Locks post saving in the WordPress editor
+ * Used when block operations are in progress (like fetching data)
+ *
+ * @param {string} clientId - The block's client ID
+ */
+export const lockPostSaving = (clientId) => {
+ if (wp.data.dispatch('core/editor')) {
+ wp.data.dispatch('core/editor').lockPostSaving('acf/block/' + clientId);
+ }
+};
+
+/**
+ * Unlocks post saving in the WordPress editor
+ * Called when block operations are complete
+ *
+ * @param {string} clientId - The block's client ID
+ */
+export const unlockPostSaving = (clientId) => {
+ if (wp.data.dispatch('core/editor')) {
+ wp.data
+ .dispatch('core/editor')
+ .unlockPostSaving('acf/block/' + clientId);
+ }
+};
+
+/**
+ * Sorts an object's keys alphabetically
+ * Used for consistent object serialization and comparison
+ *
+ * @param {Object} obj - Object to sort
+ * @returns {Object} - New object with sorted keys
+ */
+export const sortObjectKeys = (obj) =>
+ Object.keys(obj)
+ .sort()
+ .reduce((result, key) => {
+ result[key] = obj[key];
+ return result;
+ }, {});
From 45dc933aa8870d5b30c1ed95d41fddae3e9ead89 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Mon, 3 Nov 2025 17:42:05 +0100
Subject: [PATCH 12/22] V3 blocks loading correctly
---
assets/src/js/pro/_acf-blocks.js | 7 ++++++-
assets/src/js/pro/blocks-v3/components/jsx-parser.js | 8 ++++----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/assets/src/js/pro/_acf-blocks.js b/assets/src/js/pro/_acf-blocks.js
index 9b232471..a120f056 100644
--- a/assets/src/js/pro/_acf-blocks.js
+++ b/assets/src/js/pro/_acf-blocks.js
@@ -1656,7 +1656,12 @@ const md5 = require( 'md5' );
// Register block types.
const blockTypes = acf.get( 'blockTypes' );
if ( blockTypes ) {
- blockTypes.map( registerBlockType );
+ // Only register blocks with version < 3 (v3 blocks are registered separately).
+ blockTypes
+ .filter(
+ ( blockType ) => parseInt( blockType.acf_block_version ) < 3
+ )
+ .map( registerBlockType );
}
}
diff --git a/assets/src/js/pro/blocks-v3/components/jsx-parser.js b/assets/src/js/pro/blocks-v3/components/jsx-parser.js
index 3ef07066..b7c3c85e 100644
--- a/assets/src/js/pro/blocks-v3/components/jsx-parser.js
+++ b/assets/src/js/pro/blocks-v3/components/jsx-parser.js
@@ -5,7 +5,7 @@
import jQuery from 'jquery';
-const { jsx, createElement, createRef, Component } = wp.element;
+const { createElement, createRef, Component } = wp.element;
const useInnerBlocksProps =
wp.blockEditor.__experimentalUseInnerBlocksProps ||
wp.blockEditor.useInnerBlocksProps;
@@ -27,7 +27,7 @@ function getJSXNameReplacement(attrName) {
*/
class ScriptComponent extends Component {
render() {
- return jsx('div', { ref: (element) => (this.el = element) });
+ return createElement('div', { ref: (element) => (this.el = element) });
}
setHTML(scriptContent) {
@@ -74,7 +74,7 @@ function ACFInnerBlocksComponent(props) {
const { className = 'acf-innerblocks-container' } = props;
const innerBlocksProps = useInnerBlocksProps({ className }, props);
- return jsx('div', {
+ return createElement('div', {
...innerBlocksProps,
children: innerBlocksProps.children,
});
@@ -176,7 +176,7 @@ function parseNodeToJSX(node, depth = 0) {
// Handle special ACFInnerBlocks component
if (componentType === 'ACFInnerBlocks') {
- return jsx(ACFInnerBlocksComponent, { ...props });
+ return createElement(ACFInnerBlocksComponent, { ...props });
}
// Build element array: [type, props, ...children]
From 3f5d9660c9ad7d873e88b24b59fcd64d106bb0fe Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Mon, 3 Nov 2025 18:18:34 +0100
Subject: [PATCH 13/22] Fix css not showing
---
assets/src/sass/pro/acf-pro-input.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/assets/src/sass/pro/acf-pro-input.scss b/assets/src/sass/pro/acf-pro-input.scss
index 025897c5..63df30ee 100644
--- a/assets/src/sass/pro/acf-pro-input.scss
+++ b/assets/src/sass/pro/acf-pro-input.scss
@@ -1,4 +1,5 @@
@charset "UTF-8";
+@use './blocks';
/*--------------------------------------------------------------------------------------------
*
* Vars
From fd46fb318651c9db224661b1600b0b94bf5f733a Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Tue, 4 Nov 2025 13:03:18 +0100
Subject: [PATCH 14/22] fix prettier
---
.../js/pro/blocks-v3/components/block-edit.js | 393 +++++++++---------
.../js/pro/blocks-v3/components/block-form.js | 254 +++++------
.../blocks-v3/components/block-placeholder.js | 12 +-
.../pro/blocks-v3/components/block-preview.js | 6 +-
.../js/pro/blocks-v3/components/jsx-parser.js | 108 ++---
.../with-align-content.js | 44 +-
.../high-order-components/with-align-text.js | 25 +-
.../high-order-components/with-full-height.js | 14 +-
.../pro/blocks-v3/register-block-type-v3.js | 158 +++----
.../js/pro/blocks-v3/utils/post-locking.js | 26 +-
readme.txt | 2 +-
11 files changed, 538 insertions(+), 504 deletions(-)
diff --git a/assets/src/js/pro/blocks-v3/components/block-edit.js b/assets/src/js/pro/blocks-v3/components/block-edit.js
index b584e66f..5fb0c842 100644
--- a/assets/src/js/pro/blocks-v3/components/block-edit.js
+++ b/assets/src/js/pro/blocks-v3/components/block-edit.js
@@ -42,26 +42,26 @@ import {
* @param {Object} props.blockType - ACF block type configuration
* @returns {JSX.Element} - Rendered block editor
*/
-export const BlockEdit = (props) => {
+export const BlockEdit = ( props ) => {
const { attributes, setAttributes, context, isSelected, $, blockType } =
props;
const shouldValidate = blockType.validate;
const { clientId } = useBlockEditContext();
- const [validationErrors, setValidationErrors] = useState(null);
- const [showValidationErrors, setShowValidationErrors] = useState(null);
- const [theSerializedAcfData, setTheSerializedAcfData] = useState(null);
- const [blockFormHtml, setBlockFormHtml] = useState('');
- const [blockPreviewHtml, setBlockPreviewHtml] = useState(
+ const [ validationErrors, setValidationErrors ] = useState( null );
+ const [ showValidationErrors, setShowValidationErrors ] = useState( null );
+ const [ theSerializedAcfData, setTheSerializedAcfData ] = useState( null );
+ const [ blockFormHtml, setBlockFormHtml ] = useState( '' );
+ const [ blockPreviewHtml, setBlockPreviewHtml ] = useState(
'acf-block-preview-loading'
);
- const [userHasInteractedWithForm, setUserHasInteractedWithForm] =
- useState(false);
+ const [ userHasInteractedWithForm, setUserHasInteractedWithForm ] =
+ useState( false );
- const acfFormRef = useRef(null);
- const previewRef = useRef(null);
- const debounceRef = useRef(null);
+ const acfFormRef = useRef( null );
+ const previewRef = useRef( null );
+ const debounceRef = useRef( null );
/**
* Fetches block data from server (form HTML, preview HTML, validation)
@@ -72,16 +72,16 @@ export const BlockEdit = (props) => {
* @param {Object} params.theContext - Block context
* @param {boolean} params.isSelected - Whether block is selected
*/
- function fetchBlockData({
+ function fetchBlockData( {
theAttributes,
theClientId,
theContext,
isSelected,
- }) {
- if (!theAttributes) return;
+ } ) {
+ if ( ! theAttributes ) return;
// Generate hash of attributes for preload cache lookup
- const attributesHash = generateAttributesHash(theAttributes, context);
+ const attributesHash = generateAttributesHash( theAttributes, context );
// Check for preloaded block data
const preloadedData = checkPreloadedData(
@@ -90,46 +90,48 @@ export const BlockEdit = (props) => {
isSelected
);
- if (preloadedData) {
- handlePreloadedData(preloadedData);
+ if ( preloadedData ) {
+ handlePreloadedData( preloadedData );
return;
}
// Prepare query options
const queryOptions = { preview: true, form: true, validate: true };
- if (!blockFormHtml) {
+ if ( ! blockFormHtml ) {
queryOptions.validate = false;
}
- if (!shouldValidate) {
+ if ( ! shouldValidate ) {
queryOptions.validate = false;
}
const blockData = { ...theAttributes };
- wp.data.dispatch('core/editor').lockPostSaving('acf-fetching-block');
+ wp.data
+ .dispatch( 'core/editor' )
+ .lockPostSaving( 'acf-fetching-block' );
// Fetch block data via AJAX
- $.ajax({
- url: acf.get('ajaxurl'),
+ $.ajax( {
+ url: acf.get( 'ajaxurl' ),
dataType: 'json',
type: 'post',
cache: false,
- data: acf.prepareForAjax({
+ data: acf.prepareForAjax( {
action: 'acf/ajax/fetch-block',
- block: JSON.stringify(blockData),
+ block: JSON.stringify( blockData ),
clientId: theClientId,
- context: JSON.stringify(theContext),
+ context: JSON.stringify( theContext ),
query: queryOptions,
- }),
- })
- .done((response) => {
+ } ),
+ } )
+ .done( ( response ) => {
wp.data
- .dispatch('core/editor')
- .unlockPostSaving('acf-fetching-block');
+ .dispatch( 'core/editor' )
+ .unlockPostSaving( 'acf-fetching-block' );
- setBlockFormHtml(response.data.form);
+ setBlockFormHtml( response.data.form );
- if (response.data.preview) {
+ if ( response.data.preview ) {
setBlockPreviewHtml(
acf.applyFilters(
'blocks/preview/render',
@@ -149,19 +151,19 @@ export const BlockEdit = (props) => {
if (
response.data?.validation &&
- !response.data.validation.valid &&
+ ! response.data.validation.valid &&
response.data.validation.errors
) {
- setValidationErrors(response.data.validation.errors);
+ setValidationErrors( response.data.validation.errors );
} else {
- setValidationErrors(null);
+ setValidationErrors( null );
}
- })
- .fail(function () {
+ } )
+ .fail( function () {
wp.data
- .dispatch('core/editor')
- .unlockPostSaving('acf-fetching-block');
- });
+ .dispatch( 'core/editor' )
+ .unlockPostSaving( 'acf-fetching-block' );
+ } );
}
/**
@@ -171,10 +173,10 @@ export const BlockEdit = (props) => {
* @param {Object} ctx - Block context
* @returns {string} - MD5 hash of serialized attributes
*/
- function generateAttributesHash(attrs, ctx) {
+ function generateAttributesHash( attrs, ctx ) {
delete attrs.hasAcfError;
- attrs._acf_context = sortObjectKeys(ctx);
- return md5(JSON.stringify(sortObjectKeys(attrs)));
+ attrs._acf_context = sortObjectKeys( ctx );
+ return md5( JSON.stringify( sortObjectKeys( attrs ) ) );
}
/**
@@ -185,35 +187,35 @@ export const BlockEdit = (props) => {
* @param {boolean} selected - Whether block is selected
* @returns {Object|boolean} - Preloaded data or false
*/
- function checkPreloadedData(hash, clientId, selected) {
- if (selected) return false;
+ function checkPreloadedData( hash, clientId, selected ) {
+ if ( selected ) return false;
- acf.debug('Preload check', hash, clientId);
+ acf.debug( 'Preload check', hash, clientId );
// Don't preload blocks inside Query Loop blocks
- if (isInQueryLoop(clientId)) {
+ if ( isInQueryLoop( clientId ) ) {
return false;
}
- const preloadedBlocks = acf.get('preloadedBlocks');
- if (!preloadedBlocks || !preloadedBlocks[hash]) {
- acf.debug('Preload failed: not preloaded.');
+ const preloadedBlocks = acf.get( 'preloadedBlocks' );
+ if ( ! preloadedBlocks || ! preloadedBlocks[ hash ] ) {
+ acf.debug( 'Preload failed: not preloaded.' );
return false;
}
- const data = preloadedBlocks[hash];
+ const data = preloadedBlocks[ hash ];
// Replace placeholder client ID with actual client ID
- data.html = data.html.replaceAll(hash, clientId);
+ data.html = data.html.replaceAll( hash, clientId );
- if (data?.validation && data?.validation.errors) {
- data.validation.errors = data.validation.errors.map((error) => {
- error.input = error.input.replaceAll(hash, clientId);
+ if ( data?.validation && data?.validation.errors ) {
+ data.validation.errors = data.validation.errors.map( ( error ) => {
+ error.input = error.input.replaceAll( hash, clientId );
return error;
- });
+ } );
}
- acf.debug('Preload successful', data);
+ acf.debug( 'Preload successful', data );
return data;
}
@@ -223,16 +225,16 @@ export const BlockEdit = (props) => {
* @param {string} clientId - Block client ID
* @returns {boolean} - True if inside Query Loop
*/
- function isInQueryLoop(clientId) {
+ function isInQueryLoop( clientId ) {
const parentIds = wp.data
- .select('core/block-editor')
- .getBlockParents(clientId);
+ .select( 'core/block-editor' )
+ .getBlockParents( clientId );
return (
wp.data
- .select('core/block-editor')
- .getBlocksByClientId(parentIds)
- .filter((block) => block.name === 'core/query').length > 0
+ .select( 'core/block-editor' )
+ .getBlocksByClientId( parentIds )
+ .filter( ( block ) => block.name === 'core/query' ).length > 0
);
}
@@ -241,12 +243,12 @@ export const BlockEdit = (props) => {
*
* @param {Object} data - Preloaded data
*/
- function handlePreloadedData(data) {
- if (data.form) {
- setBlockFormHtml(data.html);
- } else if (data.html) {
+ function handlePreloadedData( data ) {
+ if ( data.form ) {
+ setBlockFormHtml( data.html );
+ } else if ( data.html ) {
setBlockPreviewHtml(
- acf.applyFilters('blocks/preview/render', data.html, true)
+ acf.applyFilters( 'blocks/preview/render', data.html, true )
);
} else {
setBlockPreviewHtml(
@@ -260,54 +262,54 @@ export const BlockEdit = (props) => {
if (
data?.validation &&
- !data.validation.valid &&
+ ! data.validation.valid &&
data.validation.errors
) {
- setValidationErrors(data.validation.errors);
+ setValidationErrors( data.validation.errors );
} else {
- setValidationErrors(null);
+ setValidationErrors( null );
}
}
// Initial fetch on mount and when selection changes
- useEffect(() => {
+ useEffect( () => {
function trackUserInteraction() {
- setUserHasInteractedWithForm(true);
- window.removeEventListener('click', trackUserInteraction);
- window.removeEventListener('keydown', trackUserInteraction);
+ setUserHasInteractedWithForm( true );
+ window.removeEventListener( 'click', trackUserInteraction );
+ window.removeEventListener( 'keydown', trackUserInteraction );
}
- fetchBlockData({
+ fetchBlockData( {
theAttributes: attributes,
theClientId: clientId,
theContext: context,
isSelected: isSelected,
- });
+ } );
- window.addEventListener('click', trackUserInteraction);
- window.addEventListener('keydown', trackUserInteraction);
+ window.addEventListener( 'click', trackUserInteraction );
+ window.addEventListener( 'keydown', trackUserInteraction );
return () => {
- window.removeEventListener('click', trackUserInteraction);
- window.removeEventListener('keydown', trackUserInteraction);
+ window.removeEventListener( 'click', trackUserInteraction );
+ window.removeEventListener( 'keydown', trackUserInteraction );
};
- }, []);
+ }, [] );
// Update hasAcfError attribute based on validation errors
- useEffect(() => {
+ useEffect( () => {
setAttributes(
validationErrors ? { hasAcfError: true } : { hasAcfError: false }
);
- }, [validationErrors, setAttributes]);
+ }, [ validationErrors, setAttributes ] );
// Listen for validation error events from other blocks
- useEffect(() => {
+ useEffect( () => {
const handleErrorEvent = () => {
- lockPostSaving(clientId);
- setShowValidationErrors(true);
+ lockPostSaving( clientId );
+ setShowValidationErrors( true );
};
- document.addEventListener('acf/block/has-error', handleErrorEvent);
+ document.addEventListener( 'acf/block/has-error', handleErrorEvent );
return () => {
document.removeEventListener(
@@ -315,73 +317,74 @@ export const BlockEdit = (props) => {
handleErrorEvent
);
};
- }, []);
+ }, [] );
// Cleanup: unlock post saving on unmount
useEffect(
() => () => {
- unlockPostSaving(props.clientId);
+ unlockPostSaving( props.clientId );
},
[]
);
// Handle form data changes with debouncing
- useEffect(() => {
- clearTimeout(debounceRef.current);
+ useEffect( () => {
+ clearTimeout( debounceRef.current );
- debounceRef.current = setTimeout(() => {
+ debounceRef.current = setTimeout( () => {
handleFormDataUpdate();
- }, 200);
- }, [theSerializedAcfData]);
+ }, 200 );
+ }, [ theSerializedAcfData ] );
/**
* Updates block attributes when form data changes
*/
function handleFormDataUpdate() {
- const parsedData = JSON.parse(theSerializedAcfData);
- if (!parsedData) return;
- if (theSerializedAcfData === JSON.stringify(attributes.data)) return;
+ const parsedData = JSON.parse( theSerializedAcfData );
+ if ( ! parsedData ) return;
+ if ( theSerializedAcfData === JSON.stringify( attributes.data ) )
+ return;
const updatedAttributes = { ...attributes, data: { ...parsedData } };
- setAttributes(updatedAttributes);
+ setAttributes( updatedAttributes );
- fetchBlockData({
+ fetchBlockData( {
theAttributes: updatedAttributes,
theClientId: clientId,
theContext: context,
isSelected: isSelected,
- });
+ } );
}
// Trigger ACF actions when preview is rendered
- useEffect(() => {
- if (previewRef.current && blockPreviewHtml) {
- const blockName = attributes.name.replace('acf/', '');
- const $preview = $(previewRef.current);
+ useEffect( () => {
+ if ( previewRef.current && blockPreviewHtml ) {
+ const blockName = attributes.name.replace( 'acf/', '' );
+ const $preview = $( previewRef.current );
- acf.doAction('render_block_preview', $preview, attributes);
+ acf.doAction( 'render_block_preview', $preview, attributes );
acf.doAction(
- `render_block_preview/type=${blockName}`,
+ `render_block_preview/type=${ blockName }`,
$preview,
attributes
);
}
- }, [blockPreviewHtml]);
+ }, [ blockPreviewHtml ] );
return (
);
};
@@ -390,7 +393,7 @@ export const BlockEdit = (props) => {
* Inner component that handles rendering and portals
* Separated to manage refs and portal targets properly
*/
-function BlockEditInner(props) {
+function BlockEditInner( props ) {
const {
blockType,
$,
@@ -412,169 +415,169 @@ function BlockEditInner(props) {
const { clientId } = useBlockEditContext();
const invisibleFormContainerRef = useRef();
const inspectorControlsRef = useRef();
- const [isModalOpen, setIsModalOpen] = useState(false);
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
const modalFormContainerRef = useRef();
- const [currentFormContainer, setCurrentFormContainer] = useState();
+ const [ currentFormContainer, setCurrentFormContainer ] = useState();
// Set current form container when modal opens
- useEffect(() => {
- if (isModalOpen && modalFormContainerRef?.current) {
- setCurrentFormContainer(modalFormContainerRef.current);
+ useEffect( () => {
+ if ( isModalOpen && modalFormContainerRef?.current ) {
+ setCurrentFormContainer( modalFormContainerRef.current );
}
- }, [isModalOpen, modalFormContainerRef]);
+ }, [ isModalOpen, modalFormContainerRef ] );
// Update form container when inspector panel is available
- useEffect(() => {
- if (isSelected && inspectorControlsRef?.current) {
- setCurrentFormContainer(inspectorControlsRef.current);
- } else if (isSelected && !inspectorControlsRef?.current) {
+ useEffect( () => {
+ if ( isSelected && inspectorControlsRef?.current ) {
+ setCurrentFormContainer( inspectorControlsRef.current );
+ } else if ( isSelected && ! inspectorControlsRef?.current ) {
// Wait for inspector to be available
- setTimeout(() => {
- setCurrentFormContainer(inspectorControlsRef.current);
- }, 1);
- } else if (!isSelected) {
- setCurrentFormContainer(null);
+ setTimeout( () => {
+ setCurrentFormContainer( inspectorControlsRef.current );
+ }, 1 );
+ } else if ( ! isSelected ) {
+ setCurrentFormContainer( null );
}
- }, [isSelected, inspectorControlsRef, inspectorControlsRef.current]);
+ }, [ isSelected, inspectorControlsRef, inspectorControlsRef.current ] );
// Build block CSS classes
let blockClasses = 'acf-block-component acf-block-body';
blockClasses += ' acf-block-preview';
- if (validationErrors && showValidationErrors) {
+ if ( validationErrors && showValidationErrors ) {
blockClasses += ' acf-block-has-validation-error';
}
const blockProps = {
- ...useBlockProps({ className: blockClasses, ref: previewRef }),
+ ...useBlockProps( { className: blockClasses, ref: previewRef } ),
};
// Determine portal target
let portalTarget = null;
- if (currentFormContainer) {
+ if ( currentFormContainer ) {
portalTarget = currentFormContainer;
- } else if (inspectorControlsRef?.current) {
+ } else if ( inspectorControlsRef?.current ) {
portalTarget = inspectorControlsRef.current;
}
return (
<>
- {/* Block toolbar controls */}
+ { /* Block toolbar controls */ }
{
- setIsModalOpen(true);
- }}
+ onClick={ () => {
+ setIsModalOpen( true );
+ } }
/>
- {/* Inspector panel container */}
+ { /* Inspector panel container */ }
-
+
- {/* Render form via portal when container is available */}
- {portalTarget &&
+ { /* Render form via portal when container is available */ }
+ { portalTarget &&
currentFormContainer &&
createPortal(
<>
{
- blockFetcher({
+ $={ $ }
+ clientId={ clientId }
+ blockFormHtml={ blockFormHtml }
+ onMount={ () => {
+ blockFetcher( {
theAttributes: attributes,
theClientId: clientId,
theContext: context,
isSelected: isSelected,
- });
- }}
- onChange={function ($form) {
+ } );
+ } }
+ onChange={ function ( $form ) {
const serializedData = acf.serialize(
$form,
- `acf-block_${clientId}`
+ `acf-block_${ clientId }`
);
- if (serializedData) {
+ if ( serializedData ) {
setTheSerializedAcfData(
- JSON.stringify(serializedData)
+ JSON.stringify( serializedData )
);
}
- }}
- validationErrors={validationErrors}
- showValidationErrors={showValidationErrors}
- acfFormRef={acfFormRef}
- theSerializedAcfData={theSerializedAcfData}
+ } }
+ validationErrors={ validationErrors }
+ showValidationErrors={ showValidationErrors }
+ acfFormRef={ acfFormRef }
+ theSerializedAcfData={ theSerializedAcfData }
userHasInteractedWithForm={
userHasInteractedWithForm
}
setCurrentBlockFormContainer={
setCurrentFormContainer
}
- attributes={attributes}
+ attributes={ attributes }
/>
>,
currentFormContainer || inspectorControlsRef.current
- )}
+ ) }
- {/* Hidden container for form when not in inspector/modal */}
+ { /* Hidden container for form when not in inspector/modal */ }
<>
- {/* Modal for editing block fields */}
- {isModalOpen && (
+ { /* Modal for editing block fields */ }
+ { isModalOpen && (
{
- setCurrentFormContainer(null);
- setIsModalOpen(false);
- }}
+ isFullScreen={ true }
+ title={ blockType.title }
+ onRequestClose={ () => {
+ setCurrentFormContainer( null );
+ setIsModalOpen( false );
+ } }
>
- )}
+ ) }
>
- {/* Block preview */}
+ { /* Block preview */ }
<>
- {/* Show placeholder when no HTML */}
- {blockPreviewHtml === 'acf-block-preview-no-html' ? (
+ { /* Show placeholder when no HTML */ }
+ { blockPreviewHtml === 'acf-block-preview-no-html' ? (
- ) : null}
+ ) : null }
- {/* Show spinner while loading */}
- {blockPreviewHtml === 'acf-block-preview-loading' && (
+ { /* Show spinner while loading */ }
+ { blockPreviewHtml === 'acf-block-preview-loading' && (
- )}
+ ) }
- {/* Render actual preview HTML */}
- {blockPreviewHtml !== 'acf-block-preview-loading' &&
+ { /* Render actual preview HTML */ }
+ { blockPreviewHtml !== 'acf-block-preview-loading' &&
blockPreviewHtml !== 'acf-block-preview-no-html' &&
blockPreviewHtml &&
- acf.parseJSX(blockPreviewHtml)}
+ acf.parseJSX( blockPreviewHtml ) }
>
>
diff --git a/assets/src/js/pro/blocks-v3/components/block-form.js b/assets/src/js/pro/blocks-v3/components/block-form.js
index b15f5744..7baf6ec8 100644
--- a/assets/src/js/pro/blocks-v3/components/block-form.js
+++ b/assets/src/js/pro/blocks-v3/components/block-form.js
@@ -23,7 +23,7 @@ import { lockPostSaving, unlockPostSaving } from '../utils/post-locking';
* @param {Object} props.attributes - Block attributes
* @returns {JSX.Element} - Rendered form component
*/
-export const BlockForm = ({
+export const BlockForm = ( {
$,
clientId,
blockFormHtml,
@@ -34,110 +34,118 @@ export const BlockForm = ({
acfFormRef,
userHasInteractedWithForm,
attributes,
-}) => {
- const [formHtml, setFormHtml] = useState(blockFormHtml);
- const [pendingChange, setPendingChange] = useState(false);
- const debounceTimer = useRef(null);
- const [userInteracted, setUserInteracted] = useState(false);
+} ) => {
+ const [ formHtml, setFormHtml ] = useState( blockFormHtml );
+ const [ pendingChange, setPendingChange ] = useState( false );
+ const debounceTimer = useRef( null );
+ const [ userInteracted, setUserInteracted ] = useState( false );
// Call onMount when component first mounts
- useEffect(() => {
+ useEffect( () => {
onMount();
- }, []);
+ }, [] );
// Trigger onChange when there's a pending change and user has interacted
- useEffect(() => {
- if (pendingChange && (userHasInteractedWithForm || userInteracted)) {
- onChange(pendingChange);
- setPendingChange(false);
+ useEffect( () => {
+ if (
+ pendingChange &&
+ ( userHasInteractedWithForm || userInteracted )
+ ) {
+ onChange( pendingChange );
+ setPendingChange( false );
}
- }, [pendingChange, userHasInteractedWithForm, setPendingChange, onChange]);
+ }, [
+ pendingChange,
+ userHasInteractedWithForm,
+ setPendingChange,
+ onChange,
+ ] );
// Update form HTML when blockFormHtml prop changes
- useEffect(() => {
- if (!formHtml && blockFormHtml) {
- setFormHtml(blockFormHtml);
+ useEffect( () => {
+ if ( ! formHtml && blockFormHtml ) {
+ setFormHtml( blockFormHtml );
}
- }, [blockFormHtml]);
+ }, [ blockFormHtml ] );
// Handle validation errors
- useEffect(() => {
- if (!acfFormRef?.current) return;
+ useEffect( () => {
+ if ( ! acfFormRef?.current ) return;
const validator = acf.getBlockFormValidator(
- $(acfFormRef.current).find('.acf-block-fields')
+ $( acfFormRef.current ).find( '.acf-block-fields' )
);
validator.clearErrors();
- validator.set('notice', null);
+ validator.set( 'notice', null );
- acf.doAction('blocks/validation/pre_apply', validationErrors);
+ acf.doAction( 'blocks/validation/pre_apply', validationErrors );
- if (validationErrors) {
- if (showValidationErrors) {
- lockPostSaving(clientId);
- validator.$el.find('.acf-notice').remove();
- validator.addErrors(validationErrors);
- validator.showErrors('after');
+ if ( validationErrors ) {
+ if ( showValidationErrors ) {
+ lockPostSaving( clientId );
+ validator.$el.find( '.acf-notice' ).remove();
+ validator.addErrors( validationErrors );
+ validator.showErrors( 'after' );
}
} else {
// Handle successful validation
if (
- validator.$el.find('.acf-notice').length > 0 &&
+ validator.$el.find( '.acf-notice' ).length > 0 &&
showValidationErrors
) {
- validator.$el.find('.acf-notice').remove();
- validator.addErrors([
- { message: acf.__('Validation successful') },
- ]);
- validator.showErrors('after');
- validator.get('notice').update({
+ validator.$el.find( '.acf-notice' ).remove();
+ validator.addErrors( [
+ { message: acf.__( 'Validation successful' ) },
+ ] );
+ validator.showErrors( 'after' );
+ validator.get( 'notice' ).update( {
type: 'success',
- text: acf.__('Validation successful'),
+ text: acf.__( 'Validation successful' ),
timeout: 1000,
- });
- validator.set('notice', null);
+ } );
+ validator.set( 'notice', null );
- setTimeout(() => {
- validator.$el.find('.acf-notice').remove();
- }, 1001);
+ setTimeout( () => {
+ validator.$el.find( '.acf-notice' ).remove();
+ }, 1001 );
- const noticeDispatch = wp.data.dispatch('core/notices');
+ const noticeDispatch = wp.data.dispatch( 'core/notices' );
/**
* Recursively checks for ACF errors in blocks
* @param {Array} blocks - Array of block objects
* @returns {Promise} - True if error found
*/
- function checkForErrors(blocks) {
- return new Promise(function (resolve) {
- blocks.forEach((block) => {
- if (block.innerBlocks.length > 0) {
- checkForErrors(block.innerBlocks).then(
- (hasError) => {
- if (hasError) return resolve(true);
+ function checkForErrors( blocks ) {
+ return new Promise( function ( resolve ) {
+ blocks.forEach( ( block ) => {
+ if ( block.innerBlocks.length > 0 ) {
+ checkForErrors( block.innerBlocks ).then(
+ ( hasError ) => {
+ if ( hasError ) return resolve( true );
}
);
}
- if (block.attributes.hasAcfError) {
+ if ( block.attributes.hasAcfError ) {
const errorBlockClientId = block.clientId;
- if (errorBlockClientId !== clientId) {
+ if ( errorBlockClientId !== clientId ) {
wp.data
- .dispatch('core/block-editor')
- .selectBlock(errorBlockClientId);
- return resolve(true);
+ .dispatch( 'core/block-editor' )
+ .selectBlock( errorBlockClientId );
+ return resolve( true );
}
}
- });
- return resolve(false);
- });
+ } );
+ return resolve( false );
+ } );
}
checkForErrors(
- wp.data.select('core/block-editor').getBlocks()
- ).then((hasError) => {
- if (hasError) {
+ wp.data.select( 'core/block-editor' ).getBlocks()
+ ).then( ( hasError ) => {
+ if ( hasError ) {
noticeDispatch.createErrorNotice(
acf.__(
'An ACF Block on this page requires attention before you can save.'
@@ -145,67 +153,67 @@ export const BlockForm = ({
{ id: 'acf-blocks-validation', isDismissible: true }
);
} else {
- noticeDispatch.removeNotice('acf-blocks-validation');
+ noticeDispatch.removeNotice( 'acf-blocks-validation' );
}
- });
+ } );
}
- unlockPostSaving(clientId);
+ unlockPostSaving( clientId );
}
- acf.doAction('blocks/validation/post_apply', validationErrors);
- }, [validationErrors, clientId, showValidationErrors]);
+ acf.doAction( 'blocks/validation/post_apply', validationErrors );
+ }, [ validationErrors, clientId, showValidationErrors ] );
// Handle form remounting and change detection
- useEffect(() => {
- if (!acfFormRef?.current || !formHtml) return;
+ useEffect( () => {
+ if ( ! acfFormRef?.current || ! formHtml ) return;
- acf.debug('Remounting ACF Form');
+ acf.debug( 'Remounting ACF Form' );
const formElement = acfFormRef.current;
- const $form = $(formElement);
+ const $form = $( formElement );
let isActive = true;
- acf.doAction('remount', $form);
+ acf.doAction( 'remount', $form );
const handleChange = () => {
- onChange($form);
+ onChange( $form );
};
const scheduleChange = () => {
- if (!isActive) return;
+ if ( ! isActive ) return;
- const inputs = formElement.querySelectorAll('input, textarea');
- const selects = formElement.querySelectorAll('select');
+ const inputs = formElement.querySelectorAll( 'input, textarea' );
+ const selects = formElement.querySelectorAll( 'select' );
- inputs.forEach((input) => {
- input.removeEventListener('input', handleChange);
- input.addEventListener('input', handleChange);
- });
+ inputs.forEach( ( input ) => {
+ input.removeEventListener( 'input', handleChange );
+ input.addEventListener( 'input', handleChange );
+ } );
- selects.forEach((select) => {
- select.removeEventListener('change', handleChange);
- select.addEventListener('change', handleChange);
- });
+ selects.forEach( ( select ) => {
+ select.removeEventListener( 'change', handleChange );
+ select.addEventListener( 'change', handleChange );
+ } );
- clearTimeout(debounceTimer.current);
- debounceTimer.current = setTimeout(() => {
- if (isActive) {
- setPendingChange($form);
+ clearTimeout( debounceTimer.current );
+ debounceTimer.current = setTimeout( () => {
+ if ( isActive ) {
+ setPendingChange( $form );
}
- }, 200);
+ }, 200 );
};
// Observe DOM changes to detect field additions/removals
- const domObserver = new MutationObserver(scheduleChange);
+ const domObserver = new MutationObserver( scheduleChange );
// Observe iframe content changes (for WYSIWYG editors)
- const iframeObserver = new MutationObserver(() => {
- if (isActive) {
- setUserInteracted(true);
+ const iframeObserver = new MutationObserver( () => {
+ if ( isActive ) {
+ setUserInteracted( true );
scheduleChange();
}
- });
+ } );
const observerConfig = {
attributes: true,
@@ -214,55 +222,63 @@ export const BlockForm = ({
characterData: true,
};
- domObserver.observe(formElement, observerConfig);
+ domObserver.observe( formElement, observerConfig );
// Watch for changes in iframes (WYSIWYG fields)
- [...formElement.querySelectorAll('iframe')].forEach((iframe) => {
- if (iframe && iframe.contentDocument) {
+ [ ...formElement.querySelectorAll( 'iframe' ) ].forEach( ( iframe ) => {
+ if ( iframe && iframe.contentDocument ) {
const iframeBody = iframe.contentDocument.body;
- if (iframeBody) {
- iframeObserver.observe(iframeBody, observerConfig);
+ if ( iframeBody ) {
+ iframeObserver.observe( iframeBody, observerConfig );
}
}
- });
+ } );
// Attach event listeners to form inputs
- formElement.querySelectorAll('input, textarea').forEach((input) => {
- input.addEventListener('input', handleChange);
- });
+ formElement
+ .querySelectorAll( 'input, textarea' )
+ .forEach( ( input ) => {
+ input.addEventListener( 'input', handleChange );
+ } );
- formElement.querySelectorAll('select').forEach((select) => {
- select.addEventListener('change', handleChange);
- });
+ formElement.querySelectorAll( 'select' ).forEach( ( select ) => {
+ select.addEventListener( 'change', handleChange );
+ } );
// Cleanup function
return () => {
isActive = false;
domObserver.disconnect();
iframeObserver.disconnect();
- clearTimeout(debounceTimer.current);
+ clearTimeout( debounceTimer.current );
- if (formElement) {
+ if ( formElement ) {
formElement
- .querySelectorAll('input, textarea')
- .forEach((input) => {
- input.removeEventListener('input', handleChange);
- });
-
- formElement.querySelectorAll('select').forEach((select) => {
- select.removeEventListener('change', handleChange);
- });
+ .querySelectorAll( 'input, textarea' )
+ .forEach( ( input ) => {
+ input.removeEventListener( 'input', handleChange );
+ } );
+
+ formElement
+ .querySelectorAll( 'select' )
+ .forEach( ( select ) => {
+ select.removeEventListener( 'change', handleChange );
+ } );
}
};
- }, [acfFormRef, attributes, formHtml]);
+ }, [ acfFormRef, attributes, formHtml ] );
return (
);
};
diff --git a/assets/src/js/pro/blocks-v3/components/block-placeholder.js b/assets/src/js/pro/blocks-v3/components/block-placeholder.js
index fdae3d3a..3d005207 100644
--- a/assets/src/js/pro/blocks-v3/components/block-placeholder.js
+++ b/assets/src/js/pro/blocks-v3/components/block-placeholder.js
@@ -32,15 +32,15 @@ const blockIcon = (
* @param {string} props.blockLabel - The block's title/label
* @returns {JSX.Element} - Rendered placeholder
*/
-export const BlockPlaceholder = ({ setBlockFormModalOpen, blockLabel }) => (
- } label={blockLabel}>
+export const BlockPlaceholder = ( { setBlockFormModalOpen, blockLabel } ) => (
+ } label={ blockLabel }>
{
- setBlockFormModalOpen(true);
- }}
+ onClick={ () => {
+ setBlockFormModalOpen( true );
+ } }
>
- {acf.__('Edit Block')}
+ { acf.__( 'Edit Block' ) }
);
diff --git a/assets/src/js/pro/blocks-v3/components/block-preview.js b/assets/src/js/pro/blocks-v3/components/block-preview.js
index 467a3b34..e8257953 100644
--- a/assets/src/js/pro/blocks-v3/components/block-preview.js
+++ b/assets/src/js/pro/blocks-v3/components/block-preview.js
@@ -13,8 +13,8 @@
* @param {Object} props.blockProps - Block props from useBlockProps hook
* @returns {JSX.Element} - Rendered preview wrapper
*/
-export const BlockPreview = ({ children, blockPreviewHtml, blockProps }) => (
-
- {children}
+export const BlockPreview = ( { children, blockPreviewHtml, blockProps } ) => (
+
+ { children }
);
diff --git a/assets/src/js/pro/blocks-v3/components/jsx-parser.js b/assets/src/js/pro/blocks-v3/components/jsx-parser.js
index b7c3c85e..a0e60e70 100644
--- a/assets/src/js/pro/blocks-v3/components/jsx-parser.js
+++ b/assets/src/js/pro/blocks-v3/components/jsx-parser.js
@@ -17,8 +17,8 @@ const useInnerBlocksProps =
* @param {string} attrName - HTML attribute name
* @returns {string} - JSX/React prop name
*/
-function getJSXNameReplacement(attrName) {
- return acf.isget(acf, 'jsxNameReplacements', attrName) || attrName;
+function getJSXNameReplacement( attrName ) {
+ return acf.isget( acf, 'jsxNameReplacements', attrName ) || attrName;
}
/**
@@ -27,19 +27,21 @@ function getJSXNameReplacement(attrName) {
*/
class ScriptComponent extends Component {
render() {
- return createElement('div', { ref: (element) => (this.el = element) });
+ return createElement( 'div', {
+ ref: ( element ) => ( this.el = element ),
+ } );
}
- setHTML(scriptContent) {
- jQuery(this.el).html(``);
+ setHTML( scriptContent ) {
+ jQuery( this.el ).html( `` );
}
componentDidUpdate() {
- this.setHTML(this.props.children);
+ this.setHTML( this.props.children );
}
componentDidMount() {
- this.setHTML(this.props.children);
+ this.setHTML( this.props.children );
}
}
@@ -50,8 +52,8 @@ class ScriptComponent extends Component {
* @param {string} nodeName - Lowercase node name
* @returns {string|Function|null} - Component type or null
*/
-function getComponentType(nodeName) {
- switch (nodeName) {
+function getComponentType( nodeName ) {
+ switch ( nodeName ) {
case 'innerblocks':
return 'ACFInnerBlocks';
case 'script':
@@ -59,7 +61,7 @@ function getComponentType(nodeName) {
case '#comment':
return null;
default:
- return getJSXNameReplacement(nodeName);
+ return getJSXNameReplacement( nodeName );
}
}
@@ -70,14 +72,14 @@ function getComponentType(nodeName) {
* @param {Object} props - Component props
* @returns {JSX.Element} - Wrapped InnerBlocks component
*/
-function ACFInnerBlocksComponent(props) {
+function ACFInnerBlocksComponent( props ) {
const { className = 'acf-innerblocks-container' } = props;
- const innerBlocksProps = useInnerBlocksProps({ className }, props);
+ const innerBlocksProps = useInnerBlocksProps( { className }, props );
- return createElement('div', {
+ return createElement( 'div', {
...innerBlocksProps,
children: innerBlocksProps.children,
- });
+ } );
}
/**
@@ -87,7 +89,7 @@ function ACFInnerBlocksComponent(props) {
* @param {Attr} attribute - DOM attribute object with name and value
* @returns {Object} - Transformed attribute {name, value}
*/
-function parseAttribute(attribute) {
+function parseAttribute( attribute ) {
let attrName = attribute.name;
let attrValue = attribute.value;
@@ -97,9 +99,9 @@ function parseAttribute(attribute) {
false,
attribute
);
- if (customParsed) return customParsed;
+ if ( customParsed ) return customParsed;
- switch (attrName) {
+ switch ( attrName ) {
case 'class':
// Convert HTML class to React className
attrName = 'className';
@@ -108,38 +110,38 @@ function parseAttribute(attribute) {
case 'style':
// Parse inline CSS string to JavaScript style object
const styleObject = {};
- attrValue.split(';').forEach((declaration) => {
- const colonIndex = declaration.indexOf(':');
- if (colonIndex > 0) {
- let property = declaration.substr(0, colonIndex).trim();
- const value = declaration.substr(colonIndex + 1).trim();
+ attrValue.split( ';' ).forEach( ( declaration ) => {
+ const colonIndex = declaration.indexOf( ':' );
+ if ( colonIndex > 0 ) {
+ let property = declaration.substr( 0, colonIndex ).trim();
+ const value = declaration.substr( colonIndex + 1 ).trim();
// Convert kebab-case to camelCase (except CSS variables starting with -)
- if (property.charAt(0) !== '-') {
- property = acf.strCamelCase(property);
+ if ( property.charAt( 0 ) !== '-' ) {
+ property = acf.strCamelCase( property );
}
- styleObject[property] = value;
+ styleObject[ property ] = value;
}
- });
+ } );
attrValue = styleObject;
break;
default:
// Preserve data- attributes as-is
- if (attrName.indexOf('data-') === 0) break;
+ if ( attrName.indexOf( 'data-' ) === 0 ) break;
// Apply JSX name transformations (e.g., onclick -> onClick)
- attrName = getJSXNameReplacement(attrName);
+ attrName = getJSXNameReplacement( attrName );
// Parse JSON array/object values
- const firstChar = attrValue.charAt(0);
- if (firstChar === '[' || firstChar === '{') {
- attrValue = JSON.parse(attrValue);
+ const firstChar = attrValue.charAt( 0 );
+ if ( firstChar === '[' || firstChar === '{' ) {
+ attrValue = JSON.parse( attrValue );
}
// Convert string booleans to actual booleans
- if (attrValue === 'true' || attrValue === 'false') {
+ if ( attrValue === 'true' || attrValue === 'false' ) {
attrValue = attrValue === 'true';
}
}
@@ -154,48 +156,48 @@ function parseAttribute(attribute) {
* @param {number} depth - Current recursion depth (0-based)
* @returns {JSX.Element|null} - React element or null if node should be skipped
*/
-function parseNodeToJSX(node, depth = 0) {
+function parseNodeToJSX( node, depth = 0 ) {
// Determine the component type for this node
- const componentType = getComponentType(node.nodeName.toLowerCase());
+ const componentType = getComponentType( node.nodeName.toLowerCase() );
- if (!componentType) return null;
+ if ( ! componentType ) return null;
const props = {};
// Add ref to first-level elements (except ACFInnerBlocks)
- if (depth === 1 && componentType !== 'ACFInnerBlocks') {
+ if ( depth === 1 && componentType !== 'ACFInnerBlocks' ) {
props.ref = createRef();
}
// Parse all attributes and add to props
- acf.arrayArgs(node.attributes)
- .map(parseAttribute)
- .forEach(({ name, value }) => {
- props[name] = value;
- });
+ acf.arrayArgs( node.attributes )
+ .map( parseAttribute )
+ .forEach( ( { name, value } ) => {
+ props[ name ] = value;
+ } );
// Handle special ACFInnerBlocks component
- if (componentType === 'ACFInnerBlocks') {
- return createElement(ACFInnerBlocksComponent, { ...props });
+ if ( componentType === 'ACFInnerBlocks' ) {
+ return createElement( ACFInnerBlocksComponent, { ...props } );
}
// Build element array: [type, props, ...children]
- const elementArray = [componentType, props];
+ const elementArray = [ componentType, props ];
// Recursively process child nodes
- acf.arrayArgs(node.childNodes).forEach((childNode) => {
- if (childNode instanceof Text) {
+ acf.arrayArgs( node.childNodes ).forEach( ( childNode ) => {
+ if ( childNode instanceof Text ) {
const textContent = childNode.textContent;
- if (textContent) {
- elementArray.push(textContent);
+ if ( textContent ) {
+ elementArray.push( textContent );
}
} else {
- elementArray.push(parseNodeToJSX(childNode, depth + 1));
+ elementArray.push( parseNodeToJSX( childNode, depth + 1 ) );
}
- });
+ } );
// Create and return React element
- return createElement.apply(this, elementArray);
+ return createElement.apply( this, elementArray );
}
/**
@@ -205,7 +207,7 @@ function parseNodeToJSX(node, depth = 0) {
* @param {string} htmlString - HTML markup to parse
* @returns {Array|JSX.Element} - React children from parsed HTML
*/
-export function parseJSX(htmlString) {
+export function parseJSX( htmlString ) {
// Wrap in div to ensure valid HTML structure
htmlString = '
' + htmlString + '
';
@@ -216,7 +218,7 @@ export function parseJSX(htmlString) {
);
// Parse with jQuery, convert to React, and extract children from wrapper div
- const parsedElement = parseNodeToJSX(jQuery(htmlString)[0], 0);
+ const parsedElement = parseNodeToJSX( jQuery( htmlString )[ 0 ], 0 );
return parsedElement.props.children;
}
diff --git a/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js b/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js
index a91dfe67..567123e1 100644
--- a/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js
+++ b/assets/src/js/pro/blocks-v3/high-order-components/with-align-content.js
@@ -22,8 +22,10 @@ const BlockAlignmentMatrixToolbar =
* @param {string} alignment - Alignment value
* @returns {string} - Normalized alignment (top, center, or bottom)
*/
-const normalizeVerticalAlignment = (alignment) => {
- return ['top', 'center', 'bottom'].includes(alignment) ? alignment : 'top';
+const normalizeVerticalAlignment = ( alignment ) => {
+ return [ 'top', 'center', 'bottom' ].includes( alignment )
+ ? alignment
+ : 'top';
};
/**
@@ -32,9 +34,9 @@ const normalizeVerticalAlignment = (alignment) => {
* @param {string} alignment - Current alignment value
* @returns {string} - Normalized alignment value (left, center, or right)
*/
-const getDefaultHorizontalAlignment = (alignment) => {
- const defaultAlign = acf.get('rtl') ? 'right' : 'left';
- return ['left', 'center', 'right'].includes(alignment)
+const getDefaultHorizontalAlignment = ( alignment ) => {
+ const defaultAlign = acf.get( 'rtl' ) ? 'right' : 'left';
+ return [ 'left', 'center', 'right' ].includes( alignment )
? alignment
: defaultAlign;
};
@@ -46,10 +48,12 @@ const getDefaultHorizontalAlignment = (alignment) => {
* @param {string} alignment - Alignment value
* @returns {string} - Normalized matrix alignment
*/
-const normalizeMatrixAlignment = (alignment) => {
- if (alignment) {
- const [vertical, horizontal] = alignment.split(' ');
- return `${normalizeVerticalAlignment(vertical)} ${getDefaultHorizontalAlignment(horizontal)}`;
+const normalizeMatrixAlignment = ( alignment ) => {
+ if ( alignment ) {
+ const [ vertical, horizontal ] = alignment.split( ' ' );
+ return `${ normalizeVerticalAlignment(
+ vertical
+ ) } ${ getDefaultHorizontalAlignment( horizontal ) }`;
}
return 'center center';
};
@@ -62,7 +66,7 @@ const normalizeMatrixAlignment = (alignment) => {
* @param {Object} blockConfig - ACF block configuration
* @returns {React.Component} - Enhanced component with content alignment controls
*/
-export const withAlignContent = (BlockComponent, blockConfig) => {
+export const withAlignContent = ( BlockComponent, blockConfig ) => {
let AlignmentControl;
let normalizeAlignment;
@@ -82,12 +86,12 @@ export const withAlignContent = (BlockComponent, blockConfig) => {
}
// If alignment control is not available, return original component
- if (AlignmentControl === undefined) {
+ if ( AlignmentControl === undefined ) {
return BlockComponent;
}
// Set default alignment on block config
- blockConfig.alignContent = normalizeAlignment(blockConfig.alignContent);
+ blockConfig.alignContent = normalizeAlignment( blockConfig.alignContent );
return class extends Component {
render() {
@@ -98,17 +102,17 @@ export const withAlignContent = (BlockComponent, blockConfig) => {
-
+
);
}
diff --git a/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js b/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js
index f54f5fba..27d6e353 100644
--- a/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js
+++ b/assets/src/js/pro/blocks-v3/high-order-components/with-align-text.js
@@ -12,9 +12,9 @@ const { BlockControls, AlignmentToolbar } = wp.blockEditor;
* @param {string} alignment - Current alignment value
* @returns {string} - Normalized alignment value (left, center, or right)
*/
-const getDefaultAlignment = (alignment) => {
- const defaultAlign = acf.get('rtl') ? 'right' : 'left';
- return ['left', 'center', 'right'].includes(alignment)
+const getDefaultAlignment = ( alignment ) => {
+ const defaultAlign = acf.get( 'rtl' ) ? 'right' : 'left';
+ return [ 'left', 'center', 'right' ].includes( alignment )
? alignment
: defaultAlign;
};
@@ -27,11 +27,11 @@ const getDefaultAlignment = (alignment) => {
* @param {Object} blockConfig - ACF block configuration
* @returns {React.Component} - Enhanced component with text alignment controls
*/
-export const withAlignText = (BlockComponent, blockConfig) => {
+export const withAlignText = ( BlockComponent, blockConfig ) => {
const normalizeAlignment = getDefaultAlignment;
// Set default alignment on block config
- blockConfig.alignText = normalizeAlignment(blockConfig.alignText);
+ blockConfig.alignText = normalizeAlignment( blockConfig.alignText );
return class extends Component {
render() {
@@ -42,15 +42,16 @@ export const withAlignText = (BlockComponent, blockConfig) => {
-
+
);
}
diff --git a/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js b/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js
index 7bc539b1..50782b20 100644
--- a/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js
+++ b/assets/src/js/pro/blocks-v3/high-order-components/with-full-height.js
@@ -19,9 +19,9 @@ const BlockFullHeightAlignmentControl =
* @param {React.Component} BlockComponent - The component to wrap
* @returns {React.Component} - Enhanced component with full height controls
*/
-export const withFullHeight = (BlockComponent) => {
+export const withFullHeight = ( BlockComponent ) => {
// If control is not available, return original component
- if (!BlockFullHeightAlignmentControl) {
+ if ( ! BlockFullHeightAlignmentControl ) {
return BlockComponent;
}
@@ -34,13 +34,13 @@ export const withFullHeight = (BlockComponent) => {
-
+
);
}
diff --git a/assets/src/js/pro/blocks-v3/register-block-type-v3.js b/assets/src/js/pro/blocks-v3/register-block-type-v3.js
index d310efc1..ba44bdb4 100644
--- a/assets/src/js/pro/blocks-v3/register-block-type-v3.js
+++ b/assets/src/js/pro/blocks-v3/register-block-type-v3.js
@@ -25,8 +25,8 @@ const registeredBlocks = {};
* @param {string} attributeType - Type of the attribute (string, boolean, etc.)
* @returns {Object} - Updated attributes object
*/
-const addAttribute = (attributes, attributeName, attributeType) => {
- attributes[attributeName] = { type: attributeType };
+const addAttribute = ( attributes, attributeName, attributeType ) => {
+ attributes[ attributeName ] = { type: attributeType };
return attributes;
};
@@ -36,15 +36,15 @@ const addAttribute = (attributes, attributeName, attributeType) => {
* @param {Object} blockConfig - Block configuration
* @returns {boolean} - True if block should be registered
*/
-function shouldRegisterBlock(blockConfig) {
+function shouldRegisterBlock( blockConfig ) {
const allowedPostTypes = blockConfig.post_types || [];
- if (allowedPostTypes.length) {
+ if ( allowedPostTypes.length ) {
// Always allow in reusable blocks
- allowedPostTypes.push('wp_block');
+ allowedPostTypes.push( 'wp_block' );
- const currentPostType = acf.get('postType');
- if (!allowedPostTypes.includes(currentPostType)) {
+ const currentPostType = acf.get( 'postType' );
+ if ( ! allowedPostTypes.includes( currentPostType ) ) {
return false;
}
}
@@ -57,20 +57,20 @@ function shouldRegisterBlock(blockConfig) {
*
* @param {Object} blockConfig - Block configuration
*/
-function processBlockIcon(blockConfig) {
+function processBlockIcon( blockConfig ) {
// Convert SVG string to JSX element
if (
typeof blockConfig.icon === 'string' &&
- blockConfig.icon.substr(0, 4) === '
+
);
}
// Remove icon if empty/invalid
- if (!blockConfig.icon) {
+ if ( ! blockConfig.icon ) {
delete blockConfig.icon;
}
}
@@ -81,13 +81,13 @@ function processBlockIcon(blockConfig) {
*
* @param {Object} blockConfig - Block configuration
*/
-function validateBlockCategory(blockConfig) {
+function validateBlockCategory( blockConfig ) {
const categoryExists = wp.blocks
.getCategories()
- .filter(({ slug }) => slug === blockConfig.category)
+ .filter( ( { slug } ) => slug === blockConfig.category )
.pop();
- if (!categoryExists) {
+ if ( ! categoryExists ) {
blockConfig.category = 'common';
}
}
@@ -98,8 +98,8 @@ function validateBlockCategory(blockConfig) {
* @param {Object} blockConfig - Block configuration
* @returns {Object} - Block configuration with defaults applied
*/
-function applyBlockDefaults(blockConfig) {
- return acf.parseArgs(blockConfig, {
+function applyBlockDefaults( blockConfig ) {
+ return acf.parseArgs( blockConfig, {
title: '',
name: '',
category: '',
@@ -107,7 +107,7 @@ function applyBlockDefaults(blockConfig) {
acf_block_version: 3,
attributes: {},
supports: {},
- });
+ } );
}
/**
@@ -116,13 +116,13 @@ function applyBlockDefaults(blockConfig) {
*
* @param {Object} blockConfig - Block configuration
*/
-function cleanBlockAttributes(blockConfig) {
- for (const attributeName in blockConfig.attributes) {
+function cleanBlockAttributes( blockConfig ) {
+ for ( const attributeName in blockConfig.attributes ) {
if (
- 'default' in blockConfig.attributes[attributeName] &&
- blockConfig.attributes[attributeName].default.length === 0
+ 'default' in blockConfig.attributes[ attributeName ] &&
+ blockConfig.attributes[ attributeName ].default.length === 0
) {
- delete blockConfig.attributes[attributeName].default;
+ delete blockConfig.attributes[ attributeName ].default;
}
}
}
@@ -132,8 +132,8 @@ function cleanBlockAttributes(blockConfig) {
*
* @param {Object} blockConfig - Block configuration
*/
-function configureAnchorSupport(blockConfig) {
- if (blockConfig.supports && blockConfig.supports.anchor) {
+function configureAnchorSupport( blockConfig ) {
+ if ( blockConfig.supports && blockConfig.supports.anchor ) {
blockConfig.attributes.anchor = { type: 'string' };
}
}
@@ -145,17 +145,17 @@ function configureAnchorSupport(blockConfig) {
* @param {Object} blockConfig - Block configuration
* @returns {React.Component} - Enhanced edit component
*/
-function applyHigherOrderComponents(EditComponent, blockConfig) {
+function applyHigherOrderComponents( EditComponent, blockConfig ) {
let enhancedComponent = EditComponent;
// Add text alignment support
- if (blockConfig.supports.alignText || blockConfig.supports.align_text) {
+ if ( blockConfig.supports.alignText || blockConfig.supports.align_text ) {
blockConfig.attributes = addAttribute(
blockConfig.attributes,
'align_text',
'string'
);
- enhancedComponent = withAlignText(enhancedComponent, blockConfig);
+ enhancedComponent = withAlignText( enhancedComponent, blockConfig );
}
// Add content alignment support
@@ -168,17 +168,17 @@ function applyHigherOrderComponents(EditComponent, blockConfig) {
'align_content',
'string'
);
- enhancedComponent = withAlignContent(enhancedComponent, blockConfig);
+ enhancedComponent = withAlignContent( enhancedComponent, blockConfig );
}
// Add full height support
- if (blockConfig.supports.fullHeight || blockConfig.supports.full_height) {
+ if ( blockConfig.supports.fullHeight || blockConfig.supports.full_height ) {
blockConfig.attributes = addAttribute(
blockConfig.attributes,
'full_height',
'boolean'
);
- enhancedComponent = withFullHeight(enhancedComponent);
+ enhancedComponent = withFullHeight( enhancedComponent );
}
return enhancedComponent;
@@ -190,43 +190,49 @@ function applyHigherOrderComponents(EditComponent, blockConfig) {
* @param {Object} blockConfig - ACF block configuration object
* @returns {Object|boolean} - Registered block type or false if not registered
*/
-function registerACFBlockType(blockConfig) {
+function registerACFBlockType( blockConfig ) {
// Check if block should be registered for current post type
- if (!shouldRegisterBlock(blockConfig)) {
+ if ( ! shouldRegisterBlock( blockConfig ) ) {
return false;
}
// Process icon
- processBlockIcon(blockConfig);
+ processBlockIcon( blockConfig );
// Validate category
- validateBlockCategory(blockConfig);
+ validateBlockCategory( blockConfig );
// Apply default values
- blockConfig = applyBlockDefaults(blockConfig);
+ blockConfig = applyBlockDefaults( blockConfig );
// Clean up attributes
- cleanBlockAttributes(blockConfig);
+ cleanBlockAttributes( blockConfig );
// Configure anchor support
- configureAnchorSupport(blockConfig);
+ configureAnchorSupport( blockConfig );
// Start with base BlockEdit component
let EditComponent = BlockEdit;
// Apply higher-order components based on supports
- EditComponent = applyHigherOrderComponents(EditComponent, blockConfig);
+ EditComponent = applyHigherOrderComponents( EditComponent, blockConfig );
// Create edit function that passes blockConfig and jQuery
- blockConfig.edit = function (props) {
- return
;
+ blockConfig.edit = function ( props ) {
+ return (
+
+ );
};
// Create save function (ACF blocks save to post content as HTML comments)
blockConfig.save = () =>
;
// Store in registry
- registeredBlocks[blockConfig.name] = blockConfig;
+ registeredBlocks[ blockConfig.name ] = blockConfig;
// Register with WordPress
const registeredBlockType = wp.blocks.registerBlockType(
@@ -252,8 +258,8 @@ function registerACFBlockType(blockConfig) {
* @param {string} blockName - Name of the block
* @returns {Object|boolean} - Block configuration or false
*/
-function getRegisteredBlock(blockName) {
- return registeredBlocks[blockName] || false;
+function getRegisteredBlock( blockName ) {
+ return registeredBlocks[ blockName ] || false;
}
/**
@@ -261,22 +267,22 @@ function getRegisteredBlock(blockName) {
* Handles backward compatibility for align_text -> alignText, etc.
*/
const withDefaultAttributes = createHigherOrderComponent(
- (BlockListBlock) =>
+ ( BlockListBlock ) =>
class extends Component {
- constructor(props) {
- super(props);
+ constructor( props ) {
+ super( props );
const { name, attributes } = this.props;
- const blockConfig = getRegisteredBlock(name);
+ const blockConfig = getRegisteredBlock( name );
- if (!blockConfig) return;
+ if ( ! blockConfig ) return;
// Remove empty string attributes
- Object.keys(attributes).forEach((key) => {
- if (attributes[key] === '') {
- delete attributes[key];
+ Object.keys( attributes ).forEach( ( key ) => {
+ if ( attributes[ key ] === '' ) {
+ delete attributes[ key ];
}
- });
+ } );
// Map old attribute names to new camelCase names
const attributeMap = {
@@ -285,38 +291,38 @@ const withDefaultAttributes = createHigherOrderComponent(
align_text: 'alignText',
};
- Object.keys(attributeMap).forEach((oldKey) => {
- const newKey = attributeMap[oldKey];
+ Object.keys( attributeMap ).forEach( ( oldKey ) => {
+ const newKey = attributeMap[ oldKey ];
- if (attributes[oldKey] !== undefined) {
+ if ( attributes[ oldKey ] !== undefined ) {
// Migrate old key to new key
- attributes[newKey] = attributes[oldKey];
+ attributes[ newKey ] = attributes[ oldKey ];
} else if (
- attributes[newKey] === undefined &&
- blockConfig[oldKey] !== undefined
+ attributes[ newKey ] === undefined &&
+ blockConfig[ oldKey ] !== undefined
) {
// Set default from block config if not present
- attributes[newKey] = blockConfig[oldKey];
+ attributes[ newKey ] = blockConfig[ oldKey ];
}
// Clean up old attribute names
- delete blockConfig[oldKey];
- delete attributes[oldKey];
- });
+ delete blockConfig[ oldKey ];
+ delete attributes[ oldKey ];
+ } );
// Apply default values from block config for missing attributes
- for (let key in blockConfig.attributes) {
+ for ( let key in blockConfig.attributes ) {
if (
- attributes[key] === undefined &&
- blockConfig[key] !== undefined
+ attributes[ key ] === undefined &&
+ blockConfig[ key ] !== undefined
) {
- attributes[key] = blockConfig[key];
+ attributes[ key ] = blockConfig[ key ];
}
}
}
render() {
- return
;
+ return
;
}
},
'withDefaultAttributes'
@@ -326,23 +332,23 @@ const withDefaultAttributes = createHigherOrderComponent(
* Initialize ACF blocks on the 'prepare' action
* Registers all ACF blocks with version 3 or higher
*/
-acf.addAction('prepare', function () {
+acf.addAction( 'prepare', function () {
// Ensure wp.blockEditor exists (backward compatibility)
- if (!wp.blockEditor) {
+ if ( ! wp.blockEditor ) {
wp.blockEditor = wp.editor;
}
- const blockTypes = acf.get('blockTypes');
+ const blockTypes = acf.get( 'blockTypes' );
- if (blockTypes) {
- blockTypes.forEach((blockType) => {
+ if ( blockTypes ) {
+ blockTypes.forEach( ( blockType ) => {
// Only register blocks with version 3 or higher
- if (parseInt(blockType.acf_block_version) >= 3) {
- registerACFBlockType(blockType);
+ if ( parseInt( blockType.acf_block_version ) >= 3 ) {
+ registerACFBlockType( blockType );
}
- });
+ } );
}
-});
+} );
/**
* Register WordPress filter for attribute migration
diff --git a/assets/src/js/pro/blocks-v3/utils/post-locking.js b/assets/src/js/pro/blocks-v3/utils/post-locking.js
index eae004df..f56a5693 100644
--- a/assets/src/js/pro/blocks-v3/utils/post-locking.js
+++ b/assets/src/js/pro/blocks-v3/utils/post-locking.js
@@ -9,9 +9,11 @@
*
* @param {string} clientId - The block's client ID
*/
-export const lockPostSaving = (clientId) => {
- if (wp.data.dispatch('core/editor')) {
- wp.data.dispatch('core/editor').lockPostSaving('acf/block/' + clientId);
+export const lockPostSaving = ( clientId ) => {
+ if ( wp.data.dispatch( 'core/editor' ) ) {
+ wp.data
+ .dispatch( 'core/editor' )
+ .lockPostSaving( 'acf/block/' + clientId );
}
};
@@ -21,11 +23,11 @@ export const lockPostSaving = (clientId) => {
*
* @param {string} clientId - The block's client ID
*/
-export const unlockPostSaving = (clientId) => {
- if (wp.data.dispatch('core/editor')) {
+export const unlockPostSaving = ( clientId ) => {
+ if ( wp.data.dispatch( 'core/editor' ) ) {
wp.data
- .dispatch('core/editor')
- .unlockPostSaving('acf/block/' + clientId);
+ .dispatch( 'core/editor' )
+ .unlockPostSaving( 'acf/block/' + clientId );
}
};
@@ -36,10 +38,10 @@ export const unlockPostSaving = (clientId) => {
* @param {Object} obj - Object to sort
* @returns {Object} - New object with sorted keys
*/
-export const sortObjectKeys = (obj) =>
- Object.keys(obj)
+export const sortObjectKeys = ( obj ) =>
+ Object.keys( obj )
.sort()
- .reduce((result, key) => {
- result[key] = obj[key];
+ .reduce( ( result, key ) => {
+ result[ key ] = obj[ key ];
return result;
- }, {});
+ }, {} );
diff --git a/readme.txt b/readme.txt
index 80f0289c..5b931094 100644
--- a/readme.txt
+++ b/readme.txt
@@ -2,7 +2,7 @@
Contributors: wordpressdotorg
Tags: fields, custom fields, meta, scf
Requires at least: 6.0
-Tested up to: 6.8
+Tested up to: 6.8.3
Requires PHP: 7.4
Stable tag: 6.5.7
License: GPLv2 or later
From a5fe67d2f7d5d3c77ee8d025d68a247de8d0ecc9 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Tue, 4 Nov 2025 13:08:40 +0100
Subject: [PATCH 15/22] Fix more prettier
---
assets/src/js/_acf-condition-types.js | 867 ++++++++++++-----------
assets/src/js/_acf-field-button-group.js | 78 +-
assets/src/js/_acf-field-color-picker.js | 50 +-
assets/src/js/_acf-field-radio.js | 48 +-
assets/src/js/_acf-field-taxonomy.js | 214 +++---
assets/src/js/_acf-validation.js | 547 +++++++-------
assets/src/js/_browse-fields-modal.js | 288 ++++----
assets/src/js/_field-group-field.js | 748 +++++++++----------
assets/src/js/pro/_acf-blocks.js | 416 ++++++++---
9 files changed, 1761 insertions(+), 1495 deletions(-)
diff --git a/assets/src/js/_acf-condition-types.js b/assets/src/js/_acf-condition-types.js
index ffd676c7..9761bc52 100644
--- a/assets/src/js/_acf-condition-types.js
+++ b/assets/src/js/_acf-condition-types.js
@@ -1,12 +1,14 @@
-(function ($, undefined) {
+( function ( $, undefined ) {
const __ = acf.__;
- const parseString = function (val) {
+ const parseString = function ( val ) {
return val ? '' + val : '';
};
- const isEqualTo = function (v1, v2) {
- return parseString(v1).toLowerCase() === parseString(v2).toLowerCase();
+ const isEqualTo = function ( v1, v2 ) {
+ return (
+ parseString( v1 ).toLowerCase() === parseString( v2 ).toLowerCase()
+ );
};
/**
@@ -16,44 +18,44 @@
* @param {number|string|Array} v2 - The selected value to compare.
* @returns {boolean} Returns true if the values are equal numbers, otherwise returns false.
*/
- const isEqualToNumber = function (v1, v2) {
- if (v2 instanceof Array) {
- return v2.length === 1 && isEqualToNumber(v1, v2[0]);
+ const isEqualToNumber = function ( v1, v2 ) {
+ if ( v2 instanceof Array ) {
+ return v2.length === 1 && isEqualToNumber( v1, v2[ 0 ] );
}
- return parseFloat(v1) === parseFloat(v2);
+ return parseFloat( v1 ) === parseFloat( v2 );
};
- const isGreaterThan = function (v1, v2) {
- return parseFloat(v1) > parseFloat(v2);
+ const isGreaterThan = function ( v1, v2 ) {
+ return parseFloat( v1 ) > parseFloat( v2 );
};
- const isLessThan = function (v1, v2) {
- return parseFloat(v1) < parseFloat(v2);
+ const isLessThan = function ( v1, v2 ) {
+ return parseFloat( v1 ) < parseFloat( v2 );
};
- const inArray = function (v1, array) {
+ const inArray = function ( v1, array ) {
// cast all values as string
- array = array.map(function (v2) {
- return parseString(v2);
- });
+ array = array.map( function ( v2 ) {
+ return parseString( v2 );
+ } );
- return array.indexOf(v1) > -1;
+ return array.indexOf( v1 ) > -1;
};
- const containsString = function (haystack, needle) {
- return parseString(haystack).indexOf(parseString(needle)) > -1;
+ const containsString = function ( haystack, needle ) {
+ return parseString( haystack ).indexOf( parseString( needle ) ) > -1;
};
- const matchesPattern = function (v1, pattern) {
- const regexp = new RegExp(parseString(pattern), 'gi');
- return parseString(v1).match(regexp);
+ const matchesPattern = function ( v1, pattern ) {
+ const regexp = new RegExp( parseString( pattern ), 'gi' );
+ return parseString( v1 ).match( regexp );
};
- const conditionalSelect2 = function (field, type) {
- const $select = $('
');
- let queryAction = `acf/fields/${type}/query`;
+ const conditionalSelect2 = function ( field, type ) {
+ const $select = $( '
' );
+ let queryAction = `acf/fields/${ type }/query`;
- if (type === 'user') {
+ if ( type === 'user' ) {
queryAction = 'acf/ajax/query_users';
}
@@ -64,28 +66,28 @@
type: field.data.key,
};
- const typeAttr = acf.escAttr(type);
+ const typeAttr = acf.escAttr( type );
- const template = function (selection) {
+ const template = function ( selection ) {
return (
- `
` +
- acf.strEscape(selection.text) +
+ `` +
+ acf.strEscape( selection.text ) +
' '
);
};
- const resultsTemplate = function (results) {
- let classes = results.text.startsWith('- ')
- ? `acf-${typeAttr}-select-name acf-${typeAttr}-select-sub-item`
- : `acf-${typeAttr}-select-name`;
+ const resultsTemplate = function ( results ) {
+ let classes = results.text.startsWith( '- ' )
+ ? `acf-${ typeAttr }-select-name acf-${ typeAttr }-select-sub-item`
+ : `acf-${ typeAttr }-select-name`;
return (
'' +
- acf.strEscape(results.text) +
+ acf.strEscape( results.text ) +
' ' +
- `` +
- (results.id ? results.id : '') +
+ `` +
+ ( results.id ? results.id : '' ) +
' '
);
};
@@ -94,21 +96,23 @@
field: false,
ajax: true,
ajaxAction: queryAction,
- ajaxData: function (data) {
+ ajaxData: function ( data ) {
ajaxData.paged = data.paged;
ajaxData.s = data.s;
ajaxData.conditional_logic = true;
- ajaxData.include = $.isNumeric(data.s) ? Number(data.s) : '';
- return acf.prepareForAjax(ajaxData);
+ ajaxData.include = $.isNumeric( data.s )
+ ? Number( data.s )
+ : '';
+ return acf.prepareForAjax( ajaxData );
},
- escapeMarkup: function (markup) {
- return acf.escHtml(markup);
+ escapeMarkup: function ( markup ) {
+ return acf.escHtml( markup );
},
templateSelection: template,
templateResult: resultsTemplate,
};
- $select.data('acfSelect2Props', select2Props);
+ $select.data( 'acfSelect2Props', select2Props );
return $select;
};
/**
@@ -116,721 +120,721 @@
*
* @since ACF 6.3
*/
- const HasPageLink = acf.Condition.extend({
+ const HasPageLink = acf.Condition.extend( {
type: 'hasPageLink',
operator: '==',
- label: __('Page is equal to'),
- fieldTypes: ['page_link'],
- match: function (rule, field) {
- return isEqualTo(rule.value, field.val());
+ label: __( 'Page is equal to' ),
+ fieldTypes: [ 'page_link' ],
+ match: function ( rule, field ) {
+ return isEqualTo( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'page_link');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'page_link' );
},
- });
+ } );
- acf.registerConditionType(HasPageLink);
+ acf.registerConditionType( HasPageLink );
/**
* Adds condition for Page Link not equal to.
*
* @since ACF 6.3
*/
- const HasPageLinkNotEqual = acf.Condition.extend({
+ const HasPageLinkNotEqual = acf.Condition.extend( {
type: 'hasPageLinkNotEqual',
operator: '!==',
- label: __('Page is not equal to'),
- fieldTypes: ['page_link'],
- match: function (rule, field) {
- return !isEqualTo(rule.value, field.val());
+ label: __( 'Page is not equal to' ),
+ fieldTypes: [ 'page_link' ],
+ match: function ( rule, field ) {
+ return ! isEqualTo( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'page_link');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'page_link' );
},
- });
+ } );
- acf.registerConditionType(HasPageLinkNotEqual);
+ acf.registerConditionType( HasPageLinkNotEqual );
/**
* Adds condition for Page Link containing a specific Page Link.
*
* @since ACF 6.3
*/
- const containsPageLink = acf.Condition.extend({
+ const containsPageLink = acf.Condition.extend( {
type: 'containsPageLink',
operator: '==contains',
- label: __('Pages contain'),
- fieldTypes: ['page_link'],
- match: function (rule, field) {
+ label: __( 'Pages contain' ),
+ fieldTypes: [ 'page_link' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = false;
- if (val instanceof Array) {
- match = val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = val.includes( ruleVal );
} else {
match = val === ruleVal;
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'page_link');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'page_link' );
},
- });
+ } );
- acf.registerConditionType(containsPageLink);
+ acf.registerConditionType( containsPageLink );
/**
* Adds condition for Page Link not containing a specific Page Link.
*
* @since ACF 6.3
*/
- const containsNotPageLink = acf.Condition.extend({
+ const containsNotPageLink = acf.Condition.extend( {
type: 'containsNotPageLink',
operator: '!=contains',
- label: __('Pages do not contain'),
- fieldTypes: ['page_link'],
- match: function (rule, field) {
+ label: __( 'Pages do not contain' ),
+ fieldTypes: [ 'page_link' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = true;
- if (val instanceof Array) {
- match = !val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = ! val.includes( ruleVal );
} else {
match = val !== ruleVal;
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'page_link');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'page_link' );
},
- });
+ } );
- acf.registerConditionType(containsNotPageLink);
+ acf.registerConditionType( containsNotPageLink );
/**
* Adds condition for when any page link is selected.
*
* @since ACF 6.3
*/
- const HasAnyPageLink = acf.Condition.extend({
+ const HasAnyPageLink = acf.Condition.extend( {
type: 'hasAnyPageLink',
operator: '!=empty',
- label: __('Has any page selected'),
- fieldTypes: ['page_link'],
- match: function (rule, field) {
+ label: __( 'Has any page selected' ),
+ fieldTypes: [ 'page_link' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !!val;
+ return !! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasAnyPageLink);
+ acf.registerConditionType( HasAnyPageLink );
/**
* Adds condition for when no page link is selected.
*
* @since ACF 6.3
*/
- const HasNoPageLink = acf.Condition.extend({
+ const HasNoPageLink = acf.Condition.extend( {
type: 'hasNoPageLink',
operator: '==empty',
- label: __('Has no page selected'),
- fieldTypes: ['page_link'],
- match: function (rule, field) {
+ label: __( 'Has no page selected' ),
+ fieldTypes: [ 'page_link' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !val;
+ return ! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasNoPageLink);
+ acf.registerConditionType( HasNoPageLink );
/**
* Adds condition for user field having user equal to.
*
* @since ACF 6.3
*/
- const HasUser = acf.Condition.extend({
+ const HasUser = acf.Condition.extend( {
type: 'hasUser',
operator: '==',
- label: __('User is equal to'),
- fieldTypes: ['user'],
- match: function (rule, field) {
- return isEqualToNumber(rule.value, field.val());
+ label: __( 'User is equal to' ),
+ fieldTypes: [ 'user' ],
+ match: function ( rule, field ) {
+ return isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'user');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'user' );
},
- });
+ } );
- acf.registerConditionType(HasUser);
+ acf.registerConditionType( HasUser );
/**
* Adds condition for user field having user not equal to.
*
* @since ACF 6.3
*/
- const HasUserNotEqual = acf.Condition.extend({
+ const HasUserNotEqual = acf.Condition.extend( {
type: 'hasUserNotEqual',
operator: '!==',
- label: __('User is not equal to'),
- fieldTypes: ['user'],
- match: function (rule, field) {
- return !isEqualToNumber(rule.value, field.val());
+ label: __( 'User is not equal to' ),
+ fieldTypes: [ 'user' ],
+ match: function ( rule, field ) {
+ return ! isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'user');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'user' );
},
- });
+ } );
- acf.registerConditionType(HasUserNotEqual);
+ acf.registerConditionType( HasUserNotEqual );
/**
* Adds condition for user field containing a specific user.
*
* @since ACF 6.3
*/
- const containsUser = acf.Condition.extend({
+ const containsUser = acf.Condition.extend( {
type: 'containsUser',
operator: '==contains',
- label: __('Users contain'),
- fieldTypes: ['user'],
- match: function (rule, field) {
+ label: __( 'Users contain' ),
+ fieldTypes: [ 'user' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = false;
- if (val instanceof Array) {
- match = val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = val.includes( ruleVal );
} else {
match = val === ruleVal;
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'user');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'user' );
},
- });
+ } );
- acf.registerConditionType(containsUser);
+ acf.registerConditionType( containsUser );
/**
* Adds condition for user field not containing a specific user.
*
* @since ACF 6.3
*/
- const containsNotUser = acf.Condition.extend({
+ const containsNotUser = acf.Condition.extend( {
type: 'containsNotUser',
operator: '!=contains',
- label: __('Users do not contain'),
- fieldTypes: ['user'],
- match: function (rule, field) {
+ label: __( 'Users do not contain' ),
+ fieldTypes: [ 'user' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = true;
- if (val instanceof Array) {
- match = !val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = ! val.includes( ruleVal );
} else {
- match = !val === ruleVal;
+ match = ! val === ruleVal;
}
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'user');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'user' );
},
- });
+ } );
- acf.registerConditionType(containsNotUser);
+ acf.registerConditionType( containsNotUser );
/**
* Adds condition for when any user is selected.
*
* @since ACF 6.3
*/
- const HasAnyUser = acf.Condition.extend({
+ const HasAnyUser = acf.Condition.extend( {
type: 'hasAnyUser',
operator: '!=empty',
- label: __('Has any user selected'),
- fieldTypes: ['user'],
- match: function (rule, field) {
+ label: __( 'Has any user selected' ),
+ fieldTypes: [ 'user' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !!val;
+ return !! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasAnyUser);
+ acf.registerConditionType( HasAnyUser );
/**
* Adds condition for when no user is selected.
*
* @since ACF 6.3
*/
- const HasNoUser = acf.Condition.extend({
+ const HasNoUser = acf.Condition.extend( {
type: 'hasNoUser',
operator: '==empty',
- label: __('Has no user selected'),
- fieldTypes: ['user'],
- match: function (rule, field) {
+ label: __( 'Has no user selected' ),
+ fieldTypes: [ 'user' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !val;
+ return ! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasNoUser);
+ acf.registerConditionType( HasNoUser );
/**
* Adds condition for Relationship having Relationship equal to.
*
* @since ACF 6.3
*/
- const HasRelationship = acf.Condition.extend({
+ const HasRelationship = acf.Condition.extend( {
type: 'hasRelationship',
operator: '==',
- label: __('Relationship is equal to'),
- fieldTypes: ['relationship'],
- match: function (rule, field) {
- return isEqualToNumber(rule.value, field.val());
+ label: __( 'Relationship is equal to' ),
+ fieldTypes: [ 'relationship' ],
+ match: function ( rule, field ) {
+ return isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'relationship');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'relationship' );
},
- });
+ } );
- acf.registerConditionType(HasRelationship);
+ acf.registerConditionType( HasRelationship );
/**
* Adds condition for selection having Relationship not equal to.
*
* @since ACF 6.3
*/
- const HasRelationshipNotEqual = acf.Condition.extend({
+ const HasRelationshipNotEqual = acf.Condition.extend( {
type: 'hasRelationshipNotEqual',
operator: '!==',
- label: __('Relationship is not equal to'),
- fieldTypes: ['relationship'],
- match: function (rule, field) {
- return !isEqualToNumber(rule.value, field.val());
+ label: __( 'Relationship is not equal to' ),
+ fieldTypes: [ 'relationship' ],
+ match: function ( rule, field ) {
+ return ! isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'relationship');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'relationship' );
},
- });
+ } );
- acf.registerConditionType(HasRelationshipNotEqual);
+ acf.registerConditionType( HasRelationshipNotEqual );
/**
* Adds condition for Relationship containing a specific Relationship.
*
* @since ACF 6.3
*/
- const containsRelationship = acf.Condition.extend({
+ const containsRelationship = acf.Condition.extend( {
type: 'containsRelationship',
operator: '==contains',
- label: __('Relationships contain'),
- fieldTypes: ['relationship'],
- match: function (rule, field) {
+ label: __( 'Relationships contain' ),
+ fieldTypes: [ 'relationship' ],
+ match: function ( rule, field ) {
const val = field.val();
// Relationships are stored as strings, use float to compare to field's rule value.
- const ruleVal = parseInt(rule.value);
+ const ruleVal = parseInt( rule.value );
let match = false;
- if (val instanceof Array) {
- match = val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = val.includes( ruleVal );
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'relationship');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'relationship' );
},
- });
+ } );
- acf.registerConditionType(containsRelationship);
+ acf.registerConditionType( containsRelationship );
/**
* Adds condition for Relationship not containing a specific Relationship.
*
* @since ACF 6.3
*/
- const containsNotRelationship = acf.Condition.extend({
+ const containsNotRelationship = acf.Condition.extend( {
type: 'containsNotRelationship',
operator: '!=contains',
- label: __('Relationships do not contain'),
- fieldTypes: ['relationship'],
- match: function (rule, field) {
+ label: __( 'Relationships do not contain' ),
+ fieldTypes: [ 'relationship' ],
+ match: function ( rule, field ) {
const val = field.val();
// Relationships are stored as strings, use float to compare to field's rule value.
- const ruleVal = parseInt(rule.value);
+ const ruleVal = parseInt( rule.value );
let match = true;
- if (val instanceof Array) {
- match = !val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = ! val.includes( ruleVal );
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'relationship');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'relationship' );
},
- });
+ } );
- acf.registerConditionType(containsNotRelationship);
+ acf.registerConditionType( containsNotRelationship );
/**
* Adds condition for when any relation is selected.
*
* @since ACF 6.3
*/
- const HasAnyRelation = acf.Condition.extend({
+ const HasAnyRelation = acf.Condition.extend( {
type: 'hasAnyRelation',
operator: '!=empty',
- label: __('Has any relationship selected'),
- fieldTypes: ['relationship'],
- match: function (rule, field) {
+ label: __( 'Has any relationship selected' ),
+ fieldTypes: [ 'relationship' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !!val;
+ return !! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasAnyRelation);
+ acf.registerConditionType( HasAnyRelation );
/**
* Adds condition for when no relation is selected.
*
* @since ACF 6.3
*/
- const HasNoRelation = acf.Condition.extend({
+ const HasNoRelation = acf.Condition.extend( {
type: 'hasNoRelation',
operator: '==empty',
- label: __('Has no relationship selected'),
- fieldTypes: ['relationship'],
- match: function (rule, field) {
+ label: __( 'Has no relationship selected' ),
+ fieldTypes: [ 'relationship' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !val;
+ return ! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasNoRelation);
+ acf.registerConditionType( HasNoRelation );
/**
* Adds condition for having post equal to.
*
* @since ACF 6.3
*/
- const HasPostObject = acf.Condition.extend({
+ const HasPostObject = acf.Condition.extend( {
type: 'hasPostObject',
operator: '==',
- label: __('Post is equal to'),
- fieldTypes: ['post_object'],
- match: function (rule, field) {
- return isEqualToNumber(rule.value, field.val());
+ label: __( 'Post is equal to' ),
+ fieldTypes: [ 'post_object' ],
+ match: function ( rule, field ) {
+ return isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'post_object');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'post_object' );
},
- });
+ } );
- acf.registerConditionType(HasPostObject);
+ acf.registerConditionType( HasPostObject );
/**
* Adds condition for selection having post not equal to.
*
* @since ACF 6.3
*/
- const HasPostObjectNotEqual = acf.Condition.extend({
+ const HasPostObjectNotEqual = acf.Condition.extend( {
type: 'hasPostObjectNotEqual',
operator: '!==',
- label: __('Post is not equal to'),
- fieldTypes: ['post_object'],
- match: function (rule, field) {
- return !isEqualToNumber(rule.value, field.val());
+ label: __( 'Post is not equal to' ),
+ fieldTypes: [ 'post_object' ],
+ match: function ( rule, field ) {
+ return ! isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'post_object');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'post_object' );
},
- });
+ } );
- acf.registerConditionType(HasPostObjectNotEqual);
+ acf.registerConditionType( HasPostObjectNotEqual );
/**
* Adds condition for Relationship containing a specific Relationship.
*
* @since ACF 6.3
*/
- const containsPostObject = acf.Condition.extend({
+ const containsPostObject = acf.Condition.extend( {
type: 'containsPostObject',
operator: '==contains',
- label: __('Posts contain'),
- fieldTypes: ['post_object'],
- match: function (rule, field) {
+ label: __( 'Posts contain' ),
+ fieldTypes: [ 'post_object' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = false;
- if (val instanceof Array) {
- match = val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = val.includes( ruleVal );
} else {
match = val === ruleVal;
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'post_object');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'post_object' );
},
- });
+ } );
- acf.registerConditionType(containsPostObject);
+ acf.registerConditionType( containsPostObject );
/**
* Adds condition for Relationship not containing a specific Relationship.
*
* @since ACF 6.3
*/
- const containsNotPostObject = acf.Condition.extend({
+ const containsNotPostObject = acf.Condition.extend( {
type: 'containsNotPostObject',
operator: '!=contains',
- label: __('Posts do not contain'),
- fieldTypes: ['post_object'],
- match: function (rule, field) {
+ label: __( 'Posts do not contain' ),
+ fieldTypes: [ 'post_object' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = true;
- if (val instanceof Array) {
- match = !val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = ! val.includes( ruleVal );
} else {
match = val !== ruleVal;
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'post_object');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'post_object' );
},
- });
+ } );
- acf.registerConditionType(containsNotPostObject);
+ acf.registerConditionType( containsNotPostObject );
/**
* Adds condition for when any post is selected.
*
* @since ACF 6.3
*/
- const HasAnyPostObject = acf.Condition.extend({
+ const HasAnyPostObject = acf.Condition.extend( {
type: 'hasAnyPostObject',
operator: '!=empty',
- label: __('Has any post selected'),
- fieldTypes: ['post_object'],
- match: function (rule, field) {
+ label: __( 'Has any post selected' ),
+ fieldTypes: [ 'post_object' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !!val;
+ return !! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasAnyPostObject);
+ acf.registerConditionType( HasAnyPostObject );
/**
* Adds condition for when no post is selected.
*
* @since ACF 6.3
*/
- const HasNoPostObject = acf.Condition.extend({
+ const HasNoPostObject = acf.Condition.extend( {
type: 'hasNoPostObject',
operator: '==empty',
- label: __('Has no post selected'),
- fieldTypes: ['post_object'],
- match: function (rule, field) {
+ label: __( 'Has no post selected' ),
+ fieldTypes: [ 'post_object' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !val;
+ return ! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasNoPostObject);
+ acf.registerConditionType( HasNoPostObject );
/**
* Adds condition for taxonomy having term equal to.
*
* @since ACF 6.3
*/
- const HasTerm = acf.Condition.extend({
+ const HasTerm = acf.Condition.extend( {
type: 'hasTerm',
operator: '==',
- label: __('Term is equal to'),
- fieldTypes: ['taxonomy'],
- match: function (rule, field) {
- return isEqualToNumber(rule.value, field.val());
+ label: __( 'Term is equal to' ),
+ fieldTypes: [ 'taxonomy' ],
+ match: function ( rule, field ) {
+ return isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'taxonomy');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'taxonomy' );
},
- });
+ } );
- acf.registerConditionType(HasTerm);
+ acf.registerConditionType( HasTerm );
/**
* Adds condition for taxonomy having term not equal to.
*
* @since ACF 6.3
*/
- const hasTermNotEqual = acf.Condition.extend({
+ const hasTermNotEqual = acf.Condition.extend( {
type: 'hasTermNotEqual',
operator: '!==',
- label: __('Term is not equal to'),
- fieldTypes: ['taxonomy'],
- match: function (rule, field) {
- return !isEqualToNumber(rule.value, field.val());
+ label: __( 'Term is not equal to' ),
+ fieldTypes: [ 'taxonomy' ],
+ match: function ( rule, field ) {
+ return ! isEqualToNumber( rule.value, field.val() );
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'taxonomy');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'taxonomy' );
},
- });
+ } );
- acf.registerConditionType(hasTermNotEqual);
+ acf.registerConditionType( hasTermNotEqual );
/**
* Adds condition for taxonomy containing a specific term.
*
* @since ACF 6.3
*/
- const containsTerm = acf.Condition.extend({
+ const containsTerm = acf.Condition.extend( {
type: 'containsTerm',
operator: '==contains',
- label: __('Terms contain'),
- fieldTypes: ['taxonomy'],
- match: function (rule, field) {
+ label: __( 'Terms contain' ),
+ fieldTypes: [ 'taxonomy' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = false;
- if (val instanceof Array) {
- match = val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = val.includes( ruleVal );
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'taxonomy');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'taxonomy' );
},
- });
+ } );
- acf.registerConditionType(containsTerm);
+ acf.registerConditionType( containsTerm );
/**
* Adds condition for taxonomy not containing a specific term.
*
* @since ACF 6.3
*/
- const containsNotTerm = acf.Condition.extend({
+ const containsNotTerm = acf.Condition.extend( {
type: 'containsNotTerm',
operator: '!=contains',
- label: __('Terms do not contain'),
- fieldTypes: ['taxonomy'],
- match: function (rule, field) {
+ label: __( 'Terms do not contain' ),
+ fieldTypes: [ 'taxonomy' ],
+ match: function ( rule, field ) {
const val = field.val();
const ruleVal = rule.value;
let match = true;
- if (val instanceof Array) {
- match = !val.includes(ruleVal);
+ if ( val instanceof Array ) {
+ match = ! val.includes( ruleVal );
}
return match;
},
- choices: function (fieldObject) {
- return conditionalSelect2(fieldObject, 'taxonomy');
+ choices: function ( fieldObject ) {
+ return conditionalSelect2( fieldObject, 'taxonomy' );
},
- });
+ } );
- acf.registerConditionType(containsNotTerm);
+ acf.registerConditionType( containsNotTerm );
/**
* Adds condition for when any term is selected.
*
* @since ACF 6.3
*/
- const HasAnyTerm = acf.Condition.extend({
+ const HasAnyTerm = acf.Condition.extend( {
type: 'hasAnyTerm',
operator: '!=empty',
- label: __('Has any term selected'),
- fieldTypes: ['taxonomy'],
- match: function (rule, field) {
+ label: __( 'Has any term selected' ),
+ fieldTypes: [ 'taxonomy' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !!val;
+ return !! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasAnyTerm);
+ acf.registerConditionType( HasAnyTerm );
/**
* Adds condition for when no term is selected.
*
* @since ACF 6.3
*/
- const HasNoTerm = acf.Condition.extend({
+ const HasNoTerm = acf.Condition.extend( {
type: 'hasNoTerm',
operator: '==empty',
- label: __('Has no term selected'),
- fieldTypes: ['taxonomy'],
- match: function (rule, field) {
+ label: __( 'Has no term selected' ),
+ fieldTypes: [ 'taxonomy' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return !val;
+ return ! val;
},
choices: function () {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasNoTerm);
+ acf.registerConditionType( HasNoTerm );
/**
* hasValue
@@ -841,10 +845,10 @@
* @param void
* @return void
*/
- const HasValue = acf.Condition.extend({
+ const HasValue = acf.Condition.extend( {
type: 'hasValue',
operator: '!=empty',
- label: __('Has any value'),
+ label: __( 'Has any value' ),
fieldTypes: [
'text',
'textarea',
@@ -869,19 +873,19 @@
'color_picker',
'icon_picker',
],
- match: function (rule, field) {
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
return val ? true : false;
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
return ' ';
},
- });
+ } );
- acf.registerConditionType(HasValue);
+ acf.registerConditionType( HasValue );
/**
* hasValue
@@ -892,16 +896,16 @@
* @param void
* @return void
*/
- const HasNoValue = HasValue.extend({
+ const HasNoValue = HasValue.extend( {
type: 'hasNoValue',
operator: '==empty',
- label: __('Has no value'),
- match: function (rule, field) {
- return !HasValue.prototype.match.apply(this, arguments);
+ label: __( 'Has no value' ),
+ match: function ( rule, field ) {
+ return ! HasValue.prototype.match.apply( this, arguments );
},
- });
+ } );
- acf.registerConditionType(HasNoValue);
+ acf.registerConditionType( HasNoValue );
/**
* EqualTo
@@ -912,10 +916,10 @@
* @param void
* @return void
*/
- const EqualTo = acf.Condition.extend({
+ const EqualTo = acf.Condition.extend( {
type: 'equalTo',
operator: '==',
- label: __('Value is equal to'),
+ label: __( 'Value is equal to' ),
fieldTypes: [
'text',
'textarea',
@@ -925,19 +929,19 @@
'url',
'password',
],
- match: function (rule, field) {
- if (acf.isNumeric(rule.value)) {
- return isEqualToNumber(rule.value, field.val());
+ match: function ( rule, field ) {
+ if ( acf.isNumeric( rule.value ) ) {
+ return isEqualToNumber( rule.value, field.val() );
} else {
- return isEqualTo(rule.value, field.val());
+ return isEqualTo( rule.value, field.val() );
}
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
return ' ';
},
- });
+ } );
- acf.registerConditionType(EqualTo);
+ acf.registerConditionType( EqualTo );
/**
* NotEqualTo
@@ -948,16 +952,16 @@
* @param void
* @return void
*/
- const NotEqualTo = EqualTo.extend({
+ const NotEqualTo = EqualTo.extend( {
type: 'notEqualTo',
operator: '!=',
- label: __('Value is not equal to'),
- match: function (rule, field) {
- return !EqualTo.prototype.match.apply(this, arguments);
+ label: __( 'Value is not equal to' ),
+ match: function ( rule, field ) {
+ return ! EqualTo.prototype.match.apply( this, arguments );
},
- });
+ } );
- acf.registerConditionType(NotEqualTo);
+ acf.registerConditionType( NotEqualTo );
/**
* PatternMatch
@@ -968,20 +972,27 @@
* @param void
* @return void
*/
- const PatternMatch = acf.Condition.extend({
+ const PatternMatch = acf.Condition.extend( {
type: 'patternMatch',
operator: '==pattern',
- label: __('Value matches pattern'),
- fieldTypes: ['text', 'textarea', 'email', 'url', 'password', 'wysiwyg'],
- match: function (rule, field) {
- return matchesPattern(field.val(), rule.value);
+ label: __( 'Value matches pattern' ),
+ fieldTypes: [
+ 'text',
+ 'textarea',
+ 'email',
+ 'url',
+ 'password',
+ 'wysiwyg',
+ ],
+ match: function ( rule, field ) {
+ return matchesPattern( field.val(), rule.value );
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
return ' ';
},
- });
+ } );
- acf.registerConditionType(PatternMatch);
+ acf.registerConditionType( PatternMatch );
/**
* Contains
@@ -992,10 +1003,10 @@
* @param void
* @return void
*/
- const Contains = acf.Condition.extend({
+ const Contains = acf.Condition.extend( {
type: 'contains',
operator: '==contains',
- label: __('Value contains'),
+ label: __( 'Value contains' ),
fieldTypes: [
'text',
'textarea',
@@ -1007,15 +1018,15 @@
'oembed',
'select',
],
- match: function (rule, field) {
- return containsString(field.val(), rule.value);
+ match: function ( rule, field ) {
+ return containsString( field.val(), rule.value );
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
return ' ';
},
- });
+ } );
- acf.registerConditionType(Contains);
+ acf.registerConditionType( Contains );
/**
* TrueFalseEqualTo
@@ -1026,21 +1037,21 @@
* @param void
* @return void
*/
- const TrueFalseEqualTo = EqualTo.extend({
+ const TrueFalseEqualTo = EqualTo.extend( {
type: 'trueFalseEqualTo',
choiceType: 'select',
- fieldTypes: ['true_false'],
- choices: function (field) {
+ fieldTypes: [ 'true_false' ],
+ choices: function ( field ) {
return [
{
id: 1,
- text: __('Checked'),
+ text: __( 'Checked' ),
},
];
},
- });
+ } );
- acf.registerConditionType(TrueFalseEqualTo);
+ acf.registerConditionType( TrueFalseEqualTo );
/**
* TrueFalseNotEqualTo
@@ -1051,21 +1062,21 @@
* @param void
* @return void
*/
- const TrueFalseNotEqualTo = NotEqualTo.extend({
+ const TrueFalseNotEqualTo = NotEqualTo.extend( {
type: 'trueFalseNotEqualTo',
choiceType: 'select',
- fieldTypes: ['true_false'],
- choices: function (field) {
+ fieldTypes: [ 'true_false' ],
+ choices: function ( field ) {
return [
{
id: 1,
- text: __('Checked'),
+ text: __( 'Checked' ),
},
];
},
- });
+ } );
- acf.registerConditionType(TrueFalseNotEqualTo);
+ acf.registerConditionType( TrueFalseNotEqualTo );
/**
* SelectEqualTo
@@ -1076,56 +1087,56 @@
* @param void
* @return void
*/
- const SelectEqualTo = acf.Condition.extend({
+ const SelectEqualTo = acf.Condition.extend( {
type: 'selectEqualTo',
operator: '==',
- label: __('Value is equal to'),
- fieldTypes: ['select', 'checkbox', 'radio', 'button_group'],
- match: function (rule, field) {
+ label: __( 'Value is equal to' ),
+ fieldTypes: [ 'select', 'checkbox', 'radio', 'button_group' ],
+ match: function ( rule, field ) {
const val = field.val();
- if (val instanceof Array) {
- return inArray(rule.value, val);
+ if ( val instanceof Array ) {
+ return inArray( rule.value, val );
} else {
- return isEqualTo(rule.value, val);
+ return isEqualTo( rule.value, val );
}
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
// vars
const choices = [];
const lines = fieldObject
- .$setting('choices textarea')
+ .$setting( 'choices textarea' )
.val()
- .split('\n');
+ .split( '\n' );
// allow null
- if (fieldObject.$input('allow_null').prop('checked')) {
- choices.push({
+ if ( fieldObject.$input( 'allow_null' ).prop( 'checked' ) ) {
+ choices.push( {
id: '',
- text: __('Null'),
- });
+ text: __( 'Null' ),
+ } );
}
// loop
- lines.map(function (line) {
+ lines.map( function ( line ) {
// split
- line = line.split(':');
+ line = line.split( ':' );
// default label to value
- line[1] = line[1] || line[0];
+ line[ 1 ] = line[ 1 ] || line[ 0 ];
// append
- choices.push({
- id: line[0].trim(),
- text: line[1].trim(),
- });
- });
+ choices.push( {
+ id: line[ 0 ].trim(),
+ text: line[ 1 ].trim(),
+ } );
+ } );
// return
return choices;
},
- });
+ } );
- acf.registerConditionType(SelectEqualTo);
+ acf.registerConditionType( SelectEqualTo );
/**
* SelectNotEqualTo
@@ -1136,16 +1147,16 @@
* @param void
* @return void
*/
- const SelectNotEqualTo = SelectEqualTo.extend({
+ const SelectNotEqualTo = SelectEqualTo.extend( {
type: 'selectNotEqualTo',
operator: '!=',
- label: __('Value is not equal to'),
- match: function (rule, field) {
- return !SelectEqualTo.prototype.match.apply(this, arguments);
+ label: __( 'Value is not equal to' ),
+ match: function ( rule, field ) {
+ return ! SelectEqualTo.prototype.match.apply( this, arguments );
},
- });
+ } );
- acf.registerConditionType(SelectNotEqualTo);
+ acf.registerConditionType( SelectNotEqualTo );
/**
* GreaterThan
@@ -1156,24 +1167,24 @@
* @param void
* @return void
*/
- const GreaterThan = acf.Condition.extend({
+ const GreaterThan = acf.Condition.extend( {
type: 'greaterThan',
operator: '>',
- label: __('Value is greater than'),
- fieldTypes: ['number', 'range'],
- match: function (rule, field) {
+ label: __( 'Value is greater than' ),
+ fieldTypes: [ 'number', 'range' ],
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- return isGreaterThan(val, rule.value);
+ return isGreaterThan( val, rule.value );
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
return ' ';
},
- });
+ } );
- acf.registerConditionType(GreaterThan);
+ acf.registerConditionType( GreaterThan );
/**
* LessThan
@@ -1184,26 +1195,26 @@
* @param void
* @return void
*/
- const LessThan = GreaterThan.extend({
+ const LessThan = GreaterThan.extend( {
type: 'lessThan',
operator: '<',
- label: __('Value is less than'),
- match: function (rule, field) {
+ label: __( 'Value is less than' ),
+ match: function ( rule, field ) {
let val = field.val();
- if (val instanceof Array) {
+ if ( val instanceof Array ) {
val = val.length;
}
- if (val === undefined || val === null || val === false) {
+ if ( val === undefined || val === null || val === false ) {
return true;
}
- return isLessThan(val, rule.value);
+ return isLessThan( val, rule.value );
},
- choices: function (fieldObject) {
+ choices: function ( fieldObject ) {
return ' ';
},
- });
+ } );
- acf.registerConditionType(LessThan);
+ acf.registerConditionType( LessThan );
/**
* SelectedGreaterThan
@@ -1214,9 +1225,9 @@
* @param void
* @return void
*/
- const SelectionGreaterThan = GreaterThan.extend({
+ const SelectionGreaterThan = GreaterThan.extend( {
type: 'selectionGreaterThan',
- label: __('Selection is greater than'),
+ label: __( 'Selection is greater than' ),
fieldTypes: [
'checkbox',
'select',
@@ -1226,9 +1237,9 @@
'taxonomy',
'user',
],
- });
+ } );
- acf.registerConditionType(SelectionGreaterThan);
+ acf.registerConditionType( SelectionGreaterThan );
/**
* SelectionLessThan
@@ -1239,9 +1250,9 @@
* @param void
* @return void
*/
- const SelectionLessThan = LessThan.extend({
+ const SelectionLessThan = LessThan.extend( {
type: 'selectionLessThan',
- label: __('Selection is less than'),
+ label: __( 'Selection is less than' ),
fieldTypes: [
'checkbox',
'select',
@@ -1251,7 +1262,7 @@
'taxonomy',
'user',
],
- });
+ } );
- acf.registerConditionType(SelectionLessThan);
-})(jQuery);
+ acf.registerConditionType( SelectionLessThan );
+} )( jQuery );
diff --git a/assets/src/js/_acf-field-button-group.js b/assets/src/js/_acf-field-button-group.js
index fb39e103..5a6a07ef 100644
--- a/assets/src/js/_acf-field-button-group.js
+++ b/assets/src/js/_acf-field-button-group.js
@@ -1,7 +1,7 @@
import { update } from '@wordpress/icons';
-(function ($, undefined) {
- const Field = acf.Field.extend({
+( function ( $, undefined ) {
+ const Field = acf.Field.extend( {
type: 'button_group',
events: {
@@ -10,63 +10,63 @@ import { update } from '@wordpress/icons';
},
$control: function () {
- return this.$('.acf-button-group');
+ return this.$( '.acf-button-group' );
},
$input: function () {
- return this.$('input:checked');
+ return this.$( 'input:checked' );
},
initialize: function () {
this.updateButtonStates();
},
- setValue: function (val) {
- this.$('input[value="' + val + '"]')
- .prop('checked', true)
- .trigger('change');
+ setValue: function ( val ) {
+ this.$( 'input[value="' + val + '"]' )
+ .prop( 'checked', true )
+ .trigger( 'change' );
this.updateButtonStates();
},
updateButtonStates: function () {
- const labels = this.$control().find('label');
+ const labels = this.$control().find( 'label' );
const input = this.$input();
labels
- .removeClass('selected')
- .attr('aria-checked', 'false')
- .attr('tabindex', '-1');
- if (input.length) {
+ .removeClass( 'selected' )
+ .attr( 'aria-checked', 'false' )
+ .attr( 'tabindex', '-1' );
+ if ( input.length ) {
// If there's a checked input, mark its parent label as selected
input
- .parent('label')
- .addClass('selected')
- .attr('aria-checked', 'true')
- .attr('tabindex', '0');
+ .parent( 'label' )
+ .addClass( 'selected' )
+ .attr( 'aria-checked', 'true' )
+ .attr( 'tabindex', '0' );
} else {
- labels.first().attr('tabindex', '0');
+ labels.first().attr( 'tabindex', '0' );
}
},
- onClick: function (e, $el) {
- this.selectButton($el.parent('label'));
+ onClick: function ( e, $el ) {
+ this.selectButton( $el.parent( 'label' ) );
},
- onKeyDown: function (event, label) {
+ onKeyDown: function ( event, label ) {
const key = event.which;
// Space or Enter: select the button
- if (key === 13 || key === 32) {
+ if ( key === 13 || key === 32 ) {
event.preventDefault();
- this.selectButton(label);
+ this.selectButton( label );
return;
}
// Arrow keys: move focus between buttons
- if (key === 37 || key === 39 || key === 38 || key === 40) {
+ if ( key === 37 || key === 39 || key === 38 || key === 40 ) {
event.preventDefault();
- const labels = this.$control().find('label');
- const currentIndex = labels.index(label);
+ const labels = this.$control().find( 'label' );
+ const currentIndex = labels.index( label );
let nextIndex;
// Left/Up arrow: move to previous, wrap to last if at start
- if (key === 37 || key === 38) {
+ if ( key === 37 || key === 38 ) {
nextIndex =
currentIndex > 0 ? currentIndex - 1 : labels.length - 1;
}
@@ -76,22 +76,22 @@ import { update } from '@wordpress/icons';
currentIndex < labels.length - 1 ? currentIndex + 1 : 0;
}
- const nextLabel = labels.eq(nextIndex);
- labels.attr('tabindex', '-1');
- nextLabel.attr('tabindex', '0').trigger('focus');
+ const nextLabel = labels.eq( nextIndex );
+ labels.attr( 'tabindex', '-1' );
+ nextLabel.attr( 'tabindex', '0' ).trigger( 'focus' );
}
},
- selectButton: function (element) {
- const inputRadio = element.find('input[type="radio"]');
- const isSelected = element.hasClass('selected');
- inputRadio.prop('checked', true).trigger('change');
- if (this.get('allow_null') && isSelected) {
- inputRadio.prop('checked', false).trigger('change');
+ selectButton: function ( element ) {
+ const inputRadio = element.find( 'input[type="radio"]' );
+ const isSelected = element.hasClass( 'selected' );
+ inputRadio.prop( 'checked', true ).trigger( 'change' );
+ if ( this.get( 'allow_null' ) && isSelected ) {
+ inputRadio.prop( 'checked', false ).trigger( 'change' );
}
this.updateButtonStates();
},
- });
+ } );
- acf.registerFieldType(Field);
-})(jQuery);
+ acf.registerFieldType( Field );
+} )( jQuery );
diff --git a/assets/src/js/_acf-field-color-picker.js b/assets/src/js/_acf-field-color-picker.js
index 72bec2ce..46807526 100644
--- a/assets/src/js/_acf-field-color-picker.js
+++ b/assets/src/js/_acf-field-color-picker.js
@@ -1,5 +1,5 @@
-(function ($, undefined) {
- var Field = acf.Field.extend({
+( function ( $, undefined ) {
+ var Field = acf.Field.extend( {
type: 'color_picker',
wait: 'load',
@@ -9,23 +9,23 @@
},
$control: function () {
- return this.$('.acf-color-picker');
+ return this.$( '.acf-color-picker' );
},
$input: function () {
- return this.$('input[type="hidden"]');
+ return this.$( 'input[type="hidden"]' );
},
$inputText: function () {
- return this.$('input[type="text"]');
+ return this.$( 'input[type="text"]' );
},
- setValue: function (val) {
+ setValue: function ( val ) {
// update input (with change)
- acf.val(this.$input(), val);
+ acf.val( this.$input(), val );
// update iris
- this.$inputText().iris('color', val);
+ this.$inputText().iris( 'color', val );
},
initialize: function () {
@@ -34,11 +34,11 @@
var $inputText = this.$inputText();
// event
- var onChange = function (e) {
+ var onChange = function ( e ) {
// timeout is required to ensure the $input val is correct
- setTimeout(function () {
- acf.val($input, $inputText.val());
- }, 1);
+ setTimeout( function () {
+ acf.val( $input, $inputText.val() );
+ }, 1 );
};
// args
@@ -49,33 +49,33 @@
change: onChange,
clear: onChange,
};
- if ('custom' === $inputText.data('acf-palette-type')) {
+ if ( 'custom' === $inputText.data( 'acf-palette-type' ) ) {
const paletteColor = $inputText
- .data('acf-palette-colors')
+ .data( 'acf-palette-colors' )
.match(
/#(?:[0-9a-fA-F]{3}){1,2}|rgba?\([\s*(\d|.)+\s*,]+\)/g
);
- if (paletteColor) {
- let trimmed = paletteColor.map((color) => color.trim());
+ if ( paletteColor ) {
+ let trimmed = paletteColor.map( ( color ) => color.trim() );
args.palettes = trimmed;
}
}
// filter
- var args = acf.applyFilters('color_picker_args', args, this);
+ var args = acf.applyFilters( 'color_picker_args', args, this );
// initialize
- $inputText.wpColorPicker(args);
+ $inputText.wpColorPicker( args );
},
- onDuplicate: function (e, $el, $duplicate) {
+ onDuplicate: function ( e, $el, $duplicate ) {
// The wpColorPicker library does not provide a destroy method.
// Manually reset DOM by replacing elements back to their original state.
- $colorPicker = $duplicate.find('.wp-picker-container');
- $inputText = $duplicate.find('input[type="text"]');
- $colorPicker.replaceWith($inputText);
+ $colorPicker = $duplicate.find( '.wp-picker-container' );
+ $inputText = $duplicate.find( 'input[type="text"]' );
+ $colorPicker.replaceWith( $inputText );
},
- });
+ } );
- acf.registerFieldType(Field);
-})(jQuery);
+ acf.registerFieldType( Field );
+} )( jQuery );
diff --git a/assets/src/js/_acf-field-radio.js b/assets/src/js/_acf-field-radio.js
index 1fb9bdc8..7d37078b 100644
--- a/assets/src/js/_acf-field-radio.js
+++ b/assets/src/js/_acf-field-radio.js
@@ -1,5 +1,5 @@
-(function ($, undefined) {
- var Field = acf.Field.extend({
+( function ( $, undefined ) {
+ var Field = acf.Field.extend( {
type: 'radio',
events: {
@@ -8,63 +8,63 @@
},
$control: function () {
- return this.$('.acf-radio-list');
+ return this.$( '.acf-radio-list' );
},
$input: function () {
- return this.$('input:checked');
+ return this.$( 'input:checked' );
},
$inputText: function () {
- return this.$('input[type="text"]');
+ return this.$( 'input[type="text"]' );
},
getValue: function () {
var val = this.$input().val();
- if (val === 'other' && this.get('other_choice')) {
+ if ( val === 'other' && this.get( 'other_choice' ) ) {
val = this.$inputText().val();
}
return val;
},
- onClick: function (e, $el) {
+ onClick: function ( e, $el ) {
// vars
- var $label = $el.parent('label');
- var selected = $label.hasClass('selected');
+ var $label = $el.parent( 'label' );
+ var selected = $label.hasClass( 'selected' );
var val = $el.val();
// remove previous selected
- this.$('.selected').removeClass('selected');
+ this.$( '.selected' ).removeClass( 'selected' );
// add active class
- $label.addClass('selected');
+ $label.addClass( 'selected' );
// allow null
- if (this.get('allow_null') && selected) {
- $label.removeClass('selected');
- $el.prop('checked', false).trigger('change');
+ if ( this.get( 'allow_null' ) && selected ) {
+ $label.removeClass( 'selected' );
+ $el.prop( 'checked', false ).trigger( 'change' );
val = false;
}
// other
- if (this.get('other_choice')) {
+ if ( this.get( 'other_choice' ) ) {
// enable
- if (val === 'other') {
- this.$inputText().prop('disabled', false);
+ if ( val === 'other' ) {
+ this.$inputText().prop( 'disabled', false );
// disable
} else {
- this.$inputText().prop('disabled', true);
+ this.$inputText().prop( 'disabled', true );
}
}
},
- onKeyDownInput: function (event, input) {
- if (event.which === 13) {
+ onKeyDownInput: function ( event, input ) {
+ if ( event.which === 13 ) {
event.preventDefault();
- input.prop('checked', true).trigger('change');
+ input.prop( 'checked', true ).trigger( 'change' );
}
},
- });
+ } );
- acf.registerFieldType(Field);
-})(jQuery);
+ acf.registerFieldType( Field );
+} )( jQuery );
diff --git a/assets/src/js/_acf-field-taxonomy.js b/assets/src/js/_acf-field-taxonomy.js
index 9fe651d1..2b0915c7 100644
--- a/assets/src/js/_acf-field-taxonomy.js
+++ b/assets/src/js/_acf-field-taxonomy.js
@@ -1,5 +1,5 @@
-(function ($, undefined) {
- var Field = acf.Field.extend({
+( function ( $, undefined ) {
+ var Field = acf.Field.extend( {
type: 'taxonomy',
data: {
@@ -18,19 +18,19 @@
},
$control: function () {
- return this.$('.acf-taxonomy-field');
+ return this.$( '.acf-taxonomy-field' );
},
$input: function () {
- return this.getRelatedPrototype().$input.apply(this, arguments);
+ return this.getRelatedPrototype().$input.apply( this, arguments );
},
getRelatedType: function () {
// vars
- var fieldType = this.get('ftype');
+ var fieldType = this.get( 'ftype' );
// normalize
- if (fieldType == 'multi_select') {
+ if ( fieldType == 'multi_select' ) {
fieldType = 'select';
}
@@ -39,29 +39,29 @@
},
getRelatedPrototype: function () {
- return acf.getFieldType(this.getRelatedType()).prototype;
+ return acf.getFieldType( this.getRelatedType() ).prototype;
},
getValue: function () {
- return this.getRelatedPrototype().getValue.apply(this, arguments);
+ return this.getRelatedPrototype().getValue.apply( this, arguments );
},
setValue: function () {
- return this.getRelatedPrototype().setValue.apply(this, arguments);
+ return this.getRelatedPrototype().setValue.apply( this, arguments );
},
initialize: function () {
- this.getRelatedPrototype().initialize.apply(this, arguments);
+ this.getRelatedPrototype().initialize.apply( this, arguments );
},
onRemove: function () {
var proto = this.getRelatedPrototype();
- if (proto.onRemove) {
- proto.onRemove.apply(this, arguments);
+ if ( proto.onRemove ) {
+ proto.onRemove.apply( this, arguments );
}
},
- onClickAdd: function (e, $el) {
+ onClickAdd: function ( e, $el ) {
// vars
var field = this;
var popup = false;
@@ -75,124 +75,124 @@
// step 1.
var step1 = function () {
// popup
- popup = acf.newPopup({
- title: $el.attr('title'),
+ popup = acf.newPopup( {
+ title: $el.attr( 'title' ),
loading: true,
width: '300px',
- });
+ } );
// ajax
var ajaxData = {
action: 'acf/fields/taxonomy/add_term',
- field_key: field.get('key'),
- nonce: field.get('nonce'),
+ field_key: field.get( 'key' ),
+ nonce: field.get( 'nonce' ),
};
// get HTML
- $.ajax({
- url: acf.get('ajaxurl'),
- data: acf.prepareForAjax(ajaxData),
+ $.ajax( {
+ url: acf.get( 'ajaxurl' ),
+ data: acf.prepareForAjax( ajaxData ),
type: 'post',
dataType: 'html',
success: step2,
- });
+ } );
};
// step 2.
- var step2 = function (html) {
+ var step2 = function ( html ) {
// update popup
- popup.loading(false);
- popup.content(html);
+ popup.loading( false );
+ popup.content( html );
// vars
- $form = popup.$('form');
- $name = popup.$('input[name="term_name"]');
- $parent = popup.$('select[name="term_parent"]');
- $button = popup.$('.acf-submit-button');
+ $form = popup.$( 'form' );
+ $name = popup.$( 'input[name="term_name"]' );
+ $parent = popup.$( 'select[name="term_parent"]' );
+ $button = popup.$( '.acf-submit-button' );
// focus
- $name.trigger('focus');
+ $name.trigger( 'focus' );
// submit form
- popup.on('submit', 'form', step3);
+ popup.on( 'submit', 'form', step3 );
};
// step 3.
- var step3 = function (e, $el) {
+ var step3 = function ( e, $el ) {
// prevent
e.preventDefault();
e.stopImmediatePropagation();
// basic validation
- if ($name.val() === '') {
- $name.trigger('focus');
+ if ( $name.val() === '' ) {
+ $name.trigger( 'focus' );
return false;
}
// disable
- acf.startButtonLoading($button);
+ acf.startButtonLoading( $button );
// ajax
var ajaxData = {
action: 'acf/fields/taxonomy/add_term',
- field_key: field.get('key'),
- nonce: field.get('nonce'),
+ field_key: field.get( 'key' ),
+ nonce: field.get( 'nonce' ),
term_name: $name.val(),
term_parent: $parent.length ? $parent.val() : 0,
};
- $.ajax({
- url: acf.get('ajaxurl'),
- data: acf.prepareForAjax(ajaxData),
+ $.ajax( {
+ url: acf.get( 'ajaxurl' ),
+ data: acf.prepareForAjax( ajaxData ),
type: 'post',
dataType: 'json',
success: step4,
- });
+ } );
};
// step 4.
- var step4 = function (json) {
+ var step4 = function ( json ) {
// enable
- acf.stopButtonLoading($button);
+ acf.stopButtonLoading( $button );
// remove prev notice
- if (notice) {
+ if ( notice ) {
notice.remove();
}
// success
- if (acf.isAjaxSuccess(json)) {
+ if ( acf.isAjaxSuccess( json ) ) {
// clear name
- $name.val('');
+ $name.val( '' );
// update term lists
- step5(json.data);
+ step5( json.data );
// notice
- notice = acf.newNotice({
+ notice = acf.newNotice( {
type: 'success',
- text: acf.getAjaxMessage(json),
+ text: acf.getAjaxMessage( json ),
target: $form,
timeout: 2000,
dismiss: false,
- });
+ } );
} else {
// notice
- notice = acf.newNotice({
+ notice = acf.newNotice( {
type: 'error',
- text: acf.getAjaxError(json),
+ text: acf.getAjaxError( json ),
target: $form,
timeout: 2000,
dismiss: false,
- });
+ } );
}
// focus
- $name.trigger('focus');
+ $name.trigger( 'focus' );
};
// step 5.
- var step5 = function (term) {
+ var step5 = function ( term ) {
// update parent dropdown
var $option = $(
'',
'',
' ');
- $parent.append($ul);
+ if ( ! $ul.exists() ) {
+ $ul = $( '' );
+ $parent.append( $ul );
}
}
// append
- $ul.append($li);
+ $ul.append( $li );
},
- selectTerm: function (id) {
- if (this.getRelatedType() == 'select') {
- this.select2.selectOption(id);
+ selectTerm: function ( id ) {
+ if ( this.getRelatedType() == 'select' ) {
+ this.select2.selectOption( id );
} else {
- var $input = this.$('input[value="' + id + '"]');
- $input.prop('checked', true).trigger('change');
+ var $input = this.$( 'input[value="' + id + '"]' );
+ $input.prop( 'checked', true ).trigger( 'change' );
}
},
- onClickRadio: function (e, $el) {
+ onClickRadio: function ( e, $el ) {
// vars
- var $label = $el.parent('label');
- var selected = $label.hasClass('selected');
+ var $label = $el.parent( 'label' );
+ var selected = $label.hasClass( 'selected' );
// remove previous selected
- this.$('.selected').removeClass('selected');
+ this.$( '.selected' ).removeClass( 'selected' );
// add active class
- $label.addClass('selected');
+ $label.addClass( 'selected' );
// allow null
- if (this.get('allow_null') && selected) {
- $label.removeClass('selected');
- $el.prop('checked', false).trigger('change');
+ if ( this.get( 'allow_null' ) && selected ) {
+ $label.removeClass( 'selected' );
+ $el.prop( 'checked', false ).trigger( 'change' );
}
},
- onKeyDownLabel: function (e, $el) {
+ onKeyDownLabel: function ( e, $el ) {
// bail early if not space or enter
- if (e.which !== 13) {
+ if ( e.which !== 13 ) {
return;
}
e.preventDefault();
- const firstInput = $el.find('input').first();
- if (firstInput.length) {
- firstInput.trigger('click').trigger('focus');
+ const firstInput = $el.find( 'input' ).first();
+ if ( firstInput.length ) {
+ firstInput.trigger( 'click' ).trigger( 'focus' );
}
},
- });
+ } );
- acf.registerFieldType(Field);
-})(jQuery);
+ acf.registerFieldType( Field );
+} )( jQuery );
diff --git a/assets/src/js/_acf-validation.js b/assets/src/js/_acf-validation.js
index 7845b2fc..556e94cc 100644
--- a/assets/src/js/_acf-validation.js
+++ b/assets/src/js/_acf-validation.js
@@ -1,4 +1,4 @@
-(function ($, undefined) {
+( function ( $, undefined ) {
/**
* Validator
*
@@ -10,7 +10,7 @@
* @param void
* @return void
*/
- var Validator = acf.Model.extend({
+ var Validator = acf.Model.extend( {
/** @var string The model identifier. */
id: 'Validator',
@@ -42,8 +42,8 @@
* @param array errors An array of errors.
* @return void
*/
- addErrors: function (errors) {
- errors.map(this.addError, this);
+ addErrors: function ( errors ) {
+ errors.map( this.addError, this );
},
/**
@@ -57,8 +57,8 @@
* @param object error An error object containing input and message.
* @return void
*/
- addError: function (error) {
- this.data.errors.push(error);
+ addError: function ( error ) {
+ this.data.errors.push( error );
},
/**
@@ -88,7 +88,7 @@
* @return void
*/
clearErrors: function () {
- return (this.data.errors = []);
+ return ( this.data.errors = [] );
},
/**
@@ -123,21 +123,21 @@
var inputs = [];
// loop
- this.getErrors().map(function (error) {
+ this.getErrors().map( function ( error ) {
// bail early if global
- if (!error.input) return;
+ if ( ! error.input ) return;
// update if exists
- var i = inputs.indexOf(error.input);
- if (i > -1) {
- errors[i] = error;
+ var i = inputs.indexOf( error.input );
+ if ( i > -1 ) {
+ errors[ i ] = error;
// update
} else {
- errors.push(error);
- inputs.push(error.input);
+ errors.push( error );
+ inputs.push( error.input );
}
- });
+ } );
// return
return errors;
@@ -156,9 +156,9 @@
*/
getGlobalErrors: function () {
// return array of errors that contain no input
- return this.getErrors().filter(function (error) {
- return !error.input;
- });
+ return this.getErrors().filter( function ( error ) {
+ return ! error.input;
+ } );
},
/**
@@ -171,9 +171,9 @@
* @param {string} [location=before] - The location to add the error, before or after the input. Default before. Since ACF 6.3.
* @return void
*/
- showErrors: function (location = 'before') {
+ showErrors: function ( location = 'before' ) {
// bail early if no errors
- if (!this.hasErrors()) {
+ if ( ! this.hasErrors() ) {
return;
}
@@ -186,17 +186,17 @@
var $scrollTo = false;
// loop
- fieldErrors.map(function (error) {
+ fieldErrors.map( function ( error ) {
// get input
- var $input = this.$('[name="' + error.input + '"]').first();
+ var $input = this.$( '[name="' + error.input + '"]' ).first();
// if $_POST value was an array, this $input may not exist
- if (!$input.length) {
- $input = this.$('[name^="' + error.input + '"]').first();
+ if ( ! $input.length ) {
+ $input = this.$( '[name^="' + error.input + '"]' ).first();
}
// bail early if input doesn't exist
- if (!$input.length) {
+ if ( ! $input.length ) {
return;
}
@@ -204,70 +204,70 @@
errorCount++;
// get field
- var field = acf.getClosestField($input);
+ var field = acf.getClosestField( $input );
// make sure the postbox containing this field is not hidden by screen options
- ensureFieldPostBoxIsVisible(field.$el);
+ ensureFieldPostBoxIsVisible( field.$el );
// show error
- field.showError(error.message, location);
+ field.showError( error.message, location );
// set $scrollTo
- if (!$scrollTo) {
+ if ( ! $scrollTo ) {
$scrollTo = field.$el;
}
- }, this);
+ }, this );
// errorMessage
- var errorMessage = acf.__('Validation failed');
- globalErrors.map(function (error) {
+ var errorMessage = acf.__( 'Validation failed' );
+ globalErrors.map( function ( error ) {
errorMessage += '. ' + error.message;
- });
- if (errorCount == 1) {
- errorMessage += '. ' + acf.__('1 field requires attention');
- } else if (errorCount > 1) {
+ } );
+ if ( errorCount == 1 ) {
+ errorMessage += '. ' + acf.__( '1 field requires attention' );
+ } else if ( errorCount > 1 ) {
errorMessage +=
'. ' +
acf
- .__('%d fields require attention')
- .replace('%d', errorCount);
+ .__( '%d fields require attention' )
+ .replace( '%d', errorCount );
}
// notice
- if (this.has('notice')) {
- this.get('notice').update({
+ if ( this.has( 'notice' ) ) {
+ this.get( 'notice' ).update( {
type: 'error',
text: errorMessage,
- });
+ } );
} else {
- var notice = acf.newNotice({
+ var notice = acf.newNotice( {
type: 'error',
text: errorMessage,
target: this.$el,
- });
- this.set('notice', notice);
+ } );
+ this.set( 'notice', notice );
}
// If in a modal, don't try to scroll.
- if (this.$el.parents('.acf-popup-box').length) {
+ if ( this.$el.parents( '.acf-popup-box' ).length ) {
return;
}
// if no $scrollTo, set to message
- if (!$scrollTo) {
- $scrollTo = this.get('notice').$el;
+ if ( ! $scrollTo ) {
+ $scrollTo = this.get( 'notice' ).$el;
}
// timeout
- setTimeout(function () {
- $('html, body').animate(
+ setTimeout( function () {
+ $( 'html, body' ).animate(
{
scrollTop:
- $scrollTo.offset().top - $(window).height() / 2,
+ $scrollTo.offset().top - $( window ).height() / 2,
},
500
);
- }, 10);
+ }, 10 );
},
/**
@@ -284,8 +284,8 @@
* @param string prevValue The old status.
* @return void
*/
- onChangeStatus: function (e, $el, value, prevValue) {
- this.$el.removeClass('is-' + prevValue).addClass('is-' + value);
+ onChangeStatus: function ( e, $el, value, prevValue ) {
+ this.$el.removeClass( 'is-' + prevValue ).addClass( 'is-' + value );
},
/**
@@ -299,9 +299,9 @@
* @param object args A list of settings to customize the validation process.
* @return bool True if the form is valid.
*/
- validate: function (args) {
+ validate: function ( args ) {
// default args
- args = acf.parseArgs(args, {
+ args = acf.parseArgs( args, {
// trigger event
event: false,
@@ -318,50 +318,50 @@
failure: function () {},
// success callback
- success: function ($form) {
+ success: function ( $form ) {
$form.submit();
},
- });
+ } );
// return true if is valid - allows form submit
- if (this.get('status') == 'valid') {
+ if ( this.get( 'status' ) == 'valid' ) {
return true;
}
// return false if is currently validating - prevents form submit
- if (this.get('status') == 'validating') {
+ if ( this.get( 'status' ) == 'validating' ) {
return false;
}
// return true if no ACF fields exist (no need to validate)
- if (!this.$('.acf-field').length) {
+ if ( ! this.$( '.acf-field' ).length ) {
return true;
}
// if event is provided, create a new success callback.
- if (args.event) {
- var event = $.Event(null, args.event);
+ if ( args.event ) {
+ var event = $.Event( null, args.event );
args.success = function () {
- acf.enableSubmit($(event.target)).trigger(event);
+ acf.enableSubmit( $( event.target ) ).trigger( event );
};
}
// action for 3rd party
- acf.doAction('validation_begin', this.$el);
+ acf.doAction( 'validation_begin', this.$el );
// lock form
- acf.lockForm(this.$el);
+ acf.lockForm( this.$el );
// loading callback
- args.loading(this.$el, this);
+ args.loading( this.$el, this );
// update status
- this.set('status', 'validating');
+ this.set( 'status', 'validating' );
// success callback
- var onSuccess = function (json) {
+ var onSuccess = function ( json ) {
// validate
- if (!acf.isAjaxSuccess(json)) {
+ if ( ! acf.isAjaxSuccess( json ) ) {
return;
}
@@ -374,81 +374,81 @@
);
// add errors
- if (!data.valid) {
- this.addErrors(data.errors);
+ if ( ! data.valid ) {
+ this.addErrors( data.errors );
}
};
// complete
var onComplete = function () {
// unlock form
- acf.unlockForm(this.$el);
+ acf.unlockForm( this.$el );
// failure
- if (this.hasErrors()) {
+ if ( this.hasErrors() ) {
// update status
- this.set('status', 'invalid');
+ this.set( 'status', 'invalid' );
// action
- acf.doAction('validation_failure', this.$el, this);
+ acf.doAction( 'validation_failure', this.$el, this );
// display errors
this.showErrors();
// failure callback
- args.failure(this.$el, this);
+ args.failure( this.$el, this );
// success
} else {
// update status
- this.set('status', 'valid');
+ this.set( 'status', 'valid' );
// remove previous error message
- if (this.has('notice')) {
- this.get('notice').update({
+ if ( this.has( 'notice' ) ) {
+ this.get( 'notice' ).update( {
type: 'success',
- text: acf.__('Validation successful'),
+ text: acf.__( 'Validation successful' ),
timeout: 1000,
- });
+ } );
}
// action
- acf.doAction('validation_success', this.$el, this);
- acf.doAction('submit', this.$el);
+ acf.doAction( 'validation_success', this.$el, this );
+ acf.doAction( 'submit', this.$el );
// success callback (submit form)
- args.success(this.$el, this);
+ args.success( this.$el, this );
// lock form
- acf.lockForm(this.$el);
+ acf.lockForm( this.$el );
// reset
- if (args.reset) {
+ if ( args.reset ) {
this.reset();
}
}
// complete callback
- args.complete(this.$el, this);
+ args.complete( this.$el, this );
// clear errors
this.clearErrors();
};
// serialize form data
- var data = acf.serialize(this.$el);
+ var data = acf.serialize( this.$el );
data.action = 'acf/validate_save_post';
// ajax
- $.ajax({
- url: acf.get('ajaxurl'),
- data: acf.prepareForAjax(data, true),
+ $.ajax( {
+ url: acf.get( 'ajaxurl' ),
+ data: acf.prepareForAjax( data, true ),
type: 'post',
dataType: 'json',
context: this,
success: onSuccess,
complete: onComplete,
- });
+ } );
// return false to fail validation and allow AJAX
return false;
@@ -465,7 +465,7 @@
* @param jQuery $form The form element.
* @return void
*/
- setup: function ($form) {
+ setup: function ( $form ) {
// set $el
this.$el = $form;
},
@@ -483,14 +483,14 @@
*/
reset: function () {
// reset data
- this.set('errors', []);
- this.set('notice', null);
- this.set('status', '');
+ this.set( 'errors', [] );
+ this.set( 'notice', null );
+ this.set( 'status', '' );
// unlock form
- acf.unlockForm(this.$el);
+ acf.unlockForm( this.$el );
},
- });
+ } );
/**
* getValidator
@@ -503,11 +503,11 @@
* @param jQuery $el The form element.
* @return object
*/
- var getValidator = function ($el) {
+ var getValidator = function ( $el ) {
// instantiate
- var validator = $el.data('acf');
- if (!validator) {
- validator = new Validator($el);
+ var validator = $el.data( 'acf' );
+ if ( ! validator ) {
+ validator = new Validator( $el );
}
// return
@@ -522,8 +522,8 @@
* @param $el The jQuery block form wrapper element.
* @return bool
*/
- acf.getBlockFormValidator = function ($el) {
- return getValidator($el);
+ acf.getBlockFormValidator = function ( $el ) {
+ return getValidator( $el );
};
/**
@@ -535,8 +535,8 @@
* @param object args A list of settings to customize the validation process.
* @return bool
*/
- acf.validateForm = function (args) {
- return getValidator(args.form).validate(args);
+ acf.validateForm = function ( args ) {
+ return getValidator( args.form ).validate( args );
};
/**
@@ -550,8 +550,8 @@
* @param jQuery $submit The submit button.
* @return jQuery
*/
- acf.enableSubmit = function ($submit) {
- return $submit.removeClass('disabled').removeAttr('disabled');
+ acf.enableSubmit = function ( $submit ) {
+ return $submit.removeClass( 'disabled' ).removeAttr( 'disabled' );
};
/**
@@ -565,8 +565,8 @@
* @param jQuery $submit The submit button.
* @return jQuery
*/
- acf.disableSubmit = function ($submit) {
- return $submit.addClass('disabled').attr('disabled', true);
+ acf.disableSubmit = function ( $submit ) {
+ return $submit.addClass( 'disabled' ).attr( 'disabled', true );
};
/**
@@ -580,9 +580,9 @@
* @param jQuery $spinner The spinner element.
* @return jQuery
*/
- acf.showSpinner = function ($spinner) {
- $spinner.addClass('is-active'); // add class (WP > 4.2)
- $spinner.css('display', 'inline-block'); // css (WP < 4.2)
+ acf.showSpinner = function ( $spinner ) {
+ $spinner.addClass( 'is-active' ); // add class (WP > 4.2)
+ $spinner.css( 'display', 'inline-block' ); // css (WP < 4.2)
return $spinner;
};
@@ -597,9 +597,9 @@
* @param jQuery $spinner The spinner element.
* @return jQuery
*/
- acf.hideSpinner = function ($spinner) {
- $spinner.removeClass('is-active'); // add class (WP > 4.2)
- $spinner.css('display', 'none'); // css (WP < 4.2)
+ acf.hideSpinner = function ( $spinner ) {
+ $spinner.removeClass( 'is-active' ); // add class (WP > 4.2)
+ $spinner.css( 'display', 'none' ); // css (WP < 4.2)
return $spinner;
};
@@ -614,20 +614,20 @@
* @param jQuery $form The form element.
* @return jQuery
*/
- acf.lockForm = function ($form) {
+ acf.lockForm = function ( $form ) {
// vars
- var $wrap = findSubmitWrap($form);
+ var $wrap = findSubmitWrap( $form );
var $submit = $wrap
- .find('.button, [type="submit"]')
- .not('.acf-nav, .acf-repeater-add-row');
- var $spinner = $wrap.find('.spinner, .acf-spinner');
+ .find( '.button, [type="submit"]' )
+ .not( '.acf-nav, .acf-repeater-add-row' );
+ var $spinner = $wrap.find( '.spinner, .acf-spinner' );
// hide all spinners (hides the preview spinner)
- acf.hideSpinner($spinner);
+ acf.hideSpinner( $spinner );
// lock
- acf.disableSubmit($submit);
- acf.showSpinner($spinner.last());
+ acf.disableSubmit( $submit );
+ acf.showSpinner( $spinner.last() );
return $form;
};
@@ -642,17 +642,17 @@
* @param jQuery $form The form element.
* @return jQuery
*/
- acf.unlockForm = function ($form) {
+ acf.unlockForm = function ( $form ) {
// vars
- var $wrap = findSubmitWrap($form);
+ var $wrap = findSubmitWrap( $form );
var $submit = $wrap
- .find('.button, [type="submit"]')
- .not('.acf-nav, .acf-repeater-add-row');
- var $spinner = $wrap.find('.spinner, .acf-spinner');
+ .find( '.button, [type="submit"]' )
+ .not( '.acf-nav, .acf-repeater-add-row' );
+ var $spinner = $wrap.find( '.spinner, .acf-spinner' );
// unlock
- acf.enableSubmit($submit);
- acf.hideSpinner($spinner);
+ acf.enableSubmit( $submit );
+ acf.hideSpinner( $spinner );
return $form;
};
@@ -667,40 +667,40 @@
* @param jQuery $form The form element.
* @return jQuery
*/
- var findSubmitWrap = function ($form) {
+ var findSubmitWrap = function ( $form ) {
// default post submit div
- var $wrap = $form.find('#submitdiv');
- if ($wrap.length) {
+ var $wrap = $form.find( '#submitdiv' );
+ if ( $wrap.length ) {
return $wrap;
}
// 3rd party publish box
- var $wrap = $form.find('#submitpost');
- if ($wrap.length) {
+ var $wrap = $form.find( '#submitpost' );
+ if ( $wrap.length ) {
return $wrap;
}
// term, user
- var $wrap = $form.find('p.submit').last();
- if ($wrap.length) {
+ var $wrap = $form.find( 'p.submit' ).last();
+ if ( $wrap.length ) {
return $wrap;
}
// front end form
- var $wrap = $form.find('.acf-form-submit');
- if ($wrap.length) {
+ var $wrap = $form.find( '.acf-form-submit' );
+ if ( $wrap.length ) {
return $wrap;
}
// ACF 6.2 options page modal
- var $wrap = $('#acf-create-options-page-form .acf-actions');
- if ($wrap.length) {
+ var $wrap = $( '#acf-create-options-page-form .acf-actions' );
+ if ( $wrap.length ) {
return $wrap;
}
// ACF 6.0+ headerbar submit
- var $wrap = $('.acf-headerbar-actions');
- if ($wrap.length) {
+ var $wrap = $( '.acf-headerbar-actions' );
+ if ( $wrap.length ) {
return $wrap;
}
@@ -717,9 +717,9 @@
* @param type Var Description.
* @return type Description.
*/
- var submitFormDebounced = acf.debounce(function ($form) {
+ var submitFormDebounced = acf.debounce( function ( $form ) {
$form.submit();
- });
+ } );
/**
* Ensure field is visible for validation errors
@@ -727,16 +727,16 @@
* @date 20/10/2021
* @since ACF 5.11.0
*/
- var ensureFieldPostBoxIsVisible = function ($el) {
+ var ensureFieldPostBoxIsVisible = function ( $el ) {
// Find the postbox element containing this field.
- var $postbox = $el.parents('.acf-postbox');
- if ($postbox.length) {
- var acf_postbox = acf.getPostbox($postbox);
- if (acf_postbox && acf_postbox.isHiddenByScreenOptions()) {
+ var $postbox = $el.parents( '.acf-postbox' );
+ if ( $postbox.length ) {
+ var acf_postbox = acf.getPostbox( $postbox );
+ if ( acf_postbox && acf_postbox.isHiddenByScreenOptions() ) {
// Rather than using .show() here, we don't want the field to appear next reload.
// So just temporarily show the field group so validation can complete.
- acf_postbox.$el.removeClass('hide-if-js');
- acf_postbox.$el.css('display', '');
+ acf_postbox.$el.removeClass( 'hide-if-js' );
+ acf_postbox.$el.css( 'display', '' );
}
}
};
@@ -749,13 +749,13 @@
*/
var ensureInvalidFieldVisibility = function () {
// Load each ACF input field and check it's browser validation state.
- var $inputs = $('.acf-field input');
- $inputs.each(function () {
- if (!this.checkValidity()) {
+ var $inputs = $( '.acf-field input' );
+ $inputs.each( function () {
+ if ( ! this.checkValidity() ) {
// Field is invalid, so we need to make sure it's metabox is visible.
- ensureFieldPostBoxIsVisible($(this));
+ ensureFieldPostBoxIsVisible( $( this ) );
}
- });
+ } );
};
/**
@@ -770,7 +770,7 @@
* @return void
*/
- acf.validation = new acf.Model({
+ acf.validation = new acf.Model( {
/** @var string The model identifier. */
id: 'validation',
@@ -808,7 +808,7 @@
*/
initialize: function () {
// check 'validation' setting
- if (!acf.get('validation')) {
+ if ( ! acf.get( 'validation' ) ) {
this.active = false;
this.actions = {};
this.events = {};
@@ -856,8 +856,8 @@
* @param jQuery $form The form element.
* @return void
*/
- reset: function ($form) {
- getValidator($form).reset();
+ reset: function ( $form ) {
+ getValidator( $form ).reset();
},
/**
@@ -871,16 +871,16 @@
* @param jQuery $el The element being added / readied.
* @return void
*/
- addInputEvents: function ($el) {
+ addInputEvents: function ( $el ) {
// Bug exists in Safari where custom "invalid" handling prevents draft from saving.
- if (acf.get('browser') === 'safari') return;
+ if ( acf.get( 'browser' ) === 'safari' ) return;
// vars
- var $inputs = $('.acf-field [name]', $el);
+ var $inputs = $( '.acf-field [name]', $el );
// check
- if ($inputs.length) {
- this.on($inputs, 'invalid', 'onInvalid');
+ if ( $inputs.length ) {
+ this.on( $inputs, 'invalid', 'onInvalid' );
}
},
@@ -896,26 +896,26 @@
* @param jQuery $el The input element.
* @return void
*/
- onInvalid: function (e, $el) {
+ onInvalid: function ( e, $el ) {
// prevent default
// - prevents browser error message
// - also fixes chrome bug where 'hidden-by-tab' field throws focus error
e.preventDefault();
// vars
- var $form = $el.closest('form');
+ var $form = $el.closest( 'form' );
// check form exists
- if ($form.length) {
+ if ( $form.length ) {
// add error to validator
- getValidator($form).addError({
- input: $el.attr('name'),
- message: acf.strEscape(e.target.validationMessage),
- });
+ getValidator( $form ).addError( {
+ input: $el.attr( 'name' ),
+ message: acf.strEscape( e.target.validationMessage ),
+ } );
// trigger submit on $form
// - allows for "save", "preview" and "publish" to work
- submitFormDebounced($form);
+ submitFormDebounced( $form );
}
},
@@ -931,13 +931,13 @@
* @param jQuery $el The input element.
* @return void
*/
- onClickSubmit: function (e, $el) {
+ onClickSubmit: function ( e, $el ) {
// Some browsers (safari) force their browser validation before our AJAX validation,
// so we need to make sure fields are visible earlier than showErrors()
ensureInvalidFieldVisibility();
// store the "click event" for later use in this.onSubmit()
- this.set('originalEvent', e);
+ this.set( 'originalEvent', e );
},
/**
@@ -952,8 +952,8 @@
* @param jQuery $el The input element.
* @return void
*/
- onClickSave: function (e, $el) {
- this.set('ignore', true);
+ onClickSave: function ( e, $el ) {
+ this.set( 'ignore', true );
},
/**
@@ -968,14 +968,14 @@
* @param jQuery $el The input element.
* @return void
*/
- onSubmitPost: function (e, $el) {
+ onSubmitPost: function ( e, $el ) {
// Check if is preview.
- if ($('input#wp-preview').val() === 'dopreview') {
+ if ( $( 'input#wp-preview' ).val() === 'dopreview' ) {
// Ignore validation.
- this.set('ignore', true);
+ this.set( 'ignore', true );
// Unlock form to fix conflict with core "submit.edit-post" event causing all submit buttons to be disabled.
- acf.unlockForm($el);
+ acf.unlockForm( $el );
}
},
@@ -991,13 +991,13 @@
* @param jQuery $el The input element.
* @return void
*/
- onSubmit: function (e, $el) {
+ onSubmit: function ( e, $el ) {
// Allow form to submit if...
if (
// Validation has been disabled.
- !this.active ||
+ ! this.active ||
// Or this event is to be ignored.
- this.get('ignore') ||
+ this.get( 'ignore' ) ||
// Or this event has already been prevented.
e.isDefaultPrevented()
) {
@@ -1006,13 +1006,13 @@
}
// Validate form.
- var valid = acf.validateForm({
+ var valid = acf.validateForm( {
form: $el,
- event: this.get('originalEvent'),
- });
+ event: this.get( 'originalEvent' ),
+ } );
// If not valid, stop event to prevent form submit.
- if (!valid) {
+ if ( ! valid ) {
e.preventDefault();
}
},
@@ -1030,21 +1030,21 @@
*/
allowSubmit: function () {
// Reset "ignore" state.
- this.set('ignore', false);
+ this.set( 'ignore', false );
// Reset "originalEvent" object.
- this.set('originalEvent', false);
+ this.set( 'originalEvent', false );
// Return true
return true;
},
- });
+ } );
- var gutenbergValidation = new acf.Model({
+ var gutenbergValidation = new acf.Model( {
wait: 'prepare',
initialize: function () {
// Bail early if not Gutenberg.
- if (!acf.isGutenberg()) {
+ if ( ! acf.isGutenberg() ) {
return;
}
@@ -1053,9 +1053,9 @@
},
customizeEditor: function () {
// Extract vars.
- var editor = wp.data.dispatch('core/editor');
- var editorSelect = wp.data.select('core/editor');
- var notices = wp.data.dispatch('core/notices');
+ var editor = wp.data.dispatch( 'core/editor' );
+ var editorSelect = wp.data.select( 'core/editor' );
+ var notices = wp.data.dispatch( 'core/notices' );
// Backup original method.
var savePost = editor.savePost;
@@ -1065,16 +1065,17 @@
// b) Remember last non "publish" status used for restoring after validation fail.
var useValidation = false;
var lastPostStatus = '';
- wp.data.subscribe(function () {
- var postStatus = editorSelect.getEditedPostAttribute('status');
+ wp.data.subscribe( function () {
+ var postStatus =
+ editorSelect.getEditedPostAttribute( 'status' );
useValidation =
postStatus === 'publish' || postStatus === 'future';
lastPostStatus =
postStatus !== 'publish' ? postStatus : lastPostStatus;
- });
+ } );
// Create validation version.
- editor.savePost = function (options) {
+ editor.savePost = function ( options ) {
options = options || {};
// Backup vars.
@@ -1082,21 +1083,21 @@
var _args = arguments;
// Perform validation within a Promise.
- return new Promise(function (resolve, reject) {
+ return new Promise( function ( resolve, reject ) {
// Bail early if is autosave or preview.
- if (options.isAutosave || options.isPreview) {
- return resolve('Validation ignored (autosave).');
+ if ( options.isAutosave || options.isPreview ) {
+ return resolve( 'Validation ignored (autosave).' );
}
// Bail early if validation is not needed.
- if (!useValidation) {
- return resolve('Validation ignored (draft).');
+ if ( ! useValidation ) {
+ return resolve( 'Validation ignored (draft).' );
}
// Check if we've currently got an ACF block selected which is failing validation, but might not be presented yet.
- if ('undefined' !== typeof acf.blockInstances) {
+ if ( 'undefined' !== typeof acf.blockInstances ) {
const selectedBlockId = wp.data
- .select('core/block-editor')
+ .select( 'core/block-editor' )
.getSelectedBlockClientId();
if (
@@ -1104,9 +1105,9 @@
selectedBlockId in acf.blockInstances
) {
const acfBlockState =
- acf.blockInstances[selectedBlockId];
+ acf.blockInstances[ selectedBlockId ];
- if (acfBlockState.validation_errors) {
+ if ( acfBlockState.validation_errors ) {
// Deselect the block to show the error and lock the save.
acf.debug(
'Rejecting save because the block editor has a invalid ACF block selected.'
@@ -1122,13 +1123,13 @@
);
wp.data
- .dispatch('core/editor')
+ .dispatch( 'core/editor' )
.lockPostSaving(
'acf/block/' + selectedBlockId
);
wp.data
- .dispatch('core/block-editor')
- .selectBlock(false);
+ .dispatch( 'core/block-editor' )
+ .selectBlock( false );
return reject(
'ACF Validation failed for selected block.'
@@ -1138,31 +1139,31 @@
}
// Recursive function to check all blocks (including nested innerBlocks) for ACF validation errors
- function checkBlocksForErrors(blocks) {
- return new Promise(function (resolve) {
+ function checkBlocksForErrors( blocks ) {
+ return new Promise( function ( resolve ) {
// Iterate through each block
- blocks.forEach((block) => {
+ blocks.forEach( ( block ) => {
// If this block has nested blocks, recursively check them
- if (block.innerBlocks.length > 0) {
+ if ( block.innerBlocks.length > 0 ) {
checkBlocksForErrors(
block.innerBlocks
- ).then((hasError) => {
- if (hasError) {
- return resolve(true);
+ ).then( ( hasError ) => {
+ if ( hasError ) {
+ return resolve( true );
}
- });
+ } );
}
// Check if this block has an ACF error attribute
- if (block.attributes.hasAcfError) {
+ if ( block.attributes.hasAcfError ) {
// Check if the publish panel is open and close it if so
const publishPanel =
document.getElementsByClassName(
'editor-post-publish-panel'
- )[0];
- if (publishPanel) {
+ )[ 0 ];
+ if ( publishPanel ) {
wp.data
- .dispatch('core/editor')
+ .dispatch( 'core/editor' )
.togglePublishSidebar();
}
@@ -1171,16 +1172,19 @@
// Select the block with the error in the editor
wp.data
- .dispatch('core/block-editor')
- .selectBlock(blockClientId);
+ .dispatch( 'core/block-editor' )
+ .selectBlock( blockClientId );
// Dispatch a custom event to notify about the block with validation error
document.dispatchEvent(
- new CustomEvent('acf/block/has-error', {
- acfBlocksWithValidationErrors: [
- block,
- ],
- })
+ new CustomEvent(
+ 'acf/block/has-error',
+ {
+ acfBlocksWithValidationErrors: [
+ block,
+ ],
+ }
+ )
);
// Log debug message
@@ -1189,21 +1193,21 @@
);
// Resolve with true (error found)
- return resolve(true);
+ return resolve( true );
}
- });
+ } );
// No errors found, resolve with false
- return resolve(false);
- });
+ return resolve( false );
+ } );
}
// Call the function with all blocks from the editor
checkBlocksForErrors(
- wp.data.select('core/block-editor').getBlocks()
- ).then((hasError) => {
+ wp.data.select( 'core/block-editor' ).getBlocks()
+ ).then( ( hasError ) => {
// If errors were found
- if (hasError) {
+ if ( hasError ) {
// Display an error notice
noticesDispatch.createErrorNotice(
acf.__(
@@ -1216,22 +1220,22 @@
);
// Reject the save operation
- return reject('ACF Block Validation failed');
+ return reject( 'ACF Block Validation failed' );
}
- });
+ } );
// Validate the editor form.
- var valid = acf.validateForm({
- form: $('#wpbody-content > .block-editor'),
+ var valid = acf.validateForm( {
+ form: $( '#wpbody-content > .block-editor' ),
reset: true,
- complete: function ($form, validator) {
+ complete: function ( $form, validator ) {
// Always unlock the form after AJAX.
- editor.unlockPostSaving('acf');
+ editor.unlockPostSaving( 'acf' );
},
- failure: function ($form, validator) {
+ failure: function ( $form, validator ) {
// Get validation error and append to Gutenberg notices.
- var notice = validator.get('notice');
- var action = validator.get('action');
+ var notice = validator.get( 'notice' );
+ var action = validator.get( 'action' );
if (
action &&
'object' === typeof action &&
@@ -1239,7 +1243,7 @@
action.url
) {
notices.createErrorNotice(
- notice.get('text', {
+ notice.get( 'text', {
id: 'acf-validation',
isDismissible: true,
actions: [
@@ -1248,50 +1252,53 @@
url: action.url,
},
],
- })
+ } )
);
} else {
- notices.createErrorNotice(notice.get('text'), {
- id: 'acf-validation',
- isDismissible: true,
- });
+ notices.createErrorNotice(
+ notice.get( 'text' ),
+ {
+ id: 'acf-validation',
+ isDismissible: true,
+ }
+ );
}
notice.remove();
// Restore last non "publish" status.
- if (lastPostStatus) {
- editor.editPost({
+ if ( lastPostStatus ) {
+ editor.editPost( {
status: lastPostStatus,
- });
+ } );
}
// Reject promise and prevent savePost().
- reject('Validation failed.');
+ reject( 'Validation failed.' );
},
success: function () {
- notices.removeNotice('acf-validation');
+ notices.removeNotice( 'acf-validation' );
// Resolve promise and allow savePost().
- resolve('Validation success.');
+ resolve( 'Validation success.' );
},
- });
+ } );
// Resolve promise and allow savePost() if no validation is needed.
- if (valid) {
- resolve('Validation bypassed.');
+ if ( valid ) {
+ resolve( 'Validation bypassed.' );
// Otherwise, lock the form and wait for AJAX response.
} else {
- editor.lockPostSaving('acf');
+ editor.lockPostSaving( 'acf' );
}
- }).then(
+ } ).then(
function () {
- return savePost.apply(_this, _args);
+ return savePost.apply( _this, _args );
},
- (err) => {
+ ( err ) => {
// Nothing to do here, user is alerted of validation issues.
}
);
};
},
- });
-})(jQuery);
+ } );
+} )( jQuery );
diff --git a/assets/src/js/_browse-fields-modal.js b/assets/src/js/_browse-fields-modal.js
index e66c7eac..c7ac4df1 100644
--- a/assets/src/js/_browse-fields-modal.js
+++ b/assets/src/js/_browse-fields-modal.js
@@ -2,7 +2,7 @@
* Extends acf.models.Modal to create the field browser.
*/
-(function ($, undefined, acf) {
+( function ( $, undefined, acf ) {
const browseFieldsModal = {
data: {
openedBy: null,
@@ -35,195 +35,202 @@
'click .acf-browse-popular-fields': 'onClickBrowsePopular',
},
- setup: function (props) {
- $.extend(this.data, props);
- this.$el = $(this.tmpl());
+ setup: function ( props ) {
+ $.extend( this.data, props );
+ this.$el = $( this.tmpl() );
this.render();
},
initialize: function () {
this.open();
- this.lockFocusToModal(true);
- this.$el.find('.acf-modal-title').trigger('focus');
- acf.doAction('show', this.$el);
+ this.lockFocusToModal( true );
+ this.$el.find( '.acf-modal-title' ).trigger( 'focus' );
+ acf.doAction( 'show', this.$el );
},
tmpl: function () {
- return $('#tmpl-acf-browse-fields-modal').html();
+ return $( '#tmpl-acf-browse-fields-modal' ).html();
},
- getFieldTypes: function (category, search) {
+ getFieldTypes: function ( category, search ) {
let fieldTypes;
- if (!acf.get('is_pro')) {
+ if ( ! acf.get( 'is_pro' ) ) {
// Add in the pro fields.
- fieldTypes = Object.values({
- ...acf.get('fieldTypes'),
- ...acf.get('PROFieldTypes'),
- });
+ fieldTypes = Object.values( {
+ ...acf.get( 'fieldTypes' ),
+ ...acf.get( 'PROFieldTypes' ),
+ } );
} else {
- fieldTypes = Object.values(acf.get('fieldTypes'));
+ fieldTypes = Object.values( acf.get( 'fieldTypes' ) );
}
- if (category) {
- if ('popular' === category) {
- return fieldTypes.filter((fieldType) =>
- this.get('popularFieldTypes').includes(fieldType.name)
+ if ( category ) {
+ if ( 'popular' === category ) {
+ return fieldTypes.filter( ( fieldType ) =>
+ this.get( 'popularFieldTypes' ).includes(
+ fieldType.name
+ )
);
}
- if ('pro' === category) {
- return fieldTypes.filter((fieldType) => fieldType.pro);
+ if ( 'pro' === category ) {
+ return fieldTypes.filter( ( fieldType ) => fieldType.pro );
}
fieldTypes = fieldTypes.filter(
- (fieldType) => fieldType.category === category
+ ( fieldType ) => fieldType.category === category
);
}
- if (search) {
- fieldTypes = fieldTypes.filter((fieldType) => {
+ if ( search ) {
+ fieldTypes = fieldTypes.filter( ( fieldType ) => {
const label = fieldType.label.toLowerCase();
- const labelParts = label.split(' ');
+ const labelParts = label.split( ' ' );
let match = false;
- if (label.startsWith(search.toLowerCase())) {
+ if ( label.startsWith( search.toLowerCase() ) ) {
match = true;
- } else if (labelParts.length > 1) {
- labelParts.forEach((part) => {
- if (part.startsWith(search.toLowerCase())) {
+ } else if ( labelParts.length > 1 ) {
+ labelParts.forEach( ( part ) => {
+ if ( part.startsWith( search.toLowerCase() ) ) {
match = true;
}
- });
+ } );
}
return match;
- });
+ } );
}
return fieldTypes;
},
render: function () {
- acf.doAction('append', this.$el);
+ acf.doAction( 'append', this.$el );
- const $tabs = this.$el.find('.acf-field-types-tab');
+ const $tabs = this.$el.find( '.acf-field-types-tab' );
const self = this;
- $tabs.each(function () {
- const category = $(this).data('category');
- const fieldTypes = self.getFieldTypes(category);
- fieldTypes.forEach((fieldType) => {
- $(this).append(self.getFieldTypeHTML(fieldType));
- });
- });
+ $tabs.each( function () {
+ const category = $( this ).data( 'category' );
+ const fieldTypes = self.getFieldTypes( category );
+ fieldTypes.forEach( ( fieldType ) => {
+ $( this ).append( self.getFieldTypeHTML( fieldType ) );
+ } );
+ } );
this.initializeFieldLabel();
this.initializeFieldType();
this.onChangeFieldType();
},
- getFieldTypeHTML: function (fieldType) {
- const iconName = fieldType.name.replaceAll('_', '-');
+ getFieldTypeHTML: function ( fieldType ) {
+ const iconName = fieldType.name.replaceAll( '_', '-' );
return `
-
-
- ${fieldType.label}
+
+
+ ${ fieldType.label }
`;
},
- decodeFieldTypeURL: function (url) {
- if (typeof url != 'string') return url;
- return url.replaceAll('&', '&');
+ decodeFieldTypeURL: function ( url ) {
+ if ( typeof url != 'string' ) return url;
+ return url.replaceAll( '&', '&' );
},
- renderFieldTypeDesc: function (fieldType) {
+ renderFieldTypeDesc: function ( fieldType ) {
const fieldTypeInfo =
this.getFieldTypes().filter(
- (fieldTypeFilter) => fieldTypeFilter.name === fieldType
- )[0] || {};
+ ( fieldTypeFilter ) => fieldTypeFilter.name === fieldType
+ )[ 0 ] || {};
- const args = acf.parseArgs(fieldTypeInfo, {
+ const args = acf.parseArgs( fieldTypeInfo, {
label: '',
description: '',
doc_url: false,
tutorial_url: false,
preview_image: false,
pro: false,
- });
+ } );
- this.$el.find('.field-type-name').text(args.label);
- this.$el.find('.field-type-desc').text(args.description);
+ this.$el.find( '.field-type-name' ).text( args.label );
+ this.$el.find( '.field-type-desc' ).text( args.description );
- if (args.doc_url) {
+ if ( args.doc_url ) {
this.$el
- .find('.field-type-doc')
- .attr('href', this.decodeFieldTypeURL(args.doc_url))
+ .find( '.field-type-doc' )
+ .attr( 'href', this.decodeFieldTypeURL( args.doc_url ) )
.show();
} else {
- this.$el.find('.field-type-doc').hide();
+ this.$el.find( '.field-type-doc' ).hide();
}
- if (args.tutorial_url) {
+ if ( args.tutorial_url ) {
this.$el
- .find('.field-type-tutorial')
- .attr('href', this.decodeFieldTypeURL(args.tutorial_url))
+ .find( '.field-type-tutorial' )
+ .attr(
+ 'href',
+ this.decodeFieldTypeURL( args.tutorial_url )
+ )
.parent()
.show();
} else {
- this.$el.find('.field-type-tutorial').parent().hide();
+ this.$el.find( '.field-type-tutorial' ).parent().hide();
}
- if (args.preview_image) {
+ if ( args.preview_image ) {
this.$el
- .find('.field-type-image')
- .attr('src', args.preview_image)
+ .find( '.field-type-image' )
+ .attr( 'src', args.preview_image )
.show();
} else {
- this.$el.find('.field-type-image').hide();
+ this.$el.find( '.field-type-image' ).hide();
}
const isPro = true;
const isActive = true;
- const $upgateToProButton = this.$el.find('.acf-btn-pro');
+ const $upgateToProButton = this.$el.find( '.acf-btn-pro' );
const $upgradeToUnlockButton = this.$el.find(
'.field-type-upgrade-to-unlock'
);
- if (args.pro && (!isPro || !isActive)) {
+ if ( args.pro && ( ! isPro || ! isActive ) ) {
$upgateToProButton.show();
$upgateToProButton.attr(
'href',
- $upgateToProButton.data('urlBase') + fieldType
+ $upgateToProButton.data( 'urlBase' ) + fieldType
);
$upgradeToUnlockButton.show();
$upgradeToUnlockButton.attr(
'href',
- $upgradeToUnlockButton.data('urlBase') + fieldType
+ $upgradeToUnlockButton.data( 'urlBase' ) + fieldType
);
- this.$el.find('.acf-insert-field-label').attr('disabled', true);
- this.$el.find('.acf-select-field').hide();
+ this.$el
+ .find( '.acf-insert-field-label' )
+ .attr( 'disabled', true );
+ this.$el.find( '.acf-select-field' ).hide();
} else {
$upgateToProButton.hide();
$upgradeToUnlockButton.hide();
this.$el
- .find('.acf-insert-field-label')
- .attr('disabled', false);
- this.$el.find('.acf-select-field').show();
+ .find( '.acf-insert-field-label' )
+ .attr( 'disabled', false );
+ this.$el.find( '.acf-select-field' ).show();
}
},
initializeFieldType: function () {
- const fieldObject = this.get('openedBy');
+ const fieldObject = this.get( 'openedBy' );
const fieldType = fieldObject?.data?.type;
// Select default field type
- if (fieldType) {
- this.set('currentFieldType', fieldType);
+ if ( fieldType ) {
+ this.set( 'currentFieldType', fieldType );
} else {
- this.set('currentFieldType', 'text');
+ this.set( 'currentFieldType', 'text' );
}
// Select first tab with selected field type
@@ -231,136 +238,143 @@
// Else select first tab the type belongs
const fieldTypes = this.getFieldTypes();
const isFieldTypePopular =
- this.get('popularFieldTypes').includes(fieldType);
+ this.get( 'popularFieldTypes' ).includes( fieldType );
let category = '';
- if (isFieldTypePopular) {
+ if ( isFieldTypePopular ) {
category = 'popular';
} else {
- const selectedFieldType = fieldTypes.find((x) => {
+ const selectedFieldType = fieldTypes.find( ( x ) => {
return x.name === fieldType;
- });
+ } );
category = selectedFieldType.category;
}
const uppercaseCategory =
- category[0].toUpperCase() + category.slice(1);
- const searchTabElement = `.acf-modal-content .acf-tab-wrap a:contains('${uppercaseCategory}')`;
- setTimeout(() => {
- $(searchTabElement).click();
- }, 0);
+ category[ 0 ].toUpperCase() + category.slice( 1 );
+ const searchTabElement = `.acf-modal-content .acf-tab-wrap a:contains('${ uppercaseCategory }')`;
+ setTimeout( () => {
+ $( searchTabElement ).click();
+ }, 0 );
},
initializeFieldLabel: function () {
- const fieldObject = this.get('openedBy');
+ const fieldObject = this.get( 'openedBy' );
const labelText = fieldObject.$fieldLabel().val();
- const $fieldLabel = this.$el.find('.acf-insert-field-label');
- if (labelText) {
- $fieldLabel.val(labelText);
+ const $fieldLabel = this.$el.find( '.acf-insert-field-label' );
+ if ( labelText ) {
+ $fieldLabel.val( labelText );
} else {
- $fieldLabel.val('');
+ $fieldLabel.val( '' );
}
},
updateFieldObjectFieldLabel: function () {
- const label = this.$el.find('.acf-insert-field-label').val();
- const fieldObject = this.get('openedBy');
- fieldObject.$fieldLabel().val(label);
- fieldObject.$fieldLabel().trigger('blur');
+ const label = this.$el.find( '.acf-insert-field-label' ).val();
+ const fieldObject = this.get( 'openedBy' );
+ fieldObject.$fieldLabel().val( label );
+ fieldObject.$fieldLabel().trigger( 'blur' );
},
onChangeFieldType: function () {
- const fieldType = this.get('currentFieldType');
+ const fieldType = this.get( 'currentFieldType' );
- this.$el.find('.selected').removeClass('selected');
+ this.$el.find( '.selected' ).removeClass( 'selected' );
this.$el
- .find('.acf-field-type[data-field-type="' + fieldType + '"]')
- .addClass('selected');
+ .find( '.acf-field-type[data-field-type="' + fieldType + '"]' )
+ .addClass( 'selected' );
- this.renderFieldTypeDesc(fieldType);
+ this.renderFieldTypeDesc( fieldType );
},
- onSearchFieldTypes: function (e) {
- const $modal = this.$el.find('.acf-browse-fields-modal');
- const inputVal = this.$el.find('.acf-search-field-types').val();
+ onSearchFieldTypes: function ( e ) {
+ const $modal = this.$el.find( '.acf-browse-fields-modal' );
+ const inputVal = this.$el.find( '.acf-search-field-types' ).val();
const self = this;
let searchString,
resultsHtml = '';
let matches = [];
- if ('string' === typeof inputVal) {
+ if ( 'string' === typeof inputVal ) {
searchString = inputVal.trim();
- matches = this.getFieldTypes(false, searchString);
+ matches = this.getFieldTypes( false, searchString );
}
- if (searchString.length && matches.length) {
- $modal.addClass('is-searching');
+ if ( searchString.length && matches.length ) {
+ $modal.addClass( 'is-searching' );
} else {
- $modal.removeClass('is-searching');
+ $modal.removeClass( 'is-searching' );
}
- if (!matches.length) {
- $modal.addClass('no-results-found');
- this.$el.find('.acf-invalid-search-term').text(searchString);
+ if ( ! matches.length ) {
+ $modal.addClass( 'no-results-found' );
+ this.$el
+ .find( '.acf-invalid-search-term' )
+ .text( searchString );
return;
} else {
- $modal.removeClass('no-results-found');
+ $modal.removeClass( 'no-results-found' );
}
- matches.forEach((fieldType) => {
- resultsHtml = resultsHtml + self.getFieldTypeHTML(fieldType);
- });
+ matches.forEach( ( fieldType ) => {
+ resultsHtml = resultsHtml + self.getFieldTypeHTML( fieldType );
+ } );
- $('.acf-field-type-search-results').html(resultsHtml);
+ $( '.acf-field-type-search-results' ).html( resultsHtml );
- this.set('currentFieldType', matches[0].name);
+ this.set( 'currentFieldType', matches[ 0 ].name );
this.onChangeFieldType();
},
onClickBrowsePopular: function () {
- this.$el.find('.acf-search-field-types').val('').trigger('input');
- this.$el.find('.acf-tab-wrap a').first().trigger('click');
+ this.$el
+ .find( '.acf-search-field-types' )
+ .val( '' )
+ .trigger( 'input' );
+ this.$el.find( '.acf-tab-wrap a' ).first().trigger( 'click' );
},
- onClickSelectField: function (e) {
- const fieldObject = this.get('openedBy');
+ onClickSelectField: function ( e ) {
+ const fieldObject = this.get( 'openedBy' );
- fieldObject.$fieldTypeSelect().val(this.get('currentFieldType'));
- fieldObject.$fieldTypeSelect().trigger('change');
+ fieldObject
+ .$fieldTypeSelect()
+ .val( this.get( 'currentFieldType' ) );
+ fieldObject.$fieldTypeSelect().trigger( 'change' );
this.updateFieldObjectFieldLabel();
this.close();
},
- onClickFieldType: function (e) {
- const $fieldType = $(e.currentTarget);
- this.set('currentFieldType', $fieldType.data('field-type'));
+ onClickFieldType: function ( e ) {
+ const $fieldType = $( e.currentTarget );
+ this.set( 'currentFieldType', $fieldType.data( 'field-type' ) );
},
onClickClose: function () {
this.close();
},
- onPressEscapeClose: function (e) {
- if (e.key === 'Escape') {
+ onPressEscapeClose: function ( e ) {
+ if ( e.key === 'Escape' ) {
this.close();
}
},
close: function () {
- this.lockFocusToModal(false);
+ this.lockFocusToModal( false );
this.returnFocusToOrigin();
this.remove();
},
focus: function () {
- this.$el.find('button').first().trigger('focus');
+ this.$el.find( 'button' ).first().trigger( 'focus' );
},
};
- acf.models.browseFieldsModal = acf.models.Modal.extend(browseFieldsModal);
- acf.newBrowseFieldsModal = (props) =>
- new acf.models.browseFieldsModal(props);
-})(window.jQuery, undefined, window.acf);
+ acf.models.browseFieldsModal = acf.models.Modal.extend( browseFieldsModal );
+ acf.newBrowseFieldsModal = ( props ) =>
+ new acf.models.browseFieldsModal( props );
+} )( window.jQuery, undefined, window.acf );
diff --git a/assets/src/js/_field-group-field.js b/assets/src/js/_field-group-field.js
index be5edb42..0c04f54f 100644
--- a/assets/src/js/_field-group-field.js
+++ b/assets/src/js/_field-group-field.js
@@ -1,5 +1,5 @@
-(function ($, undefined) {
- acf.FieldObject = acf.Model.extend({
+( function ( $, undefined ) {
+ acf.FieldObject = acf.Model.extend( {
// class used to avoid nested event triggers
eventScope: '.acf-field-object',
@@ -52,165 +52,165 @@
//menu_order: 0
},
- setup: function ($field) {
+ setup: function ( $field ) {
// set $el
this.$el = $field;
// inherit $field data (id, key, type)
- this.inherit($field);
+ this.inherit( $field );
// load additional props
// - this won't trigger 'changed'
- this.prop('ID');
- this.prop('parent');
- this.prop('menu_order');
+ this.prop( 'ID' );
+ this.prop( 'parent' );
+ this.prop( 'menu_order' );
},
- $input: function (name) {
- return $('#' + this.getInputId() + '-' + name);
+ $input: function ( name ) {
+ return $( '#' + this.getInputId() + '-' + name );
},
$meta: function () {
- return this.$('.meta:first');
+ return this.$( '.meta:first' );
},
$handle: function () {
- return this.$('.handle:first');
+ return this.$( '.handle:first' );
},
$settings: function () {
- return this.$('.settings:first');
+ return this.$( '.settings:first' );
},
- $setting: function (name) {
+ $setting: function ( name ) {
return this.$(
'.acf-field-settings:first .acf-field-setting-' + name
);
},
$fieldTypeSelect: function () {
- return this.$('.field-type');
+ return this.$( '.field-type' );
},
$fieldLabel: function () {
- return this.$('.field-label');
+ return this.$( '.field-label' );
},
getParent: function () {
- return acf.getFieldObjects({ child: this.$el, limit: 1 }).pop();
+ return acf.getFieldObjects( { child: this.$el, limit: 1 } ).pop();
},
getParents: function () {
- return acf.getFieldObjects({ child: this.$el });
+ return acf.getFieldObjects( { child: this.$el } );
},
getFields: function () {
- return acf.getFieldObjects({ parent: this.$el });
+ return acf.getFieldObjects( { parent: this.$el } );
},
getInputName: function () {
- return 'acf_fields[' + this.get('id') + ']';
+ return 'acf_fields[' + this.get( 'id' ) + ']';
},
getInputId: function () {
- return 'acf_fields-' + this.get('id');
+ return 'acf_fields-' + this.get( 'id' );
},
- newInput: function (name, value) {
+ newInput: function ( name, value ) {
// vars
var inputId = this.getInputId();
var inputName = this.getInputName();
// append name
- if (name) {
+ if ( name ) {
inputId += '-' + name;
inputName += '[' + name + ']';
}
// create input (avoid HTML + JSON value issues)
- var $input = $(' ').attr({
+ var $input = $( ' ' ).attr( {
id: inputId,
name: inputName,
value: value,
- });
- this.$('> .meta').append($input);
+ } );
+ this.$( '> .meta' ).append( $input );
// return
return $input;
},
- getProp: function (name) {
+ getProp: function ( name ) {
// check data
- if (this.has(name)) {
- return this.get(name);
+ if ( this.has( name ) ) {
+ return this.get( name );
}
// get input value
- var $input = this.$input(name);
+ var $input = this.$input( name );
var value = $input.length ? $input.val() : null;
// set data silently (cache)
- this.set(name, value, true);
+ this.set( name, value, true );
// return
return value;
},
- setProp: function (name, value) {
+ setProp: function ( name, value ) {
// get input
- var $input = this.$input(name);
+ var $input = this.$input( name );
var prevVal = $input.val();
// create if new
- if (!$input.length) {
- $input = this.newInput(name, value);
+ if ( ! $input.length ) {
+ $input = this.newInput( name, value );
}
// remove
- if (value === null) {
+ if ( value === null ) {
$input.remove();
// update
} else {
- $input.val(value);
+ $input.val( value );
}
//console.log('setProp', name, value, this);
// set data silently (cache)
- if (!this.has(name)) {
+ if ( ! this.has( name ) ) {
//console.log('setting silently');
- this.set(name, value, true);
+ this.set( name, value, true );
// set data allowing 'change' event to fire
} else {
//console.log('setting loudly!');
- this.set(name, value);
+ this.set( name, value );
}
// return
return this;
},
- prop: function (name, value) {
- if (value !== undefined) {
- return this.setProp(name, value);
+ prop: function ( name, value ) {
+ if ( value !== undefined ) {
+ return this.setProp( name, value );
} else {
- return this.getProp(name);
+ return this.getProp( name );
}
},
- props: function (props) {
- Object.keys(props).map(function (key) {
- this.setProp(key, props[key]);
- }, this);
+ props: function ( props ) {
+ Object.keys( props ).map( function ( key ) {
+ this.setProp( key, props[ key ] );
+ }, this );
},
getLabel: function () {
// get label with empty default
- var label = this.prop('label');
- if (label === '') {
- label = acf.__('(no label)');
+ var label = this.prop( 'label' );
+ if ( label === '' ) {
+ label = acf.__( '(no label)' );
}
// return
@@ -218,29 +218,29 @@
},
getName: function () {
- return this.prop('name');
+ return this.prop( 'name' );
},
getType: function () {
- return this.prop('type');
+ return this.prop( 'type' );
},
getTypeLabel: function () {
- var type = this.prop('type');
- var types = acf.get('fieldTypes');
- return types[type] ? types[type].label : type;
+ var type = this.prop( 'type' );
+ var types = acf.get( 'fieldTypes' );
+ return types[ type ] ? types[ type ].label : type;
},
getKey: function () {
- return this.prop('key');
+ return this.prop( 'key' );
},
initialize: function () {
this.checkCopyable();
},
- makeCopyable: function (text) {
- if (!navigator.clipboard)
+ makeCopyable: function ( text ) {
+ if ( ! navigator.clipboard )
return (
'' +
text +
@@ -250,81 +250,81 @@
},
checkCopyable: function () {
- if (!navigator.clipboard) {
- this.$el.find('.copyable').addClass('copy-unsupported');
+ if ( ! navigator.clipboard ) {
+ this.$el.find( '.copyable' ).addClass( 'copy-unsupported' );
}
},
initializeFieldTypeSelect2: function () {
- if (this.fieldTypeSelect2) return;
+ if ( this.fieldTypeSelect2 ) return;
// Support disabling via filter.
- if (this.$fieldTypeSelect().hasClass('disable-select2')) return;
+ if ( this.$fieldTypeSelect().hasClass( 'disable-select2' ) ) return;
// Check for a full modern version of select2, bail loading if not found with a console warning.
try {
- $.fn.select2.amd.require('select2/compat/dropdownCss');
- } catch (err) {
+ $.fn.select2.amd.require( 'select2/compat/dropdownCss' );
+ } catch ( err ) {
console.warn(
'ACF was not able to load the full version of select2 due to a conflicting version provided by another plugin or theme taking precedence. Select2 fields may not work as expected.'
);
return;
}
- this.fieldTypeSelect2 = acf.newSelect2(this.$fieldTypeSelect(), {
+ this.fieldTypeSelect2 = acf.newSelect2( this.$fieldTypeSelect(), {
field: false,
ajax: false,
multiple: false,
allowNull: false,
suppressFilters: true,
dropdownCssClass: 'field-type-select-results',
- templateResult: function (selection) {
+ templateResult: function ( selection ) {
if (
selection.loading ||
- (selection.element &&
- selection.element.nodeName === 'OPTGROUP')
+ ( selection.element &&
+ selection.element.nodeName === 'OPTGROUP' )
) {
var $selection = $(
' '
);
- $selection.html(acf.strEscape(selection.text));
+ $selection.html( acf.strEscape( selection.text ) );
} else {
var $selection = $(
'' +
- acf.strEscape(selection.text) +
+ acf.strEscape( selection.text ) +
' '
);
}
- $selection.data('element', selection.element);
+ $selection.data( 'element', selection.element );
return $selection;
},
- templateSelection: function (selection) {
+ templateSelection: function ( selection ) {
var $selection = $(
'' +
- acf.strEscape(selection.text) +
+ acf.strEscape( selection.text ) +
' '
);
- $selection.data('element', selection.element);
+ $selection.data( 'element', selection.element );
return $selection;
},
- });
+ } );
- this.fieldTypeSelect2.on('select2:open', function () {
+ this.fieldTypeSelect2.on( 'select2:open', function () {
$(
'.field-type-select-results input.select2-search__field'
- ).attr('placeholder', acf.__('Type to search...'));
- });
+ ).attr( 'placeholder', acf.__( 'Type to search...' ) );
+ } );
- this.fieldTypeSelect2.on('change', function (e) {
- $(e.target)
- .parents('ul:first')
- .find('button.browse-fields')
- .prop('disabled', true);
- });
+ this.fieldTypeSelect2.on( 'change', function ( e ) {
+ $( e.target )
+ .parents( 'ul:first' )
+ .find( 'button.browse-fields' )
+ .prop( 'disabled', true );
+ } );
// When typing happens on the li element above the select2.
this.fieldTypeSelect2.$el
@@ -338,128 +338,132 @@
addProFields: function () {
// Don't run if on pro.
- if (acf.get('is_pro')) {
+ if ( acf.get( 'is_pro' ) ) {
return;
}
// Make sure we haven't appended these fields before.
var $fieldTypeSelect = this.$fieldTypeSelect();
- if ($fieldTypeSelect.hasClass('acf-free-field-type')) return;
+ if ( $fieldTypeSelect.hasClass( 'acf-free-field-type' ) ) return;
// Loop over each pro field type and append it to the select.
- const PROFieldTypes = acf.get('PROFieldTypes');
- if (typeof PROFieldTypes !== 'object') return;
+ const PROFieldTypes = acf.get( 'PROFieldTypes' );
+ if ( typeof PROFieldTypes !== 'object' ) return;
const $layoutGroup = $fieldTypeSelect
- .find('optgroup option[value="group"]')
+ .find( 'optgroup option[value="group"]' )
.parent();
const $contentGroup = $fieldTypeSelect
- .find('optgroup option[value="image"]')
+ .find( 'optgroup option[value="image"]' )
.parent();
- for (const [name, field] of Object.entries(PROFieldTypes)) {
+ for ( const [ name, field ] of Object.entries( PROFieldTypes ) ) {
const $useGroup =
field.category === 'content' ? $contentGroup : $layoutGroup;
- const $existing = $useGroup.children('[value="' + name + '"]');
- const label = `${acf.strEscape(
+ const $existing = $useGroup.children(
+ '[value="' + name + '"]'
+ );
+ const label = `${ acf.strEscape(
field.label
- )} (${acf.strEscape(acf.__('PRO Only'))})`;
+ ) } (${ acf.strEscape( acf.__( 'PRO Only' ) ) })`;
- if ($existing.length) {
+ if ( $existing.length ) {
// Already added by pro, update existing option.
- $existing.text(label);
+ $existing.text( label );
// Don't disable if already selected (prevents re-save from overriding field type).
- if ($fieldTypeSelect.val() !== name) {
- $existing.attr('disabled', 'disabled');
+ if ( $fieldTypeSelect.val() !== name ) {
+ $existing.attr( 'disabled', 'disabled' );
}
} else {
// Append new disabled option.
$useGroup.append(
- `${label} `
+ `${ label } `
);
}
}
- $fieldTypeSelect.addClass('acf-free-field-type');
+ $fieldTypeSelect.addClass( 'acf-free-field-type' );
},
render: function () {
// vars
- var $handle = this.$('.handle:first');
- var menu_order = this.prop('menu_order');
- var label = acf.strEscape(this.getLabel());
- var name = this.prop('name');
+ var $handle = this.$( '.handle:first' );
+ var menu_order = this.prop( 'menu_order' );
+ var label = acf.strEscape( this.getLabel() );
+ var name = this.prop( 'name' );
var type = this.getTypeLabel();
- var key = this.prop('key');
- var required = this.$input('required').prop('checked');
+ var key = this.prop( 'key' );
+ var required = this.$input( 'required' ).prop( 'checked' );
// update menu order
- $handle.find('.acf-icon').html(parseInt(menu_order) + 1);
+ $handle.find( '.acf-icon' ).html( parseInt( menu_order ) + 1 );
// update required
- if (required) {
+ if ( required ) {
label += ' * ';
}
// update label
- $handle.find('.li-field-label strong a').html(label);
+ $handle.find( '.li-field-label strong a' ).html( label );
// update name
$handle
- .find('.li-field-name')
- .html(this.makeCopyable(acf.strSanitize(name)));
+ .find( '.li-field-name' )
+ .html( this.makeCopyable( acf.strSanitize( name ) ) );
// update type
- const iconName = acf.strSlugify(this.getType());
- $handle.find('.field-type-label').text(' ' + type);
+ const iconName = acf.strSlugify( this.getType() );
+ $handle.find( '.field-type-label' ).text( ' ' + type );
$handle
- .find('.field-type-icon')
+ .find( '.field-type-icon' )
.removeClass()
- .addClass('field-type-icon field-type-icon-' + iconName);
+ .addClass( 'field-type-icon field-type-icon-' + iconName );
// update key
- $handle.find('.li-field-key').html(this.makeCopyable(key));
+ $handle.find( '.li-field-key' ).html( this.makeCopyable( key ) );
// action for 3rd party customization
- acf.doAction('render_field_object', this);
+ acf.doAction( 'render_field_object', this );
},
refresh: function () {
- acf.doAction('refresh_field_object', this);
+ acf.doAction( 'refresh_field_object', this );
},
isOpen: function () {
- return this.$el.hasClass('open');
+ return this.$el.hasClass( 'open' );
},
- onClickCopy: function (e) {
+ onClickCopy: function ( e ) {
e.stopPropagation();
- if (!navigator.clipboard || $(e.target).is('input')) return;
+ if ( ! navigator.clipboard || $( e.target ).is( 'input' ) ) return;
// Find the value to copy depending on input or text elements.
let copyValue;
- if ($(e.target).hasClass('acf-input-wrap')) {
- copyValue = $(e.target).find('input').first().val();
+ if ( $( e.target ).hasClass( 'acf-input-wrap' ) ) {
+ copyValue = $( e.target ).find( 'input' ).first().val();
} else {
- copyValue = $(e.target).text().trim();
+ copyValue = $( e.target ).text().trim();
}
- navigator.clipboard.writeText(copyValue).then(() => {
- $(e.target).closest('.copyable').addClass('copied');
- setTimeout(function () {
- $(e.target).closest('.copyable').removeClass('copied');
- }, 2000);
- });
+ navigator.clipboard.writeText( copyValue ).then( () => {
+ $( e.target ).closest( '.copyable' ).addClass( 'copied' );
+ setTimeout( function () {
+ $( e.target )
+ .closest( '.copyable' )
+ .removeClass( 'copied' );
+ }, 2000 );
+ } );
},
- onClickEdit: function (e) {
- const $target = $(e.target);
+ onClickEdit: function ( e ) {
+ const $target = $( e.target );
if (
- $target.parent().hasClass('row-options') &&
- !$target.hasClass('edit-field')
+ $target.parent().hasClass( 'row-options' ) &&
+ ! $target.hasClass( 'edit-field' )
) {
return;
}
@@ -468,170 +472,172 @@
},
onChangeSettingsTab: function () {
- const $settings = this.$el.children('.settings');
- acf.doAction('show', $settings);
+ const $settings = this.$el.children( '.settings' );
+ acf.doAction( 'show', $settings );
},
/**
* Adds 'active' class to row options nearest to the target.
*/
- onFocusEdit: function (e) {
- var $rowOptions = $(e.target).closest('li').find('.row-options');
- $rowOptions.addClass('active');
+ onFocusEdit: function ( e ) {
+ var $rowOptions = $( e.target )
+ .closest( 'li' )
+ .find( '.row-options' );
+ $rowOptions.addClass( 'active' );
},
/**
* Removes 'active' class from row options if links in same row options area are no longer in focus.
*/
- onBlurEdit: function (e) {
+ onBlurEdit: function ( e ) {
var focusDelayMilliseconds = 50;
- var $rowOptionsBlurElement = $(e.target)
- .closest('li')
- .find('.row-options');
+ var $rowOptionsBlurElement = $( e.target )
+ .closest( 'li' )
+ .find( '.row-options' );
// Timeout so that `activeElement` gives the new element in focus instead of the body.
- setTimeout(function () {
- var $rowOptionsFocusElement = $(document.activeElement)
- .closest('li')
- .find('.row-options');
- if (!$rowOptionsBlurElement.is($rowOptionsFocusElement)) {
- $rowOptionsBlurElement.removeClass('active');
+ setTimeout( function () {
+ var $rowOptionsFocusElement = $( document.activeElement )
+ .closest( 'li' )
+ .find( '.row-options' );
+ if ( ! $rowOptionsBlurElement.is( $rowOptionsFocusElement ) ) {
+ $rowOptionsBlurElement.removeClass( 'active' );
}
- }, focusDelayMilliseconds);
+ }, focusDelayMilliseconds );
},
open: function () {
// vars
- var $settings = this.$el.children('.settings');
+ var $settings = this.$el.children( '.settings' );
// initialise field type select
this.addProFields();
this.initializeFieldTypeSelect2();
// action (open)
- acf.doAction('open_field_object', this);
- this.trigger('openFieldObject');
+ acf.doAction( 'open_field_object', this );
+ this.trigger( 'openFieldObject' );
// action (show)
- acf.doAction('show', $settings);
+ acf.doAction( 'show', $settings );
this.hideEmptyTabs();
// open
$settings.slideDown();
- this.$el.addClass('open');
+ this.$el.addClass( 'open' );
},
- onKeyDownSelect: function (e) {
+ onKeyDownSelect: function ( e ) {
// Omit events from special keys.
if (
- !(
- (e.which >= 186 && e.which <= 222) || // punctuation and special characters
+ ! (
+ ( e.which >= 186 && e.which <= 222 ) || // punctuation and special characters
[
8, 9, 13, 16, 17, 18, 19, 20, 27, 32, 33, 34, 35, 36,
37, 38, 39, 40, 45, 46, 91, 92, 93, 144, 145,
- ].includes(e.which) || // Special keys
- (e.which >= 112 && e.which <= 123)
+ ].includes( e.which ) || // Special keys
+ ( e.which >= 112 && e.which <= 123 )
)
) {
// Function keys
- $(this)
- .closest('.select2-container')
- .siblings('select:enabled')
- .select2('open');
+ $( this )
+ .closest( '.select2-container' )
+ .siblings( 'select:enabled' )
+ .select2( 'open' );
return;
}
},
close: function () {
// vars
- var $settings = this.$el.children('.settings');
+ var $settings = this.$el.children( '.settings' );
// close
$settings.slideUp();
- this.$el.removeClass('open');
+ this.$el.removeClass( 'open' );
// action (close)
- acf.doAction('close_field_object', this);
- this.trigger('closeFieldObject');
+ acf.doAction( 'close_field_object', this );
+ this.trigger( 'closeFieldObject' );
// action (hide)
- acf.doAction('hide', $settings);
+ acf.doAction( 'hide', $settings );
},
serialize: function () {
- return acf.serialize(this.$el, this.getInputName());
+ return acf.serialize( this.$el, this.getInputName() );
},
- save: function (type) {
+ save: function ( type ) {
// defaults
type = type || 'settings'; // meta, settings
// vars
- var save = this.getProp('save');
+ var save = this.getProp( 'save' );
// bail if already saving settings
- if (save === 'settings') {
+ if ( save === 'settings' ) {
return;
}
// prop
- this.setProp('save', type);
+ this.setProp( 'save', type );
// debug
- this.$el.attr('data-save', type);
+ this.$el.attr( 'data-save', type );
// action
- acf.doAction('save_field_object', this, type);
+ acf.doAction( 'save_field_object', this, type );
},
submit: function () {
// vars
var inputName = this.getInputName();
- var save = this.get('save');
+ var save = this.get( 'save' );
// close
- if (this.isOpen()) {
+ if ( this.isOpen() ) {
this.close();
}
// allow all inputs to save
- if (save == 'settings') {
+ if ( save == 'settings' ) {
// do nothing
// allow only meta inputs to save
- } else if (save == 'meta') {
- this.$('> .settings [name^="' + inputName + '"]').remove();
+ } else if ( save == 'meta' ) {
+ this.$( '> .settings [name^="' + inputName + '"]' ).remove();
// prevent all inputs from saving
} else {
- this.$('[name^="' + inputName + '"]').remove();
+ this.$( '[name^="' + inputName + '"]' ).remove();
}
// action
- acf.doAction('submit_field_object', this);
+ acf.doAction( 'submit_field_object', this );
},
- onChange: function (e, $el) {
+ onChange: function ( e, $el ) {
// save settings
this.save();
// action for 3rd party customization
- acf.doAction('change_field_object', this);
+ acf.doAction( 'change_field_object', this );
},
- onChanged: function (e, $el, name, value) {
- if (this.getType() === $el.attr('data-type')) {
- $('button.acf-btn.browse-fields').prop('disabled', false);
+ onChanged: function ( e, $el, name, value ) {
+ if ( this.getType() === $el.attr( 'data-type' ) ) {
+ $( 'button.acf-btn.browse-fields' ).prop( 'disabled', false );
}
// ignore 'save'
- if (name == 'save') {
+ if ( name == 'save' ) {
return;
}
// save meta
- if (['menu_order', 'parent'].indexOf(name) > -1) {
- this.save('meta');
+ if ( [ 'menu_order', 'parent' ].indexOf( name ) > -1 ) {
+ this.save( 'meta' );
// save field
} else {
@@ -647,42 +653,42 @@
'name',
'type',
'key',
- ].indexOf(name) > -1
+ ].indexOf( name ) > -1
) {
this.render();
}
// action for 3rd party customization
- acf.doAction('change_field_object_' + name, this, value);
+ acf.doAction( 'change_field_object_' + name, this, value );
},
- onChangeLabel: function (e, $el) {
+ onChangeLabel: function ( e, $el ) {
// set
const label = $el.val();
- const safeLabel = acf.strEscape(label);
- this.set('label', safeLabel);
+ const safeLabel = acf.strEscape( label );
+ this.set( 'label', safeLabel );
// render name
- if (this.prop('name') == '') {
+ if ( this.prop( 'name' ) == '' ) {
var name = acf.applyFilters(
'generate_field_object_name',
- acf.strSanitize(label),
+ acf.strSanitize( label ),
this
);
- this.prop('name', name);
+ this.prop( 'name', name );
}
},
- onChangeName: function (e, $el) {
- const id = this.get('id');
+ onChangeName: function ( e, $el ) {
+ const id = this.get( 'id' );
let forceSanitize = false;
// If id is not a number or is zero, force sanitize
- if (typeof id !== 'number' || id === 0) {
+ if ( typeof id !== 'number' || id === 0 ) {
forceSanitize = true;
}
// Get the input's value attribute
- const valueAttr = input.attr('value');
+ const valueAttr = input.attr( 'value' );
// If value is a lowercase string, force sanitize
if (
@@ -693,16 +699,16 @@
}
// Sanitize the input value (force if needed)
- const sanitized = acf.strSanitize($el.val(), forceSanitize);
+ const sanitized = acf.strSanitize( $el.val(), forceSanitize );
// Set the sanitized value back to the input
- $el.val(sanitized);
+ $el.val( sanitized );
// Update the field's name property
- this.set('name', sanitized);
+ this.set( 'name', sanitized );
// Warn if the name starts with "field_"
- if (sanitized.startsWith('field_')) {
+ if ( sanitized.startsWith( 'field_' ) ) {
alert(
acf.__(
'The string "field_" may not be used at the start of a field name'
@@ -711,49 +717,49 @@
}
},
- onChangeRequired: function (e, $el) {
+ onChangeRequired: function ( e, $el ) {
// set
- var required = $el.prop('checked') ? 1 : 0;
- this.set('required', required);
+ var required = $el.prop( 'checked' ) ? 1 : 0;
+ this.set( 'required', required );
},
- delete: function (args) {
+ delete: function ( args ) {
// defaults
- args = acf.parseArgs(args, {
+ args = acf.parseArgs( args, {
animate: true,
- });
+ } );
// add to remove list
- var id = this.prop('ID');
+ var id = this.prop( 'ID' );
- if (id) {
- var $input = $('#_acf_delete_fields');
+ if ( id ) {
+ var $input = $( '#_acf_delete_fields' );
var newVal = $input.val() + '|' + id;
- $input.val(newVal);
+ $input.val( newVal );
}
// action
- acf.doAction('delete_field_object', this);
+ acf.doAction( 'delete_field_object', this );
// animate
- if (args.animate) {
+ if ( args.animate ) {
this.removeAnimate();
} else {
this.remove();
}
},
- onClickDelete: function (e, $el) {
+ onClickDelete: function ( e, $el ) {
// Bypass confirmation when holding down "shift" key.
- if (e.shiftKey) {
+ if ( e.shiftKey ) {
return this.delete();
}
// add class
- this.$el.addClass('-hover');
+ this.$el.addClass( '-hover' );
// add tooltip
- var tooltip = acf.newTooltip({
+ var tooltip = acf.newTooltip( {
confirmRemove: true,
target: $el,
context: this,
@@ -761,70 +767,70 @@
this.delete();
},
cancel: function () {
- this.$el.removeClass('-hover');
+ this.$el.removeClass( '-hover' );
},
- });
+ } );
},
removeAnimate: function () {
// vars
var field = this;
var $list = this.$el.parent();
- var $fields = acf.findFieldObjects({
+ var $fields = acf.findFieldObjects( {
sibling: this.$el,
- });
+ } );
// remove
- acf.remove({
+ acf.remove( {
target: this.$el,
endHeight: $fields.length ? 0 : 50,
complete: function () {
field.remove();
- acf.doAction('removed_field_object', field, $list);
+ acf.doAction( 'removed_field_object', field, $list );
},
- });
+ } );
// action
- acf.doAction('remove_field_object', field, $list);
+ acf.doAction( 'remove_field_object', field, $list );
},
duplicate: function () {
// vars
- var newKey = acf.uniqid('field_');
+ var newKey = acf.uniqid( 'field_' );
// duplicate
- var $newField = acf.duplicate({
+ var $newField = acf.duplicate( {
target: this.$el,
- search: this.get('id'),
+ search: this.get( 'id' ),
replace: newKey,
- });
+ } );
// set new key
- $newField.attr('data-key', newKey);
+ $newField.attr( 'data-key', newKey );
// get instance
- var newField = acf.getFieldObject($newField);
+ var newField = acf.getFieldObject( $newField );
// update newField label / name
- var label = newField.prop('label');
- var name = newField.prop('name');
- var end = name.split('_').pop();
- var copy = acf.__('copy');
+ var label = newField.prop( 'label' );
+ var name = newField.prop( 'name' );
+ var end = name.split( '_' ).pop();
+ var copy = acf.__( 'copy' );
// increase suffix "1"
- if (acf.isNumeric(end)) {
+ if ( acf.isNumeric( end ) ) {
var i = end * 1 + 1;
- label = label.replace(end, i);
- name = name.replace(end, i);
+ label = label.replace( end, i );
+ name = name.replace( end, i );
// increase suffix "(copy1)"
- } else if (end.indexOf(copy) === 0) {
- var i = end.replace(copy, '') * 1;
+ } else if ( end.indexOf( copy ) === 0 ) {
+ var i = end.replace( copy, '' ) * 1;
i = i ? i + 1 : 2;
// replace
- label = label.replace(end, copy + i);
- name = name.replace(end, copy + i);
+ label = label.replace( end, copy + i );
+ name = name.replace( end, copy + i );
// add default "(copy)"
} else {
@@ -832,13 +838,13 @@
name += '_' + copy;
}
- newField.prop('ID', 0);
- newField.prop('label', label);
- newField.prop('name', name);
- newField.prop('key', newKey);
+ newField.prop( 'ID', 0 );
+ newField.prop( 'label', label );
+ newField.prop( 'name', name );
+ newField.prop( 'key', newKey );
// close the current field if it's open.
- if (this.isOpen()) {
+ if ( this.isOpen() ) {
this.close();
}
@@ -846,66 +852,66 @@
newField.open();
// focus label
- var $label = newField.$setting('label input');
- setTimeout(function () {
- $label.trigger('focus');
- }, 251);
+ var $label = newField.$setting( 'label input' );
+ setTimeout( function () {
+ $label.trigger( 'focus' );
+ }, 251 );
// action
- acf.doAction('duplicate_field_object', this, newField);
- acf.doAction('append_field_object', newField);
+ acf.doAction( 'duplicate_field_object', this, newField );
+ acf.doAction( 'append_field_object', newField );
},
wipe: function () {
// vars
- var prevId = this.get('id');
- var prevKey = this.get('key');
- var newKey = acf.uniqid('field_');
+ var prevId = this.get( 'id' );
+ var prevKey = this.get( 'key' );
+ var newKey = acf.uniqid( 'field_' );
// rename
- acf.rename({
+ acf.rename( {
target: this.$el,
search: prevId,
replace: newKey,
- });
+ } );
// data
- this.set('id', newKey);
- this.set('prevId', prevId);
- this.set('prevKey', prevKey);
+ this.set( 'id', newKey );
+ this.set( 'prevId', prevId );
+ this.set( 'prevKey', prevKey );
// props
- this.prop('key', newKey);
- this.prop('ID', 0);
+ this.prop( 'key', newKey );
+ this.prop( 'ID', 0 );
// attr
- this.$el.attr('data-key', newKey);
- this.$el.attr('data-id', newKey);
+ this.$el.attr( 'data-key', newKey );
+ this.$el.attr( 'data-id', newKey );
// action
- acf.doAction('wipe_field_object', this);
+ acf.doAction( 'wipe_field_object', this );
},
move: function () {
// helper
- var hasChanged = function (field) {
- return field.get('save') == 'settings';
+ var hasChanged = function ( field ) {
+ return field.get( 'save' ) == 'settings';
};
// vars
- var changed = hasChanged(this);
+ var changed = hasChanged( this );
// has sub fields changed
- if (!changed) {
- acf.getFieldObjects({
+ if ( ! changed ) {
+ acf.getFieldObjects( {
parent: this.$el,
- }).map(function (field) {
- changed = hasChanged(field) || field.changed;
- });
+ } ).map( function ( field ) {
+ changed = hasChanged( field ) || field.changed;
+ } );
}
// bail early if changed
- if (changed) {
+ if ( changed ) {
alert(
acf.__(
'This field cannot be moved until its changes have been saved'
@@ -915,17 +921,17 @@
}
// step 1.
- var id = this.prop('ID');
+ var id = this.prop( 'ID' );
var field = this;
var popup = false;
var step1 = function () {
// popup
- popup = acf.newPopup({
- title: acf.__('Move Custom Field'),
+ popup = acf.newPopup( {
+ title: acf.__( 'Move Custom Field' ),
loading: true,
width: '300px',
- openedBy: field.$el.find('.move-field'),
- });
+ openedBy: field.$el.find( '.move-field' ),
+ } );
// ajax
var ajaxData = {
@@ -934,59 +940,59 @@
};
// get HTML
- $.ajax({
- url: acf.get('ajaxurl'),
- data: acf.prepareForAjax(ajaxData),
+ $.ajax( {
+ url: acf.get( 'ajaxurl' ),
+ data: acf.prepareForAjax( ajaxData ),
type: 'post',
dataType: 'html',
success: step2,
- });
+ } );
};
- var step2 = function (html) {
+ var step2 = function ( html ) {
// update popup
- popup.loading(false);
- popup.content(html);
+ popup.loading( false );
+ popup.content( html );
// submit form
- popup.on('submit', 'form', step3);
+ popup.on( 'submit', 'form', step3 );
};
- var step3 = function (e, $el) {
+ var step3 = function ( e, $el ) {
// prevent
e.preventDefault();
// disable
- acf.startButtonLoading(popup.$('.button'));
+ acf.startButtonLoading( popup.$( '.button' ) );
// ajax
var ajaxData = {
action: 'acf/field_group/move_field',
field_id: id,
- field_group_id: popup.$('select').val(),
+ field_group_id: popup.$( 'select' ).val(),
};
// get HTML
- $.ajax({
- url: acf.get('ajaxurl'),
- data: acf.prepareForAjax(ajaxData),
+ $.ajax( {
+ url: acf.get( 'ajaxurl' ),
+ data: acf.prepareForAjax( ajaxData ),
type: 'post',
dataType: 'html',
success: step4,
- });
+ } );
};
- var step4 = function (html) {
- popup.content(html);
+ var step4 = function ( html ) {
+ popup.content( html );
- if (wp.a11y && wp.a11y.speak && acf.__) {
+ if ( wp.a11y && wp.a11y.speak && acf.__ ) {
wp.a11y.speak(
- acf.__('Field moved to other group'),
+ acf.__( 'Field moved to other group' ),
'polite'
);
}
- popup.$('.acf-close-popup').trigger('focus');
+ popup.$( '.acf-close-popup' ).trigger( 'focus' );
field.removeAnimate();
};
@@ -995,40 +1001,40 @@
step1();
},
- browseFields: function (e, $el) {
+ browseFields: function ( e, $el ) {
e.preventDefault();
- const modal = acf.newBrowseFieldsModal({
+ const modal = acf.newBrowseFieldsModal( {
openedBy: this,
- });
+ } );
},
- onChangeType: function (e, $el) {
+ onChangeType: function ( e, $el ) {
// clea previous timout
- if (this.changeTimeout) {
- clearTimeout(this.changeTimeout);
+ if ( this.changeTimeout ) {
+ clearTimeout( this.changeTimeout );
}
// set new timeout
// - prevents changing type multiple times whilst user types in newType
- this.changeTimeout = this.setTimeout(function () {
- this.changeType($el.val());
- }, 300);
+ this.changeTimeout = this.setTimeout( function () {
+ this.changeType( $el.val() );
+ }, 300 );
},
- changeType: function (newType) {
- var prevType = this.prop('type');
- var prevClass = acf.strSlugify('acf-field-object-' + prevType);
- var newClass = acf.strSlugify('acf-field-object-' + newType);
+ changeType: function ( newType ) {
+ var prevType = this.prop( 'type' );
+ var prevClass = acf.strSlugify( 'acf-field-object-' + prevType );
+ var newClass = acf.strSlugify( 'acf-field-object-' + newType );
// Update props.
- this.$el.removeClass(prevClass).addClass(newClass);
- this.$el.attr('data-type', newType);
- this.$el.data('type', newType);
+ this.$el.removeClass( prevClass ).addClass( newClass );
+ this.$el.attr( 'data-type', newType );
+ this.$el.data( 'type', newType );
// Abort XHR if this field is already loading AJAX data.
- if (this.has('xhr')) {
- this.get('xhr').abort();
+ if ( this.has( 'xhr' ) ) {
+ this.get( 'xhr' ).abort();
}
// Store old settings so they can be reused later.
@@ -1038,23 +1044,23 @@
.find(
'.acf-field-settings:first > .acf-field-settings-main > .acf-field-type-settings'
)
- .each(function () {
- let tab = $(this).data('parent-tab');
- let $tabSettings = $(this).children().removeData();
+ .each( function () {
+ let tab = $( this ).data( 'parent-tab' );
+ let $tabSettings = $( this ).children().removeData();
- $oldSettings[tab] = $tabSettings;
+ $oldSettings[ tab ] = $tabSettings;
$tabSettings.detach();
- });
+ } );
- this.set('settings-' + prevType, $oldSettings);
+ this.set( 'settings-' + prevType, $oldSettings );
// Show the settings if we already have them cached.
- if (this.has('settings-' + newType)) {
- let $newSettings = this.get('settings-' + newType);
+ if ( this.has( 'settings-' + newType ) ) {
+ let $newSettings = this.get( 'settings-' + newType );
- this.showFieldTypeSettings($newSettings);
- this.set('type', newType);
+ this.showFieldTypeSettings( $newSettings );
+ this.set( 'type', newType );
return;
}
@@ -1066,7 +1072,7 @@
.find(
'.acf-field-settings-main-general .acf-field-type-settings'
)
- .before($loading);
+ .before( $loading );
const ajaxData = {
action: 'acf/field_group/render_field_settings',
@@ -1075,70 +1081,72 @@
};
// Get the settings for this field type over AJAX.
- var xhr = $.ajax({
- url: acf.get('ajaxurl'),
- data: acf.prepareForAjax(ajaxData),
+ var xhr = $.ajax( {
+ url: acf.get( 'ajaxurl' ),
+ data: acf.prepareForAjax( ajaxData ),
type: 'post',
dataType: 'json',
context: this,
- success: function (response) {
- if (!acf.isAjaxSuccess(response)) {
+ success: function ( response ) {
+ if ( ! acf.isAjaxSuccess( response ) ) {
return;
}
- this.showFieldTypeSettings(response.data);
+ this.showFieldTypeSettings( response.data );
},
complete: function () {
// also triggered by xhr.abort();
$loading.remove();
- this.set('type', newType);
+ this.set( 'type', newType );
//this.refresh();
},
- });
+ } );
// set
- this.set('xhr', xhr);
+ this.set( 'xhr', xhr );
},
- showFieldTypeSettings: function (settings) {
- if ('object' !== typeof settings) {
+ showFieldTypeSettings: function ( settings ) {
+ if ( 'object' !== typeof settings ) {
return;
}
const self = this;
- const tabs = Object.keys(settings);
+ const tabs = Object.keys( settings );
- tabs.forEach((tab) => {
+ tabs.forEach( ( tab ) => {
const $tab = self.$el.find(
'.acf-field-settings-main-' +
- tab.replace('_', '-') +
+ tab.replace( '_', '-' ) +
' .acf-field-type-settings'
);
let tabContent = '';
- if (['object', 'string'].includes(typeof settings[tab])) {
- tabContent = settings[tab];
+ if (
+ [ 'object', 'string' ].includes( typeof settings[ tab ] )
+ ) {
+ tabContent = settings[ tab ];
}
- $tab.prepend(tabContent);
- acf.doAction('append', $tab);
- });
+ $tab.prepend( tabContent );
+ acf.doAction( 'append', $tab );
+ } );
this.hideEmptyTabs();
},
updateParent: function () {
// vars
- var ID = acf.get('post_id');
+ var ID = acf.get( 'post_id' );
// check parent
var parent = this.getParent();
- if (parent) {
- ID = parseInt(parent.prop('ID')) || parent.prop('key');
+ if ( parent ) {
+ ID = parseInt( parent.prop( 'ID' ) ) || parent.prop( 'key' );
}
// update
- this.prop('parent', ID);
+ this.prop( 'parent', ID );
},
hideEmptyTabs: function () {
@@ -1147,21 +1155,21 @@
'.acf-field-settings:first > .acf-field-settings-main'
);
- $tabs.each(function () {
- const $tabContent = $(this);
+ $tabs.each( function () {
+ const $tabContent = $( this );
const tabName = $tabContent
- .find('.acf-field-type-settings:first')
- .data('parentTab');
+ .find( '.acf-field-type-settings:first' )
+ .data( 'parentTab' );
const $tabLink = $settings
- .find('.acf-settings-type-' + tabName)
+ .find( '.acf-settings-type-' + tabName )
.first();
- if ($.trim($tabContent.text()) === '') {
+ if ( $.trim( $tabContent.text() ) === '' ) {
$tabLink.hide();
- } else if ($tabLink.is(':hidden')) {
+ } else if ( $tabLink.is( ':hidden' ) ) {
$tabLink.show();
}
- });
+ } );
},
- });
-})(jQuery);
+ } );
+} )( jQuery );
diff --git a/assets/src/js/pro/_acf-blocks.js b/assets/src/js/pro/_acf-blocks.js
index a120f056..dc9e7db5 100644
--- a/assets/src/js/pro/_acf-blocks.js
+++ b/assets/src/js/pro/_acf-blocks.js
@@ -19,15 +19,19 @@ const md5 = require( 'md5' );
// Potentially experimental dependencies.
const BlockAlignmentMatrixToolbar =
- wp.blockEditor.__experimentalBlockAlignmentMatrixToolbar || wp.blockEditor.BlockAlignmentMatrixToolbar;
+ wp.blockEditor.__experimentalBlockAlignmentMatrixToolbar ||
+ wp.blockEditor.BlockAlignmentMatrixToolbar;
// Gutenberg v10.x begins transition from Toolbar components to Control components.
const BlockAlignmentMatrixControl =
- wp.blockEditor.__experimentalBlockAlignmentMatrixControl || wp.blockEditor.BlockAlignmentMatrixControl;
+ wp.blockEditor.__experimentalBlockAlignmentMatrixControl ||
+ wp.blockEditor.BlockAlignmentMatrixControl;
const BlockFullHeightAlignmentControl =
wp.blockEditor.__experimentalBlockFullHeightAligmentControl ||
wp.blockEditor.__experimentalBlockFullHeightAlignmentControl ||
wp.blockEditor.BlockFullHeightAlignmentControl;
- const useInnerBlocksProps = wp.blockEditor.__experimentalUseInnerBlocksProps || wp.blockEditor.useInnerBlocksProps;
+ const useInnerBlocksProps =
+ wp.blockEditor.__experimentalUseInnerBlocksProps ||
+ wp.blockEditor.useInnerBlocksProps;
/**
* Storage for registered block types.
@@ -96,9 +100,14 @@ const md5 = require( 'md5' );
* @return boolean
*/
function isBlockInQueryLoop( clientId ) {
- const parents = wp.data.select( 'core/block-editor' ).getBlockParents( clientId );
- const parentsData = wp.data.select( 'core/block-editor' ).getBlocksByClientId( parents );
- return parentsData.filter( ( block ) => block.name === 'core/query' ).length;
+ const parents = wp.data
+ .select( 'core/block-editor' )
+ .getBlockParents( clientId );
+ const parentsData = wp.data
+ .select( 'core/block-editor' )
+ .getBlocksByClientId( parents );
+ return parentsData.filter( ( block ) => block.name === 'core/query' )
+ .length;
}
/**
@@ -110,7 +119,10 @@ const md5 = require( 'md5' );
* @return boolean
*/
function isSiteEditor() {
- return document.querySelectorAll( 'iframe[name="editor-canvas"]' ).length > 0;
+ return (
+ document.querySelectorAll( 'iframe[name="editor-canvas"]' ).length >
+ 0
+ );
}
/**
@@ -132,7 +144,9 @@ const md5 = require( 'md5' );
// Check if function exists (experimental or not) and return true if it's Desktop, or doesn't exist.
if ( editPostStore.__experimentalGetPreviewDeviceType ) {
- return 'Desktop' === editPostStore.__experimentalGetPreviewDeviceType();
+ return (
+ 'Desktop' === editPostStore.__experimentalGetPreviewDeviceType()
+ );
} else if ( editPostStore.getPreviewDeviceType ) {
return 'Desktop' === editPostStore.getPreviewDeviceType();
} else {
@@ -169,7 +183,10 @@ const md5 = require( 'md5' );
* @return boolean
*/
function isiFramedMobileDevicePreview() {
- return $( 'iframe[name=editor-canvas]' ).length && ! isDesktopPreviewDeviceType();
+ return (
+ $( 'iframe[name=editor-canvas]' ).length &&
+ ! isDesktopPreviewDeviceType()
+ );
}
/**
@@ -196,7 +213,10 @@ const md5 = require( 'md5' );
}
// Handle svg HTML.
- if ( typeof blockType.icon === 'string' && blockType.icon.substr( 0, 4 ) === '{ iconHTML } ;
}
@@ -229,7 +249,10 @@ const md5 = require( 'md5' );
// Remove all empty attribute defaults from PHP values to allow serialisation.
// https://github.com/WordPress/gutenberg/issues/7342
for ( const key in blockType.attributes ) {
- if ( 'default' in blockType.attributes[ key ] && blockType.attributes[ key ].default.length === 0 ) {
+ if (
+ 'default' in blockType.attributes[ key ] &&
+ blockType.attributes[ key ].default.length === 0
+ ) {
delete blockType.attributes[ key ].default;
}
}
@@ -247,20 +270,41 @@ const md5 = require( 'md5' );
// Apply alignText functionality.
if ( blockType.supports.alignText || blockType.supports.align_text ) {
- blockType.attributes = addBackCompatAttribute( blockType.attributes, 'align_text', 'string' );
+ blockType.attributes = addBackCompatAttribute(
+ blockType.attributes,
+ 'align_text',
+ 'string'
+ );
ThisBlockEdit = withAlignTextComponent( ThisBlockEdit, blockType );
}
// Apply alignContent functionality.
- if ( blockType.supports.alignContent || blockType.supports.align_content ) {
- blockType.attributes = addBackCompatAttribute( blockType.attributes, 'align_content', 'string' );
- ThisBlockEdit = withAlignContentComponent( ThisBlockEdit, blockType );
+ if (
+ blockType.supports.alignContent ||
+ blockType.supports.align_content
+ ) {
+ blockType.attributes = addBackCompatAttribute(
+ blockType.attributes,
+ 'align_content',
+ 'string'
+ );
+ ThisBlockEdit = withAlignContentComponent(
+ ThisBlockEdit,
+ blockType
+ );
}
// Apply fullHeight functionality.
if ( blockType.supports.fullHeight || blockType.supports.full_height ) {
- blockType.attributes = addBackCompatAttribute( blockType.attributes, 'full_height', 'boolean' );
- ThisBlockEdit = withFullHeightComponent( ThisBlockEdit, blockType.blockType );
+ blockType.attributes = addBackCompatAttribute(
+ blockType.attributes,
+ 'full_height',
+ 'boolean'
+ );
+ ThisBlockEdit = withFullHeightComponent(
+ ThisBlockEdit,
+ blockType.blockType
+ );
}
// Set edit and save functions.
@@ -269,7 +313,9 @@ const md5 = require( 'md5' );
wp.element.useEffect( () => {
return () => {
if ( ! wp.data.dispatch( 'core/editor' ) ) return;
- wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'acf/block/' + props.clientId );
+ wp.data
+ .dispatch( 'core/editor' )
+ .unlockPostSaving( 'acf/block/' + props.clientId );
};
}, [] );
@@ -307,7 +353,10 @@ const md5 = require( 'md5' );
*/
function select( selector ) {
if ( selector === 'core/block-editor' ) {
- return wp.data.select( 'core/block-editor' ) || wp.data.select( 'core/editor' );
+ return (
+ wp.data.select( 'core/block-editor' ) ||
+ wp.data.select( 'core/editor' )
+ );
}
return wp.data.select( selector );
}
@@ -340,7 +389,9 @@ const md5 = require( 'md5' );
// Local function to recurse through all child blocks and add to the blocks array.
const recurseBlocks = ( block ) => {
blocks.push( block );
- select( 'core/block-editor' ).getBlocks( block.clientId ).forEach( recurseBlocks );
+ select( 'core/block-editor' )
+ .getBlocks( block.clientId )
+ .forEach( recurseBlocks );
};
// Trigger initial recursion for parent level blocks.
@@ -348,7 +399,9 @@ const md5 = require( 'md5' );
// Loop over args and filter.
for ( const k in args ) {
- blocks = blocks.filter( ( { attributes } ) => attributes[ k ] === args[ k ] );
+ blocks = blocks.filter(
+ ( { attributes } ) => attributes[ k ] === args[ k ]
+ );
}
// Return results.
@@ -381,10 +434,18 @@ const md5 = require( 'md5' );
* @return object The AJAX promise.
*/
function fetchBlock( args ) {
- const { attributes = {}, context = {}, query = {}, clientId = null, delay = 0 } = args;
+ const {
+ attributes = {},
+ context = {},
+ query = {},
+ clientId = null,
+ delay = 0,
+ } = args;
// Build a unique queue ID from block data, including the clientId for edit forms.
- const queueId = md5( JSON.stringify( { ...attributes, ...context, ...query } ) );
+ const queueId = md5(
+ JSON.stringify( { ...attributes, ...context, ...query } )
+ );
const data = ajaxQueue[ queueId ] || {
query: {},
@@ -404,7 +465,10 @@ const md5 = require( 'md5' );
data.started = true;
if ( fetchCache[ queueId ] ) {
ajaxQueue[ queueId ] = null;
- data.promise.resolve.apply( fetchCache[ queueId ][ 0 ], fetchCache[ queueId ][ 1 ] );
+ data.promise.resolve.apply(
+ fetchCache[ queueId ][ 0 ],
+ fetchCache[ queueId ][ 1 ]
+ );
} else {
$.ajax( {
url: acf.get( 'ajaxurl' ),
@@ -468,7 +532,10 @@ const md5 = require( 'md5' );
// Apply a temporary wrapper for the jQuery parse to prevent text nodes triggering errors.
html = '' + html + '
';
// Correctly balance InnerBlocks tags for jQuery's initial parse.
- html = html.replace( /]+)?\/>/, ' ' );
+ html = html.replace(
+ /]+)?\/>/,
+ ' '
+ );
return parseNode( $( html )[ 0 ], acfBlockVersion, 0 ).props.children;
};
@@ -485,7 +552,10 @@ const md5 = require( 'md5' );
*/
function parseNode( node, acfBlockVersion, level = 0 ) {
// Get node name.
- const nodeName = parseNodeName( node.nodeName.toLowerCase(), acfBlockVersion );
+ const nodeName = parseNodeName(
+ node.nodeName.toLowerCase(),
+ acfBlockVersion
+ );
if ( ! nodeName ) {
return null;
}
@@ -578,7 +648,10 @@ const md5 = require( 'md5' );
*/
function ACFInnerBlocks( props ) {
const { className = 'acf-innerblocks-container' } = props;
- const innerBlockProps = useInnerBlocksProps( { className: className }, props );
+ const innerBlockProps = useInnerBlocksProps(
+ { className: className },
+ props
+ );
return { innerBlockProps.children }
;
}
@@ -597,7 +670,11 @@ const md5 = require( 'md5' );
let value = nodeAttr.value;
// Allow overrides for third party libraries who might use specific attributes.
- let shortcut = acf.applyFilters( 'acf_blocks_parse_node_attr', false, nodeAttr );
+ let shortcut = acf.applyFilters(
+ 'acf_blocks_parse_node_attr',
+ false,
+ nodeAttr
+ );
if ( shortcut ) return shortcut;
@@ -698,10 +775,13 @@ const md5 = require( 'md5' );
Object.keys( upgrades ).forEach( ( key ) => {
if ( attributes[ key ] !== undefined ) {
attributes[ upgrades[ key ] ] = attributes[ key ];
- } else if ( attributes[ upgrades[ key ] ] === undefined ) {
+ } else if (
+ attributes[ upgrades[ key ] ] === undefined
+ ) {
//Check for a default
if ( blockType[ key ] !== undefined ) {
- attributes[ upgrades[ key ] ] = blockType[ key ];
+ attributes[ upgrades[ key ] ] =
+ blockType[ key ];
}
}
delete blockType[ key ];
@@ -710,7 +790,10 @@ const md5 = require( 'md5' );
// Set default attributes for those undefined.
for ( let attribute in blockType.attributes ) {
- if ( attributes[ attribute ] === undefined && blockType[ attribute ] !== undefined ) {
+ if (
+ attributes[ attribute ] === undefined &&
+ blockType[ attribute ] !== undefined
+ ) {
attributes[ attribute ] = blockType[ attribute ];
}
}
@@ -721,7 +804,11 @@ const md5 = require( 'md5' );
},
'withDefaultAttributes'
);
- wp.hooks.addFilter( 'editor.BlockListBlock', 'acf/with-default-attributes', withDefaultAttributes );
+ wp.hooks.addFilter(
+ 'editor.BlockListBlock',
+ 'acf/with-default-attributes',
+ withDefaultAttributes
+ );
/**
* The BlockSave functional component.
@@ -756,10 +843,7 @@ const md5 = require( 'md5' );
}
}
- if (
- isBlockInQueryLoop( clientId ) ||
- isSiteEditor()
- ) {
+ if ( isBlockInQueryLoop( clientId ) || isSiteEditor() ) {
restrictMode( [ 'preview' ] );
} else {
switch ( blockType.mode ) {
@@ -780,8 +864,7 @@ const md5 = require( 'md5' );
const { name, attributes, setAttributes, clientId } = this.props;
const blockType = getBlockType( name );
const forcePreview =
- isBlockInQueryLoop( clientId ) ||
- isSiteEditor();
+ isBlockInQueryLoop( clientId ) || isSiteEditor();
let { mode } = attributes;
if ( forcePreview ) {
@@ -795,8 +878,12 @@ const md5 = require( 'md5' );
}
// Configure toggle variables.
- const toggleText = mode === 'preview' ? acf.__( 'Switch to Edit' ) : acf.__( 'Switch to Preview' );
- const toggleIcon = mode === 'preview' ? 'edit' : 'welcome-view-site';
+ const toggleText =
+ mode === 'preview'
+ ? acf.__( 'Switch to Edit' )
+ : acf.__( 'Switch to Preview' );
+ const toggleIcon =
+ mode === 'preview' ? 'edit' : 'welcome-view-site';
function toggleMode() {
setAttributes( {
mode: mode === 'preview' ? 'edit' : 'preview',
@@ -843,8 +930,12 @@ const md5 = require( 'md5' );
const { mode } = attributes;
const index = useSelect( ( select ) => {
- const rootClientId = select( 'core/block-editor' ).getBlockRootClientId( clientId );
- return select( 'core/block-editor' ).getBlockIndex( clientId, rootClientId );
+ const rootClientId =
+ select( 'core/block-editor' ).getBlockRootClientId( clientId );
+ return select( 'core/block-editor' ).getBlockIndex(
+ clientId,
+ rootClientId
+ );
} );
let showForm = true;
@@ -865,7 +956,10 @@ const md5 = require( 'md5' );
acf.blockInstances[ clientId ].mode = mode;
if ( ! isSelected ) {
- if ( blockSupportsValidation( name ) && acf.blockInstances[ clientId ].validation_errors ) {
+ if (
+ blockSupportsValidation( name ) &&
+ acf.blockInstances[ clientId ].validation_errors
+ ) {
additionalClasses += ' acf-block-has-validation-error';
}
acf.blockInstances[ clientId ].has_been_deselected = true;
@@ -907,7 +1001,11 @@ const md5 = require( 'md5' );
*/
class Div extends Component {
render() {
- return
;
+ return (
+
+ );
}
}
@@ -1003,20 +1101,32 @@ const md5 = require( 'md5' );
}
// Set HTML to the preloaded version.
- preloadedBlocks[ blockId ].html = preloadedBlocks[ blockId ].html.replaceAll( blockId, clientId );
+ preloadedBlocks[ blockId ].html = preloadedBlocks[
+ blockId
+ ].html.replaceAll( blockId, clientId );
// Replace blockId in errors.
- if ( preloadedBlocks[ blockId ].validation && preloadedBlocks[ blockId ].validation.errors ) {
- preloadedBlocks[ blockId ].validation.errors = preloadedBlocks[ blockId ].validation.errors.map(
- ( error ) => {
- error.input = error.input.replaceAll( blockId, clientId );
- return error;
- }
- );
+ if (
+ preloadedBlocks[ blockId ].validation &&
+ preloadedBlocks[ blockId ].validation.errors
+ ) {
+ preloadedBlocks[ blockId ].validation.errors =
+ preloadedBlocks[ blockId ].validation.errors.map(
+ ( error ) => {
+ error.input = error.input.replaceAll(
+ blockId,
+ clientId
+ );
+ return error;
+ }
+ );
}
// Return preloaded object.
- acf.debug( 'Preload successful', preloadedBlocks[ blockId ] );
+ acf.debug(
+ 'Preload successful',
+ preloadedBlocks[ blockId ]
+ );
return preloadedBlocks[ blockId ];
}
}
@@ -1030,10 +1140,11 @@ const md5 = require( 'md5' );
}
setState( state ) {
- acf.blockInstances[ this.props.clientId ][ this.constructor.name ] = {
- ...this.state,
- ...state,
- };
+ acf.blockInstances[ this.props.clientId ][ this.constructor.name ] =
+ {
+ ...this.state,
+ ...state,
+ };
// Update component state if subscribed.
// - Allows AJAX callback to update store without modifying state of an unmounted component.
@@ -1046,7 +1157,12 @@ const md5 = require( 'md5' );
Object.assign( {}, this ),
this.props.clientId,
this.constructor.name,
- Object.assign( {}, acf.blockInstances[ this.props.clientId ][ this.constructor.name ] )
+ Object.assign(
+ {},
+ acf.blockInstances[ this.props.clientId ][
+ this.constructor.name
+ ]
+ )
);
}
@@ -1064,7 +1180,10 @@ const md5 = require( 'md5' );
};
if ( this.renderMethod === 'jsx' ) {
- state.jsx = acf.parseJSX( html, getBlockVersion( this.props.name ) );
+ state.jsx = acf.parseJSX(
+ html,
+ getBlockVersion( this.props.name )
+ );
// Handle templates which don't contain any valid JSX parsable elements.
if ( ! state.jsx ) {
@@ -1072,12 +1191,17 @@ const md5 = require( 'md5' );
'Your ACF block template contains no valid HTML elements. Appending a empty div to prevent React JS errors.'
);
state.html += '
';
- state.jsx = acf.parseJSX( state.html, getBlockVersion( this.props.name ) );
+ state.jsx = acf.parseJSX(
+ state.html,
+ getBlockVersion( this.props.name )
+ );
}
// If we've got an object (as an array) find the first valid React ref.
if ( Array.isArray( state.jsx ) ) {
- let refElement = state.jsx.find( ( element ) => React.isValidElement( element ) );
+ let refElement = state.jsx.find( ( element ) =>
+ React.isValidElement( element )
+ );
state.ref = refElement.ref;
} else {
state.ref = state.jsx.ref;
@@ -1138,7 +1262,10 @@ const md5 = require( 'md5' );
// This causes all instances to share the same state (cool), which unfortunately
// pulls $el back and forth between the last rendered reusable block.
// This simple fix leaves a "clone" behind :)
- if ( $prevParent.length && $prevParent[ 0 ] !== $thisParent[ 0 ] ) {
+ if (
+ $prevParent.length &&
+ $prevParent[ 0 ] !== $thisParent[ 0 ]
+ ) {
$prevParent.html( $el.clone() );
}
}
@@ -1213,11 +1340,17 @@ const md5 = require( 'md5' );
}
isNotNewlyAdded() {
- return acf.blockInstances[ this.props.clientId ].has_been_deselected || false;
+ return (
+ acf.blockInstances[ this.props.clientId ].has_been_deselected ||
+ false
+ );
}
hasShownValidation() {
- return acf.blockInstances[ this.props.clientId ].shown_validation || false;
+ return (
+ acf.blockInstances[ this.props.clientId ].shown_validation ||
+ false
+ );
}
setShownValidation() {
@@ -1225,7 +1358,8 @@ const md5 = require( 'md5' );
}
setValidationErrors( errors ) {
- acf.blockInstances[ this.props.clientId ].validation_errors = errors;
+ acf.blockInstances[ this.props.clientId ].validation_errors =
+ errors;
}
getValidationErrors() {
@@ -1238,12 +1372,16 @@ const md5 = require( 'md5' );
lockBlockForSaving() {
if ( ! wp.data.dispatch( 'core/editor' ) ) return;
- wp.data.dispatch( 'core/editor' ).lockPostSaving( 'acf/block/' + this.props.clientId );
+ wp.data
+ .dispatch( 'core/editor' )
+ .lockPostSaving( 'acf/block/' + this.props.clientId );
}
unlockBlockForSaving() {
if ( ! wp.data.dispatch( 'core/editor' ) ) return;
- wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'acf/block/' + this.props.clientId );
+ wp.data
+ .dispatch( 'core/editor' )
+ .unlockPostSaving( 'acf/block/' + this.props.clientId );
}
displayValidation( $formEl ) {
@@ -1257,7 +1395,12 @@ const md5 = require( 'md5' );
}
const errors = this.getValidationErrors();
- acf.debug( 'Starting handle validation', Object.assign( {}, this ), Object.assign( {}, $formEl ), errors );
+ acf.debug(
+ 'Starting handle validation',
+ Object.assign( {}, this ),
+ Object.assign( {}, $formEl ),
+ errors
+ );
this.setShownValidation();
@@ -1323,8 +1466,15 @@ const md5 = require( 'md5' );
const preloaded = this.maybePreload( hash, clientId, true );
if ( preloaded ) {
- this.setHtml( acf.applyFilters( 'blocks/form/render', preloaded.html, true ) );
- if ( preloaded.validation ) this.setValidationErrors( preloaded.validation.errors );
+ this.setHtml(
+ acf.applyFilters(
+ 'blocks/form/render',
+ preloaded.html,
+ true
+ )
+ );
+ if ( preloaded.validation )
+ this.setValidationErrors( preloaded.validation.errors );
return;
}
@@ -1342,20 +1492,31 @@ const md5 = require( 'md5' );
acf.debug( 'fetch block form promise' );
if ( ! data ) {
- this.setHtml( `${acf.__( 'Error loading block form' )}
` );
+ this.setHtml(
+ `${ acf.__(
+ 'Error loading block form'
+ ) }
`
+ );
return;
}
if ( data.form ) {
this.setHtml(
- acf.applyFilters( 'blocks/form/render', data.form.replaceAll( data.clientId, clientId ), false )
+ acf.applyFilters(
+ 'blocks/form/render',
+ data.form.replaceAll( data.clientId, clientId ),
+ false
+ )
);
}
- if ( data.validation ) this.setValidationErrors( data.validation.errors );
+ if ( data.validation )
+ this.setValidationErrors( data.validation.errors );
if ( this.isNotNewlyAdded() ) {
- acf.debug( "Block has already shown it's invalid. The form needs to show validation errors" );
+ acf.debug(
+ "Block has already shown it's invalid. The form needs to show validation errors"
+ );
this.validate();
}
} );
@@ -1366,7 +1527,10 @@ const md5 = require( 'md5' );
this.loadState();
}
- acf.debug( 'BlockForm calling validate with state', Object.assign( {}, this ) );
+ acf.debug(
+ 'BlockForm calling validate with state',
+ Object.assign( {}, this )
+ );
super.displayValidation( this.state.$el );
}
@@ -1398,8 +1562,13 @@ const md5 = require( 'md5' );
const { $el } = this.state;
- if ( blockSupportsValidation( this.props.name ) && this.isNotNewlyAdded() ) {
- acf.debug( "Block has already shown it's invalid. The form needs to show validation errors" );
+ if (
+ blockSupportsValidation( this.props.name ) &&
+ this.isNotNewlyAdded()
+ ) {
+ acf.debug(
+ "Block has already shown it's invalid. The form needs to show validation errors"
+ );
this.validate();
}
@@ -1431,8 +1600,14 @@ const md5 = require( 'md5' );
} );
}
- if ( blockSupportsValidation( name ) && ! silent && thisBlockForm.getMode() === 'edit' ) {
- acf.debug( 'No block preview currently available. Need to trigger a validation only fetch.' );
+ if (
+ blockSupportsValidation( name ) &&
+ ! silent &&
+ thisBlockForm.getMode() === 'edit'
+ ) {
+ acf.debug(
+ 'No block preview currently available. Need to trigger a validation only fetch.'
+ );
thisBlockForm.fetch( true, data );
}
}
@@ -1508,10 +1683,20 @@ const md5 = require( 'md5' );
if ( preloaded ) {
if ( getBlockVersion( name ) == 1 ) {
- preloaded.html = '' + preloaded.html + '
';
+ preloaded.html =
+ '' +
+ preloaded.html +
+ '
';
}
- this.setHtml( acf.applyFilters( 'blocks/preview/render', preloaded.html, true ) );
- if ( preloaded.validation ) this.setValidationErrors( preloaded.validation.errors );
+ this.setHtml(
+ acf.applyFilters(
+ 'blocks/preview/render',
+ preloaded.html,
+ true
+ )
+ );
+ if ( preloaded.validation )
+ this.setValidationErrors( preloaded.validation.errors );
return;
}
@@ -1530,16 +1715,32 @@ const md5 = require( 'md5' );
delay,
} ).done( ( { data } ) => {
if ( ! data ) {
- this.setHtml( `${acf.__( 'Error previewing block' )}
` );
+ this.setHtml(
+ `${ acf.__(
+ 'Error previewing block'
+ ) }
`
+ );
return;
}
- let replaceHtml = data.preview.replaceAll( data.clientId, clientId );
+ let replaceHtml = data.preview.replaceAll(
+ data.clientId,
+ clientId
+ );
if ( getBlockVersion( name ) == 1 ) {
- replaceHtml = '' + replaceHtml + '
';
+ replaceHtml =
+ '' +
+ replaceHtml +
+ '
';
}
acf.debug( 'fetch block render promise' );
- this.setHtml( acf.applyFilters( 'blocks/preview/render', replaceHtml, false ) );
+ this.setHtml(
+ acf.applyFilters(
+ 'blocks/preview/render',
+ replaceHtml,
+ false
+ )
+ );
if ( data.validation ) {
this.setValidationErrors( data.validation.errors );
}
@@ -1582,7 +1783,9 @@ const md5 = require( 'md5' );
delay = 300;
}
- acf.debug( 'Triggering fetch from block preview shouldComponentUpdate' );
+ acf.debug(
+ 'Triggering fetch from block preview shouldComponentUpdate'
+ );
this.fetch( {
attributes: nextAttributes,
@@ -1613,7 +1816,11 @@ const md5 = require( 'md5' );
// Do action.
acf.doAction( 'render_block_preview', blockElement, attributes );
- acf.doAction( `render_block_preview/type=${ type }`, blockElement, attributes );
+ acf.doAction(
+ `render_block_preview/type=${ type }`,
+ blockElement,
+ attributes
+ );
}
componentDidRemount() {
@@ -1629,10 +1836,15 @@ const md5 = require( 'md5' );
// Update preview if data has changed since last render (changing from "edit" to "preview").
if (
- ! compareObjects( this.state.prevAttributes, this.props.attributes ) ||
+ ! compareObjects(
+ this.state.prevAttributes,
+ this.props.attributes
+ ) ||
! compareObjects( this.state.prevContext, this.props.context )
) {
- acf.debug( 'Triggering block preview fetch from componentDidRemount' );
+ acf.debug(
+ 'Triggering block preview fetch from componentDidRemount'
+ );
this.fetch();
}
@@ -1714,7 +1926,9 @@ const md5 = require( 'md5' );
const DEFAULT = 'center center';
if ( align ) {
const [ y, x ] = align.split( ' ' );
- return `${ validateVerticalAlignment( y ) } ${ validateHorizontalAlignment( x ) }`;
+ return `${ validateVerticalAlignment(
+ y
+ ) } ${ validateHorizontalAlignment( x ) }`;
}
return DEFAULT;
}
@@ -1731,12 +1945,14 @@ const md5 = require( 'md5' );
*/
function withAlignContentComponent( OriginalBlockEdit, blockType ) {
// Determine alignment vars
- let type = blockType.supports.align_content || blockType.supports.alignContent;
+ let type =
+ blockType.supports.align_content || blockType.supports.alignContent;
let AlignmentComponent;
let validateAlignment;
switch ( type ) {
case 'matrix':
- AlignmentComponent = BlockAlignmentMatrixControl || BlockAlignmentMatrixToolbar;
+ AlignmentComponent =
+ BlockAlignmentMatrixControl || BlockAlignmentMatrixToolbar;
validateAlignment = validateMatrixAlignment;
break;
default:
@@ -1747,7 +1963,9 @@ const md5 = require( 'md5' );
// Ensure alignment component exists.
if ( AlignmentComponent === undefined ) {
- console.warn( `The "${ type }" alignment component was not found.` );
+ console.warn(
+ `The "${ type }" alignment component was not found.`
+ );
return OriginalBlockEdit;
}
@@ -1811,7 +2029,10 @@ const md5 = require( 'md5' );
return (
-
+
@@ -1848,7 +2069,10 @@ const md5 = require( 'md5' );
return (
-
+
From c67effcaac0387d0340dd7c931196c8d72e4ccd1 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Tue, 4 Nov 2025 13:32:05 +0100
Subject: [PATCH 16/22] Migrate 6-6-1 except blocks
---
assets/src/js/_acf-field-color-picker.js | 7 +++-
assets/src/js/_acf-tinymce.js | 4 +-
assets/src/sass/acf-input.scss | 52 ++++++++++++++++++------
includes/blocks.php | 14 +++----
4 files changed, 54 insertions(+), 23 deletions(-)
diff --git a/assets/src/js/_acf-field-color-picker.js b/assets/src/js/_acf-field-color-picker.js
index 46807526..3974c26e 100644
--- a/assets/src/js/_acf-field-color-picker.js
+++ b/assets/src/js/_acf-field-color-picker.js
@@ -63,7 +63,12 @@
// filter
var args = acf.applyFilters( 'color_picker_args', args, this );
-
+ if ( Array.isArray( args.palettes ) && args.palettes.length > 10 ) {
+ // Add class for large custom palette styling
+ this.$control().addClass(
+ 'acf-color-picker-large-custom-palette'
+ );
+ }
// initialize
$inputText.wpColorPicker( args );
},
diff --git a/assets/src/js/_acf-tinymce.js b/assets/src/js/_acf-tinymce.js
index 3b66f8f7..d9e1637e 100644
--- a/assets/src/js/_acf-tinymce.js
+++ b/assets/src/js/_acf-tinymce.js
@@ -352,7 +352,9 @@
// Ensure textarea element is visible
// - Fixes bug in block editor when switching between "Block" and "Document" tabs.
- $( '#' + id ).show();
+ if ( ! tinymce.get( id ) ) {
+ $( '#' + id ).show();
+ }
// toggle
switchEditors.go( id, 'tmce' );
diff --git a/assets/src/sass/acf-input.scss b/assets/src/sass/acf-input.scss
index 4cf97da5..cf41acf7 100644
--- a/assets/src/sass/acf-input.scss
+++ b/assets/src/sass/acf-input.scss
@@ -655,23 +655,51 @@ html[dir=rtl] input.acf-is-prepended.acf-is-appended {
position: relative;
z-index: 1;
}
+.acf-color-picker.acf-color-picker-large-custom-palette .iris-picker {
+ display: flex;
+ flex-direction: column;
+ height: inherit !important;
+}
+
+.acf-color-picker.acf-color-picker-large-custom-palette .iris-picker .iris-picker-inner {
+ position: initial;
+ margin: 10px;
+}
+
+.acf-color-picker.acf-color-picker-large-custom-palette .iris-picker .iris-palette-container {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 4px;
+ position: relative;
+ padding-top: 10px;
+}
+
+.acf-color-picker.acf-color-picker-large-custom-palette .iris-picker .iris-palette-container .iris-palette {
+ width: 20px !important;
+ height: 20px !important;
+ margin: 0 !important;
+}
+
+.acf-color-picker.acf-hide-color-picker-color-wheel:not(.acf-color-picker-large-custom-palette) .iris-picker {
+ width: inherit !important;
+ margin-right: 25px !important;
+}
+
.acf-color-picker.acf-hide-color-picker-color-wheel .iris-picker {
- width: inherit !important;
- height: inherit !important;
- padding: 10px !important;
- margin-right: 99px;
+ height: inherit !important;
+ padding: 10px 0 !important;
}
-.acf-color-picker.acf-hide-color-picker-color-wheel .iris-palette-container {
- display: flex;
- position: relative;
- left: inherit;
- bottom: inherit;
+.acf-color-picker.acf-hide-color-picker-color-wheel .iris-picker .iris-picker-inner {
+ display: none;
}
-.acf-color-picker.acf-hide-color-picker-color-wheel .iris-square,
-.acf-color-picker.acf-hide-color-picker-color-wheel .iris-slider {
- display: none;
+.acf-color-picker.acf-hide-color-picker-color-wheel .iris-picker .iris-palette-container {
+ display: flex;
+ position: relative;
+ bottom: inherit;
+ padding-top: 0 !important;
}
/*-----------------------------------------------------------------------------
diff --git a/includes/blocks.php b/includes/blocks.php
index 4d792ba3..fddcaaa8 100644
--- a/includes/blocks.php
+++ b/includes/blocks.php
@@ -597,7 +597,10 @@ function acf_render_block_callback( $attributes, $content = '', $wp_block = null
* @return string The block HTML.
*/
function acf_rendered_block( $attributes, $content = '', $is_preview = false, $post_id = 0, $wp_block = null, $context = false, $is_ajax_render = false ) {
- if ( isset( $wp_block->block_type->acf_block_version ) && $wp_block->block_type->acf_block_version >= 3 ) {
+ $registry = WP_Block_Type_Registry::get_instance();
+ $wp_block_type = $registry->get_registered( $attributes['name'] );
+
+ if ( isset( $wp_block_type->acf_block_version ) && $wp_block_type->acf_block_version >= 3 ) {
$mode = 'preview';
$form = false;
} else {
@@ -1083,15 +1086,8 @@ function acf_ajax_fetch_block() {
$content = '';
$is_preview = true;
- $registry = WP_Block_Type_Registry::get_instance();
- $wp_block_type = $registry->get_registered( $block['name'] );
-
- // We need to match what gets automatically passed to acf_rendered_block by WP core.
- $wp_block = new stdClass();
- $wp_block->block_type = $wp_block_type;
-
// Render and store HTML.
- $response['preview'] = acf_rendered_block( $block, $content, $is_preview, $post_id, $wp_block, $context, true );
+ $response['preview'] = acf_rendered_block( $block, $content, $is_preview, $post_id, null, $context, true );
}
// Send response.
From 40843de64c81653b88735abdd5eb7041834cf2a9 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Wed, 5 Nov 2025 13:57:27 +0100
Subject: [PATCH 17/22] Add 6.6.1 blocks changes
---
.../js/pro/blocks-v3/components/block-edit.js | 141 ++++++++++++------
.../js/pro/blocks-v3/utils/post-locking.js | 64 +++++---
2 files changed, 137 insertions(+), 68 deletions(-)
diff --git a/assets/src/js/pro/blocks-v3/components/block-edit.js b/assets/src/js/pro/blocks-v3/components/block-edit.js
index 5fb0c842..498d56cd 100644
--- a/assets/src/js/pro/blocks-v3/components/block-edit.js
+++ b/assets/src/js/pro/blocks-v3/components/block-edit.js
@@ -5,7 +5,13 @@
*/
import md5 from 'md5';
-import { useState, useEffect, useRef, createPortal } from '@wordpress/element';
+import {
+ useState,
+ useEffect,
+ useRef,
+ createPortal,
+ useMemo,
+} from '@wordpress/element';
import {
BlockControls,
@@ -27,8 +33,29 @@ import {
lockPostSaving,
unlockPostSaving,
sortObjectKeys,
+ lockPostSavingByName,
} from '../utils/post-locking';
+/**
+ * InspectorBlockFormContainer
+ * Small helper component that manages the inspector panel container ref
+ * Sets the current form container when the inspector panel is available
+ *
+ * @param {Object} props
+ * @param {React.RefObject} props.inspectorBlockFormRef - Ref to inspector container
+ * @param {Function} props.setCurrentBlockFormContainer - Setter for current container
+ */
+const InspectorBlockFormContainer = ( {
+ inspectorBlockFormRef,
+ setCurrentBlockFormContainer,
+} ) => {
+ useEffect( () => {
+ setCurrentBlockFormContainer( inspectorBlockFormRef.current );
+ }, [] );
+
+ return
;
+};
+
/**
* Main BlockEdit component wrapper
* Manages block data fetching and initial setup
@@ -58,11 +85,18 @@ export const BlockEdit = ( props ) => {
);
const [ userHasInteractedWithForm, setUserHasInteractedWithForm ] =
useState( false );
+ const [ hasFetchedOnce, setHasFetchedOnce ] = useState( false );
+ const [ ajaxRequest, setAjaxRequest ] = useState();
const acfFormRef = useRef( null );
const previewRef = useRef( null );
const debounceRef = useRef( null );
+ const attributesWithoutError = useMemo( () => {
+ const { hasAcfError, ...rest } = attributes;
+ return rest;
+ }, [ attributes ] );
+
/**
* Fetches block data from server (form HTML, preview HTML, validation)
*
@@ -80,6 +114,11 @@ export const BlockEdit = ( props ) => {
} ) {
if ( ! theAttributes ) return;
+ // NEW: Abort any pending request
+ if ( ajaxRequest ) {
+ ajaxRequest.abort();
+ }
+
// Generate hash of attributes for preload cache lookup
const attributesHash = generateAttributesHash( theAttributes, context );
@@ -106,12 +145,10 @@ export const BlockEdit = ( props ) => {
const blockData = { ...theAttributes };
- wp.data
- .dispatch( 'core/editor' )
- .lockPostSaving( 'acf-fetching-block' );
+ lockPostSavingByName( 'acf-fetching-block' );
// Fetch block data via AJAX
- $.ajax( {
+ const request = $.ajax( {
url: acf.get( 'ajaxurl' ),
dataType: 'json',
type: 'post',
@@ -125,9 +162,7 @@ export const BlockEdit = ( props ) => {
} ),
} )
.done( ( response ) => {
- wp.data
- .dispatch( 'core/editor' )
- .unlockPostSaving( 'acf-fetching-block' );
+ unlockPostSavingByName( 'acf-fetching-block' );
setBlockFormHtml( response.data.form );
@@ -158,12 +193,14 @@ export const BlockEdit = ( props ) => {
} else {
setValidationErrors( null );
}
+
+ setHasFetchedOnce( true );
} )
.fail( function () {
- wp.data
- .dispatch( 'core/editor' )
- .unlockPostSaving( 'acf-fetching-block' );
+ setHasFetchedOnce( true );
+ unlockPostSavingByName( 'acf-fetching-block' );
} );
+ setAjaxRequest( request );
}
/**
@@ -332,29 +369,37 @@ export const BlockEdit = ( props ) => {
clearTimeout( debounceRef.current );
debounceRef.current = setTimeout( () => {
- handleFormDataUpdate();
+ const parsedData = JSON.parse( theSerializedAcfData );
+
+ if ( ! parsedData ) {
+ return void fetchBlockData( {
+ theAttributes: attributesWithoutError,
+ theClientId: clientId,
+ theContext: context,
+ isSelected: isSelected,
+ } );
+ }
+
+ if (
+ theSerializedAcfData ===
+ JSON.stringify( attributesWithoutError.data )
+ ) {
+ return void fetchBlockData( {
+ theAttributes: attributesWithoutError,
+ theClientId: clientId,
+ theContext: context,
+ isSelected: isSelected,
+ } );
+ }
+
+ // Use original attributes (with hasAcfError) when updating
+ const updatedAttributes = {
+ ...attributes, // ← Keep this as 'attributes', not 'attributesWithoutError'
+ data: { ...parsedData },
+ };
+ setAttributes( updatedAttributes );
}, 200 );
- }, [ theSerializedAcfData ] );
-
- /**
- * Updates block attributes when form data changes
- */
- function handleFormDataUpdate() {
- const parsedData = JSON.parse( theSerializedAcfData );
- if ( ! parsedData ) return;
- if ( theSerializedAcfData === JSON.stringify( attributes.data ) )
- return;
-
- const updatedAttributes = { ...attributes, data: { ...parsedData } };
- setAttributes( updatedAttributes );
-
- fetchBlockData( {
- theAttributes: updatedAttributes,
- theClientId: clientId,
- theContext: context,
- isSelected: isSelected,
- } );
- }
+ }, [ theSerializedAcfData, attributesWithoutError ] );
// Trigger ACF actions when preview is rendered
useEffect( () => {
@@ -385,6 +430,7 @@ export const BlockEdit = ( props ) => {
userHasInteractedWithForm={ userHasInteractedWithForm }
setUserHasInteractedWithForm={ setUserHasInteractedWithForm }
previewRef={ previewRef }
+ hasFetchedOnce={ hasFetchedOnce }
/>
);
};
@@ -410,10 +456,10 @@ function BlockEditInner( props ) {
blockFetcher,
userHasInteractedWithForm,
previewRef,
+ hasFetchedOnce,
} = props;
const { clientId } = useBlockEditContext();
- const invisibleFormContainerRef = useRef();
const inspectorControlsRef = useRef();
const [ isModalOpen, setIsModalOpen ] = useState( false );
const modalFormContainerRef = useRef();
@@ -478,7 +524,10 @@ function BlockEditInner( props ) {
{ /* Inspector panel container */ }
-
+
{ /* Render form via portal when container is available */ }
@@ -491,12 +540,14 @@ function BlockEditInner( props ) {
clientId={ clientId }
blockFormHtml={ blockFormHtml }
onMount={ () => {
- blockFetcher( {
- theAttributes: attributes,
- theClientId: clientId,
- theContext: context,
- isSelected: isSelected,
- } );
+ if ( ! hasFetchedOnce ) {
+ blockFetcher( {
+ theAttributes: attributes,
+ theClientId: clientId,
+ theContext: context,
+ isSelected: isSelected,
+ } );
+ }
} }
onChange={ function ( $form ) {
const serializedData = acf.serialize(
@@ -524,15 +575,7 @@ function BlockEditInner( props ) {
>,
currentFormContainer || inspectorControlsRef.current
) }
-
- { /* Hidden container for form when not in inspector/modal */ }
<>
-
-
{ /* Modal for editing block fields */ }
{ isModalOpen && (
{
- if ( wp.data.dispatch( 'core/editor' ) ) {
- wp.data
- .dispatch( 'core/editor' )
- .lockPostSaving( 'acf/block/' + clientId );
+ const dispatch = wp.data.dispatch( 'core/editor' );
+ if ( dispatch ) {
+ dispatch.lockPostSaving( 'acf/block/' + clientId );
}
};
/**
- * Unlocks post saving in the WordPress editor
- * Called when block operations are complete
+ * Unlocks post saving in the WordPress editor for a specific block
+ * Called when block operations are complete for a specific block instance
*
* @param {string} clientId - The block's client ID
*/
export const unlockPostSaving = ( clientId ) => {
- if ( wp.data.dispatch( 'core/editor' ) ) {
- wp.data
- .dispatch( 'core/editor' )
- .unlockPostSaving( 'acf/block/' + clientId );
+ const dispatch = wp.data.dispatch( 'core/editor' );
+ if ( dispatch ) {
+ dispatch.unlockPostSaving( 'acf/block/' + clientId );
}
};
/**
- * Sorts an object's keys alphabetically
+ * Locks post saving with a custom lock name
+ * Used for global operations that aren't tied to a specific block
+ *
+ * @param {string} lockName - The name of the lock
+ */
+export const lockPostSavingByName = ( lockName ) => {
+ const dispatch = wp.data.dispatch( 'core/editor' );
+ if ( dispatch ) {
+ dispatch.lockPostSaving( 'acf/block/' + lockName );
+ }
+};
+
+/**
+ * Unlocks post saving with a custom lock name
+ * Used for global operations that aren't tied to a specific block
+ *
+ * @param {string} lockName - The name of the lock
+ */
+export const unlockPostSavingByName = ( lockName ) => {
+ const dispatch = wp.data.dispatch( 'core/editor' );
+ if ( dispatch ) {
+ dispatch.unlockPostSaving( 'acf/block/' + lockName );
+ }
+};
+
+/**
+ * Sorts an object's keys alphabetically and returns a new object
* Used for consistent object serialization and comparison
+ * Ensures that objects with same properties in different order produce same hash
*
* @param {Object} obj - Object to sort
- * @returns {Object} - New object with sorted keys
+ * @returns {Object} - New object with sorted keys in alphabetical order
*/
-export const sortObjectKeys = ( obj ) =>
- Object.keys( obj )
+export const sortObjectKeys = ( obj ) => {
+ return Object.keys( obj )
.sort()
- .reduce( ( result, key ) => {
- result[ key ] = obj[ key ];
- return result;
+ .reduce( ( sortedObj, key ) => {
+ sortedObj[ key ] = obj[ key ];
+ return sortedObj;
}, {} );
+};
From c4e5193973e46133d9fdacb912127152485aee05 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Thu, 6 Nov 2025 21:55:45 +0100
Subject: [PATCH 18/22] Backport 6.6.2
---
assets/src/js/_acf-validation.js | 19 +-
assets/src/js/_field-group-field.js | 6 +
.../js/pro/blocks-v3/components/block-edit.js | 196 +++++++++++++++---
.../js/pro/blocks-v3/components/block-form.js | 2 +
.../blocks-v3/components/block-placeholder.js | 32 ++-
assets/src/sass/acf-input.scss | 4 +
assets/src/sass/pro/_blocks.scss | 51 +++++
includes/blocks.php | 34 +--
package-lock.json | 34 +--
package.json | 3 +-
10 files changed, 297 insertions(+), 84 deletions(-)
diff --git a/assets/src/js/_acf-validation.js b/assets/src/js/_acf-validation.js
index 556e94cc..7723ab07 100644
--- a/assets/src/js/_acf-validation.js
+++ b/assets/src/js/_acf-validation.js
@@ -1081,7 +1081,6 @@
// Backup vars.
var _this = this;
var _args = arguments;
-
// Perform validation within a Promise.
return new Promise( function ( resolve, reject ) {
// Bail early if is autosave or preview.
@@ -1140,6 +1139,7 @@
// Recursive function to check all blocks (including nested innerBlocks) for ACF validation errors
function checkBlocksForErrors( blocks ) {
+ const errors = [];
return new Promise( function ( resolve ) {
// Iterate through each block
blocks.forEach( ( block ) => {
@@ -1167,13 +1167,8 @@
.togglePublishSidebar();
}
- // Get the block's client ID
- const blockClientId = block.clientId;
-
- // Select the block with the error in the editor
- wp.data
- .dispatch( 'core/block-editor' )
- .selectBlock( blockClientId );
+ // Add block to errors array
+ errors.push( block );
// Dispatch a custom event to notify about the block with validation error
document.dispatchEvent(
@@ -1197,6 +1192,14 @@
}
} );
+ // If errors were found, select the first one
+ if ( errors.length > 0 ) {
+ const blockClientId = errors[ 0 ].clientId;
+ wp.data
+ .dispatch( 'core/block-editor' )
+ .selectBlock( blockClientId );
+ }
+
// No errors found, resolve with false
return resolve( false );
} );
diff --git a/assets/src/js/_field-group-field.js b/assets/src/js/_field-group-field.js
index 0c04f54f..a00417e3 100644
--- a/assets/src/js/_field-group-field.js
+++ b/assets/src/js/_field-group-field.js
@@ -698,6 +698,12 @@
forceSanitize = true;
}
+ forceSanitize = acf.applyFilters(
+ 'convert_field_name_to_lowercase',
+ forceSanitize,
+ this
+ );
+
// Sanitize the input value (force if needed)
const sanitized = acf.strSanitize( $el.val(), forceSanitize );
diff --git a/assets/src/js/pro/blocks-v3/components/block-edit.js b/assets/src/js/pro/blocks-v3/components/block-edit.js
index 498d56cd..1827c5c0 100644
--- a/assets/src/js/pro/blocks-v3/components/block-edit.js
+++ b/assets/src/js/pro/blocks-v3/components/block-edit.js
@@ -11,6 +11,8 @@ import {
useRef,
createPortal,
useMemo,
+ Component,
+ createContext,
} from '@wordpress/element';
import {
@@ -20,6 +22,7 @@ import {
useBlockEditContext,
} from '@wordpress/block-editor';
import {
+ Button,
ToolbarGroup,
ToolbarButton,
Placeholder,
@@ -36,6 +39,92 @@ import {
lockPostSavingByName,
} from '../utils/post-locking';
+// Error Boundary Context
+const ErrorBoundaryContext = createContext( null );
+
+// Error Boundary Component
+class ErrorBoundary extends Component {
+ constructor( props ) {
+ super( props );
+ this.resetErrorBoundary = this.resetErrorBoundary.bind( this );
+ this.state = { didCatch: false, error: null };
+ }
+
+ static getDerivedStateFromError( error ) {
+ return { didCatch: true, error: error };
+ }
+
+ resetErrorBoundary() {
+ const { error } = this.state;
+ if ( error !== null ) {
+ this.setState( { didCatch: false, error: null } );
+ }
+ }
+
+ componentDidCatch( error, errorInfo ) {
+ acf.debug( 'Block preview error caught:', error, errorInfo );
+ }
+
+ render() {
+ const { children, fallbackRender, FallbackComponent, fallback } =
+ this.props;
+ const { didCatch, error } = this.state;
+
+ let content = children;
+
+ if ( didCatch ) {
+ const errorProps = {
+ error: error,
+ resetErrorBoundary: this.resetErrorBoundary,
+ };
+
+ if ( typeof fallbackRender === 'function' ) {
+ content = fallbackRender( errorProps );
+ } else if ( FallbackComponent ) {
+ content = ;
+ } else if ( fallback !== undefined ) {
+ content = fallback;
+ } else {
+ throw error;
+ }
+ }
+
+ return (
+
+ { content }
+
+ );
+ }
+}
+
+// Fallback component to show when preview errors
+const BlockPreviewErrorFallback = ( {
+ setBlockFormModalOpen,
+ blockLabel,
+ error,
+} ) => {
+ let errorMessage = null;
+
+ if ( error ) {
+ acf.debug( 'Block preview error:', error );
+ errorMessage = acf.__( 'Error previewing block v3' );
+ }
+
+ return (
+
+ );
+};
+
/**
* InspectorBlockFormContainer
* Small helper component that manages the inspector panel container ref
@@ -76,13 +165,31 @@ export const BlockEdit = ( props ) => {
const shouldValidate = blockType.validate;
const { clientId } = useBlockEditContext();
- const [ validationErrors, setValidationErrors ] = useState( null );
+ const preloadedData = useMemo( () => {
+ return checkPreloadedData(
+ generateAttributesHash( attributes, context ),
+ clientId,
+ isSelected
+ );
+ }, [] );
+
+ const [ validationErrors, setValidationErrors ] = useState( () => {
+ return preloadedData?.validation?.errors ?? null;
+ } );
+
const [ showValidationErrors, setShowValidationErrors ] = useState( null );
const [ theSerializedAcfData, setTheSerializedAcfData ] = useState( null );
const [ blockFormHtml, setBlockFormHtml ] = useState( '' );
- const [ blockPreviewHtml, setBlockPreviewHtml ] = useState(
- 'acf-block-preview-loading'
- );
+ const [ blockPreviewHtml, setBlockPreviewHtml ] = useState( () => {
+ if ( preloadedData?.html ) {
+ return acf.applyFilters(
+ 'blocks/preview/render',
+ preloadedData.html,
+ true
+ );
+ }
+ return 'acf-block-preview-loading';
+ } );
const [ userHasInteractedWithForm, setUserHasInteractedWithForm ] =
useState( false );
const [ hasFetchedOnce, setHasFetchedOnce ] = useState( false );
@@ -353,6 +460,7 @@ export const BlockEdit = ( props ) => {
'acf/block/has-error',
handleErrorEvent
);
+ unlockPostSaving( clientId );
};
}, [] );
@@ -486,6 +594,17 @@ function BlockEditInner( props ) {
}
}, [ isSelected, inspectorControlsRef, inspectorControlsRef.current ] );
+ useEffect( () => {
+ if (
+ isSelected &&
+ validationErrors &&
+ showValidationErrors &&
+ blockType?.hide_fields_in_sidebar
+ ) {
+ setIsModalOpen( true );
+ }
+ }, [ isSelected, showValidationErrors, validationErrors, blockType ] );
+
// Build block CSS classes
let blockClasses = 'acf-block-component acf-block-body';
blockClasses += ' acf-block-preview';
@@ -524,6 +643,17 @@ function BlockEditInner( props ) {
{ /* Inspector panel container */ }
+
+ {
+ setIsModalOpen( true );
+ } }
+ >
+ { acf.__( 'Open Expanded Editor' ) }
+
+
>,
currentFormContainer || inspectorControlsRef.current
@@ -601,26 +737,38 @@ function BlockEditInner( props ) {
blockPreviewHtml={ blockPreviewHtml }
blockProps={ blockProps }
>
- { /* Show placeholder when no HTML */ }
- { blockPreviewHtml === 'acf-block-preview-no-html' ? (
-
- ) : null }
-
- { /* Show spinner while loading */ }
- { blockPreviewHtml === 'acf-block-preview-loading' && (
-
-
-
- ) }
-
- { /* Render actual preview HTML */ }
- { blockPreviewHtml !== 'acf-block-preview-loading' &&
- blockPreviewHtml !== 'acf-block-preview-no-html' &&
- blockPreviewHtml &&
- acf.parseJSX( blockPreviewHtml ) }
+ (
+
+ ) }
+ >
+ { /* Show placeholder when no HTML */ }
+ { blockPreviewHtml === 'acf-block-preview-no-html' ? (
+
+ ) : null }
+
+ { /* Show spinner while loading */ }
+ { blockPreviewHtml === 'acf-block-preview-loading' && (
+
+
+
+ ) }
+
+ { /* Render actual preview HTML */ }
+ { blockPreviewHtml !== 'acf-block-preview-loading' &&
+ blockPreviewHtml !== 'acf-block-preview-no-html' &&
+ blockPreviewHtml &&
+ acf.parseJSX( blockPreviewHtml ) }
+
>
>
diff --git a/assets/src/js/pro/blocks-v3/components/block-form.js b/assets/src/js/pro/blocks-v3/components/block-form.js
index 7baf6ec8..be551aec 100644
--- a/assets/src/js/pro/blocks-v3/components/block-form.js
+++ b/assets/src/js/pro/blocks-v3/components/block-form.js
@@ -34,6 +34,7 @@ export const BlockForm = ( {
acfFormRef,
userHasInteractedWithForm,
attributes,
+ hideFieldsInSidebar,
} ) => {
const [ formHtml, setFormHtml ] = useState( blockFormHtml );
const [ pendingChange, setPendingChange ] = useState( false );
@@ -272,6 +273,7 @@ export const BlockForm = ( {
(
-
} label={ blockLabel }>
-
{
- setBlockFormModalOpen( true );
- } }
+export const BlockPlaceholder = ( {
+ setBlockFormModalOpen,
+ blockLabel,
+ instructions,
+} ) => {
+ return (
+ }
+ label={ blockLabel }
+ instructions={ instructions }
>
- { acf.__( 'Edit Block' ) }
-
-
-);
+
{
+ setBlockFormModalOpen( true );
+ } }
+ >
+ { acf.__( 'Edit Block' ) }
+
+
+ );
+};
diff --git a/assets/src/sass/acf-input.scss b/assets/src/sass/acf-input.scss
index cf41acf7..2300b8b4 100644
--- a/assets/src/sass/acf-input.scss
+++ b/assets/src/sass/acf-input.scss
@@ -3371,4 +3371,8 @@ body.is-dragging-metaboxes #acf_after_title-sortables {
display: flow-root;
min-height: 60px;
margin-bottom: 3px !important;
+}
+
+.editor-sidebar__panel .is-side #poststuff .acf-postbox .postbox-header {
+ margin-top: -1px
}
\ No newline at end of file
diff --git a/assets/src/sass/pro/_blocks.scss b/assets/src/sass/pro/_blocks.scss
index 16840548..280acaff 100644
--- a/assets/src/sass/pro/_blocks.scss
+++ b/assets/src/sass/pro/_blocks.scss
@@ -243,6 +243,16 @@
position: absolute;
right: 0;
}
+
+ html[dir=rtl] .acf-block-form-modal {
+ right: auto;
+ left: 0
+ }
+
+ html[dir=rtl] .acf-block-form-modal .components-modal__header .components-button {
+ left: 0;
+ right: auto
+ }
@keyframes components-modal__appear-animation {
0% {
@@ -271,4 +281,45 @@
transform: scale(1);
}
}
+
+ @keyframes components-modal__appear-animation-rtl {
+ 0% {
+ left: -20px;
+ opacity: 0;
+ transform: scale(1)
+ }
+
+ to {
+ left: 0;
+ opacity: 1;
+ transform: scale(1)
+ }
+ }
+
+ @keyframes components-modal__disappear-animation-rtl {
+ 0% {
+ left: 0;
+ opacity: 1;
+ transform: scale(1)
+ }
+
+ to {
+ left: -20px;
+ opacity: 0;
+ transform: scale(1)
+ }
+ }
+
+ html[dir=rtl] .acf-block-form-modal.components-modal__frame {
+ animation-name: components-modal__appear-animation-rtl !important
+ }
+
+ html[dir=rtl] .acf-block-form-modal.is-closing {
+ animation-name: components-modal__disappear-animation-rtl !important
+ }
+}
+
+.acf-blocks-open-expanded-editor-btn.has-text.has-icon {
+ width: 100%;
+ justify-content: center
}
\ No newline at end of file
diff --git a/includes/blocks.php b/includes/blocks.php
index fddcaaa8..baea5c0d 100644
--- a/includes/blocks.php
+++ b/includes/blocks.php
@@ -115,14 +115,15 @@ function acf_handle_json_block_registration( $settings, $metadata ) {
// Map custom SCF properties from the SCF key, with localization.
$property_mappings = array(
- 'renderCallback' => 'render_callback',
- 'renderTemplate' => 'render_template',
- 'mode' => 'mode',
- 'blockVersion' => 'acf_block_version',
- 'postTypes' => 'post_types',
- 'validate' => 'validate',
- 'validateOnLoad' => 'validate_on_load',
- 'usePostMeta' => 'use_post_meta',
+ 'renderCallback' => 'render_callback',
+ 'renderTemplate' => 'render_template',
+ 'mode' => 'mode',
+ 'blockVersion' => 'acf_block_version',
+ 'postTypes' => 'post_types',
+ 'validate' => 'validate',
+ 'validateOnLoad' => 'validate_on_load',
+ 'usePostMeta' => 'use_post_meta',
+ 'hideFieldsInSidebar' => 'hide_fields_in_sidebar',
);
$textdomain = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : 'secure-custom-fields';
$i18n_schema = get_block_metadata_i18n_schema();
@@ -858,14 +859,17 @@ function acf_enqueue_block_assets() {
// Localize text.
acf_localize_text(
array(
- 'Switch to Edit' => __( 'Switch to Edit', 'secure-custom-fields' ),
- 'Switch to Preview' => __( 'Switch to Preview', 'secure-custom-fields' ),
- 'Change content alignment' => __( 'Change content alignment', 'secure-custom-fields' ),
- 'Error previewing block' => __( 'An error occurred when loading the preview for this block.', 'secure-custom-fields' ),
- 'Error loading block form' => __( 'An error occurred when loading the block in edit mode.', 'secure-custom-fields' ),
- 'Edit Block' => __( 'Edit Block', 'secure-custom-fields' ),
+ 'Switch to Edit' => __( 'Switch to Edit', 'secure-custom-fields' ),
+ 'Switch to Preview' => __( 'Switch to Preview', 'secure-custom-fields' ),
+ 'Change content alignment' => __( 'Change content alignment', 'secure-custom-fields' ),
+ 'Error previewing block' => __( 'An error occurred when loading the preview for this block.', 'secure-custom-fields' ),
+ 'Error loading block form' => __( 'An error occurred when loading the block in edit mode.', 'secure-custom-fields' ),
+ 'Edit Block' => __( 'Edit Block', 'secure-custom-fields' ),
+ 'Open Expanded Editor' => __( 'Open Expanded Editor', 'secure-custom-fields' ),
+ 'Error previewing block v3' => __( 'The preview for this block couldn’t be loaded. Review its content or settings for issues.', 'secure-custom-fields' ),
+ 'ACF Block' => __( 'ACF Block', 'secure-custom-fields' ),
/* translators: %s: Block type title */
- '%s settings' => __( '%s settings', 'secure-custom-fields' ),
+ '%s settings' => __( '%s settings', 'secure-custom-fields' ),
)
);
diff --git a/package-lock.json b/package-lock.json
index ae887fcb..afd23bf8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"": {
"dependencies": {
"@wordpress/icons": "^10.26.0",
- "dompurify": "3.2.7",
+ "dompurify": "3.3.0",
"md5": "^2.3.0"
},
"devDependencies": {
@@ -25,6 +25,7 @@
"husky": "^9.1.7",
"markdownlint-cli": "^0.39.0",
"mini-css-extract-plugin": "^2.9.1",
+ "prettier": "npm:wp-prettier@3.0.3",
"sass": "^1.79.5",
"sass-loader": "^16.0.2",
"sort-package-json": "^2.14.0",
@@ -6402,23 +6403,6 @@
"node": "*"
}
},
- "node_modules/@wordpress/scripts/node_modules/prettier": {
- "name": "wp-prettier",
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz",
- "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
"node_modules/@wordpress/scripts/node_modules/run-con": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.12.tgz",
@@ -9962,9 +9946,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
- "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
+ "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -17681,12 +17665,12 @@
}
},
"node_modules/prettier": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
- "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "name": "wp-prettier",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz",
+ "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
diff --git a/package.json b/package.json
index 293d48fa..6421b42f 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@wordpress/icons": "^10.26.0",
- "dompurify": "3.2.7",
+ "dompurify": "3.3.0",
"md5": "^2.3.0"
},
"devDependencies": {
@@ -31,6 +31,7 @@
"husky": "^9.1.7",
"markdownlint-cli": "^0.39.0",
"mini-css-extract-plugin": "^2.9.1",
+ "prettier": "npm:wp-prettier@3.0.3",
"sass": "^1.79.5",
"sass-loader": "^16.0.2",
"sort-package-json": "^2.14.0",
From 7de6e456fd26d791665ac8e871d6a54e26a1b410 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Fri, 7 Nov 2025 11:40:53 +0100
Subject: [PATCH 19/22] More fixes
---
assets/src/js/bindings/block-editor.js | 10 +++++-----
assets/src/js/pro/blocks-v3/components/block-edit.js | 9 +++++----
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js
index 76315566..34bed689 100644
--- a/assets/src/js/bindings/block-editor.js
+++ b/assets/src/js/bindings/block-editor.js
@@ -303,8 +303,8 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => {
};
}, 'withCustomControls' );
-addFilter(
- 'editor.BlockEdit',
- 'secure-custom-fields/with-custom-controls',
- withCustomControls
-);
+// addFilter(
+// 'editor.BlockEdit',
+// 'secure-custom-fields/with-custom-controls',
+// withCustomControls
+// );
diff --git a/assets/src/js/pro/blocks-v3/components/block-edit.js b/assets/src/js/pro/blocks-v3/components/block-edit.js
index 1827c5c0..f369713b 100644
--- a/assets/src/js/pro/blocks-v3/components/block-edit.js
+++ b/assets/src/js/pro/blocks-v3/components/block-edit.js
@@ -37,6 +37,7 @@ import {
unlockPostSaving,
sortObjectKeys,
lockPostSavingByName,
+ unlockPostSavingByName,
} from '../utils/post-locking';
// Error Boundary Context
@@ -650,9 +651,9 @@ function BlockEditInner( props ) {
onClick={ () => {
setIsModalOpen( true );
} }
- >
- { acf.__( 'Open Expanded Editor' ) }
-
+ text={ acf.__( 'Open Expanded Editor' ) }
+ icon="edit"
+ />
Date: Fri, 7 Nov 2025 13:04:47 +0100
Subject: [PATCH 20/22] Add unit test
---
.../js/pro/blocks-v3/components/block-edit.js | 89 +----
.../blocks-v3/components/error-boundary.js | 93 +++++
jest.config.js | 19 ++
package-lock.json | 260 ++++++++++++++
package.json | 5 +
.../block-edit-error-boundary.test.js | 317 ++++++++++++++++++
tests/js/setup-tests.js | 18 +
7 files changed, 713 insertions(+), 88 deletions(-)
create mode 100644 assets/src/js/pro/blocks-v3/components/error-boundary.js
create mode 100644 jest.config.js
create mode 100644 tests/js/blocks-v3/block-edit-error-boundary.test.js
create mode 100644 tests/js/setup-tests.js
diff --git a/assets/src/js/pro/blocks-v3/components/block-edit.js b/assets/src/js/pro/blocks-v3/components/block-edit.js
index f369713b..6a4ba378 100644
--- a/assets/src/js/pro/blocks-v3/components/block-edit.js
+++ b/assets/src/js/pro/blocks-v3/components/block-edit.js
@@ -11,8 +11,6 @@ import {
useRef,
createPortal,
useMemo,
- Component,
- createContext,
} from '@wordpress/element';
import {
@@ -32,6 +30,7 @@ import {
import { BlockPlaceholder } from './block-placeholder';
import { BlockForm } from './block-form';
import { BlockPreview } from './block-preview';
+import { ErrorBoundary, BlockPreviewErrorFallback } from './error-boundary';
import {
lockPostSaving,
unlockPostSaving,
@@ -40,92 +39,6 @@ import {
unlockPostSavingByName,
} from '../utils/post-locking';
-// Error Boundary Context
-const ErrorBoundaryContext = createContext( null );
-
-// Error Boundary Component
-class ErrorBoundary extends Component {
- constructor( props ) {
- super( props );
- this.resetErrorBoundary = this.resetErrorBoundary.bind( this );
- this.state = { didCatch: false, error: null };
- }
-
- static getDerivedStateFromError( error ) {
- return { didCatch: true, error: error };
- }
-
- resetErrorBoundary() {
- const { error } = this.state;
- if ( error !== null ) {
- this.setState( { didCatch: false, error: null } );
- }
- }
-
- componentDidCatch( error, errorInfo ) {
- acf.debug( 'Block preview error caught:', error, errorInfo );
- }
-
- render() {
- const { children, fallbackRender, FallbackComponent, fallback } =
- this.props;
- const { didCatch, error } = this.state;
-
- let content = children;
-
- if ( didCatch ) {
- const errorProps = {
- error: error,
- resetErrorBoundary: this.resetErrorBoundary,
- };
-
- if ( typeof fallbackRender === 'function' ) {
- content = fallbackRender( errorProps );
- } else if ( FallbackComponent ) {
- content = ;
- } else if ( fallback !== undefined ) {
- content = fallback;
- } else {
- throw error;
- }
- }
-
- return (
-
- { content }
-
- );
- }
-}
-
-// Fallback component to show when preview errors
-const BlockPreviewErrorFallback = ( {
- setBlockFormModalOpen,
- blockLabel,
- error,
-} ) => {
- let errorMessage = null;
-
- if ( error ) {
- acf.debug( 'Block preview error:', error );
- errorMessage = acf.__( 'Error previewing block v3' );
- }
-
- return (
-
- );
-};
-
/**
* InspectorBlockFormContainer
* Small helper component that manages the inspector panel container ref
diff --git a/assets/src/js/pro/blocks-v3/components/error-boundary.js b/assets/src/js/pro/blocks-v3/components/error-boundary.js
new file mode 100644
index 00000000..2734f80e
--- /dev/null
+++ b/assets/src/js/pro/blocks-v3/components/error-boundary.js
@@ -0,0 +1,93 @@
+/**
+ * Error Boundary Components for V3 Blocks
+ * Handles errors during block preview rendering, particularly from invalid HTML
+ */
+
+import { Component, createContext } from '@wordpress/element';
+import { BlockPlaceholder } from './block-placeholder';
+
+// Error Boundary Context
+const ErrorBoundaryContext = createContext( null );
+
+// Error Boundary Component
+export class ErrorBoundary extends Component {
+ constructor( props ) {
+ super( props );
+ this.resetErrorBoundary = this.resetErrorBoundary.bind( this );
+ this.state = { didCatch: false, error: null };
+ }
+
+ static getDerivedStateFromError( error ) {
+ return { didCatch: true, error: error };
+ }
+
+ resetErrorBoundary() {
+ const { error } = this.state;
+ if ( error !== null ) {
+ this.setState( { didCatch: false, error: null } );
+ }
+ }
+
+ componentDidCatch( error, errorInfo ) {
+ acf.debug( 'Block preview error caught:', error, errorInfo );
+ }
+
+ render() {
+ const { children, fallbackRender, FallbackComponent, fallback } =
+ this.props;
+ const { didCatch, error } = this.state;
+
+ let content = children;
+
+ if ( didCatch ) {
+ const errorProps = {
+ error: error,
+ resetErrorBoundary: this.resetErrorBoundary,
+ };
+
+ if ( typeof fallbackRender === 'function' ) {
+ content = fallbackRender( errorProps );
+ } else if ( FallbackComponent ) {
+ content = ;
+ } else if ( fallback !== undefined ) {
+ content = fallback;
+ } else {
+ throw error;
+ }
+ }
+
+ return (
+
+ { content }
+
+ );
+ }
+}
+
+// Fallback component to show when preview errors
+export const BlockPreviewErrorFallback = ( {
+ setBlockFormModalOpen,
+ blockLabel,
+ error,
+} ) => {
+ let errorMessage = null;
+
+ if ( error ) {
+ acf.debug( 'Block preview error:', error );
+ errorMessage = acf.__( 'Error previewing block v3' );
+ }
+
+ return (
+
+ );
+};
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..b8dfd14c
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,19 @@
+/**
+ * Jest configuration for unit tests
+ */
+module.exports = {
+ ...require( '@wordpress/scripts/config/jest-unit.config' ),
+ testMatch: [ '**/tests/js/**/*.test.js', '**/tests/js/**/*.test.jsx' ],
+ setupFilesAfterEnv: [ '/tests/js/setup-tests.js' ],
+ testEnvironment: 'jsdom',
+ moduleNameMapper: {
+ '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
+ },
+ collectCoverageFrom: [
+ 'assets/src/js/**/*.{js,jsx}',
+ '!assets/src/js/**/*.min.js',
+ '!**/node_modules/**',
+ '!**/vendor/**',
+ ],
+ transformIgnorePatterns: [ 'node_modules/(?!(react-jsx-parser)/)' ],
+};
diff --git a/package-lock.json b/package-lock.json
index afd23bf8..e0f7e4dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,8 @@
"@babel/core": "^7.25.8",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
+ "@testing-library/jest-dom": "^6.1.5",
+ "@testing-library/react": "^14.1.2",
"@wordpress/dependency-extraction-webpack-plugin": "^6.20.0",
"@wordpress/e2e-test-utils-playwright": "^1.20.0",
"@wordpress/icons": "^10.26.0",
@@ -23,6 +25,7 @@
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"husky": "^9.1.7",
+ "identity-obj-proxy": "^3.0.0",
"markdownlint-cli": "^0.39.0",
"mini-css-extract-plugin": "^2.9.1",
"prettier": "npm:wp-prettier@3.0.3",
@@ -39,6 +42,13 @@
"npm": ">=10.8.1"
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
+ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -4578,6 +4588,117 @@
"url": "https://github.com/sponsors/gregberge"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
+ "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.1.3",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "deep-equal": "^2.0.5"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/dom/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
+ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "14.3.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz",
+ "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^9.0.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -4605,6 +4726,13 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -9309,6 +9437,13 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -9655,6 +9790,39 @@
}
}
},
+ "node_modules/deep-equal": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+ "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.5",
+ "es-get-iterator": "^1.1.3",
+ "get-intrinsic": "^1.2.2",
+ "is-arguments": "^1.1.1",
+ "is-array-buffer": "^3.0.2",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.1",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -9887,6 +10055,13 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -10275,6 +10450,27 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-get-iterator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "is-arguments": "^1.1.1",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.7",
+ "isarray": "^2.0.5",
+ "stop-iteration-iterator": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/es-iterator-helpers": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
@@ -12442,6 +12638,13 @@
"node": ">=6"
}
},
+ "node_modules/harmony-reflect": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
+ "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
+ "dev": true,
+ "license": "(Apache-2.0 OR MPL-1.1)"
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -12865,6 +13068,19 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/identity-obj-proxy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
+ "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "harmony-reflect": "^1.4.6"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -13134,6 +13350,23 @@
"node": ">=8"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
+ "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -15174,6 +15407,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -16098,6 +16341,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-is": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
diff --git a/package.json b/package.json
index 6421b42f..dcaf2883 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,8 @@
"sort-package-json": "sort-package-json",
"test:e2e": "wp-scripts test-playwright",
"test:e2e:debug": "wp-scripts test-playwright --debug",
+ "test:unit": "wp-scripts test-unit-js",
+ "test:unit:watch": "wp-scripts test-unit-js --watch",
"watch": "webpack --watch"
},
"dependencies": {
@@ -19,6 +21,8 @@
"@babel/core": "^7.25.8",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
+ "@testing-library/jest-dom": "^6.1.5",
+ "@testing-library/react": "^14.1.2",
"@wordpress/dependency-extraction-webpack-plugin": "^6.20.0",
"@wordpress/e2e-test-utils-playwright": "^1.20.0",
"@wordpress/icons": "^10.26.0",
@@ -29,6 +33,7 @@
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"husky": "^9.1.7",
+ "identity-obj-proxy": "^3.0.0",
"markdownlint-cli": "^0.39.0",
"mini-css-extract-plugin": "^2.9.1",
"prettier": "npm:wp-prettier@3.0.3",
diff --git a/tests/js/blocks-v3/block-edit-error-boundary.test.js b/tests/js/blocks-v3/block-edit-error-boundary.test.js
new file mode 100644
index 00000000..b294c4f7
--- /dev/null
+++ b/tests/js/blocks-v3/block-edit-error-boundary.test.js
@@ -0,0 +1,317 @@
+/**
+ * Unit tests for ErrorBoundary component in V3 Blocks
+ * Tests the fallback behavior when block preview rendering fails due to invalid HTML
+ */
+
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+// Mock BlockPlaceholder before importing the components that use it
+jest.mock(
+ '../../../assets/src/js/pro/blocks-v3/components/block-placeholder',
+ () => ( {
+ BlockPlaceholder: ( { blockLabel, instructions } ) => (
+
+
{ blockLabel }
+ { instructions && (
+
{ instructions }
+ ) }
+
+ ),
+ } )
+);
+
+import {
+ ErrorBoundary,
+ BlockPreviewErrorFallback,
+} from '../../../assets/src/js/pro/blocks-v3/components/error-boundary';
+
+// Mock the acf global object
+global.acf = {
+ __: jest.fn( ( key ) => {
+ const translations = {
+ 'Error previewing block v3':
+ "The preview for this block couldn't be loaded. Review its content or settings for issues.",
+ 'ACF Block': 'ACF Block',
+ };
+ return translations[ key ] || key;
+ } ),
+ debug: jest.fn(),
+ parseJSX: jest.fn(),
+};
+
+// Component that throws an error (simulating invalid HTML parsing)
+const ThrowError = ( { shouldThrow } ) => {
+ if ( shouldThrow ) {
+ throw new Error( 'Invalid HTML: Unclosed tag detected' );
+ }
+ return Valid preview content
;
+};
+
+describe( 'ErrorBoundary Component', () => {
+ beforeEach( () => {
+ jest.clearAllMocks();
+ // Suppress console.error for these tests since we're intentionally throwing errors
+ jest.spyOn( console, 'error' ).mockImplementation( () => {} );
+ } );
+
+ afterEach( () => {
+ console.error.mockRestore();
+ } );
+
+ test( 'renders children normally when no error occurs', () => {
+ render(
+
+
+
+ );
+
+ expect(
+ screen.getByText( 'Valid preview content' )
+ ).toBeInTheDocument();
+ } );
+
+ test( 'catches error and renders fallback when child component throws', () => {
+ const mockSetModalOpen = jest.fn();
+
+ render(
+ (
+
+ ) }
+ >
+
+
+ );
+
+ // Should render the error placeholder
+ expect( screen.getByTestId( 'block-placeholder' ) ).toBeInTheDocument();
+ expect( screen.getByTestId( 'block-label' ) ).toHaveTextContent(
+ 'Test Block'
+ );
+ expect( screen.getByTestId( 'error-message' ) ).toHaveTextContent(
+ "The preview for this block couldn't be loaded"
+ );
+ } );
+
+ test( 'calls acf.debug when error is caught', () => {
+ const mockSetModalOpen = jest.fn();
+
+ render(
+ (
+
+ ) }
+ >
+
+
+ );
+
+ // Verify debug was called
+ expect( global.acf.debug ).toHaveBeenCalledWith(
+ 'Block preview error caught:',
+ expect.any( Error ),
+ expect.any( Object )
+ );
+
+ expect( global.acf.debug ).toHaveBeenCalledWith(
+ 'Block preview error:',
+ expect.any( Error )
+ );
+ } );
+
+ test( 'displays correct error message from translation', () => {
+ const mockSetModalOpen = jest.fn();
+
+ render(
+ (
+
+ ) }
+ >
+
+
+ );
+
+ // Verify translation function was called
+ expect( global.acf.__ ).toHaveBeenCalledWith(
+ 'Error previewing block v3'
+ );
+
+ // Verify the translated message appears
+ expect( screen.getByTestId( 'error-message' ) ).toHaveTextContent(
+ "The preview for this block couldn't be loaded. Review its content or settings for issues."
+ );
+ } );
+
+ test( 'uses fallback block label when blockType title is not available', () => {
+ const mockSetModalOpen = jest.fn();
+
+ render(
+ (
+
+ ) }
+ >
+
+
+ );
+
+ expect( screen.getByTestId( 'block-label' ) ).toHaveTextContent(
+ 'ACF Block'
+ );
+ } );
+} );
+
+describe( 'BlockPreviewErrorFallback Component', () => {
+ test( 'renders placeholder with error message when error is provided', () => {
+ const mockError = new Error( 'Invalid HTML' );
+ const mockSetModalOpen = jest.fn();
+
+ render(
+
+ );
+
+ expect( screen.getByTestId( 'block-placeholder' ) ).toBeInTheDocument();
+ expect( screen.getByTestId( 'error-message' ) ).toBeInTheDocument();
+ } );
+
+ test( 'does not render error message when error is null', () => {
+ const mockSetModalOpen = jest.fn();
+
+ render(
+
+ );
+
+ expect( screen.getByTestId( 'block-placeholder' ) ).toBeInTheDocument();
+ expect(
+ screen.queryByTestId( 'error-message' )
+ ).not.toBeInTheDocument();
+ } );
+} );
+
+describe( 'Invalid HTML Scenarios', () => {
+ test( 'handles error from parseJSX with malformed HTML', () => {
+ // Simulate parseJSX throwing an error with invalid HTML
+ global.acf.parseJSX.mockImplementation( ( html ) => {
+ if ( html.includes( 'Unclosed' ) ) {
+ throw new Error( 'jQuery parsing error: Unclosed tag' );
+ }
+ return
{ html }
;
+ } );
+
+ const InvalidHTMLComponent = () => {
+ const html = '
Unclosed';
+ return global.acf.parseJSX( html );
+ };
+
+ const mockSetModalOpen = jest.fn();
+
+ render(
+ (
+
+ ) }
+ >
+
+
+ );
+
+ expect( screen.getByTestId( 'block-placeholder' ) ).toBeInTheDocument();
+ expect( screen.getByTestId( 'error-message' ) ).toHaveTextContent(
+ "The preview for this block couldn't be loaded"
+ );
+ } );
+
+ test( 'handles error from parseJSX with script injection attempt', () => {
+ global.acf.parseJSX.mockImplementation( ( html ) => {
+ if ( html.includes( '
';
+ return global.acf.parseJSX( html );
+ };
+
+ const mockSetModalOpen = jest.fn();
+
+ render(
+
(
+
+ ) }
+ >
+
+
+ );
+
+ expect( screen.getByTestId( 'block-placeholder' ) ).toBeInTheDocument();
+ expect( global.acf.debug ).toHaveBeenCalled();
+ } );
+
+ test( 'renders successfully with valid HTML', () => {
+ global.acf.parseJSX.mockImplementation( ( html ) => {
+ return
;
+ } );
+
+ const ValidHTMLComponent = () => {
+ const html = '
This is valid HTML
';
+ return global.acf.parseJSX( html );
+ };
+
+ render(
+
(
+
+ ) }
+ >
+
+
+ );
+
+ // Should not show error placeholder
+ expect(
+ screen.queryByTestId( 'block-placeholder' )
+ ).not.toBeInTheDocument();
+ } );
+} );
diff --git a/tests/js/setup-tests.js b/tests/js/setup-tests.js
new file mode 100644
index 00000000..81f07e0e
--- /dev/null
+++ b/tests/js/setup-tests.js
@@ -0,0 +1,18 @@
+/**
+ * Jest test setup file
+ * Runs before all tests to set up the testing environment
+ */
+
+// Add React to global scope
+import React from 'react';
+global.React = React;
+
+// Mock jQuery for parseJSX tests
+global.jQuery = jest.fn( ( html ) => {
+ if ( typeof html === 'string' ) {
+ // Simple mock that returns an array-like object
+ return [ { innerHTML: html, tagName: 'DIV' } ];
+ }
+ return [];
+} );
+global.$ = global.jQuery;
From d42f49ebe8b8a525dd32dc1e7aaf25ae731da6b6 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Fri, 7 Nov 2025 13:15:29 +0100
Subject: [PATCH 21/22] V3 Blocks now save default field values even if the
block wasn't interacted with before saving
---
.../js/pro/blocks-v3/components/block-form.js | 25 +++++++++++++------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/assets/src/js/pro/blocks-v3/components/block-form.js b/assets/src/js/pro/blocks-v3/components/block-form.js
index be551aec..24e744c8 100644
--- a/assets/src/js/pro/blocks-v3/components/block-form.js
+++ b/assets/src/js/pro/blocks-v3/components/block-form.js
@@ -40,24 +40,35 @@ export const BlockForm = ( {
const [ pendingChange, setPendingChange ] = useState( false );
const debounceTimer = useRef( null );
const [ userInteracted, setUserInteracted ] = useState( false );
+ const [ initialValuesCaptured, setInitialValuesCaptured ] =
+ useState( false );
// Call onMount when component first mounts
useEffect( () => {
onMount();
}, [] );
- // Trigger onChange when there's a pending change and user has interacted
+ // Trigger onChange when there's a pending change
useEffect( () => {
- if (
- pendingChange &&
- ( userHasInteractedWithForm || userInteracted )
- ) {
- onChange( pendingChange );
- setPendingChange( false );
+ if ( pendingChange ) {
+ // For the first change, capture default values even without interaction
+ if (
+ ! initialValuesCaptured ||
+ userHasInteractedWithForm ||
+ userInteracted
+ ) {
+ onChange( pendingChange );
+ setPendingChange( false );
+ if ( ! initialValuesCaptured ) {
+ setInitialValuesCaptured( true );
+ }
+ }
}
}, [
pendingChange,
userHasInteractedWithForm,
+ userInteracted,
+ initialValuesCaptured,
setPendingChange,
onChange,
] );
From f35edae3c028fa77d599dad2d7811640e424599e Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
Date: Tue, 11 Nov 2025 15:53:16 +0100
Subject: [PATCH 22/22] Add more changes
---
assets/src/js/pro/_acf-blocks.js | 10 ++-
.../js/pro/blocks-v3/components/block-edit.js | 21 ++++-
.../js/pro/blocks-v3/components/block-form.js | 19 ++---
.../blocks-v3/components/error-boundary.js | 80 +++++++++++++++----
4 files changed, 100 insertions(+), 30 deletions(-)
diff --git a/assets/src/js/pro/_acf-blocks.js b/assets/src/js/pro/_acf-blocks.js
index dc9e7db5..91748001 100644
--- a/assets/src/js/pro/_acf-blocks.js
+++ b/assets/src/js/pro/_acf-blocks.js
@@ -1138,7 +1138,6 @@ const md5 = require( 'md5' );
const client = acf.blockInstances[ this.props.clientId ] || {};
this.state = client[ this.constructor.name ] || {};
}
-
setState( state ) {
acf.blockInstances[ this.props.clientId ][ this.constructor.name ] =
{
@@ -1148,7 +1147,7 @@ const md5 = require( 'md5' );
// Update component state if subscribed.
// - Allows AJAX callback to update store without modifying state of an unmounted component.
- if ( this.subscribed ) {
+ if ( this.subscribed || acf.get( 'StrictMode' ) ) {
super.setState( state );
}
@@ -1313,9 +1312,12 @@ const md5 = require( 'md5' );
}
componentWillUnmount() {
- acf.doAction( 'unmount', this.state.$el );
+ // Only skip unmount action if in StrictMode AND component is not subscribed
+ if ( ! acf.get( 'StrictMode' ) || this.subscribed ) {
+ acf.doAction( 'unmount', this.state.$el );
+ }
- // Unsubscribe this component from state.
+ // Unsubscribe this component from state
this.subscribed = false;
}
diff --git a/assets/src/js/pro/blocks-v3/components/block-edit.js b/assets/src/js/pro/blocks-v3/components/block-edit.js
index 6a4ba378..d1dc15f9 100644
--- a/assets/src/js/pro/blocks-v3/components/block-edit.js
+++ b/assets/src/js/pro/blocks-v3/components/block-edit.js
@@ -416,7 +416,7 @@ export const BlockEdit = ( props ) => {
// Use original attributes (with hasAcfError) when updating
const updatedAttributes = {
- ...attributes, // ← Keep this as 'attributes', not 'attributesWithoutError'
+ ...attributes,
data: { ...parsedData },
};
setAttributes( updatedAttributes );
@@ -661,6 +661,25 @@ function BlockEditInner( props ) {
error={ error }
/>
) }
+ onError={ ( error, errorInfo ) => {
+ acf.debug(
+ 'Block preview error caught:',
+ error,
+ errorInfo
+ );
+ } }
+ resetKeys={ [ blockPreviewHtml ] }
+ onReset={ ( { reason, next, prev } ) => {
+ acf.debug( 'Error boundary reset:', reason );
+ if ( reason === 'keys' ) {
+ acf.debug(
+ 'Preview HTML changed from',
+ prev,
+ 'to',
+ next
+ );
+ }
+ } }
>
{ /* Show placeholder when no HTML */ }
{ blockPreviewHtml === 'acf-block-preview-no-html' ? (
diff --git a/assets/src/js/pro/blocks-v3/components/block-form.js b/assets/src/js/pro/blocks-v3/components/block-form.js
index 24e744c8..35b04b4b 100644
--- a/assets/src/js/pro/blocks-v3/components/block-form.js
+++ b/assets/src/js/pro/blocks-v3/components/block-form.js
@@ -140,14 +140,11 @@ export const BlockForm = ( {
);
}
- if ( block.attributes.hasAcfError ) {
- const errorBlockClientId = block.clientId;
- if ( errorBlockClientId !== clientId ) {
- wp.data
- .dispatch( 'core/block-editor' )
- .selectBlock( errorBlockClientId );
- return resolve( true );
- }
+ if (
+ block.attributes.hasAcfError &&
+ block.clientId !== clientId
+ ) {
+ return resolve( true );
}
} );
return resolve( false );
@@ -187,6 +184,10 @@ export const BlockForm = ( {
let isActive = true;
acf.doAction( 'remount', $form );
+ if ( ! initialValuesCaptured ) {
+ onChange( $form );
+ setInitialValuesCaptured( true );
+ }
const handleChange = () => {
onChange( $form );
@@ -213,7 +214,7 @@ export const BlockForm = ( {
if ( isActive ) {
setPendingChange( $form );
}
- }, 200 );
+ }, 300 );
};
// Observe DOM changes to detect field additions/removals
diff --git a/assets/src/js/pro/blocks-v3/components/error-boundary.js b/assets/src/js/pro/blocks-v3/components/error-boundary.js
index 2734f80e..ecc35cb3 100644
--- a/assets/src/js/pro/blocks-v3/components/error-boundary.js
+++ b/assets/src/js/pro/blocks-v3/components/error-boundary.js
@@ -1,20 +1,17 @@
-/**
- * Error Boundary Components for V3 Blocks
- * Handles errors during block preview rendering, particularly from invalid HTML
- */
-
import { Component, createContext } from '@wordpress/element';
-import { BlockPlaceholder } from './block-placeholder';
-// Error Boundary Context
-const ErrorBoundaryContext = createContext( null );
+// Create context outside the class
+export const ErrorBoundaryContext = createContext( null );
+
+// Initial state constant
+const initialState = { didCatch: false, error: null };
// Error Boundary Component
export class ErrorBoundary extends Component {
constructor( props ) {
super( props );
this.resetErrorBoundary = this.resetErrorBoundary.bind( this );
- this.state = { didCatch: false, error: null };
+ this.state = initialState;
}
static getDerivedStateFromError( error ) {
@@ -24,12 +21,47 @@ export class ErrorBoundary extends Component {
resetErrorBoundary() {
const { error } = this.state;
if ( error !== null ) {
- this.setState( { didCatch: false, error: null } );
+ // Collect all arguments passed to reset
+ const args = Array.from( arguments );
+
+ // Call optional onReset callback with context
+ if ( this.props.onReset ) {
+ this.props.onReset( {
+ args: args,
+ reason: 'imperative-api',
+ } );
+ }
+
+ this.setState( initialState );
}
}
componentDidCatch( error, errorInfo ) {
- acf.debug( 'Block preview error caught:', error, errorInfo );
+ // Call optional onError callback
+ if ( this.props.onError ) {
+ this.props.onError( error, errorInfo );
+ }
+ }
+
+ componentDidUpdate( prevProps, prevState ) {
+ const { didCatch } = this.state;
+ const { resetKeys } = this.props;
+
+ // Auto-reset if resetKeys prop changed
+ if (
+ didCatch &&
+ prevState.error !== null &&
+ hasResetKeysChanged( prevProps.resetKeys, resetKeys )
+ ) {
+ if ( this.props.onReset ) {
+ this.props.onReset( {
+ next: resetKeys,
+ prev: prevProps.resetKeys,
+ reason: 'keys',
+ } );
+ }
+ this.setState( initialState );
+ }
}
render() {
@@ -70,7 +102,14 @@ export class ErrorBoundary extends Component {
}
}
-// Fallback component to show when preview errors
+// Helper function to check if reset keys changed
+function hasResetKeysChanged( prevKeys = [], nextKeys = [] ) {
+ return (
+ prevKeys.length !== nextKeys.length ||
+ prevKeys.some( ( key, index ) => ! Object.is( key, nextKeys[ index ] ) )
+ );
+}
+
export const BlockPreviewErrorFallback = ( {
setBlockFormModalOpen,
blockLabel,
@@ -84,10 +123,19 @@ export const BlockPreviewErrorFallback = ( {
}
return (
-
}
+ label={ blockLabel }
instructions={ errorMessage }
- />
+ >
+
{
+ setBlockFormModalOpen( true );
+ } }
+ >
+ { acf.__( 'Edit Block' ) }
+
+
);
};