Make WordPress Core

Ticket #35855: 35855.2.diff

File 35855.2.diff, 71.7 KB (added by westonruter, 9 years ago)
  • src/wp-admin/js/customize-widgets.js

    diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js
    index 91a6516..6b27b8d 100644
     
    3434                multi_number: null,
    3535                name: null,
    3636                id_base: null,
    37                 transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',
     37                transport: null,
    3838                params: [],
    3939                width: null,
    4040                height: null,
     
    16331633                 * Update ordering of widget control forms when the setting is updated
    16341634                 */
    16351635                _setupModel: function() {
    1636                         var self = this;
     1636                        var sidebarControl = this, setWidgetSettingTransports;
     1637
     1638                        setWidgetSettingTransports = function( widgetIds ) {
     1639                                _.each( widgetIds, function( widgetId ) {
     1640                                        var widgetSetting = api( widgetIdToSettingId( widgetId ) );
     1641                                        if ( ! widgetSetting ) {
     1642                                                return;
     1643                                        }
     1644                                        if ( api.Widgets.data.selectiveRefreshableWidgets[ parseWidgetId( widgetId ).id_base ] ) {
     1645                                                widgetSetting.transport = sidebarControl.setting.transport;
     1646                                        } else {
     1647                                                widgetSetting.transport = 'refresh';
     1648                                        }
     1649                                } );
     1650                        };
     1651                        setWidgetSettingTransports( sidebarControl.setting() );
    16371652
    1638                         this.setting.bind( function( newWidgetIds, oldWidgetIds ) {
     1653                        sidebarControl.setting.bind( function( newWidgetIds, oldWidgetIds ) {
    16391654                                var widgetFormControls, removedWidgetIds, priority;
    16401655
    16411656                                removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds );
     
    16511666                                        var widgetFormControl = api.Widgets.getWidgetFormControlForWidget( widgetId );
    16521667
    16531668                                        if ( ! widgetFormControl ) {
    1654                                                 widgetFormControl = self.addWidget( widgetId );
     1669                                                widgetFormControl = sidebarControl.addWidget( widgetId );
    16551670                                        }
    16561671
    16571672                                        return widgetFormControl;
     
    16671682                                priority = 0;
    16681683                                _( widgetFormControls ).each( function ( control ) {
    16691684                                        control.priority( priority );
    1670                                         control.section( self.section() );
     1685                                        control.section( sidebarControl.section() );
    16711686                                        priority += 1;
    16721687                                });
    1673                                 self.priority( priority ); // Make sure sidebar control remains at end
     1688                                sidebarControl.priority( priority ); // Make sure sidebar control remains at end
    16741689
    16751690                                // Re-sort widget form controls (including widgets form other sidebars newly moved here)
    1676                                 self._applyCardinalOrderClassNames();
     1691                                sidebarControl._applyCardinalOrderClassNames();
    16771692
    16781693                                // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated
    16791694                                _( widgetFormControls ).each( function( widgetFormControl ) {
    1680                                         widgetFormControl.params.sidebar_id = self.params.sidebar_id;
     1695                                        widgetFormControl.params.sidebar_id = sidebarControl.params.sidebar_id;
    16811696                                } );
    16821697
     1698                                // Update added widget setting transports for selective refresh since this sidebar may not support it.
     1699                                setWidgetSettingTransports( newWidgetIds );
     1700
    16831701                                // Cleanup after widget removal
    16841702                                _( removedWidgetIds ).each( function( removedWidgetId ) {
    16851703
     
    16901708
    16911709                                                // Check if the widget is in another sidebar
    16921710                                                api.each( function( otherSetting ) {
    1693                                                         if ( otherSetting.id === self.setting.id || 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) || otherSetting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
     1711                                                        if ( otherSetting.id === sidebarControl.setting.id || 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) || 'sidebars_widgets[wp_inactive_widgets]' === otherSetting.id ) {
    16941712                                                                return;
    16951713                                                        }
    16961714
     
    17101728                                                removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );
    17111729
    17121730                                                // Detect if widget control was dragged to another sidebar
    1713                                                 wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );
     1731                                                wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( sidebarControl.$sectionContent[0], removedControl.container[0] );
    17141732
    17151733                                                // Delete any widget form controls for removed widgets
    17161734                                                if ( removedControl && ! wasDraggedToAnotherSidebar ) {
     
    19231941                 * @returns {object|false} widget_form control instance, or false on error
    19241942                 */
    19251943                addWidget: function( widgetId ) {
    1926                         var self = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor,
     1944                        var control = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor,
    19271945                                parsedWidgetId = parseWidgetId( widgetId ),
    19281946                                widgetNumber = parsedWidgetId.number,
    19291947                                widgetIdBase = parsedWidgetId.id_base,
    19301948                                widget = api.Widgets.availableWidgets.findWhere( {id_base: widgetIdBase} ),
    1931                                 settingId, isExistingWidget, widgetFormControl, sidebarWidgets, settingArgs, setting;
     1949                                settingId, isExistingWidget, widgetFormControl, sidebar, sidebarWidgets, settingArgs, setting, settingTransport;
    19321950
    19331951                        if ( ! widget ) {
    19341952                                return false;
     
    19801998
    19811999                        // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget)
    19822000                        isExistingWidget = api.has( settingId );
     2001                        sidebar = api.Widgets.registeredSidebars.get( control.params.sidebar_id );
     2002                        settingTransport = ( sidebar.get( 'customize_selective_refresh' ) && widget.get( 'customize_selective_refresh' ) ) ? 'postMessage' : 'refresh';
    19832003                        if ( ! isExistingWidget ) {
    19842004                                settingArgs = {
    1985                                         transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',
    1986                                         previewer: this.setting.previewer
     2005                                        transport: settingTransport,
     2006                                        previewer: control.setting.previewer
    19872007                                };
    19882008                                setting = api.create( settingId, settingId, '', settingArgs );
    19892009                                setting.set( {} ); // mark dirty, changing from '' to {}
     2010                        } else {
     2011                                setting = api( settingId );
     2012                                setting.transport = settingTransport;
    19902013                        }
    19912014
    19922015                        controlConstructor = api.controlConstructor[controlType];
     
    19962019                                                'default': settingId
    19972020                                        },
    19982021                                        content: controlContainer,
    1999                                         sidebar_id: self.params.sidebar_id,
     2022                                        sidebar_id: control.params.sidebar_id,
    20002023                                        widget_id: widgetId,
    20012024                                        widget_id_base: widget.get( 'id_base' ),
    20022025                                        type: controlType,
     
    20062029                                        is_wide: widget.get( 'is_wide' ),
    20072030                                        active: true
    20082031                                },
    2009                                 previewer: self.setting.previewer
     2032                                previewer: control.setting.previewer
    20102033                        } );
    20112034                        api.control.add( settingId, widgetFormControl );
    20122035
    20132036                        // Make sure widget is removed from the other sidebars
    20142037                        api.each( function( otherSetting ) {
    2015                                 if ( otherSetting.id === self.setting.id ) {
     2038                                if ( otherSetting.id === control.setting.id ) {
    20162039                                        return;
    20172040                                }
    20182041
  • src/wp-content/themes/twentyeleven/functions.php

    diff --git src/wp-content/themes/twentyeleven/functions.php src/wp-content/themes/twentyeleven/functions.php
    index 3ae1756..57f5b3c 100644
    function twentyeleven_widgets_init() { 
    449449                'after_widget' => '</aside>',
    450450                'before_title' => '<h3 class="widget-title">',
    451451                'after_title' => '</h3>',
     452                'customize_selective_refresh' => true,
    452453        ) );
    453454
    454455        register_sidebar( array(
    function twentyeleven_widgets_init() { 
    459460                'after_widget' => '</aside>',
    460461                'before_title' => '<h3 class="widget-title">',
    461462                'after_title' => '</h3>',
     463                'customize_selective_refresh' => true,
    462464        ) );
    463465
    464466        register_sidebar( array(
    function twentyeleven_widgets_init() { 
    469471                'after_widget' => '</aside>',
    470472                'before_title' => '<h3 class="widget-title">',
    471473                'after_title' => '</h3>',
     474                'customize_selective_refresh' => true,
    472475        ) );
    473476
    474477        register_sidebar( array(
    function twentyeleven_widgets_init() { 
    479482                'after_widget' => '</aside>',
    480483                'before_title' => '<h3 class="widget-title">',
    481484                'after_title' => '</h3>',
     485                'customize_selective_refresh' => true,
    482486        ) );
    483487
    484488        register_sidebar( array(
    function twentyeleven_widgets_init() { 
    489493                'after_widget' => '</aside>',
    490494                'before_title' => '<h3 class="widget-title">',
    491495                'after_title' => '</h3>',
     496                'customize_selective_refresh' => true,
    492497        ) );
    493498}
    494499add_action( 'widgets_init', 'twentyeleven_widgets_init' );
  • src/wp-content/themes/twentyfifteen/functions.php

    diff --git src/wp-content/themes/twentyfifteen/functions.php src/wp-content/themes/twentyfifteen/functions.php
    index 7daeefa..aa88bf3 100644
    function twentyfifteen_widgets_init() { 
    145145                'after_widget'  => '</aside>',
    146146                'before_title'  => '<h2 class="widget-title">',
    147147                'after_title'   => '</h2>',
     148                'customize_selective_refresh' => true,
    148149        ) );
    149150}
    150151add_action( 'widgets_init', 'twentyfifteen_widgets_init' );
  • src/wp-content/themes/twentyfourteen/functions.php

    diff --git src/wp-content/themes/twentyfourteen/functions.php src/wp-content/themes/twentyfourteen/functions.php
    index 4e65214..5d09c6c 100644
    function twentyfourteen_widgets_init() { 
    175175                'after_widget'  => '</aside>',
    176176                'before_title'  => '<h1 class="widget-title">',
    177177                'after_title'   => '</h1>',
     178                'customize_selective_refresh' => true,
    178179        ) );
    179180        register_sidebar( array(
    180181                'name'          => __( 'Content Sidebar', 'twentyfourteen' ),
    function twentyfourteen_widgets_init() { 
    184185                'after_widget'  => '</aside>',
    185186                'before_title'  => '<h1 class="widget-title">',
    186187                'after_title'   => '</h1>',
     188                'customize_selective_refresh' => true,
    187189        ) );
    188190        register_sidebar( array(
    189191                'name'          => __( 'Footer Widget Area', 'twentyfourteen' ),
    function twentyfourteen_widgets_init() { 
    193195                'after_widget'  => '</aside>',
    194196                'before_title'  => '<h1 class="widget-title">',
    195197                'after_title'   => '</h1>',
     198                'customize_selective_refresh' => true,
    196199        ) );
    197200}
    198201add_action( 'widgets_init', 'twentyfourteen_widgets_init' );
  • src/wp-content/themes/twentythirteen/functions.php

    diff --git src/wp-content/themes/twentythirteen/functions.php src/wp-content/themes/twentythirteen/functions.php
    index 33c8efd..c4e9a78 100644
    function twentythirteen_widgets_init() { 
    235235                'after_widget'  => '</aside>',
    236236                'before_title'  => '<h3 class="widget-title">',
    237237                'after_title'   => '</h3>',
     238                'customize_selective_refresh' => true,
    238239        ) );
    239240
    240241        register_sidebar( array(
    function twentythirteen_widgets_init() { 
    245246                'after_widget'  => '</aside>',
    246247                'before_title'  => '<h3 class="widget-title">',
    247248                'after_title'   => '</h3>',
     249                'customize_selective_refresh' => true,
    248250        ) );
    249251}
    250252add_action( 'widgets_init', 'twentythirteen_widgets_init' );
  • src/wp-content/themes/twentytwelve/functions.php

    diff --git src/wp-content/themes/twentytwelve/functions.php src/wp-content/themes/twentytwelve/functions.php
    index e305447..76c53d5 100644
    function twentytwelve_widgets_init() { 
    245245                'after_widget' => '</aside>',
    246246                'before_title' => '<h3 class="widget-title">',
    247247                'after_title' => '</h3>',
     248                'customize_selective_refresh' => true,
    248249        ) );
    249250
    250251        register_sidebar( array(
    function twentytwelve_widgets_init() { 
    255256                'after_widget' => '</aside>',
    256257                'before_title' => '<h3 class="widget-title">',
    257258                'after_title' => '</h3>',
     259                'customize_selective_refresh' => true,
    258260        ) );
    259261
    260262        register_sidebar( array(
    function twentytwelve_widgets_init() { 
    265267                'after_widget' => '</aside>',
    266268                'before_title' => '<h3 class="widget-title">',
    267269                'after_title' => '</h3>',
     270                'customize_selective_refresh' => true,
    268271        ) );
    269272}
    270273add_action( 'widgets_init', 'twentytwelve_widgets_init' );
  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index ea30b93..9a13b6d 100644
    final class WP_Customize_Manager { 
    109109         * @access protected
    110110         * @var array
    111111         */
    112         protected $components = array( 'widgets', 'nav_menus', 'selective_refresh' );
     112        protected $components = array( 'widgets', 'nav_menus' );
    113113
    114114        /**
    115115         * Registered instances of WP_Customize_Section.
    final class WP_Customize_Manager { 
    258258                 */
    259259                $components = apply_filters( 'customize_loaded_components', $this->components, $this );
    260260
     261                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
     262                $this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
     263
    261264                if ( in_array( 'widgets', $components, true ) ) {
    262265                        require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
    263266                        $this->widgets = new WP_Customize_Widgets( $this );
    final class WP_Customize_Manager { 
    268271                        $this->nav_menus = new WP_Customize_Nav_Menus( $this );
    269272                }
    270273
    271                 if ( in_array( 'selective_refresh', $components, true ) ) {
    272                         require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
    273                         $this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
    274                 }
    275 
    276274                add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
    277275
    278276                add_action( 'setup_theme', array( $this, 'setup_theme' ) );
    final class WP_Customize_Manager { 
    17301728                        'autofocus' => $this->get_autofocus(),
    17311729                        'documentTitleTmpl' => $this->get_document_title_template(),
    17321730                        'previewableDevices' => $this->get_previewable_devices(),
    1733                         'selectiveRefreshEnabled' => isset( $this->selective_refresh ),
    17341731                );
    17351732
    17361733                // Prepare Customize Section objects to pass to JavaScript.
    final class WP_Customize_Manager { 
    19781975                        ),
    19791976                ) ) );
    19801977
    1981                 if ( isset( $this->selective_refresh ) ) {
    1982                         $this->selective_refresh->add_partial( 'custom_logo', array(
    1983                                 'settings'            => array( 'custom_logo' ),
    1984                                 'selector'            => '.custom-logo-link',
    1985                                 'render_callback'     => array( $this, '_render_custom_logo_partial' ),
    1986                                 'container_inclusive' => true,
    1987                         ) );
    1988                 }
     1978                $this->selective_refresh->add_partial( 'site_logo', array(
     1979                        'settings'            => array( 'site_logo' ),
     1980                        'selector'            => '.site-logo-link',
     1981                        'render_callback'     => array( $this, '_render_site_logo_partial' ),
     1982                        'container_inclusive' => true,
     1983                ) );
    19891984
    19901985                /* Colors */
    19911986
  • src/wp-includes/class-wp-customize-nav-menus.php

    diff --git src/wp-includes/class-wp-customize-nav-menus.php src/wp-includes/class-wp-customize-nav-menus.php
    index cb8994c..7784567 100644
    final class WP_Customize_Nav_Menus { 
    393393                                'reorderLabelOn'    => esc_attr__( 'Reorder menu items' ),
    394394                                'reorderLabelOff'   => esc_attr__( 'Close reorder mode' ),
    395395                        ),
    396                         'settingTransport'     => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     396                        'settingTransport'     => 'postMessage',
    397397                        'phpIntMax'            => PHP_INT_MAX,
    398398                        'defaultSettingValues' => array(
    399399                                'nav_menu'      => $temp_nav_menu_setting->default,
    final class WP_Customize_Nav_Menus { 
    445445                if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
    446446                        $setting_args = array(
    447447                                'type'      => WP_Customize_Nav_Menu_Setting::TYPE,
    448                                 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     448                                'transport' => 'postMessage',
    449449                        );
    450450                } elseif ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
    451451                        $setting_args = array(
    452452                                'type'      => WP_Customize_Nav_Menu_Item_Setting::TYPE,
    453                                 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     453                                'transport' => 'postMessage',
    454454                        );
    455455                }
    456456                return $setting_args;
    final class WP_Customize_Nav_Menus { 
    535535
    536536                        $setting = $this->manager->get_setting( $setting_id );
    537537                        if ( $setting ) {
    538                                 $setting->transport = isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh';
     538                                $setting->transport = 'postMessage';
    539539                                remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
    540540                                add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
    541541                        } else {
    final class WP_Customize_Nav_Menus { 
    543543                                        'sanitize_callback' => array( $this, 'intval_base10' ),
    544544                                        'theme_supports'    => 'menus',
    545545                                        'type'              => 'theme_mod',
    546                                         'transport'         => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     546                                        'transport'         => 'postMessage',
    547547                                        'default'           => 0,
    548548                                ) );
    549549                        }
    final class WP_Customize_Nav_Menus { 
    570570
    571571                        $nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
    572572                        $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id, array(
    573                                 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     573                                'transport' => 'postMessage',
    574574                        ) ) );
    575575
    576576                        // Add the menu contents.
    final class WP_Customize_Nav_Menus { 
    585585                                $value['nav_menu_term_id'] = $menu_id;
    586586                                $this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id, array(
    587587                                        'value'     => $value,
    588                                         'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     588                                        'transport' => 'postMessage',
    589589                                ) ) );
    590590
    591591                                // Create a control for each menu item.
    final class WP_Customize_Nav_Menus { 
    988988         * @access public
    989989         */
    990990        public function customize_preview_enqueue_deps() {
    991                 if ( isset( $this->manager->selective_refresh ) ) {
    992                         $script = wp_scripts()->registered['customize-preview-nav-menus'];
    993                         $script->deps[] = 'customize-selective-refresh';
    994                 }
    995 
    996991                wp_enqueue_script( 'customize-preview-nav-menus' ); // Note that we have overridden this.
    997992                wp_enqueue_style( 'customize-preview' );
    998993        }
  • src/wp-includes/class-wp-customize-widgets.php

    diff --git src/wp-includes/class-wp-customize-widgets.php src/wp-includes/class-wp-customize-widgets.php
    index a417c46..cefc508 100644
    final class WP_Customize_Widgets { 
    6969         * @var array
    7070         */
    7171        protected $setting_id_patterns = array(
    72                 'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
    73                 'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
     72                'widget_instance' => '/^widget_(?P<id_base>.+?)(?:\[(?P<widget_number>\d+)\])?$/',
     73                'sidebar_widgets' => '/^sidebars_widgets\[(?P<sidebar_id>.+?)\]$/',
    7474        );
    7575
    7676        /**
    final class WP_Customize_Widgets { 
    112112        }
    113113
    114114        /**
     115         * Gets whether each registered widget can be use selective refresh.
     116         *
     117         * @since 4.5.0
     118         * @access private
     119         *
     120         * @return array Mapping of id_base to support. If theme doesn't support
     121         *               selective refresh, an empty array is returned.
     122         */
     123        protected function get_selective_refreshable_widgets() {
     124                global $wp_widget_factory;
     125
     126                $selective_refreshable_widgets = array();
     127                foreach ( $wp_widget_factory->widgets as $wp_widget ) {
     128                        $selective_refreshable_widgets[ $wp_widget->id_base ] = ! empty( $wp_widget->widget_options['customize_selective_refresh'] );
     129                }
     130                return $selective_refreshable_widgets;
     131        }
     132
     133        /**
    115134         * Retrieves the widget setting type given a setting ID.
    116135         *
    117136         * @since 4.2.0
    final class WP_Customize_Widgets { 
    119138         *
    120139         * @staticvar array $cache
    121140         *
    122          * @param $setting_id Setting ID.
     141         * @param string $setting_id Setting ID.
    123142         * @return string|void Setting type.
    124143         */
    125144        protected function get_setting_type( $setting_id ) {
    final class WP_Customize_Widgets { 
    690709                                'widgetReorderNav' => $widget_reorder_nav_tpl,
    691710                                'moveWidgetArea'   => $move_widget_area_tpl,
    692711                        ),
    693                         'selectiveRefresh'     => isset( $this->manager->selective_refresh ),
     712                        'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
    694713                );
    695714
    696715                foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
    final class WP_Customize_Widgets { 
    762781         *
    763782         * @since 3.9.0
    764783         * @access public
     784         * @global array $wp_registered_sidebars
    765785         *
    766786         * @param string $id        Widget setting ID.
    767787         * @param array  $overrides Array of setting overrides.
    768788         * @return array Possibly modified setting arguments.
    769789         */
    770790        public function get_setting_args( $id, $overrides = array() ) {
     791                global $wp_registered_sidebars;
    771792                $args = array(
    772793                        'type'       => 'option',
    773794                        'capability' => 'edit_theme_options',
    774                         'transport'  => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
    775795                        'default'    => array(),
    776796                );
    777797
    778798                if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
    779799                        $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
    780800                        $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
     801                        $args['transport'] = ! empty( $wp_registered_sidebars[ $matches['sidebar_id'] ]['customize_selective_refresh'] ) ? 'postMessage' : 'refresh';
    781802                } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
    782803                        $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
    783804                        $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
     805                        $args['transport'] = 'refresh'; // Will switch to postMessage in client if widget and sidebar support it.
    784806                }
    785807
    786808                $args = array_merge( $args, $overrides );
    final class WP_Customize_Widgets { 
    845867                usort( $sort, array( $this, '_sort_name_callback' ) );
    846868                $done = array();
    847869
     870                $selective_refreshable_widgets = $this->get_selective_refreshable_widgets();
     871
    848872                foreach ( $sort as $widget ) {
    849873                        if ( in_array( $widget['callback'], $done, true ) ) { // We already showed this multi-widget
    850874                                continue;
    final class WP_Customize_Widgets { 
    893917                                'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
    894918                                'is_disabled'  => $is_disabled,
    895919                                'id_base'      => $id_base,
    896                                 'transport'    => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
     920                                'transport'    => ! empty( $selective_refreshable_widgets[ $id_base ] ),
    897921                                'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
    898922                                'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
    899923                                'is_wide'      => $this->is_wide_widget( $widget['id'] ),
    final class WP_Customize_Widgets { 
    10601084         */
    10611085        public function export_preview_data() {
    10621086                global $wp_registered_sidebars, $wp_registered_widgets;
     1087
    10631088                // Prepare Customizer settings to pass to JavaScript.
    10641089                $settings = array(
    10651090                        'renderedSidebars'   => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
    final class WP_Customize_Widgets { 
    10691094                        'l10n'               => array(
    10701095                                'widgetTooltip'  => __( 'Shift-click to edit this widget.' ),
    10711096                        ),
    1072                         'selectiveRefresh'   => isset( $this->manager->selective_refresh ),
     1097                        'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
    10731098                );
    10741099                foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
    10751100                        unset( $registered_widget['callback'] ); // may not be JSON-serializeable
    final class WP_Customize_Widgets { 
    14791504         * @return array (Maybe) modified partial arguments.
    14801505         */
    14811506        public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
    1482 
    14831507                if ( preg_match( '/^widget\[(?P<widget_id>.+)\]$/', $partial_id, $matches ) ) {
    14841508                        if ( false === $partial_args ) {
    14851509                                $partial_args = array();
    final class WP_Customize_Widgets { 
    15061530         * @access public
    15071531         */
    15081532        public function selective_refresh_init() {
    1509                 if ( ! isset( $this->manager->selective_refresh ) ) {
    1510                         return;
    1511                 }
    1512 
    15131533                add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
    15141534                add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
    15151535                add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
    final class WP_Customize_Widgets { 
    15241544         * @access public
    15251545         */
    15261546        public function customize_preview_enqueue_deps() {
    1527                 if ( isset( $this->manager->selective_refresh ) ) {
    1528                         $script = wp_scripts()->registered['customize-preview-widgets'];
    1529                         $script->deps[] = 'customize-selective-refresh';
    1530                 }
    1531 
    15321547                wp_enqueue_script( 'customize-preview-widgets' );
    15331548                wp_enqueue_style( 'customize-preview' );
    15341549        }
    final class WP_Customize_Widgets { 
    15431558         *     @type array $widget_args Widget args.
    15441559         * }
    15451560         * @see WP_Customize_Nav_Menus_Partial_Refresh::filter_wp_nav_menu_args()
     1561         * @global array $wp_registered_sidebars
    15461562         *
    15471563         * @return array Params.
    15481564         */
    15491565        public function filter_dynamic_sidebar_params( $params ) {
     1566                global $wp_registered_sidebars;
     1567
    15501568                $sidebar_args = array_merge(
    15511569                        array(
    15521570                                'before_widget' => '',
    final class WP_Customize_Widgets { 
    15621580                        &&
    15631581                        is_registered_sidebar( $sidebar_args['id'] )
    15641582                        &&
     1583                        ! empty( $wp_registered_sidebars[ $sidebar_args['id'] ]['customize_selective_refresh'] )
     1584                        &&
    15651585                        ( isset( $this->current_dynamic_sidebar_id_stack[0] ) && $this->current_dynamic_sidebar_id_stack[0] === $sidebar_args['id'] )
    15661586                        &&
    15671587                        preg_match( '#^<(?P<tag_name>\w+)#', $sidebar_args['before_widget'], $matches )
  • src/wp-includes/class-wp-widget.php

    diff --git src/wp-includes/class-wp-widget.php src/wp-includes/class-wp-widget.php
    index e8f4a1e..bb0de50 100644
    class WP_Widget { 
    155155                $this->id_base = empty($id_base) ? preg_replace( '/(wp_)?widget_/', '', strtolower(get_class($this)) ) : strtolower($id_base);
    156156                $this->name = $name;
    157157                $this->option_name = 'widget_' . $this->id_base;
    158                 $this->widget_options = wp_parse_args( $widget_options, array('classname' => $this->option_name) );
    159                 $this->control_options = wp_parse_args( $control_options, array('id_base' => $this->id_base) );
     158                $this->widget_options = wp_parse_args( $widget_options, array( 'classname' => $this->option_name, 'customize_selective_refresh' => false ) );
     159                $this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base ) );
    160160        }
    161161
    162162        /**
  • src/wp-includes/js/customize-preview-widgets.js

    diff --git src/wp-includes/js/customize-preview-widgets.js src/wp-includes/js/customize-preview-widgets.js
    index 92e7732..ebee98e 100644
    wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 
    1212                preview: null,
    1313                l10n: {
    1414                        widgetTooltip: ''
    15                 }
     15                },
     16                selectiveRefreshableWidgets: {}
    1617        };
    1718
    1819        /**
    wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 
    2425                var self = this;
    2526
    2627                self.preview = api.preview;
    27                 if ( api.selectiveRefresh ) {
    28                         self.addPartials();
    29                 }
    30 
     28                self.addPartials();
    3129                self.buildWidgetSelectors();
    3230                self.highlightControls();
    3331
    wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 
    3836                } );
    3937        };
    4038
    41         if ( api.selectiveRefresh ) {
     39        /**
     40         * Partial representing a widget instance.
     41         *
     42         * @class
     43         * @augments wp.customize.selectiveRefresh.Partial
     44         * @since 4.5.0
     45         */
     46        self.WidgetPartial = api.selectiveRefresh.Partial.extend({
    4247
    4348                /**
    44                  * Partial representing a widget instance.
     49                 * Constructor.
    4550                 *
    46                  * @class
    47                  * @augments wp.customize.selectiveRefresh.Partial
    4851                 * @since 4.5.0
     52                 * @param {string} id - Partial ID.
     53                 * @param {Object} options
     54                 * @param {Object} options.params
    4955                 */
    50                 self.WidgetPartial = api.selectiveRefresh.Partial.extend({
    51 
    52                         /**
    53                          * Constructor.
    54                          *
    55                          * @since 4.5.0
    56                          * @param {string} id - Partial ID.
    57                          * @param {Object} options
    58                          * @param {Object} options.params
    59                          */
    60                         initialize: function( id, options ) {
    61                                 var partial = this, matches;
    62                                 matches = id.match( /^widget\[(.+)]$/ );
    63                                 if ( ! matches ) {
    64                                         throw new Error( 'Illegal id for widget partial.' );
    65                                 }
     56                initialize: function( id, options ) {
     57                        var partial = this, matches;
     58                        matches = id.match( /^widget\[(.+)]$/ );
     59                        if ( ! matches ) {
     60                                throw new Error( 'Illegal id for widget partial.' );
     61                        }
    6662
    67                                 partial.widgetId = matches[1];
    68                                 options = options || {};
    69                                 options.params = _.extend(
    70                                         {
    71                                                 /* Note that a selector of ('#' + partial.widgetId) is faster, but jQuery will only return the one result. */
    72                                                 selector: '[id="' + partial.widgetId + '"]', // Alternatively, '[data-customize-widget-id="' + partial.widgetId + '"]'
    73                                                 settings: [ self.getWidgetSettingId( partial.widgetId ) ],
    74                                                 containerInclusive: true
    75                                         },
    76                                         options.params || {}
    77                                 );
     63                        partial.widgetId = matches[1];
     64                        partial.widgetIdParts = self.parseWidgetId( partial.widgetId );
     65                        options = options || {};
     66                        options.params = _.extend(
     67                                {
     68                                        settings: [ self.getWidgetSettingId( partial.widgetId ) ],
     69                                        containerInclusive: true
     70                                },
     71                                options.params || {}
     72                        );
    7873
    79                                 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
    80                         },
    81 
    82                         /**
    83                          * Send widget-updated message to parent so spinner will get removed from widget control.
    84                          *
    85                          * @inheritdoc
    86                          * @param {wp.customize.selectiveRefresh.Placement} placement
    87                          */
    88                         renderContent: function( placement ) {
    89                                 var partial = this;
    90                                 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
    91                                         api.preview.send( 'widget-updated', partial.widgetId );
    92                                         api.selectiveRefresh.trigger( 'widget-updated', partial );
    93                                 }
     74                        api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
     75                },
     76
     77                /**
     78                 * Refresh widget partial.
     79                 *
     80                 * @returns {Promise}
     81                 */
     82                refresh: function() {
     83                        var partial = this, refreshDeferred;
     84                        if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) {
     85                                refreshDeferred = $.Deferred();
     86                                refreshDeferred.reject();
     87                                partial.fallback();
     88                                return refreshDeferred.promise();
     89                        } else {
     90                                return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
    9491                        }
    95                 });
     92                },
     93
     94                /**
     95                 * Send widget-updated message to parent so spinner will get removed from widget control.
     96                 *
     97                 * @inheritdoc
     98                 * @param {wp.customize.selectiveRefresh.Placement} placement
     99                 */
     100                renderContent: function( placement ) {
     101                        var partial = this;
     102                        if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
     103                                api.preview.send( 'widget-updated', partial.widgetId );
     104                                api.selectiveRefresh.trigger( 'widget-updated', partial );
     105                        }
     106                }
     107        });
     108
     109        /**
     110         * Partial representing a widget area.
     111         *
     112         * @class
     113         * @augments wp.customize.selectiveRefresh.Partial
     114         * @since 4.5.0
     115         */
     116        self.SidebarPartial = api.selectiveRefresh.Partial.extend({
    96117
    97118                /**
    98                  * Partial representing a widget area.
     119                 * Constructor.
    99120                 *
    100                  * @class
    101                  * @augments wp.customize.selectiveRefresh.Partial
    102121                 * @since 4.5.0
     122                 * @param {string} id - Partial ID.
     123                 * @param {Object} options
     124                 * @param {Object} options.params
    103125                 */
    104                 self.SidebarPartial = api.selectiveRefresh.Partial.extend({
    105 
    106                         /**
    107                          * Constructor.
    108                          *
    109                          * @since 4.5.0
    110                          * @param {string} id - Partial ID.
    111                          * @param {Object} options
    112                          * @param {Object} options.params
    113                          */
    114                         initialize: function( id, options ) {
    115                                 var partial = this, matches;
    116                                 matches = id.match( /^sidebar\[(.+)]$/ );
    117                                 if ( ! matches ) {
    118                                         throw new Error( 'Illegal id for sidebar partial.' );
    119                                 }
    120                                 partial.sidebarId = matches[1];
    121 
    122                                 options = options || {};
    123                                 options.params = _.extend(
    124                                         {
    125                                                 settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
    126                                         },
    127                                         options.params || {}
    128                                 );
     126                initialize: function( id, options ) {
     127                        var partial = this, matches;
     128                        matches = id.match( /^sidebar\[(.+)]$/ );
     129                        if ( ! matches ) {
     130                                throw new Error( 'Illegal id for sidebar partial.' );
     131                        }
     132                        partial.sidebarId = matches[1];
    129133
    130                                 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
     134                        options = options || {};
     135                        options.params = _.extend(
     136                                {
     137                                        settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
     138                                },
     139                                options.params || {}
     140                        );
    131141
    132                                 if ( ! partial.params.sidebarArgs ) {
    133                                         throw new Error( 'The sidebarArgs param was not provided.' );
    134                                 }
    135                                 if ( partial.params.settings.length > 1 ) {
    136                                         throw new Error( 'Expected SidebarPartial to only have one associated setting' );
    137                                 }
    138                         },
    139 
    140                         /**
    141                          * Set up the partial.
    142                          *
    143                          * @since 4.5.0
    144                          */
    145                         ready: function() {
    146                                 var sidebarPartial = this;
    147 
    148                                 // Watch for changes to the sidebar_widgets setting.
    149                                 _.each( sidebarPartial.settings(), function( settingId ) {
    150                                         api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
    151                                 } );
     142                        api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
    152143
    153                                 // Trigger an event for this sidebar being updated whenever a widget inside is rendered.
    154                                 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
    155                                         var isAssignedWidgetPartial = (
    156                                                 placement.partial.extended( self.WidgetPartial ) &&
    157                                                 ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
    158                                         );
    159                                         if ( isAssignedWidgetPartial ) {
    160                                                 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
    161                                         }
    162                                 } );
     144                        if ( ! partial.params.sidebarArgs ) {
     145                                throw new Error( 'The sidebarArgs param was not provided.' );
     146                        }
     147                        if ( partial.params.settings.length > 1 ) {
     148                                throw new Error( 'Expected SidebarPartial to only have one associated setting' );
     149                        }
     150                },
    163151
    164                                 // Make sure that a widget partial has a container in the DOM prior to a refresh.
    165                                 api.bind( 'change', function( widgetSetting ) {
    166                                         var widgetId, parsedId;
    167                                         parsedId = self.parseWidgetSettingId( widgetSetting.id );
    168                                         if ( ! parsedId ) {
    169                                                 return;
    170                                         }
    171                                         widgetId = parsedId.idBase;
    172                                         if ( parsedId.number ) {
    173                                                 widgetId += '-' + String( parsedId.number );
    174                                         }
    175                                         if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
    176                                                 sidebarPartial.ensureWidgetPlacementContainers( widgetId );
    177                                         }
    178                                 } );
    179                         },
    180 
    181                         /**
    182                          * Get the before/after boundary nodes for all instances of this sidebar (usually one).
    183                          *
    184                          * Note that TreeWalker is not implemented in IE8.
    185                          *
    186                          * @since 4.5.0
    187                          * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
    188                          */
    189                         findDynamicSidebarBoundaryNodes: function() {
    190                                 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
    191                                 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
    192                                 recursiveCommentTraversal = function( childNodes ) {
    193                                         _.each( childNodes, function( node ) {
    194                                                 var matches;
    195                                                 if ( 8 === node.nodeType ) {
    196                                                         matches = node.nodeValue.match( regExp );
    197                                                         if ( ! matches || matches[2] !== partial.sidebarId ) {
    198                                                                 return;
    199                                                         }
    200                                                         if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
    201                                                                 boundaryNodes[ matches[3] ] = {
    202                                                                         before: null,
    203                                                                         after: null,
    204                                                                         instanceNumber: parseInt( matches[3], 10 )
    205                                                                 };
    206                                                         }
    207                                                         if ( 'dynamic_sidebar_before' === matches[1] ) {
    208                                                                 boundaryNodes[ matches[3] ].before = node;
    209                                                         } else {
    210                                                                 boundaryNodes[ matches[3] ].after = node;
    211                                                         }
    212                                                 } else if ( 1 === node.nodeType ) {
    213                                                         recursiveCommentTraversal( node.childNodes );
    214                                                 }
    215                                         } );
    216                                 };
    217 
    218                                 recursiveCommentTraversal( document.body.childNodes );
    219                                 return _.values( boundaryNodes );
    220                         },
    221 
    222                         /**
    223                          * Get the placements for this partial.
    224                          *
    225                          * @since 4.5.0
    226                          * @returns {Array}
    227                          */
    228                         placements: function() {
    229                                 var partial = this;
    230                                 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
    231                                         return new api.selectiveRefresh.Placement( {
    232                                                 partial: partial,
    233                                                 container: null,
    234                                                 startNode: boundaryNodes.before,
    235                                                 endNode: boundaryNodes.after,
    236                                                 context: {
    237                                                         instanceNumber: boundaryNodes.instanceNumber
    238                                                 }
    239                                         } );
    240                                 } );
    241                         },
    242 
    243                         /**
    244                          * Get the list of widget IDs associated with this widget area.
    245                          *
    246                          * @since 4.5.0
    247                          *
    248                          * @returns {Array}
    249                          */
    250                         getWidgetIds: function() {
    251                                 var sidebarPartial = this, settingId, widgetIds;
    252                                 settingId = sidebarPartial.settings()[0];
    253                                 if ( ! settingId ) {
    254                                         throw new Error( 'Missing associated setting.' );
     152                /**
     153                 * Set up the partial.
     154                 *
     155                 * @since 4.5.0
     156                 */
     157                ready: function() {
     158                        var sidebarPartial = this;
     159
     160                        // Watch for changes to the sidebar_widgets setting.
     161                        _.each( sidebarPartial.settings(), function( settingId ) {
     162                                api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
     163                        } );
     164
     165                        // Trigger an event for this sidebar being updated whenever a widget inside is rendered.
     166                        api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
     167                                var isAssignedWidgetPartial = (
     168                                        placement.partial.extended( self.WidgetPartial ) &&
     169                                        ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
     170                                );
     171                                if ( isAssignedWidgetPartial ) {
     172                                        api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
     173                                }
     174                        } );
     175
     176                        // Make sure that a widget partial has a container in the DOM prior to a refresh.
     177                        api.bind( 'change', function( widgetSetting ) {
     178                                var widgetId, parsedId;
     179                                parsedId = self.parseWidgetSettingId( widgetSetting.id );
     180                                if ( ! parsedId ) {
     181                                        return;
    255182                                }
    256                                 if ( ! api.has( settingId ) ) {
    257                                         throw new Error( 'Setting does not exist.' );
     183                                widgetId = parsedId.idBase;
     184                                if ( parsedId.number ) {
     185                                        widgetId += '-' + String( parsedId.number );
    258186                                }
    259                                 widgetIds = api( settingId ).get();
    260                                 if ( ! _.isArray( widgetIds ) ) {
    261                                         throw new Error( 'Expected setting to be array of widget IDs' );
     187                                if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
     188                                        sidebarPartial.ensureWidgetPlacementContainers( widgetId );
    262189                                }
    263                                 return widgetIds.slice( 0 );
    264                         },
    265 
    266                         /**
    267                          * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM.
    268                          *
    269                          * @since 4.5.0
    270                          *
    271                          * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed.
    272                          */
    273                         reflowWidgets: function() {
    274                                 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
    275                                 widgetIds = sidebarPartial.getWidgetIds();
    276                                 sidebarPlacements = sidebarPartial.placements();
    277 
    278                                 widgetPartials = {};
    279                                 _.each( widgetIds, function( widgetId ) {
    280                                         var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
    281                                         if ( widgetPartial ) {
    282                                                 widgetPartials[ widgetId ] = widgetPartial;
     190                        } );
     191                },
     192
     193                /**
     194                 * Get the before/after boundary nodes for all instances of this sidebar (usually one).
     195                 *
     196                 * Note that TreeWalker is not implemented in IE8.
     197                 *
     198                 * @since 4.5.0
     199                 * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
     200                 */
     201                findDynamicSidebarBoundaryNodes: function() {
     202                        var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
     203                        regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
     204                        recursiveCommentTraversal = function( childNodes ) {
     205                                _.each( childNodes, function( node ) {
     206                                        var matches;
     207                                        if ( 8 === node.nodeType ) {
     208                                                matches = node.nodeValue.match( regExp );
     209                                                if ( ! matches || matches[2] !== partial.sidebarId ) {
     210                                                        return;
     211                                                }
     212                                                if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
     213                                                        boundaryNodes[ matches[3] ] = {
     214                                                                before: null,
     215                                                                after: null,
     216                                                                instanceNumber: parseInt( matches[3], 10 )
     217                                                        };
     218                                                }
     219                                                if ( 'dynamic_sidebar_before' === matches[1] ) {
     220                                                        boundaryNodes[ matches[3] ].before = node;
     221                                                } else {
     222                                                        boundaryNodes[ matches[3] ].after = node;
     223                                                }
     224                                        } else if ( 1 === node.nodeType ) {
     225                                                recursiveCommentTraversal( node.childNodes );
    283226                                        }
    284227                                } );
     228                        };
    285229
    286                                 _.each( sidebarPlacements, function( sidebarPlacement ) {
    287                                         var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
    288 
    289                                         // Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
    290                                         _.each( widgetPartials, function( widgetPartial ) {
    291                                                 _.each( widgetPartial.placements(), function( widgetPlacement ) {
    292 
    293                                                         if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
    294                                                                 thisPosition = widgetPlacement.container.index();
    295                                                                 sidebarWidgets.push( {
    296                                                                         partial: widgetPartial,
    297                                                                         placement: widgetPlacement,
    298                                                                         position: thisPosition
    299                                                                 } );
    300                                                                 if ( thisPosition < lastPosition ) {
    301                                                                         needsSort = true;
    302                                                                 }
    303                                                                 lastPosition = thisPosition;
    304                                                         }
    305                                                 } );
    306                                         } );
    307 
    308                                         if ( needsSort ) {
    309                                                 _.each( sidebarWidgets, function( sidebarWidget ) {
    310                                                         sidebarPlacement.endNode.parentNode.insertBefore(
    311                                                                 sidebarWidget.placement.container[0],
    312                                                                 sidebarPlacement.endNode
    313                                                         );
     230                        recursiveCommentTraversal( document.body.childNodes );
     231                        return _.values( boundaryNodes );
     232                },
    314233
    315                                                         // @todo Rename partial-placement-moved?
    316                                                         api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
    317                                                 } );
    318 
    319                                                 sortedSidebarContainers.push( sidebarPlacement );
     234                /**
     235                 * Get the placements for this partial.
     236                 *
     237                 * @since 4.5.0
     238                 * @returns {Array}
     239                 */
     240                placements: function() {
     241                        var partial = this;
     242                        return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
     243                                return new api.selectiveRefresh.Placement( {
     244                                        partial: partial,
     245                                        container: null,
     246                                        startNode: boundaryNodes.before,
     247                                        endNode: boundaryNodes.after,
     248                                        context: {
     249                                                instanceNumber: boundaryNodes.instanceNumber
    320250                                        }
    321251                                } );
     252                        } );
     253                },
    322254
    323                                 if ( sortedSidebarContainers.length > 0 ) {
    324                                         api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
    325                                 }
     255                /**
     256                 * Get the list of widget IDs associated with this widget area.
     257                 *
     258                 * @since 4.5.0
     259                 *
     260                 * @returns {Array}
     261                 */
     262                getWidgetIds: function() {
     263                        var sidebarPartial = this, settingId, widgetIds;
     264                        settingId = sidebarPartial.settings()[0];
     265                        if ( ! settingId ) {
     266                                throw new Error( 'Missing associated setting.' );
     267                        }
     268                        if ( ! api.has( settingId ) ) {
     269                                throw new Error( 'Setting does not exist.' );
     270                        }
     271                        widgetIds = api( settingId ).get();
     272                        if ( ! _.isArray( widgetIds ) ) {
     273                                throw new Error( 'Expected setting to be array of widget IDs' );
     274                        }
     275                        return widgetIds.slice( 0 );
     276                },
    326277
    327                                 return sortedSidebarContainers;
    328                         },
    329 
    330                         /**
    331                          * Make sure there is a widget instance container in this sidebar for the given widget ID.
    332                          *
    333                          * @since 4.5.0
    334                          *
    335                          * @param {string} widgetId
    336                          * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial.
    337                          */
    338                         ensureWidgetPlacementContainers: function( widgetId ) {
    339                                 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
    340                                 widgetPartial = api.selectiveRefresh.partial( partialId );
    341                                 if ( ! widgetPartial ) {
    342                                         widgetPartial = new self.WidgetPartial( partialId, {
    343                                                 params: {}
    344                                         } );
    345                                         api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
     278                /**
     279                 * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM.
     280                 *
     281                 * @since 4.5.0
     282                 *
     283                 * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed.
     284                 */
     285                reflowWidgets: function() {
     286                        var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
     287                        widgetIds = sidebarPartial.getWidgetIds();
     288                        sidebarPlacements = sidebarPartial.placements();
     289
     290                        widgetPartials = {};
     291                        _.each( widgetIds, function( widgetId ) {
     292                                var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
     293                                if ( widgetPartial ) {
     294                                        widgetPartials[ widgetId ] = widgetPartial;
    346295                                }
     296                        } );
    347297
    348                                 // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
    349                                 _.each( sidebarPartial.placements(), function( sidebarPlacement ) {
    350                                         var foundWidgetPlacement, widgetContainerElement;
    351 
    352                                         foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
    353                                                 return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
     298                        _.each( sidebarPlacements, function( sidebarPlacement ) {
     299                                var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
     300
     301                                // Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
     302                                _.each( widgetPartials, function( widgetPartial ) {
     303                                        _.each( widgetPartial.placements(), function( widgetPlacement ) {
     304
     305                                                if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
     306                                                        thisPosition = widgetPlacement.container.index();
     307                                                        sidebarWidgets.push( {
     308                                                                partial: widgetPartial,
     309                                                                placement: widgetPlacement,
     310                                                                position: thisPosition
     311                                                        } );
     312                                                        if ( thisPosition < lastPosition ) {
     313                                                                needsSort = true;
     314                                                        }
     315                                                        lastPosition = thisPosition;
     316                                                }
    354317                                        } );
    355                                         if ( foundWidgetPlacement ) {
    356                                                 return;
    357                                         }
     318                                } );
    358319
    359                                         widgetContainerElement = $(
    360                                                 sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) +
    361                                                 sidebarPartial.params.sidebarArgs.after_widget
    362                                         );
    363 
    364                                         widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
    365                                         widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
    366                                         widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
    367 
    368                                         /*
    369                                          * Make sure the widget container element has the customize-container context data.
    370                                          * The sidebar_instance_number is used to disambiguate multiple instances of the
    371                                          * same sidebar are rendered onto the template, and so the same widget is embedded
    372                                          * multiple times.
    373                                          */
    374                                         widgetContainerElement.data( 'customize-partial-placement-context', {
    375                                                 'sidebar_id': sidebarPartial.sidebarId,
    376                                                 'sidebar_instance_number': sidebarPlacement.context.instanceNumber
    377                                         } );
     320                                if ( needsSort ) {
     321                                        _.each( sidebarWidgets, function( sidebarWidget ) {
     322                                                sidebarPlacement.endNode.parentNode.insertBefore(
     323                                                        sidebarWidget.placement.container[0],
     324                                                        sidebarPlacement.endNode
     325                                                );
    378326
    379                                         sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
    380                                         wasInserted = true;
    381                                 } );
     327                                                // @todo Rename partial-placement-moved?
     328                                                api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
     329                                        } );
    382330
    383                                 if ( wasInserted ) {
    384                                         sidebarPartial.reflowWidgets();
     331                                        sortedSidebarContainers.push( sidebarPlacement );
    385332                                }
     333                        } );
    386334
    387                                 return widgetPartial;
    388                         },
    389 
    390                         /**
    391                          * Handle change to the sidebars_widgets[] setting.
    392                          *
    393                          * @since 4.5.0
    394                          *
    395                          * @param {Array} newWidgetIds New widget ids.
    396                          * @param {Array} oldWidgetIds Old widget ids.
    397                          */
    398                         handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
    399                                 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
    400 
    401                                 needsRefresh = (
    402                                         ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
    403                                         ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
    404                                 );
    405                                 if ( needsRefresh ) {
    406                                         sidebarPartial.fallback();
    407                                         return;
    408                                 }
     335                        if ( sortedSidebarContainers.length > 0 ) {
     336                                api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
     337                        }
    409338
    410                                 // Handle removal of widgets.
    411                                 widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
    412                                 _.each( widgetsRemoved, function( removedWidgetId ) {
    413                                         var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
    414                                         if ( widgetPartial ) {
    415                                                 _.each( widgetPartial.placements(), function( placement ) {
    416                                                         var isRemoved = (
    417                                                                 placement.context.sidebar_id === sidebarPartial.sidebarId ||
    418                                                                 ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
    419                                                         );
    420                                                         if ( isRemoved ) {
    421                                                                 placement.container.remove();
    422                                                         }
    423                                                 } );
    424                                         }
    425                                 } );
     339                        return sortedSidebarContainers;
     340                },
    426341
    427                                 // Handle insertion of widgets.
    428                                 widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
    429                                 _.each( widgetsAdded, function( addedWidgetId ) {
    430                                         var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
    431                                         addedWidgetPartials.push( widgetPartial );
     342                /**
     343                 * Make sure there is a widget instance container in this sidebar for the given widget ID.
     344                 *
     345                 * @since 4.5.0
     346                 *
     347                 * @param {string} widgetId
     348                 * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial.
     349                 */
     350                ensureWidgetPlacementContainers: function( widgetId ) {
     351                        var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
     352                        widgetPartial = api.selectiveRefresh.partial( partialId );
     353                        if ( ! widgetPartial ) {
     354                                widgetPartial = new self.WidgetPartial( partialId, {
     355                                        params: {}
    432356                                } );
     357                                api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
     358                        }
    433359
    434                                 _.each( addedWidgetPartials, function( widgetPartial ) {
    435                                         widgetPartial.refresh();
     360                        // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
     361                        _.each( sidebarPartial.placements(), function( sidebarPlacement ) {
     362                                var foundWidgetPlacement, widgetContainerElement;
     363
     364                                foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
     365                                        return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
    436366                                } );
     367                                if ( foundWidgetPlacement ) {
     368                                        return;
     369                                }
    437370
    438                                 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
    439                         },
    440 
    441                         /**
    442                          * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed.
    443                          *
    444                          * @since 4.5.0
    445                          */
    446                         refresh: function() {
    447                                 var partial = this, deferred = $.Deferred();
    448 
    449                                 deferred.fail( function() {
    450                                         partial.fallback();
     371                                widgetContainerElement = $(
     372                                        sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) +
     373                                        sidebarPartial.params.sidebarArgs.after_widget
     374                                );
     375
     376                                widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
     377                                widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
     378                                widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
     379
     380                                /*
     381                                 * Make sure the widget container element has the customize-container context data.
     382                                 * The sidebar_instance_number is used to disambiguate multiple instances of the
     383                                 * same sidebar are rendered onto the template, and so the same widget is embedded
     384                                 * multiple times.
     385                                 */
     386                                widgetContainerElement.data( 'customize-partial-placement-context', {
     387                                        'sidebar_id': sidebarPartial.sidebarId,
     388                                        'sidebar_instance_number': sidebarPlacement.context.instanceNumber
    451389                                } );
    452390
    453                                 if ( 0 === partial.placements().length ) {
    454                                         deferred.reject();
    455                                 } else {
    456                                         _.each( partial.reflowWidgets(), function( sidebarPlacement ) {
    457                                                 api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
    458                                         } );
    459                                         deferred.resolve();
    460                                 }
     391                                sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
     392                                wasInserted = true;
     393                        } );
    461394
    462                                 return deferred.promise();
     395                        if ( wasInserted ) {
     396                                sidebarPartial.reflowWidgets();
    463397                        }
    464                 });
    465398
    466                 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
    467                 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
     399                        return widgetPartial;
     400                },
    468401
    469402                /**
    470                  * Add partials for the registered widget areas (sidebars).
     403                 * Handle change to the sidebars_widgets[] setting.
    471404                 *
    472405                 * @since 4.5.0
     406                 *
     407                 * @param {Array} newWidgetIds New widget ids.
     408                 * @param {Array} oldWidgetIds Old widget ids.
    473409                 */
    474                 self.addPartials = function() {
    475                         _.each( self.registeredSidebars, function( registeredSidebar ) {
    476                                 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
    477                                 partial = api.selectiveRefresh.partial( partialId );
    478                                 if ( ! partial ) {
    479                                         partial = new self.SidebarPartial( partialId, {
    480                                                 params: {
    481                                                         sidebarArgs: registeredSidebar
     410                handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
     411                        var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
     412
     413                        needsRefresh = (
     414                                ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
     415                                ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
     416                        );
     417                        if ( needsRefresh ) {
     418                                sidebarPartial.fallback();
     419                                return;
     420                        }
     421
     422                        // Handle removal of widgets.
     423                        widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
     424                        _.each( widgetsRemoved, function( removedWidgetId ) {
     425                                var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
     426                                if ( widgetPartial ) {
     427                                        _.each( widgetPartial.placements(), function( placement ) {
     428                                                var isRemoved = (
     429                                                        placement.context.sidebar_id === sidebarPartial.sidebarId ||
     430                                                        ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
     431                                                );
     432                                                if ( isRemoved ) {
     433                                                        placement.container.remove();
    482434                                                }
    483435                                        } );
    484                                         api.selectiveRefresh.partial.add( partial.id, partial );
    485436                                }
    486437                        } );
    487                 };
    488438
    489         }
     439                        // Handle insertion of widgets.
     440                        widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
     441                        _.each( widgetsAdded, function( addedWidgetId ) {
     442                                var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
     443                                addedWidgetPartials.push( widgetPartial );
     444                        } );
     445
     446                        _.each( addedWidgetPartials, function( widgetPartial ) {
     447                                widgetPartial.refresh();
     448                        } );
     449
     450                        api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
     451                },
     452
     453                /**
     454                 * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed.
     455                 *
     456                 * @since 4.5.0
     457                 */
     458                refresh: function() {
     459                        var partial = this, deferred = $.Deferred();
     460
     461                        deferred.fail( function() {
     462                                partial.fallback();
     463                        } );
     464
     465                        if ( 0 === partial.placements().length ) {
     466                                deferred.reject();
     467                        } else {
     468                                _.each( partial.reflowWidgets(), function( sidebarPlacement ) {
     469                                        api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
     470                                } );
     471                                deferred.resolve();
     472                        }
     473
     474                        return deferred.promise();
     475                }
     476        });
     477
     478        api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
     479        api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
     480
     481        /**
     482         * Add partials for the registered widget areas (sidebars).
     483         *
     484         * @since 4.5.0
     485         */
     486        self.addPartials = function() {
     487                _.each( self.registeredSidebars, function( registeredSidebar ) {
     488                        var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
     489                        partial = api.selectiveRefresh.partial( partialId );
     490                        if ( ! partial ) {
     491                                partial = new self.SidebarPartial( partialId, {
     492                                        params: {
     493                                                sidebarArgs: registeredSidebar
     494                                        }
     495                                } );
     496                                api.selectiveRefresh.partial.add( partial.id, partial );
     497                        }
     498                } );
     499        };
    490500
    491501        /**
    492502         * Calculate the selector for the sidebar's widgets based on the registered sidebar's info.
  • src/wp-includes/js/customize-selective-refresh.js

    diff --git src/wp-includes/js/customize-selective-refresh.js src/wp-includes/js/customize-selective-refresh.js
    index 8927716..7efee3d 100644
    wp.customize.selectiveRefresh = ( function( $, api ) { 
    109109                placements: function() {
    110110                        var partial = this, selector;
    111111
    112                         selector = partial.params.selector;
     112                        selector = partial.params.selector || '';
    113113                        if ( selector ) {
    114114                                selector += ', ';
    115115                        }
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index 9b4643f..63bbab2 100644
    function wp_default_scripts( &$scripts ) { 
    455455        $scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
    456456
    457457        $scripts->add( 'customize-widgets', "/wp-admin/js/customize-widgets$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
    458         $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
     458        $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
    459459
    460460        $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 );
    461         $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
     461        $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
    462462
    463463        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    464464
  • src/wp-includes/theme.php

    diff --git src/wp-includes/theme.php src/wp-includes/theme.php
    index 15681e5..1ad720c 100644
    function current_theme_supports( $feature ) { 
    19141914         *
    19151915         * The dynamic portion of the hook name, `$feature`, refers to the specific theme
    19161916         * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
    1917          * 'custom-header', 'menus', 'automatic-feed-links', and 'html5'.
     1917         * 'custom-header', 'menus', 'automatic-feed-links', 'html5', and `customize-selective-refresh-widgets`.
    19181918         *
    19191919         * @since 3.4.0
    19201920         *
  • src/wp-includes/widgets/class-wp-nav-menu-widget.php

    diff --git src/wp-includes/widgets/class-wp-nav-menu-widget.php src/wp-includes/widgets/class-wp-nav-menu-widget.php
    index d6ac26c..d465525 100644
     
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array( 'description' => __('Add a custom menu to your sidebar.') );
     26                $widget_ops = array(
     27                        'description' => __( 'Add a custom menu to your sidebar.' ),
     28                        'customize_selective_refresh' => true,
     29                );
    2730                parent::__construct( 'nav_menu', __('Custom Menu'), $widget_ops );
    2831        }
    2932
  • src/wp-includes/widgets/class-wp-widget-archives.php

    diff --git src/wp-includes/widgets/class-wp-widget-archives.php src/wp-includes/widgets/class-wp-widget-archives.php
    index 30e4ee0..ba12572 100644
    class WP_Widget_Archives extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('classname' => 'widget_archive', 'description' => __( 'A monthly archive of your site&#8217;s Posts.') );
     26                $widget_ops = array(
     27                        'classname' => 'widget_archive',
     28                        'description' => __( 'A monthly archive of your site&#8217;s Posts.' ),
     29                        'customize_selective_refresh' => true,
     30                );
    2731                parent::__construct('archives', __('Archives'), $widget_ops);
    2832        }
    2933
  • src/wp-includes/widgets/class-wp-widget-calendar.php

    diff --git src/wp-includes/widgets/class-wp-widget-calendar.php src/wp-includes/widgets/class-wp-widget-calendar.php
    index 2a8911a..9969678 100644
    class WP_Widget_Calendar extends WP_Widget { 
    3333         * @access public
    3434         */
    3535        public function __construct() {
    36                 $widget_ops = array('classname' => 'widget_calendar', 'description' => __( 'A calendar of your site&#8217;s Posts.') );
    37                 parent::__construct('calendar', __('Calendar'), $widget_ops);
     36                $widget_ops = array(
     37                        'classname' => 'widget_calendar',
     38                        'description' => __( 'A calendar of your site&#8217;s Posts.' ),
     39                        'customize_selective_refresh' => true,
     40                );
     41                parent::__construct( 'calendar', __( 'Calendar' ), $widget_ops );
    3842        }
    3943
    4044        /**
  • src/wp-includes/widgets/class-wp-widget-categories.php

    diff --git src/wp-includes/widgets/class-wp-widget-categories.php src/wp-includes/widgets/class-wp-widget-categories.php
    index 058fd90..2fbc62a 100644
    class WP_Widget_Categories extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array( 'classname' => 'widget_categories', 'description' => __( "A list or dropdown of categories." ) );
    27                 parent::__construct('categories', __('Categories'), $widget_ops);
     26                $widget_ops = array(
     27                        'classname' => 'widget_categories',
     28                        'description' => __( 'A list or dropdown of categories.' ),
     29                        'customize_selective_refresh' => true,
     30                );
     31                parent::__construct( 'categories', __( 'Categories' ), $widget_ops );
    2832        }
    2933
    3034        /**
  • src/wp-includes/widgets/class-wp-widget-links.php

    diff --git src/wp-includes/widgets/class-wp-widget-links.php src/wp-includes/widgets/class-wp-widget-links.php
    index 96ecc77..68d86f1 100644
    class WP_Widget_Links extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('description' => __( "Your blogroll" ) );
    27                 parent::__construct('links', __('Links'), $widget_ops);
     26                $widget_ops = array(
     27                        'description' => __( 'Your blogroll' ),
     28                        'customize_selective_refresh' => true,
     29                );
     30                parent::__construct( 'links', __( 'Links' ), $widget_ops );
    2831        }
    2932
    3033        /**
  • src/wp-includes/widgets/class-wp-widget-meta.php

    diff --git src/wp-includes/widgets/class-wp-widget-meta.php src/wp-includes/widgets/class-wp-widget-meta.php
    index c12238f..2de9844 100644
    class WP_Widget_Meta extends WP_Widget { 
    2525         * @access public
    2626         */
    2727        public function __construct() {
    28                 $widget_ops = array('classname' => 'widget_meta', 'description' => __( "Login, RSS, &amp; WordPress.org links.") );
    29                 parent::__construct('meta', __('Meta'), $widget_ops);
     28                $widget_ops = array(
     29                        'classname' => 'widget_meta',
     30                        'description' => __( 'Login, RSS, &amp; WordPress.org links.' ),
     31                        'customize_selective_refresh' => true,
     32                );
     33                parent::__construct( 'meta', __( 'Meta' ), $widget_ops );
    3034        }
    3135
    3236        /**
  • src/wp-includes/widgets/class-wp-widget-pages.php

    diff --git src/wp-includes/widgets/class-wp-widget-pages.php src/wp-includes/widgets/class-wp-widget-pages.php
    index e8737ac..165f334 100644
    class WP_Widget_Pages extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('classname' => 'widget_pages', 'description' => __( 'A list of your site&#8217;s Pages.') );
    27                 parent::__construct('pages', __('Pages'), $widget_ops);
     26                $widget_ops = array(
     27                        'classname' => 'widget_pages',
     28                        'description' => __( 'A list of your site&#8217;s Pages.' ),
     29                        'customize_selective_refresh' => true,
     30                );
     31                parent::__construct( 'pages', __( 'Pages' ), $widget_ops );
    2832        }
    2933
    3034        /**
  • src/wp-includes/widgets/class-wp-widget-recent-comments.php

    diff --git src/wp-includes/widgets/class-wp-widget-recent-comments.php src/wp-includes/widgets/class-wp-widget-recent-comments.php
    index 0f1a3b5..71fb4d2 100644
    class WP_Widget_Recent_Comments extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('classname' => 'widget_recent_comments', 'description' => __( 'Your site&#8217;s most recent comments.' ) );
    27                 parent::__construct('recent-comments', __('Recent Comments'), $widget_ops);
     26                $widget_ops = array(
     27                        'classname' => 'widget_recent_comments',
     28                        'description' => __( 'Your site&#8217;s most recent comments.' ),
     29                        'customize_selective_refresh' => true,
     30                );
     31                parent::__construct( 'recent-comments', __( 'Recent Comments' ), $widget_ops );
    2832                $this->alt_option_name = 'widget_recent_comments';
    2933
    30                 if ( is_active_widget(false, false, $this->id_base) )
    31                         add_action( 'wp_head', array($this, 'recent_comments_style') );
     34                if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
     35                        add_action( 'wp_head', array( $this, 'recent_comments_style' ) );
     36                }
    3237        }
    3338
    3439        /**
  • src/wp-includes/widgets/class-wp-widget-recent-posts.php

    diff --git src/wp-includes/widgets/class-wp-widget-recent-posts.php src/wp-includes/widgets/class-wp-widget-recent-posts.php
    index 8f92bf3..fdb54a0 100644
    class WP_Widget_Recent_Posts extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('classname' => 'widget_recent_entries', 'description' => __( "Your site&#8217;s most recent Posts.") );
    27                 parent::__construct('recent-posts', __('Recent Posts'), $widget_ops);
     26                $widget_ops = array(
     27                        'classname' => 'widget_recent_entries',
     28                        'description' => __( 'Your site&#8217;s most recent Posts.' ),
     29                        'customize_selective_refresh' => true,
     30                );
     31                parent::__construct( 'recent-posts', __( 'Recent Posts' ), $widget_ops );
    2832                $this->alt_option_name = 'widget_recent_entries';
    2933        }
    3034
  • src/wp-includes/widgets/class-wp-widget-rss.php

    diff --git src/wp-includes/widgets/class-wp-widget-rss.php src/wp-includes/widgets/class-wp-widget-rss.php
    index b477a6e..a2548c4 100644
    class WP_Widget_RSS extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array( 'description' => __('Entries from any RSS or Atom feed.') );
     26                $widget_ops = array(
     27                        'description' => __( 'Entries from any RSS or Atom feed.' ),
     28                        'customize_selective_refresh' => true,
     29                );
    2730                $control_ops = array( 'width' => 400, 'height' => 200 );
    28                 parent::__construct( 'rss', __('RSS'), $widget_ops, $control_ops );
     31                parent::__construct( 'rss', __( 'RSS' ), $widget_ops, $control_ops );
    2932        }
    3033
    3134        /**
  • src/wp-includes/widgets/class-wp-widget-search.php

    diff --git src/wp-includes/widgets/class-wp-widget-search.php src/wp-includes/widgets/class-wp-widget-search.php
    index 29bbbf8..f7d67bb 100644
    class WP_Widget_Search extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('classname' => 'widget_search', 'description' => __( "A search form for your site.") );
     26                $widget_ops = array(
     27                        'classname' => 'widget_search',
     28                        'description' => __( 'A search form for your site.' ),
     29                        'customize_selective_refresh' => true,
     30                );
    2731                parent::__construct( 'search', _x( 'Search', 'Search widget' ), $widget_ops );
    2832        }
    2933
  • src/wp-includes/widgets/class-wp-widget-tag-cloud.php

    diff --git src/wp-includes/widgets/class-wp-widget-tag-cloud.php src/wp-includes/widgets/class-wp-widget-tag-cloud.php
    index 4115c79..1251e1d 100644
    class WP_Widget_Tag_Cloud extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array( 'description' => __( "A cloud of your most used tags.") );
    27                 parent::__construct('tag_cloud', __('Tag Cloud'), $widget_ops);
     26                $widget_ops = array(
     27                        'description' => __( 'A cloud of your most used tags.' ),
     28                        'customize_selective_refresh' => true,
     29                );
     30                parent::__construct( 'tag_cloud', __( 'Tag Cloud' ), $widget_ops );
    2831        }
    2932
    3033        /**
  • src/wp-includes/widgets/class-wp-widget-text.php

    diff --git src/wp-includes/widgets/class-wp-widget-text.php src/wp-includes/widgets/class-wp-widget-text.php
    index 5a1a056..a8ec3d4 100644
    class WP_Widget_Text extends WP_Widget { 
    2323         * @access public
    2424         */
    2525        public function __construct() {
    26                 $widget_ops = array('classname' => 'widget_text', 'description' => __('Arbitrary text or HTML.'));
    27                 $control_ops = array('width' => 400, 'height' => 350);
    28                 parent::__construct('text', __('Text'), $widget_ops, $control_ops);
     26                $widget_ops = array(
     27                        'classname' => 'widget_text',
     28                        'description' => __( 'Arbitrary text or HTML.' ),
     29                        'customize_selective_refresh' => true,
     30                );
     31                $control_ops = array( 'width' => 400, 'height' => 350 );
     32                parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops );
    2933        }
    3034
    3135        /**
  • tests/phpunit/tests/customize/manager.php

    diff --git tests/phpunit/tests/customize/manager.php tests/phpunit/tests/customize/manager.php
    index 6f5789d..0b86b4c 100644
    class Tests_WP_Customize_Manager extends WP_UnitTestCase { 
    425425                $data = json_decode( $json, true );
    426426                $this->assertNotEmpty( $data );
    427427
    428                 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'selectiveRefreshEnabled' ), array_keys( $data ) );
     428                $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices' ), array_keys( $data ) );
    429429                $this->assertEquals( $autofocus, $data['autofocus'] );
    430430                $this->assertArrayHasKey( 'save', $data['nonce'] );
    431431                $this->assertArrayHasKey( 'preview', $data['nonce'] );
  • tests/phpunit/tests/customize/widgets.php

    diff --git tests/phpunit/tests/customize/widgets.php tests/phpunit/tests/customize/widgets.php
    index c485b36..0099e56 100644
    class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 
    4343                remove_action( 'after_setup_theme', 'twentyfifteen_setup' );
    4444                remove_action( 'after_setup_theme', 'twentysixteen_setup' );
    4545                remove_action( 'customize_register', 'twentysixteen_customize_register', 11 );
     46                add_theme_support( 'customize-selective-refresh-widgets' );
    4647
    4748                $this->backup_registered_sidebars = $GLOBALS['wp_registered_sidebars'];
    4849        }
    class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 
    116117         * Test WP_Customize_Widgets::get_setting_args()
    117118         */
    118119        function test_get_setting_args() {
     120                global $wp_registered_sidebars;
    119121
    120122                add_filter( 'widget_customizer_setting_args', array( $this, 'filter_widget_customizer_setting_args' ), 10, 2 );
    121123
    122124                $default_args = array(
    123125                        'type' => 'option',
    124126                        'capability' => 'edit_theme_options',
    125                         'transport' => 'postMessage',
     127                        'transport' => 'refresh',
    126128                        'default' => array(),
    127129                        'sanitize_callback' => array( $this->manager->widgets, 'sanitize_widget_instance' ),
    128130                        'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_widget_js_instance' ),
    class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 
    148150                }
    149151                $this->assertEquals( 'WIDGET_BAR[3]', $args['uppercase_id_set_by_filter'] );
    150152
     153                $wp_registered_sidebars['sidebar-1']['customize_selective_refresh'] = true;
    151154                $default_args = array(
    152155                        'type' => 'option',
    153156                        'capability' => 'edit_theme_options',
    class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 
    162165                }
    163166                $this->assertEquals( 'SIDEBARS_WIDGETS[SIDEBAR-1]', $args['uppercase_id_set_by_filter'] );
    164167
     168                $wp_registered_sidebars['sidebar-2']['customize_selective_refresh'] = false;
    165169                $override_args = array(
    166170                        'type' => 'theme_mod',
    167171                        'capability' => 'edit_posts',
    168                         'transport' => 'postMessage',
     172                        'transport' => 'refresh',
    169173                        'default' => array( 'title' => 'asd' ),
    170174                        'sanitize_callback' => '__return_empty_array',
    171175                        'sanitize_js_callback' => '__return_empty_array',
    class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 
    408412                global $wp_registered_sidebars;
    409413                register_sidebar( array(
    410414                        'id' => 'foo',
     415                        'customize_selective_refresh' => true,
    411416                ) );
    412417
    413418                $this->manager->widgets->selective_refresh_init();