From 464fa1f8cf5b86ea6dc3ceda2641df1412a32780 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:26:01 +0200 Subject: [PATCH 01/18] First commit --- assets/src/js/bindings/block-editor.js | 129 +++++++++++++++++++++++++ assets/src/js/bindings/index.js | 1 + 2 files changed, 130 insertions(+) create mode 100644 assets/src/js/bindings/block-editor.js diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js new file mode 100644 index 00000000..5cad25a2 --- /dev/null +++ b/assets/src/js/bindings/block-editor.js @@ -0,0 +1,129 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody, MenuGroup, FormTokenField } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; +import { store as editorStore } from '@wordpress/editor'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'id', 'url', 'title', 'alt' ], + 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], +}; + +/** + * Gets the bindable attributes for a given block. + * + * @param {string} blockName The name of the block. + * + * @return {string[]} The bindable attributes for the block. + */ +function getBindableAttributes( blockName ) { + return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; +} + +/** + * Add custom attributes to all blocks + */ +const addCustomAttributes = ( settings ) => { + // Add custom attribute to all blocks + if ( settings.attributes ) { + settings.attributes.customAttribute = { + type: 'string', + default: '', + }; + } + return settings; +}; + +addFilter( + 'blocks.registerBlockType', + 'secure-custom-fields/add-custom-attributes', + addCustomAttributes +); + +/** + * Add custom controls to all blocks + */ +const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + const bindableAttributes = getBindableAttributes( props.name ); + + const { postType, postId } = useSelect( ( select ) => { + const { getCurrentPostType, getCurrentPostId } = + select( editorStore ); + return { + postType: getCurrentPostType(), + postId: getCurrentPostId(), + }; + }, [] ); + + const fields = useSelect( + ( select ) => { + const { getEditedEntityRecord } = select( coreDataStore ); + + if ( ! postType || ! postId ) { + return undefined; + } + + const record = getEditedEntityRecord( + 'postType', + postType, + postId + ); + return record?.acf; + }, + [ postType, postId ] + ); + + const fieldsSuggestions = Object.keys( fields ); + + return ( + <> + + + + + { bindableAttributes.map( ( attribute ) => ( + { + console.log( 'change' ); + console.log( value ); + } } + suggestions={ fieldsSuggestions } + value={ [] } + /> + ) ) } + + + + + ); + }; +}, 'withCustomControls' ); + +addFilter( + 'editor.BlockEdit', + 'secure-custom-fields/with-custom-controls', + withCustomControls +); diff --git a/assets/src/js/bindings/index.js b/assets/src/js/bindings/index.js index 278d4879..c81a6544 100644 --- a/assets/src/js/bindings/index.js +++ b/assets/src/js/bindings/index.js @@ -1 +1,2 @@ import './sources.js'; +import './block-editor.js'; From 66c9f181b351355f5ac66856a6c9c4aa8058a241 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:38:21 +0200 Subject: [PATCH 02/18] Set binding --- assets/src/js/bindings/block-editor.js | 38 ++++++++++---------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index 5cad25a2..6b34ca1c 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -3,7 +3,10 @@ */ import { addFilter } from '@wordpress/hooks'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { InspectorControls } from '@wordpress/block-editor'; +import { + InspectorControls, + useBlockBindingsUtils, +} from '@wordpress/block-editor'; import { PanelBody, MenuGroup, FormTokenField } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -28,33 +31,13 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { function getBindableAttributes( blockName ) { return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; } - -/** - * Add custom attributes to all blocks - */ -const addCustomAttributes = ( settings ) => { - // Add custom attribute to all blocks - if ( settings.attributes ) { - settings.attributes.customAttribute = { - type: 'string', - default: '', - }; - } - return settings; -}; - -addFilter( - 'blocks.registerBlockType', - 'secure-custom-fields/add-custom-attributes', - addCustomAttributes -); - /** * Add custom controls to all blocks */ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { const bindableAttributes = getBindableAttributes( props.name ); + const { updateBlockBindings } = useBlockBindingsUtils(); const { postType, postId } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPostId } = @@ -107,11 +90,18 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { label={ attribute } maxLength={ 1 } onChange={ ( value ) => { - console.log( 'change' ); - console.log( value ); + updateBlockBindings( { + [ attribute ]: { + source: 'acf/field', + args: { + key: value[ 0 ], + }, + }, + } ); } } suggestions={ fieldsSuggestions } value={ [] } + key={ `scf-field-${ attribute }` } /> ) ) } From 3606d3600009203bf7edd3170c4c3dc38530004c Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:46:53 +0200 Subject: [PATCH 03/18] fix margin --- assets/src/js/bindings/block-editor.js | 55 ++++++++++++++------------ assets/src/sass/_forms.scss | 3 ++ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index 6b34ca1c..ff874ff7 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -7,7 +7,12 @@ import { InspectorControls, useBlockBindingsUtils, } from '@wordpress/block-editor'; -import { PanelBody, MenuGroup, FormTokenField } from '@wordpress/components'; +import { + PanelBody, + BaseControl, + FormTokenField, + MenuItem, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; @@ -79,32 +84,30 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { ) } initialOpen={ true } > - - { bindableAttributes.map( ( attribute ) => ( - { - updateBlockBindings( { - [ attribute ]: { - source: 'acf/field', - args: { - key: value[ 0 ], - }, + { bindableAttributes.map( ( attribute ) => ( + { + updateBlockBindings( { + [ attribute ]: { + source: 'acf/field', + args: { + key: value[ 0 ], }, - } ); - } } - suggestions={ fieldsSuggestions } - value={ [] } - key={ `scf-field-${ attribute }` } - /> - ) ) } - + }, + } ); + } } + suggestions={ fieldsSuggestions } + value={ [] } + key={ `scf-field-${ attribute }` } + /> + ) ) } diff --git a/assets/src/sass/_forms.scss b/assets/src/sass/_forms.scss index 7cb7883d..5aa4edf3 100644 --- a/assets/src/sass/_forms.scss +++ b/assets/src/sass/_forms.scss @@ -315,3 +315,6 @@ p.submit .acf-spinner { } } +.scf-field-token-field { + margin-bottom: 2em; +} \ No newline at end of file From 07ae7d7b993e69f5db9f9233dd1247fb8fff3bfd Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Thu, 12 Jun 2025 22:17:37 +0200 Subject: [PATCH 04/18] Include it as beta feature --- assets/src/js/bindings/block-editor.js | 149 +++++++++++++----- includes/admin/beta-features.php | 51 +++--- .../class-scf-beta-feature-connect-fields.php | 38 +++++ .../class-acf-rest-types-endpoint.php | 25 +-- secure-custom-fields.php | 1 + 5 files changed, 192 insertions(+), 72 deletions(-) create mode 100644 includes/admin/beta-features/class-scf-beta-feature-connect-fields.php diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index ff874ff7..b4e4c564 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -1,23 +1,18 @@ /** * WordPress dependencies */ +import { useState, useEffect, useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { InspectorControls, useBlockBindingsUtils, } from '@wordpress/block-editor'; -import { - PanelBody, - BaseControl, - FormTokenField, - MenuItem, -} from '@wordpress/components'; +import { PanelBody, ComboboxControl, PanelRow } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; -import { store as blockEditorStore } from '@wordpress/block-editor'; const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], @@ -36,6 +31,7 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { function getBindableAttributes( blockName ) { return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; } + /** * Add custom controls to all blocks */ @@ -53,7 +49,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { }; }, [] ); - const fields = useSelect( + const fieldsGroups = useSelect( ( select ) => { const { getEditedEntityRecord } = select( coreDataStore ); @@ -66,12 +62,90 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { postType, postId ); - return record?.acf; + return record?.scf_field_groups; }, [ postType, postId ] ); - const fieldsSuggestions = Object.keys( fields ); + const currentBindings = props.attributes?.metadata?.bindings || {}; + + // Memoize the fields transformation to prevent unnecessary recalculations + const fields = useMemo( + () => + fieldsGroups?.reduce( ( acc, fieldGroup ) => { + const groupFields = + fieldGroup.fields?.map( ( field ) => ( { + ...field, + fieldGroupTitle: fieldGroup.title, + name: field.name, + label: field.label, + value: field.value, + } ) ) || []; + + return [ ...acc, ...groupFields ]; + }, [] ) || [], + [ fieldsGroups ] + ); + + // Memoize the fieldsSuggestions to avoid recreating on every render + const fieldsSuggestions = useMemo( + () => + fields.map( ( field ) => ( { + value: field.name, + label: field.label, + } ) ), + [ fields ] + ); + + // Initialize the field state with an empty object to track multiple attributes + const [ boundFields, setBoundFields ] = useState( {} ); + + // Memoize the stringified currentBindings to avoid unnecessary effect runs + const currentBindingsKey = useMemo( + () => JSON.stringify( currentBindings ), + [ currentBindings ] + ); + + // Initialize bound fields from current bindings when they change + useEffect( () => { + if ( Object.keys( currentBindings ).length > 0 ) { + const initialBoundFields = {}; + + // Extract field values from current bindings + Object.keys( currentBindings ).forEach( ( attribute ) => { + if ( currentBindings[ attribute ]?.args?.key ) { + initialBoundFields[ attribute ] = + currentBindings[ attribute ].args.key; + } + } ); + + setBoundFields( initialBoundFields ); + } + }, [ currentBindingsKey ] ); + + // Memoize the change handler to prevent creating new function on each render + const handleFieldChange = useCallback( + ( attribute, value ) => { + setBoundFields( ( prevState ) => ( { + ...prevState, + [ attribute ]: value, + } ) ); + + updateBlockBindings( { + [ attribute ]: { + source: 'acf/field', + args: { + key: value, + }, + }, + } ); + }, + [ updateBlockBindings ] + ); + + if ( fieldsSuggestions.length === 0 || ! bindableAttributes ) { + return ; + } return ( <> @@ -85,28 +159,26 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { initialOpen={ true } > { bindableAttributes.map( ( attribute ) => ( - { - updateBlockBindings( { - [ attribute ]: { - source: 'acf/field', - args: { - key: value[ 0 ], - }, - }, - } ); - } } - suggestions={ fieldsSuggestions } - value={ [] } - key={ `scf-field-${ attribute }` } - /> + + + handleFieldChange( attribute, value ) + } + key={ `scf-field-${ attribute }` } + /> + ) ) } @@ -115,8 +187,11 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { }; }, 'withCustomControls' ); -addFilter( - 'editor.BlockEdit', - 'secure-custom-fields/with-custom-controls', - withCustomControls -); +// Only register the filter if the connect_fields beta feature is enabled +if ( window.scf?.betaFeatures?.connect_fields ) { + addFilter( + 'editor.BlockEdit', + 'secure-custom-fields/with-custom-controls', + withCustomControls + ); +} diff --git a/includes/admin/beta-features.php b/includes/admin/beta-features.php index 1051fd58..9baebbe4 100644 --- a/includes/admin/beta-features.php +++ b/includes/admin/beta-features.php @@ -35,8 +35,7 @@ class SCF_Admin_Beta_Features { * @return void */ public function __construct() { - // Temporarily disabled - will be enabled when beta feature is ready - // add_action( 'admin_menu', array( $this, 'admin_menu' ), 20 ); + add_action( 'admin_menu', array( $this, 'admin_menu' ), 20 ); } /** @@ -78,26 +77,6 @@ public function get_beta_features() { return $this->beta_features; } - /** - * Localizes the beta features data. - * - * @since SCF 6.5.0 - * - * @return void - */ - public function localize_beta_features() { - $beta_features = array(); - foreach ( $this->get_beta_features() as $name => $beta_feature ) { - $beta_features[ $name ] = $beta_feature->is_enabled(); - } - - acf_localize_data( - array( - 'betaFeatures' => $beta_features, - ) - ); - } - /** * This function will add the SCF beta features menu item to the WP admin * @@ -115,7 +94,7 @@ public function admin_menu() { $page = add_submenu_page( 'edit.php?post_type=acf-field-group', __( 'Beta Features', 'secure-custom-fields' ), __( 'Beta Features', 'secure-custom-fields' ), acf_get_setting( 'capability' ), 'scf-beta-features', array( $this, 'html' ) ); add_action( 'load-' . $page, array( $this, 'load' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'localize_beta_features' ), 20 ); + add_action( 'admin_enqueue_scripts', array( $this, 'add_beta_features_script' ), 20 ); } /** @@ -155,7 +134,7 @@ public function admin_body_class( $classes ) { */ private function include_beta_features() { acf_include( 'includes/admin/beta-features/class-scf-beta-feature.php' ); - acf_include( 'includes/admin/beta-features/class-scf-beta-feature-editor-sidebar.php' ); + acf_include( 'includes/admin/beta-features/class-scf-beta-feature-connect-fields.php' ); add_action( 'scf/include_admin_beta_features', array( $this, 'register_beta_features' ) ); @@ -170,7 +149,7 @@ private function include_beta_features() { * @return void */ public function register_beta_features() { - scf_register_admin_beta_feature( 'SCF_Admin_Beta_Feature_Editor_Sidebar' ); + scf_register_admin_beta_feature( 'SCF_Admin_Beta_Feature_Connect_Fields' ); } /** @@ -255,6 +234,28 @@ public function metabox_html( $post, $metabox ) { acf_nonce_input( $beta_feature->name ); echo ''; } + + /** + * Adds the editor sidebar script to the page. + * + * @since SCF 6.5.0 + * + * @return void + */ + public function add_beta_features_script() { + // Check if the connected fields feature is enabled + + $script = 'window.scf = window.scf || {}; +window.scf = window.scf || {}; +window.scf.betaFeatures = window.scf.betaFeatures || {};'; + foreach ( $this->get_beta_features() as $name => $beta_feature ) { + if ( $beta_feature->is_enabled() ) { + $script .= sprintf( 'window.scf.betaFeatures.%s = true;', esc_js( $name ) ); + } + } + + wp_add_inline_script( 'wp-block-editor', $script, 'before' ); + } } // initialize diff --git a/includes/admin/beta-features/class-scf-beta-feature-connect-fields.php b/includes/admin/beta-features/class-scf-beta-feature-connect-fields.php new file mode 100644 index 00000000..5f09643e --- /dev/null +++ b/includes/admin/beta-features/class-scf-beta-feature-connect-fields.php @@ -0,0 +1,38 @@ +name = 'connect_fields'; + $this->title = __( 'Connect Fields', 'secure-custom-fields' ); + $this->description = __( 'Connects field to binding compatible blocks.', 'secure-custom-fields' ); + } + } +endif; diff --git a/includes/rest-api/class-acf-rest-types-endpoint.php b/includes/rest-api/class-acf-rest-types-endpoint.php index d1867cbd..78a20e64 100644 --- a/includes/rest-api/class-acf-rest-types-endpoint.php +++ b/includes/rest-api/class-acf-rest-types-endpoint.php @@ -40,8 +40,9 @@ public function register_extra_fields() { if ( ! (bool) get_option( 'scf_beta_feature_editor_sidebar_enabled', false ) ) { return; } + $post_types = get_post_types( array( 'show_in_rest' => true ) ); register_rest_field( - 'type', + $post_types, 'scf_field_groups', array( 'get_callback' => array( $this, 'get_scf_fields' ), @@ -50,16 +51,19 @@ public function register_extra_fields() { ); } - /** - * Get SCF fields for a post type. - * - * @since SCF 6.5.0 - * - * @param array $post_type_object The post type object. - * @return array Array of field data. - */ + /** + * Get SCF fields for a post type. + * + * @since 6.5.0 + * + * @param array $post_type_object The post type object. + * @return array Array of field data. + */ public function get_scf_fields( $post_type_object ) { - $post_type = $post_type_object['slug']; + if ( ! isset( $post_type_object['id'] ) || ! isset( $post_type_object['type'] ) ) { + return array(); + } + $post_type = $post_type_object['type']; $field_groups = acf_get_field_groups( array( 'post_type' => $post_type ) ); $field_groups_data = array(); @@ -71,6 +75,7 @@ public function get_scf_fields( $post_type_object ) { $group_fields[] = array( 'label' => $field['label'], 'type' => $field['type'], + 'name' => $field['name'], ); } diff --git a/secure-custom-fields.php b/secure-custom-fields.php index 6a98a943..e13ac475 100644 --- a/secure-custom-fields.php +++ b/secure-custom-fields.php @@ -879,6 +879,7 @@ function scf_plugin_uninstall() { // List of known beta features. $beta_features = array( 'editor_sidebar', + 'connect_fields', ); foreach ( $beta_features as $beta_feature ) { From 8b3183912529e7e0dcfafd6ae5c934d8ef96d94a Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Sun, 15 Jun 2025 11:49:48 +0200 Subject: [PATCH 05/18] Add clear all fields button --- assets/src/js/bindings/block-editor.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index b4e4c564..1bb2f56f 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -8,7 +8,12 @@ import { InspectorControls, useBlockBindingsUtils, } from '@wordpress/block-editor'; -import { PanelBody, ComboboxControl, PanelRow } from '@wordpress/components'; +import { + PanelBody, + ComboboxControl, + PanelRow, + Button, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; @@ -38,7 +43,8 @@ function getBindableAttributes( blockName ) { const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { const bindableAttributes = getBindableAttributes( props.name ); - const { updateBlockBindings } = useBlockBindingsUtils(); + const { updateBlockBindings, removeAllBlockBindings } = + useBlockBindingsUtils(); const { postType, postId } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPostId } = @@ -180,6 +186,21 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { /> ) ) } + + + From e8d2d18d819dcdacd612a9456db417890ab4c2ba Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:37:28 +0200 Subject: [PATCH 06/18] Fix format date --- assets/src/js/bindings/sources.js | 20 ++++++++++++++++--- .../class-acf-rest-types-endpoint.php | 7 ++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/assets/src/js/bindings/sources.js b/assets/src/js/bindings/sources.js index 0dcfced6..c0c184b8 100644 --- a/assets/src/js/bindings/sources.js +++ b/assets/src/js/bindings/sources.js @@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n'; import { registerBlockBindingsSource } from '@wordpress/blocks'; import { store as coreDataStore } from '@wordpress/core-data'; +import { dateI18n } from '@wordpress/date'; /** * Get the value of a specific field from the ACF fields. @@ -43,12 +44,19 @@ registerBlockBindingsSource( { ) : undefined; const result = {}; - Object.entries( bindings ).forEach( ( [ attribute, { args } = {} ] ) => { const fieldName = args?.key; - + const mergedFields = fields?.scf_field_groups + ? Object.fromEntries( + fields.scf_field_groups + .flatMap( ( group ) => group.fields || [] ) + .map( ( field ) => [ field.name, field ] ) + ) + : {}; const fieldValue = getFieldValue( fields, fieldName ); + const fieldType = mergedFields[ fieldName ]?.type; + if ( typeof fieldValue === 'object' && fieldValue !== null ) { let value = ''; @@ -59,7 +67,7 @@ registerBlockBindingsSource( { } result[ attribute ] = value; - } else if ( typeof fieldValue === 'number' ) { + } else if ( 'number' === typeof fieldValue ) { if ( attribute === 'content' ) { result[ attribute ] = fieldValue.toString() || ''; } else { @@ -69,6 +77,12 @@ registerBlockBindingsSource( { attribute ); } + } else if ( 'date_picker' === fieldType && fieldValue ) { + result[ attribute ] = + dateI18n( + mergedFields[ fieldName ]?.display_format, + fieldValue + ) || ''; } else { result[ attribute ] = fieldValue || ''; } diff --git a/includes/rest-api/class-acf-rest-types-endpoint.php b/includes/rest-api/class-acf-rest-types-endpoint.php index 78a20e64..d9cc001b 100644 --- a/includes/rest-api/class-acf-rest-types-endpoint.php +++ b/includes/rest-api/class-acf-rest-types-endpoint.php @@ -73,9 +73,10 @@ public function get_scf_fields( $post_type_object ) { foreach ( $fields as $field ) { $group_fields[] = array( - 'label' => $field['label'], - 'type' => $field['type'], - 'name' => $field['name'], + 'label' => $field['label'], + 'type' => $field['type'], + 'name' => $field['name'], + 'display_format' => isset( $field['display_format'] ) ? $field['display_format'] : '', ); } From dc2f05a10bb74656474d8fc358eb696496343053 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Tue, 17 Jun 2025 10:25:24 +0200 Subject: [PATCH 07/18] Add edit button, still not working --- assets/src/js/bindings/block-editor.js | 63 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index 1bb2f56f..a4cdbfc1 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -165,26 +165,49 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { initialOpen={ true } > { bindableAttributes.map( ( attribute ) => ( - - - handleFieldChange( attribute, value ) - } - key={ `scf-field-${ attribute }` } - /> - + <> + + + handleFieldChange( + attribute, + value + ) + } + key={ `scf-field-${ attribute }` } + /> + + { boundFields[ attribute ] && ( + + + + ) } + ) ) } + options={ fieldsSuggestions } + value={ + boundFields[ attribute ] || '' + } + onChange={ ( value ) => + handleFieldChange( + attribute, + value + ) + } + /> - ) } - - ) ) } + { boundFields[ attribute ] && ( + + + + ) } + + ) ) + ) } + ) : ( - bindableAttributes.map( ( attribute ) => ( - <> - - - handleFieldChange( - attribute, - value - ) - } - /> - - { boundFields[ attribute ] && ( + <> + { bindableAttributes.map( ( attribute ) => ( +
+ + handleFieldChange( + attribute, + value + ) + } + /> + + { boundFields[ attribute ] && ( + + + + ) } +
+ ) ) } + { ! allBoundFields && + 'core/image' === props.name && ( + ) } - - ) ) + ) } @@ -340,6 +393,32 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { + { modalOpen && ( + { + setModalOpen( false ); + setEditingField( null ); + setFieldContent( '' ); + } } + > + { + setFieldContent( value ); + } } + /> + + + ) } ); }; From 6cd65042ebd1ba8fbc52267d573830ed676f7f76 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:49:42 +0200 Subject: [PATCH 12/18] Remove edit stuff --- assets/src/js/bindings/block-editor.js | 46 -------------------------- 1 file changed, 46 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index f1eadfa1..bdc28576 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -11,10 +11,8 @@ import { import { Button, ComboboxControl, - Modal, PanelBody, PanelRow, - TextControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -335,24 +333,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { } /> - { boundFields[ attribute ] && ( - - - - ) } ) ) } { ! allBoundFields && @@ -393,32 +373,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { - { modalOpen && ( - { - setModalOpen( false ); - setEditingField( null ); - setFieldContent( '' ); - } } - > - { - setFieldContent( value ); - } } - /> - - - ) } ); }; From d9a43fd5efc5e2c7def9b626a8057885785509af Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 26 Jun 2025 16:55:00 +0200 Subject: [PATCH 13/18] Remove now-obsolete modal related code --- assets/src/js/bindings/block-editor.js | 49 -------------------------- 1 file changed, 49 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index bdc28576..ccac9476 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -18,8 +18,6 @@ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; -import apiFetch from '@wordpress/api-fetch'; -import { dispatch } from '@wordpress/data'; const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], @@ -144,53 +142,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { } }, [ currentBindingsKey ] ); - const [ modalOpen, setModalOpen ] = useState( false ); - const [ editingField, setEditingField ] = useState( null ); - const [ fieldContent, setFieldContent ] = useState( '' ); - const [ isSubmitting, setIsSubmitting ] = useState( false ); - - const handleEditField = ( attribute ) => { - setEditingField( attribute ); - setFieldContent( '' ); // Reset field content - setModalOpen( true ); - }; - - const handleSubmitField = async () => { - if ( ! editingField || ! fieldContent.trim() ) { - return; - } - - setIsSubmitting( true ); - try { - // Update the WordPress data store - dispatch( 'core/editor' ).editPost( { - meta: { - [ editingField ]: fieldContent, - }, - } ); - - // Update the ACF field value via REST API - await apiFetch( { - path: `/wp/v2/${ postType }/${ postId }`, - method: 'POST', - data: { - acf: { - [ editingField ]: fieldContent, - }, - }, - } ); - - // Close modal and reset state - setModalOpen( false ); - setEditingField( null ); - setFieldContent( '' ); - } catch ( error ) { - console.error( 'Error updating field:', error ); - } finally { - setIsSubmitting( false ); - } - }; - // Memoize the change handler to prevent creating new function on each render const handleFieldChange = useCallback( ( attributes, value ) => { From 24db51ec9c89ea9e714a562a21c958f55ccd1364 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 26 Jun 2025 17:26:33 +0200 Subject: [PATCH 14/18] Use link/unlink button --- assets/src/js/bindings/block-editor.js | 102 +++++++----------- .../block-attributes-control-linked-button.js | 27 +++++ package-lock.json | 56 +++++----- package.json | 1 + 4 files changed, 99 insertions(+), 87 deletions(-) create mode 100644 assets/src/js/bindings/components/block-attributes-control-linked-button.js diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index ccac9476..d3d981ce 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -19,6 +19,11 @@ import { useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; +/** + * Internal dependencies + */ +import BlockAttributesControlLinkedButton from './components/block-attributes-control-linked-button'; + const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], 'core/heading': [ 'content' ], @@ -204,51 +209,41 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { ) } initialOpen={ true } > + { 'core/image' === props.name && ( + { + setAllBoundFields( ! allBoundFields ); + } } + /> + ) } { allBoundFields ? ( - <> - - - handleFieldChange( - bindableAttributes, - value - ) - } - /> - - - - - + + + handleFieldChange( + bindableAttributes, + value + ) + } + /> + ) : ( <> { bindableAttributes.map( ( attribute ) => ( @@ -286,25 +281,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { ) ) } - { ! allBoundFields && - 'core/image' === props.name && ( - - - - ) } ) } diff --git a/assets/src/js/bindings/components/block-attributes-control-linked-button.js b/assets/src/js/bindings/components/block-attributes-control-linked-button.js new file mode 100644 index 00000000..5824344a --- /dev/null +++ b/assets/src/js/bindings/components/block-attributes-control-linked-button.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { link, linkOff } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; + +const BlockAttributesControlLinkedButton = ( + props +) => { + const { className, isLinked, onClick } = props; + + const label = isLinked ? __( 'Unlink sides' ) : __( 'Link sides' ); + + return ( + - - + ); From 8493b04a215b9a89ba5fb2fe6df38e2e4276a321 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:08:39 +0200 Subject: [PATCH 16/18] Use better link button --- assets/src/js/bindings/block-editor.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index ed9c6233..dd19aae2 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -201,9 +201,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { setBoundFields( {} ); }, [ removeAllBlockBindings ] ); - // Check if any fields are bound to determine if reset should be shown - const hasBoundFields = Object.keys( boundFields ).length > 0; - if ( fieldsSuggestions.length === 0 || ! bindableAttributes ) { return ; } @@ -220,8 +217,15 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { resetAll={ handleReset } > { 'core/image' === props.name && ( - - + + { allBoundFields ? __( 'Unlink all attributes', @@ -245,7 +249,10 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { hasValue={ () => !! boundFields[ bindableAttributes[ 0 ] ] } - label={ __( 'All attributes' ) } + label={ __( + 'All attributes', + 'secure-custom-fields' + ) } onDeselect={ () => handleFieldChange( bindableAttributes, '' ) } @@ -257,7 +264,10 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { __experimentalShowHowTo={ false } __experimentalExpandOnFocus={ true } __experimentalAutoSelectFirstMatch={ true } - label={ __( 'All attributes' ) } + label={ __( + 'All attributes', + 'secure-custom-fields' + ) } placeholder={ __( 'Select a field', 'secure-custom-fields' From ffa345bc97422f3bb30399edcdc7ed47e3fde825 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:20:03 +0200 Subject: [PATCH 17/18] Make some cleaning --- assets/src/js/bindings/block-editor.js | 14 +++++++------- assets/src/sass/_forms.scss | 4 ---- secure-custom-fields.php | 1 - 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/assets/src/js/bindings/block-editor.js b/assets/src/js/bindings/block-editor.js index dd19aae2..b49aca93 100644 --- a/assets/src/js/bindings/block-editor.js +++ b/assets/src/js/bindings/block-editor.js @@ -25,6 +25,8 @@ import { store as editorStore } from '@wordpress/editor'; */ import BlockAttributesControlLinkedButton from './components/block-attributes-control-linked-button'; +// These constant and the function above have been copied from Gutenberg. It should be public, eventually. + const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], 'core/heading': [ 'content' ], @@ -49,6 +51,7 @@ function getBindableAttributes( blockName ) { const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { const bindableAttributes = getBindableAttributes( props.name ); + const showLinkedButton = props.name === 'core/image'; const { updateBlockBindings, removeAllBlockBindings } = useBlockBindingsUtils(); @@ -81,7 +84,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { const currentBindings = props.attributes?.metadata?.bindings || {}; - // Memoize the fields transformation to prevent unnecessary recalculations const fields = useMemo( () => fieldsGroups?.reduce( ( acc, fieldGroup ) => { @@ -98,7 +100,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { }, [] ) || [], [ fieldsGroups ] ); - // Memoize the fieldsSuggestions to avoid recreating on every render + const fieldsSuggestions = useMemo( () => { if ( props.name === 'core/image' ) { // return only the type image fields @@ -151,7 +153,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { // Memoize the change handler to prevent creating new function on each render const handleFieldChange = useCallback( ( attributes, value ) => { - // Ensure attributes is always an array + // Ensure attributes is always an array. const attributeArray = Array.isArray( attributes ) ? attributes : [ attributes ]; @@ -171,7 +173,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { }; } ); - // Update all bindings at once + // Update all bindings at once. updateBlockBindings( bindings ); return newState; @@ -195,7 +197,6 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { [ updateBlockBindings ] ); - // Handle reset for ToolsPanel const handleReset = useCallback( () => { removeAllBlockBindings(); setBoundFields( {} ); @@ -216,7 +217,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { ) } resetAll={ handleReset } > - { 'core/image' === props.name && ( + { showLinkedButton && ( { }; }, 'withCustomControls' ); -// Only register the filter if the connect_fields beta feature is enabled if ( window.scf?.betaFeatures?.connect_fields ) { addFilter( 'editor.BlockEdit', diff --git a/assets/src/sass/_forms.scss b/assets/src/sass/_forms.scss index 5aa4edf3..c9430736 100644 --- a/assets/src/sass/_forms.scss +++ b/assets/src/sass/_forms.scss @@ -313,8 +313,4 @@ p.submit .acf-spinner { margin: 0; } } -} - -.scf-field-token-field { - margin-bottom: 2em; } \ No newline at end of file diff --git a/secure-custom-fields.php b/secure-custom-fields.php index e13ac475..2475a3b6 100644 --- a/secure-custom-fields.php +++ b/secure-custom-fields.php @@ -878,7 +878,6 @@ function scf_plugin_deactivated_notice() { function scf_plugin_uninstall() { // List of known beta features. $beta_features = array( - 'editor_sidebar', 'connect_fields', ); From 7fa68ab3c23fafef4d9d4c36f426d986bd41f729 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:09:27 +0200 Subject: [PATCH 18/18] update lock son --- package-lock.json | 55 +++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15680d0d..6116c2ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1850,6 +1850,7 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -4011,10 +4012,7 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "MIT" }, "node_modules/@types/qs": { @@ -4035,10 +4033,7 @@ "version": "18.3.23", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4049,10 +4044,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -4942,10 +4934,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.26.0.tgz", "integrity": "sha512-IlzQE7oVG4fuwRA5N7vhnr57kvf1HS08kwJwP+EC/olREnFEi8XOIeDa7rAEVXNAx2xeoLKQ6+K7Banp7+c6GA==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -4966,10 +4955,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4979,10 +4965,7 @@ "version": "3.26.0", "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.26.0.tgz", "integrity": "sha512-SQfSmUOMP32duStoxvrkydCtD/ELyNXpAwkE414swo8AQAKxBJMQDYE3PZy1uZ6YCtbSX7EHHAX9G1EeoHUzgg==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -5086,10 +5069,7 @@ "version": "10.26.0", "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.26.0.tgz", "integrity": "sha512-7XPcJbvy4s8USfcuMxdVE6qTaEYzRv0+TZa6Epbe61HFrvaMl9X0Mr+jcCyQ7qBp4jKHfHInWfywNeYOxc5SMg==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -5188,10 +5168,7 @@ "version": "4.26.0", "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.26.0.tgz", "integrity": "sha512-vmqKlqQxyv9XDKeIntd70SpRJeU0uXWj6iQDZmmbsOcDWL3UNIOFeN5dB25vDeyoseQ+r+JNnoU+hq7cpQa/8Q==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -7491,6 +7468,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", @@ -7575,6 +7553,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -7603,6 +7582,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, "license": "MIT", "dependencies": { "camel-case": "^4.1.2", @@ -7864,10 +7844,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "MIT", "engines": { "node": ">=6" @@ -8049,6 +8026,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -8613,10 +8591,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> a803028 (Use link/unlink button) "license": "MIT" }, "node_modules/cwd": { @@ -9211,6 +9186,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -11891,6 +11867,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, "license": "MIT", "dependencies": { "capital-case": "^1.0.4", @@ -14176,6 +14153,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -14775,6 +14753,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -14787,6 +14766,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -15460,6 +15440,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, "license": "MIT", "dependencies": { "lower-case": "^2.0.2", @@ -16116,6 +16097,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -16197,6 +16179,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -16207,6 +16190,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -17470,6 +17454,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -17482,6 +17467,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -17756,7 +17742,8 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -18255,6 +18242,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -18379,6 +18367,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -18753,6 +18742,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -20494,6 +20484,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/tsutils": { @@ -20829,6 +20820,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -20838,6 +20830,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3"