add_setting( * 'bongoto_woocommerce_header_components', * array( * 'default' => wp_json_encode( array( * 'enabled' => array( 'logo', 'primary-menu', 'search', 'account', 'cart', 'upload' ), * 'disabled' => array(), * ) ), * // JSON string; sanitize as text (we re-sanitize inside control as well). * 'sanitize_callback' => 'bongoto_woocommerce_customize_sanitize_text', * 'transport' => 'postMessage', * ) * ); * * $wp_customize->add_control( * new Bongoto_WooCommerce_Sortable_Control( * $wp_customize, * 'bongoto_woocommerce_header_components', * array( * 'label' => __( 'Header Components', 'bongoto-woocommerce' ), * 'description' => __( 'Drag items between Enabled and Disabled, and reorder within Enabled.', 'bongoto-woocommerce' ), * 'section' => 'bongoto_woocommerce_section_header', * 'choices' => array( * 'logo' => __( 'Logo', 'bongoto-woocommerce' ), * 'site-title' => __( 'Site Title', 'bongoto-woocommerce' ), * 'primary-menu' => __( 'Primary Menu', 'bongoto-woocommerce' ), * 'search' => __( 'Search', 'bongoto-woocommerce' ), * 'account' => __( 'Account', 'bongoto-woocommerce' ), * 'cart' => __( 'Cart', 'bongoto-woocommerce' ), * 'upload' => __( 'Upload CTA', 'bongoto-woocommerce' ), * ), * ) * ) * ); * --------------------------------------------------------- * * Stored value is a JSON string: * {"enabled":["logo","primary-menu","search","account","cart","upload"],"disabled":[]} * * @package BongotoWooCommerce */ if ( ! defined( 'ABSPATH' ) ) { exit; } if ( class_exists( 'WP_Customize_Control' ) && ! class_exists( 'Bongoto_WooCommerce_Sortable_Control' ) ) : class Bongoto_WooCommerce_Sortable_Control extends WP_Customize_Control { /** * Control type. * * @var string */ public $type = 'bongoto-woocommerce-sortable'; /** * Available choices (slug => label). * * @var array */ public $choices = array(); /** * Enqueue required scripts/styles. */ public function enqueue() { wp_enqueue_script( 'jquery-ui-sortable' ); // Tiny inline CSS (scoped). $css = ' .customize-control-bongoto-woocommerce-sortable .bt-sortable-wrap{display:flex;gap:12px;flex-wrap:wrap} .customize-control-bongoto-woocommerce-sortable .bt-sortable-col{flex:1 1 220px;background:#fff;border:1px solid #ccd0d4;border-radius:6px;overflow:hidden} .customize-control-bongoto-woocommerce-sortable .bt-sortable-col h4{margin:0;padding:8px 10px;background:#f6f7f7;border-bottom:1px solid #e2e4e7;font-size:12px;text-transform:uppercase;letter-spacing:.02em;color:#4b5563} .customize-control-bongoto-woocommerce-sortable ul.bt-sortable-list{list-style:none;margin:0;padding:6px;min-height:42px} .customize-control-bongoto-woocommerce-sortable .bt-item{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:6px 0;padding:8px 10px;background:#fff;border:1px solid #e2e4e7;border-radius:6px;cursor:move} .customize-control-bongoto-woocommerce-sortable .bt-item .bt-handle{opacity:.7} .customize-control-bongoto-woocommerce-sortable .bt-placeholder{height:34px;margin:6px 0;border:1px dashed #94a3b8;border-radius:6px} .customize-control-bongoto-woocommerce-sortable .bt-help{font-size:12px;color:#64748b;margin-top:6px} '; wp_add_inline_style( 'customize-controls', $css ); // Inline JS to serialize state. $js = " jQuery(function($){ function serializeLists($control){ var enabled = [], disabled = []; $control.find('ul.bt-enabled .bt-item').each(function(){ enabled.push($(this).data('key')); }); $control.find('ul.bt-disabled .bt-item').each(function(){ disabled.push($(this).data('key')); }); var json = JSON.stringify({enabled: enabled, disabled: disabled}); $control.find('input.bt-sortable-input').val(json).trigger('change'); } $('.customize-control-bongoto-woocommerce-sortable').each(function(){ var \$c = $(this); var opts = { connectWith: \$c.find('ul.bt-sortable-list'), placeholder: 'bt-placeholder', forcePlaceholderSize: true, update: function(){ serializeLists(\$c); } }; \$c.find('ul.bt-sortable-list').sortable(opts).disableSelection(); // Initialize to ensure value reflects DOM (esp. after refresh). serializeLists(\$c); }); }); "; wp_add_inline_script( 'jquery-ui-sortable', $js ); } /** * Render control content. */ public function render_content() { // Parse current value. $value = array( 'enabled' => array(), 'disabled' => array(), ); $raw = (string) $this->value(); if ( ! empty( $raw ) ) { $decoded = json_decode( $raw, true ); if ( is_array( $decoded ) ) { $value = wp_parse_args( array( 'enabled' => array(), 'disabled' => array(), ), $decoded ); $value['enabled'] = array_values( array_unique( array_map( 'sanitize_key', (array) $value['enabled'] ) ) ); $value['disabled'] = array_values( array_unique( array_map( 'sanitize_key', (array) $value['disabled'] ) ) ); } } // Fallback: if enabled empty, fill with all choices initially. if ( empty( $value['enabled'] ) && ! empty( $this->choices ) ) { $value['enabled'] = array_keys( (array) $this->choices ); $value['disabled'] = array(); } // Ensure every choice appears in one of the lists. $all = array_keys( (array) $this->choices ); foreach ( $all as $slug ) { if ( ! in_array( $slug, $value['enabled'], true ) && ! in_array( $slug, $value['disabled'], true ) ) { $value['disabled'][] = $slug; } } // Title/desc. if ( ! empty( $this->label ) ) { echo '' . esc_html( $this->label ) . ''; } if ( ! empty( $this->description ) ) { echo '' . esc_html( $this->description ) . ''; } // Hidden input to store JSON. ?> link(); ?> value="" />