Make WordPress Core

Ticket #31336: 30737-and-31336.diff

File 30737-and-31336.diff, 74.1 KB (added by westonruter, 9 years ago)

Resolved merged conflicts between #30737 and #31336: https://github.com/xwp/wordpress-develop/pull/89

  • src/wp-admin/css/customize-controls.css

    diff --git src/wp-admin/css/customize-controls.css src/wp-admin/css/customize-controls.css
    index b256106..016ac20 100644
    body { 
    2020}
    2121
    2222#customize-controls .description {
    23         color: #666666;
     23        color: #555;
    2424}
    2525
    2626#customize-header-actions .button-primary {
    body { 
    5050        overflow-x: hidden;
    5151}
    5252
    53 #customize-info {
     53#customize-controls .customize-info {
    5454        border: none;
    5555        border-top: 1px solid #ddd;
     56        border-bottom: 1px solid #ddd;
     57        margin-bottom: 15px;
    5658}
    5759
    58 #customize-info .accordion-section-title {
    59         background-color: #fff;
    60         color: #666666;
     60#customize-controls .customize-info .accordion-section-title {
     61        background: #fff;
     62        color: #555;
    6163        border-left: none;
    6264        border-right: none;
    63         border-bottom: 1px solid #eeeeee;
     65        border-bottom: none;
     66        cursor: default;
    6467}
    6568
    66 #customize-info.open .accordion-section-title:after,
    67 #customize-info .accordion-section-title:hover:after,
    68 #customize-info .accordion-section-title:focus:after {
    69         color: #555555;
     69#customize-controls .customize-info.open .accordion-section-title:after,
     70#customize-controls .customize-info .accordion-section-title:hover:after,
     71#customize-controls .customize-info .accordion-section-title:focus:after {
     72        color: #333;
    7073}
    7174
    72 #customize-info .preview-notice {
     75#customize-controls .customize-info .accordion-section-title:after {
     76        display: none;
     77}
     78
     79#customize-controls .customize-info .preview-notice {
    7380        font-size: 13px;
    7481        line-height: 24px;
    7582}
    7683
    77 #customize-info .theme-name {
     84#customize-controls .control-section .customize-section-title h3,
     85#customize-controls .control-section h3.customize-section-title,
     86#customize-controls .customize-info .panel-title {
    7887        font-size: 20px;
    7988        font-weight: 200;
    8089        line-height: 24px;
    8190        display: block;
     91        overflow: hidden;
     92        white-space: nowrap;
     93        text-overflow: ellipsis;
    8294}
    8395
    84 #customize-info .theme-screenshot {
    85         width: 258px;
     96#customize-controls .customize-section-title span.customize-action {
     97        overflow: hidden;
     98        white-space: nowrap;
     99        text-overflow: ellipsis;
    86100}
    87101
    88 #customize-info .theme-description {
    89         margin-top: 1em;
    90         color: #666666;
    91         line-height: 20px;
     102#customize-controls .customize-info .customize-help-toggle {
     103        position: absolute;
     104        top: 4px;
     105        right: 1px;
     106        padding: 20px 20px 10px 10px;
     107        width: 20px;
     108        height: 20px;
     109        cursor: pointer;
     110        box-shadow: none;
     111        -webkit-appearance: none;
     112        background: transparent;
     113        color: #555;
     114        border: none;
     115}
     116
     117#customize-controls .customize-info .customize-help-toggle:before {
     118        position: absolute;
     119        top: 5px;
     120        left: 5px;
     121}
     122
     123#customize-controls .customize-info.open .customize-help-toggle,
     124#customize-controls .customize-info .customize-help-toggle:focus,
     125#customize-controls .customize-info .customize-help-toggle:hover {
     126        color: #0073aa;
     127}
     128
     129#customize-controls .customize-info .customize-panel-description {
     130        color: #555;
     131        display: none;
     132        background: #fff;
     133        padding: 12px 15px;
     134        border-top: 1px solid #ddd;
     135}
     136
     137#customize-controls .customize-info .customize-panel-description p:first-child {
     138        margin-top: 0;
     139}
     140
     141#customize-controls .customize-info .customize-panel-description p:last-child {
     142        margin-bottom: 0;
     143}
     144
     145#customize-controls .current-panel .control-section > h3.accordion-section-title {
     146        padding-right: 30px;
    92147}
    93148
    94149#customize-theme-controls .control-section {
    body { 
    96151}
    97152
    98153#customize-theme-controls .accordion-section-title {
    99         color: #555555;
     154        color: #555;
    100155        background-color: #fff;
    101         border-bottom: 1px solid #eeeeee;
     156        border-bottom: 1px solid #eee;
     157}
     158
     159#customize-theme-controls .accordion-section-title:after {
     160        content: "\f345";
     161}
     162
     163.rtl #customize-theme-controls .accordion-section-title:after {
     164        content: "\f341";
    102165}
    103166
    104167#customize-theme-controls .accordion-section-content {
    105         color: #555555;
    106         background: #fff;
     168        color: #555;
     169        background: transparent;
    107170}
    108171
    109 #customize-info.open .accordion-section-title,
    110 #customize-info .accordion-section-title:hover,
    111 #customize-info .accordion-section-title:focus,
    112 #customize-theme-controls .control-section:hover > .accordion-section-title,
    113 #customize-theme-controls .control-section .accordion-section-title:hover,
    114 #customize-theme-controls .control-section.open .accordion-section-title,
    115 #customize-theme-controls .control-section .accordion-section-title:focus {
    116         color: #23282d;
    117         background: #f5f5f5;
     172#customize-controls .control-section:hover > .accordion-section-title,
     173#customize-controls .control-section .accordion-section-title:hover,
     174#customize-controls .control-section.open .accordion-section-title,
     175#customize-controls .control-section .accordion-section-title:focus {
     176        color: #fff;
     177        background: #0073aa;
    118178}
    119179
    120180.js .control-section:hover .accordion-section-title,
    body { 
    128188#customize-theme-controls .control-section .accordion-section-title:hover:after,
    129189#customize-theme-controls .control-section.open .accordion-section-title:after,
    130190#customize-theme-controls .control-section .accordion-section-title:focus:after {
    131         color: #555;
     191        color: #fff;
    132192}
    133193
    134 #customize-info.open,
    135194#customize-theme-controls .control-section.open {
    136         border-bottom: 1px solid #eeeeee;
     195        border-bottom: 1px solid #eee;
    137196}
    138197
    139198#customize-theme-controls .control-section.open .accordion-section-title {
    140         border-bottom-color: #eeeeee !important;
     199        border-bottom-color: #eee !important;
    141200}
    142201
    143202#customize-theme-controls .control-section:last-of-type.open,
    body { 
    145204        border-bottom-color: #ddd;
    146205}
    147206
    148 #customize-theme-controls > ul,
     207#customize-theme-controls > ul {
     208        margin: 0;
     209}
     210
    149211#customize-theme-controls .accordion-section-content {
    150212        margin: 0;
     213        position: absolute;
     214        left: 100%;
     215        top: 0;
     216        width: -webkit-calc(100% - 24px);
     217        width: calc(100% - 24px);
     218        padding: 12px;
    151219}
    152220
    153 .control-section.control-panel > .accordion-section-title {
    154         padding-right: 54px;
     221.customize-section-description-container {
     222        margin-bottom: 15px;
    155223}
    156224
    157 .control-section.control-panel > .accordion-section-title:after {
    158         content: "\f345";
    159         background: #f5f5f5;
     225.customize-section-title {
     226        margin: -12px;
     227        border-bottom: 1px solid #ddd;
     228        background: #fff;
     229}
     230
     231#customize-theme-controls .customize-themes-panel h3.customize-section-title:first-child {
     232        border-bottom: 1px solid #ddd;
     233        padding: 12px 12px 12px 12px;
     234}
     235
     236.ios #customize-theme-controls .customize-themes-panel h3.customize-section-title:first-child {
     237        padding: 12px 12px 13px 12px;
     238}
     239
     240.customize-section-title h3,
     241h3.customize-section-title {
     242        padding: 10px 10px 12px 14px;
     243        margin: 0;
     244        line-height: 21px;
    160245        color: #555;
    161         width: 38px;
    162         height: 100%;
    163         margin: -11px -10px -11px 0; /* compensate for positioning */
    164         line-height: 45px;
    165         padding-left: 5px;
    166         border-left: 1px solid #eee;
    167         z-index: 0;
    168246}
    169247
    170 #customize-theme-controls .control-section.control-panel > h3.accordion-section-title:focus:after,
    171 #customize-theme-controls .control-section.control-panel > h3.accordion-section-title:hover:after {
    172         background: #ddd;
    173         color: #000;
    174         border: 1px solid #d9d9d9;
    175         border-right: none;
    176         margin-top: -12px;
    177         line-height: 44px;
    178         z-index: 1;
     248#customize-theme-controls .control-section .accordion-section-content > li.customize-control:nth-child(2) {
     249        margin-top: 12px;
     250}
     251
     252#customize-theme-controls {
     253        position: relative;
     254        left: 0;
     255        transition: .18s left ease-in-out;
     256}
     257
     258.section-open #customize-info,
     259.section-open #customize-theme-controls {
     260        left: -100%;
     261}
     262
     263.section-open .control-panel-back {
     264        display: none;
    179265}
    180266
    181267.accordion-sub-container.control-panel-content {
    body { 
    184270        left: 300px;
    185271        top: 0;
    186272        width: 300px;
    187         border-top: 1px solid #ddd;
    188273        -webkit-transition: left ease-in-out .18s;
    189274        transition: left ease-in-out .18s;
    190275}
    191276
     277.ios .accordion-sub-container.control-panel-content {
     278        -webkit-transition: left 0s;
     279        transition: left 0s;
     280}
     281
    192282.accordion-sub-container.control-panel-content.animating {
    193283        display: block;
    194284}
    body { 
    242332        transition: left .18s ease-in-out, color .1s ease-in-out, background .1s ease-in-out;
    243333}
    244334
    245 .ios .control-panel-back {
     335.customize-panel-back,
     336.customize-section-back {
     337        display: block;
     338        float: left;
     339        width: 48px;
     340        height: 69px;
     341        padding: 0 24px 0 0;
     342        margin: 0;
     343        background: #fff;
     344        border: none;
     345        border-right: 1px solid #ddd;
     346        box-shadow: none;
     347        cursor: pointer;
     348        -webkit-transition: left .18s ease-in-out, color .1s ease-in-out, background .1s ease-in-out;
     349        transition: left .18s ease-in-out, color .1s ease-in-out, background .1s ease-in-out;
     350}
     351
     352.customize-section-back {
     353        height: 70px;
     354}
     355
     356.ios .control-panel-back,
     357.ios .customize-panel-back,
     358.ios .customize-section-back {
    246359        -webkit-transition: left 0s;
    247360        transition: left 0s;
    248361}
    249362
    250 .collapsed .control-panel-back {
     363.collapsed .control-panel-back,
     364.ios .customize-panel-back {
    251365        display: none;
    252366}
    253367
     368.ios .expanded.in-sub-panel .customize-panel-back {
     369        display: block;
     370}
     371
     372.panel-meta.customize-info .accordion-section-title {
     373        margin-left: 48px;
     374}
     375
     376#customize-controls .panel-meta.customize-info .accordion-section-title:hover {
     377        background: #fff;
     378        color: #555;
     379}
     380
    254381.customize-overlay-close:focus,
    255382.customize-overlay-close:hover,
    256383.customize-controls-close:focus,
    body { 
    267394        box-shadow: none;
    268395}
    269396
     397.customize-panel-back:hover,
     398.customize-panel-back:focus,
     399.customize-section-back:hover,
     400.customize-section-back:focus {
     401        background: #0073aa;
     402        border-color: #ccc;
     403        color: #fff;
     404        outline: none;
     405        -webkit-box-shadow: none;
     406        box-shadow: none;
     407}
     408
    270409.customize-overlay-close:before,
    271410.customize-controls-close:before {
    272411        font: normal 22px/45px dashicons;
    body { 
    276415        left: 13px;
    277416}
    278417
     418.customize-panel-back:before,
     419.customize-section-back:before {
     420        font: normal 20px/69px dashicons;
     421        content: "\f341";
     422        position: relative;
     423        left: 13px;
     424}
     425
    279426.control-panel-back:before {
    280427        font: normal 20px/45px dashicons;
    281428        content: "\f341";
    body { 
    284431        left: 13px;
    285432}
    286433
    287 .in-sub-panel .control-panel-back {
    288         left: 0;
    289 }
    290 
    291 .current-panel > .accordion-section-title {
    292         height: 22px;
    293 }
    294 
    295434.wp-full-overlay-sidebar .wp-full-overlay-header {
    296435        -webkit-transition: padding ease-in-out .18s;
    297436        transition: padding ease-in-out .18s;
    body { 
    345484        transition: left 0s;
    346485}
    347486
    348 .control-section.control-panel .accordion-section-title .panel-title {
    349         font-size: 20px;
    350         font-weight: 200;
    351         line-height: 24px;
    352         display: block;
    353         border: none;
    354 }
    355 
    356 .control-section.control-panel .preview-notice {
    357         font-size: 13px;
    358         line-height: 24px;
    359 }
    360 
    361487p.customize-section-description {
    362488        font-style: normal;
     489        margin-top: 22px;
     490        margin-bottom: 0;
    363491}
    364492
    365493.customize-control {
    366494        width: 100%;
    367495        float: left;
    368496        clear: both;
    369         margin-bottom: 8px;
     497        margin-bottom: 12px;
    370498}
    371499
    372500.customize-control select,
    p.customize-section-description { 
    498626}
    499627
    500628.wp-full-overlay-sidebar {
    501         background: #eeeeee;
     629        background: #eee;
    502630        border-right: 1px solid #ddd;
    503631}
    504632
    p.customize-section-description { 
    542670        line-height: 16px;
    543671        margin-right: 16px;
    544672        padding: 4px 5px;
    545         border: 2px solid #eeeeee;
     673        border: 2px solid #eee;
    546674        -webkit-user-select: none;
    547675        -moz-user-select: none;
    548676        -ms-user-select: none;
    p.customize-section-description { 
    555683        bottom: 0;
    556684        right: 0;
    557685        width: 20px;
    558         background: #eeeeee;
     686        background: #eee;
    559687}
    560688
    561689.customize-control .dropdown-arrow:after {
    p.customize-section-description { 
    575703
    576704.customize-control .dropdown-status {
    577705        color: #32373c;
    578         background: #eeeeee;
     706        background: #eee;
    579707        display: none;
    580708        max-width: 112px;
    581709}
    p.customize-section-description { 
    595723}
    596724
    597725.customize-control-color .dropdown .dropdown-content {
    598         background-color: #555555;
     726        background-color: #555;
    599727        border: 1px solid rgba(0, 0, 0, 0.15);
    600728}
    601729
    p.customize-section-description { 
    8971025
    8981026#customize-theme-controls .control-section-themes .accordion-section-title:hover,
    8991027#customize-theme-controls .control-section-themes .accordion-section-title:focus {
    900         color: #555555;
     1028        color: #555;
    9011029        background-color: #fff;
    9021030}
    9031031
    p.customize-section-description { 
    9131041        padding-right: 100px; /* Space for the button */
    9141042}
    9151043
    916 .control-section-themes .accordion-section-title span {
    917         font-size: small;
     1044.control-section-themes .accordion-section-title span.customize-action,
     1045#customize-controls .customize-section-title span.customize-action {
     1046        font-size: 13px;
    9181047        display: block;
    9191048        font-weight: 400;
    9201049}
    p.customize-section-description { 
    9281057        font-weight: normal;
    9291058}
    9301059
     1060.control-section-themes .accordion-section-title:before {
     1061        display: none;
     1062}
     1063
    9311064.customize-themes-panel {
    9321065        display: none;
    9331066        padding: 0 8px;
    p.customize-section-description { 
    9381071        box-sizing: border-box;
    9391072}
    9401073
     1074.customize-themes-panel .accordion-section-title:first-child {
     1075        margin-top: 0;
     1076}
     1077
     1078#customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) {
     1079        font-size: 14px;
     1080        font-weight: 600;
     1081}
    9411082
    9421083.customize-themes-panel > h2 {
    9431084        padding: 15px 8px 0 8px;
    p.customize-section-description { 
    9561097        margin-bottom: 8px;
    9571098}
    9581099
     1100#customize-theme-controls .themes.accordion-section-content {
     1101        position: relative;
     1102        left: 0;
     1103        padding: 0;
     1104        width: 100%;
     1105}
     1106
    9591107.wp-customizer .theme-browser .themes {
    9601108        padding-bottom: 8px;
    9611109}
    body.cheatin p { 
    11701318                margin-bottom: 4px;
    11711319        }
    11721320
    1173         .adding-widget #customize-header-actions .primary-actions {
    1174                 display: none;
    1175         }
    1176 
    1177         .adding-widget #customize-header-actions .secondary-actions {
    1178                 display: block;
    1179         }
    1180 
    11811321        #customize-header-actions .button-primary {
    11821322                margin-top: 6px;
    11831323        }
  • src/wp-admin/css/customize-widgets.css

    diff --git src/wp-admin/css/customize-widgets.css src/wp-admin/css/customize-widgets.css
    index ba95022..3601cae 100644
     
    1414        display: none;
    1515}
    1616
     17.control-section.control-section-sidebar .accordion-section-content.ui-sortable {
     18        overflow: visible;
     19}
     20
    1721.customize-control-widget_form .widget-top {
    1822        -webkit-transition: opacity 0.5s;
    1923        transition: opacity 0.5s;
    body.adding-widget .add-new-widget:before { 
    348352        width: 300px;
    349353        margin: 0;
    350354        z-index: 1;
    351         background: #fff !important;
     355        background: #eee !important;
    352356        -webkit-transition: all 0.2s;
    353357        transition: all 0.2s;
    354         border-right: 1px solid #dddddd;
     358        border-right: 1px solid #ddd;
    355359}
    356360
    357361#available-widgets-list {
    body.adding-widget .add-new-widget:before { 
    384388#available-widgets .widget-tpl {
    385389        position: relative;
    386390        padding: 20px 15px 20px 60px;
     391        background: #fff;
    387392        border-bottom: 1px solid #e4e4e4;
    388393        cursor: pointer;
    389394        display: none;
    body.adding-widget .add-new-widget:before { 
    391396
    392397#available-widgets .widget-tpl:hover,
    393398#available-widgets .widget-tpl.selected {
    394         background: #fafafa;
     399        background: #eee;
     400        border-bottom-color: #ccc;
    395401}
    396402
    397403#available-widgets .widget-top,
    body.adding-widget #customize-preview { 
    583589#available-widgets [class*="tweet"] .widget-title:before,
    584590#available-widgets [class*="twitter"] .widget-title:before { content: "\f301"; }
    585591
     592#available-widgets .customize-section-title {
     593        display: none;
     594}
    586595
    587596@media screen and (max-height: 700px) and (min-width: 981px) {
    588         .customize-control {
     597        .customize-control-widget {
    589598                margin-bottom: 0;
    590599        }
    591600        .widget-top {
    body.adding-widget #customize-preview { 
    628637                width: 100%;
    629638        }
    630639
     640        #available-widgets .customize-section-title {
     641                display: block;
     642                margin: 0;
     643        }
     644       
     645        #available-widgets .customize-section-back {
     646                height: 69px;
     647        }
     648
     649        #available-widgets .customize-section-title h3 {
     650                font-size: 20px;
     651                font-weight: 200;
     652                padding: 9px 10px 12px 14px;
     653                margin: 0;
     654                line-height: 24px;
     655                color: #555;
     656                display: block;
     657                overflow: hidden;
     658                white-space: nowrap;
     659                text-overflow: ellipsis;
     660        }
     661
     662        #available-widgets .customize-section-title .customize-action {
     663                font-size: 13px;
     664                display: block;
     665                font-weight: 400;
     666                overflow: hidden;
     667                white-space: nowrap;
     668                text-overflow: ellipsis;
     669        }
     670
    631671        #available-widgets-filter {
    632                 position: static;
     672                position: relative;
    633673                width: 100%;
     674                background: #fff;
    634675                height: auto;
     676                padding: 10px 15px;
     677        }
     678
     679        #available-widgets-list {
     680                top: 140px;
    635681        }
    636682}
  • src/wp-admin/customize.php

    diff --git src/wp-admin/customize.php src/wp-admin/customize.php
    index 65caa29..e3ea406 100644
    do_action( 'customize_controls_print_scripts' ); 
    147147
    148148                <div id="widgets-right"><!-- For Widget Customizer, many widgets try to look for instances under div#widgets-right, so we have to add that ID to a container div in the Customizer for compat -->
    149149                <div class="wp-full-overlay-sidebar-content" tabindex="-1">
    150                         <div id="customize-info" class="accordion-section">
    151                                 <div class="accordion-section-title" aria-label="<?php esc_attr_e( 'Customizer Options' ); ?>" tabindex="0">
     150                        <div id="customize-info" class="accordion-section customize-info">
     151                                <div class="accordion-section-title" aria-label="<?php esc_attr_e( 'Customizer Options' ); ?>">
    152152                                        <span class="preview-notice"><?php
    153                                                 echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
     153                                                echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title site-title">' . get_bloginfo( 'name' ) . '</strong>' );
    154154                                        ?></span>
     155                                        <button class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button>
    155156                                </div>
    156                                 <div class="accordion-section-content"><?php
     157                                <div class="customize-panel-description"><?php
    157158                                        _e( 'The Customizer allows you to preview changes to your site before publishing them. You can also navigate to different pages on your site to preview them.' );
    158159                                ?></div>
    159160                        </div>
    do_action( 'customize_controls_print_scripts' ); 
    174175        <div id="customize-preview" class="wp-full-overlay-main"></div>
    175176        <?php
    176177
    177         // Render control templates.
     178        // Render Panel, Section, and Control templates.
     179        $wp_customize->render_panel_templates();
     180        $wp_customize->render_section_templates();
    178181        $wp_customize->render_control_templates();
    179182
    180183        /**
    do_action( 'customize_controls_print_scripts' ); 
    258261
    259262        // Prepare Customize Setting objects to pass to JavaScript.
    260263        foreach ( $wp_customize->settings() as $id => $setting ) {
    261                 $settings['settings'][ $id ] = array(
    262                         'value'     => $setting->js_value(),
    263                         'transport' => $setting->transport,
    264                         'dirty'     => $setting->dirty,
    265                 );
     264                if ( $setting->check_capabilities() ) {
     265                        $settings['settings'][ $id ] = array(
     266                                'value'     => $setting->js_value(),
     267                                'transport' => $setting->transport,
     268                                'dirty'     => $setting->dirty,
     269                        );
     270                }
    266271        }
    267272
    268273        // Prepare Customize Control objects to pass to JavaScript.
    269274        foreach ( $wp_customize->controls() as $id => $control ) {
    270                 $settings['controls'][ $id ] = $control->json();
     275                if ( $control->check_capabilities() ) {
     276                        $settings['controls'][ $id ] = $control->json();
     277                }
    271278        }
    272279
    273280        // Prepare Customize Section objects to pass to JavaScript.
    274281        foreach ( $wp_customize->sections() as $id => $section ) {
    275                 $settings['sections'][ $id ] = $section->json();
     282                if ( $section->check_capabilities() ) {
     283                        $settings['sections'][ $id ] = $section->json();
     284                }
    276285        }
    277286
    278287        // Prepare Customize Panel objects to pass to JavaScript.
    279         foreach ( $wp_customize->panels() as $id => $panel ) {
    280                 $settings['panels'][ $id ] = $panel->json();
    281                 foreach ( $panel->sections as $section_id => $section ) {
    282                         $settings['sections'][ $section_id ] = $section->json();
     288        foreach ( $wp_customize->panels() as $panel_id => $panel ) {
     289                if ( $panel->check_capabilities() ) {
     290                        $settings['panels'][ $panel_id ] = $panel->json();
     291                        foreach ( $panel->sections as $section_id => $section ) {
     292                                if ( $section->check_capabilities() ) {
     293                                        $settings['sections'][ $section_id ] = $section->json();
     294                                }
     295                        }
    283296                }
    284297        }
    285298
  • src/wp-admin/js/customize-controls.js

    diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
    index e211a41..0c46d72 100644
     
    156156        Container = api.Class.extend({
    157157                defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
    158158                defaultExpandedArguments: { duration: 'fast', completeCallback: $.noop },
     159                containerType: 'container',
    159160
    160161                /**
    161162                 * @since 4.1.0
     
    168169                        container.id = id;
    169170                        container.params = {};
    170171                        $.extend( container, options || {} );
     172                        container.templateSelector = 'customize-' + container.containerType + '-' + container.params.type;
    171173                        container.container = $( container.params.content );
     174                        if ( 0 === container.container.length ) {
     175                                container.container = $( container.getContainer() );
     176                        }
    172177
    173178                        container.deferred = {
    174179                                embedded: new $.Deferred()
     
    191196                                container.onChangeExpanded( expanded, args );
    192197                        });
    193198
    194                         container.attachEvents();
     199                        container.deferred.embedded.done( function () {
     200                                container.attachEvents();
     201                        });
    195202
    196203                        api.utils.bubbleChildValueChanges( container, [ 'priority', 'active' ] );
    197204
     
    366373                 * Bring the container into view and then expand this and bring it into view
    367374                 * @param {Object} [params]
    368375                 */
    369                 focus: focus
     376                focus: focus,
     377
     378                /**
     379                 * Return the container html, generated from its JS template, if it exists.
     380                 *
     381                 * @since 4.3.0
     382                 */
     383                getContainer: function () {
     384                        var template,
     385                                container = this;
     386
     387                        if ( 0 !== $( '#tmpl-' + container.templateSelector ).length ) {
     388                                template = wp.template( container.templateSelector );
     389                                if ( template && container.container ) {
     390                                        return $.trim( template( container.params ) );
     391                                }
     392                        }
     393
     394                        return '<li></li>';
     395                }
    370396        });
    371397
    372398        /**
     
    376402         * @augments wp.customize.Class
    377403         */
    378404        api.Section = Container.extend({
     405                containerType: 'section',
    379406
    380407                /**
    381408                 * @since 4.1.0
     
    443470                 * @since 4.1.0
    444471                 */
    445472                attachEvents: function () {
    446                         var section = this;
     473                        var section = this,
     474                                backBtn = section.container.find( '.customize-section-back' ),
     475                                sectionTitle = section.container.find( '.accordion-section-title' ).first();
    447476
    448477                        // Expand/Collapse accordion sections on click.
    449                         section.container.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
     478                        section.container.find( '.accordion-section-title, .customize-section-back' ).on( 'click keydown', function( event ) {
    450479                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    451480                                        return;
    452481                                }
     
    454483
    455484                                if ( section.expanded() ) {
    456485                                        section.collapse();
     486                                        backBtn.attr( 'tabindex', '-1' );
     487                                        sectionTitle.attr( 'tabindex', '0' );
     488                                        sectionTitle.focus();
    457489                                } else {
    458490                                        section.expand();
     491                                        sectionTitle.attr( 'tabindex', '-1' );
     492                                        backBtn.attr( 'tabindex', '0' );
     493                                        backBtn.focus();
    459494                                }
    460495                        });
    461496                },
     
    499534                 * @param {Object}  args
    500535                 */
    501536                onChangeExpanded: function ( expanded, args ) {
    502                         var section = this,
     537                        var position, scroll, section = this,
     538                                container = section.container.closest( '.wp-full-overlay-sidebar-content' ),
    503539                                content = section.container.find( '.accordion-section-content' ),
     540                                overlay = section.container.closest( '.wp-full-overlay' ),
    504541                                expand;
    505542
    506                         if ( expanded ) {
     543                        if ( expanded && ! section.container.hasClass( 'open' ) ) {
    507544
    508545                                if ( args.unchanged ) {
    509546                                        expand = args.completeCallback;
    510547                                } else {
     548                                        container.scrollTop( 0 );
    511549                                        expand = function () {
    512                                                 content.stop().slideDown( args.duration, args.completeCallback );
    513550                                                section.container.addClass( 'open' );
     551                                                overlay.addClass( 'section-open' );
     552                                                position = content.offset().top;
     553                                                scroll = container.scrollTop();
     554                                                content.css( 'margin-top', ( 45 - position - scroll ) );
    514555                                        };
    515556                                }
    516557
     
    531572                                        expand();
    532573                                }
    533574
    534                         } else {
     575                        } else if ( section.container.hasClass( 'open' ) ) {
    535576                                section.container.removeClass( 'open' );
    536                                 content.slideUp( args.duration, args.completeCallback );
     577                                overlay.removeClass( 'section-open' );
     578                                content.css( 'margin-top', 'inherit' );
     579                                container.scrollTop( 0 );
     580                                section.container.find( '.accordion-section-title' ).focus();
    537581                        }
    538582                }
    539583        });
     
    718762                                overlay = section.closest( '.wp-full-overlay' ),
    719763                                container = section.closest( '.wp-full-overlay-sidebar-content' ),
    720764                                siblings = container.find( '.open' ),
    721                                 topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
     765                                topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ),
    722766                                customizeBtn = section.find( '.customize-theme' ),
    723767                                changeBtn = section.find( '.change-theme' ),
    724768                                content = section.find( '.control-panel-content' );
     
    748792                                                args.completeCallback();
    749793                                        }
    750794                                } );
    751                                 topPanel.attr( 'tabindex', '-1' );
    752                                 changeBtn.attr( 'tabindex', '-1' );
    753795                                customizeBtn.focus();
    754796                        } else {
    755797                                siblings.removeClass( 'open' );
     
    762804                                                args.completeCallback();
    763805                                        }
    764806                                } );
    765                                 topPanel.attr( 'tabindex', '0' );
    766807                                customizeBtn.attr( 'tabindex', '0' );
    767808                                changeBtn.focus();
    768809                                container.scrollTop( 0 );
     
    9641005         * @augments wp.customize.Class
    9651006         */
    9661007        api.Panel = Container.extend({
     1008                containerType: 'panel',
     1009
    9671010                /**
    9681011                 * @since 4.1.0
    9691012                 *
     
    9901033
    9911034                        if ( ! panel.container.parent().is( parentContainer ) ) {
    9921035                                parentContainer.append( panel.container );
     1036                                panel.renderContent();
    9931037                        }
    9941038                        panel.deferred.embedded.resolve();
    9951039                },
     
    10121056                                }
    10131057                        });
    10141058
     1059                        // Close panel.
     1060                        panel.container.find( '.customize-panel-back' ).on( 'click keydown', function( event ) {
     1061                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1062                                        return;
     1063                                }
     1064                                event.preventDefault(); // Keep this AFTER the key filter above
     1065
     1066                                if ( panel.expanded() ) {
     1067                                        panel.collapse();
     1068                                }
     1069                        });
     1070
    10151071                        meta = panel.container.find( '.panel-meta:first' );
    10161072
    1017                         meta.find( '> .accordion-section-title' ).on( 'click keydown', function( event ) {
     1073                        meta.find( '> .accordion-section-title .customize-help-toggle' ).on( 'click keydown', function( event ) {
    10181074                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    10191075                                        return;
    10201076                                }
    10211077                                event.preventDefault(); // Keep this AFTER the key filter above
    10221078
     1079                                meta = panel.container.find( '.panel-meta' );
    10231080                                if ( meta.hasClass( 'cannot-expand' ) ) {
    10241081                                        return;
    10251082                                }
    10261083
    1027                                 var content = meta.find( '.accordion-section-content:first' );
     1084                                var content = meta.find( '.customize-panel-description:first' );
    10281085                                if ( meta.hasClass( 'open' ) ) {
    10291086                                        meta.toggleClass( 'open' );
    10301087                                        content.slideUp( panel.defaultExpandedArguments.duration );
     1088                                        $( this ).attr( 'aria-expanded', false );
    10311089                                } else {
    10321090                                        content.slideDown( panel.defaultExpandedArguments.duration );
    10331091                                        meta.toggleClass( 'open' );
     1092                                        $( this ).attr( 'aria-expanded', true );
    10341093                                }
    10351094                        });
    10361095
     
    10891148                        // Note: there is a second argument 'args' passed
    10901149                        var position, scroll,
    10911150                                panel = this,
    1092                                 section = panel.container.closest( '.accordion-section' ),
     1151                                section = panel.container.closest( '.accordion-section' ), // This is actually the panel.
    10931152                                overlay = section.closest( '.wp-full-overlay' ),
    10941153                                container = section.closest( '.wp-full-overlay-sidebar-content' ),
    10951154                                siblings = container.find( '.open' ),
    1096                                 topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
    1097                                 backBtn = overlay.find( '.control-panel-back' ),
     1155                                topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ),
     1156                                backBtn = section.find( '.customize-panel-back' ),
    10981157                                panelTitle = section.find( '.accordion-section-title' ).first(),
    10991158                                content = section.find( '.control-panel-content' );
    11001159
     
    11421201                                panelTitle.focus();
    11431202                                container.scrollTop( 0 );
    11441203                        }
     1204                },
     1205
     1206                /**
     1207                 * Render the panel from its JS template, if it exists.
     1208                 *
     1209                 * The panel's container must already exist in the DOM.
     1210                 *
     1211                 * @since 4.3.0
     1212                 */
     1213                renderContent: function () {
     1214                        var template,
     1215                                panel = this;
     1216
     1217                        // Add the content to the container.
     1218                        if ( 0 !== $( '#tmpl-' + panel.templateSelector + '-content' ).length ) {
     1219                                template = wp.template( panel.templateSelector + '-content' );
     1220                                if ( template && panel.container ) {
     1221                                        panel.container.find( '.accordion-sub-container' ).html( template( panel.params ) );
     1222                                }
     1223                        }
    11451224                }
    11461225        });
    11471226
     
    25732652                var parent, topFocus,
    25742653                        body = $( document.body ),
    25752654                        overlay = body.children( '.wp-full-overlay' ),
    2576                         title = $( '#customize-info .theme-name.site-title' ),
     2655                        title = $( '#customize-info .panel-title.site-title' ),
    25772656                        closeBtn = $( '.customize-controls-close' ),
    25782657                        saveBtn = $( '#save' );
    25792658
     
    25882667                });
    25892668
    25902669                // Expand/Collapse the main customizer customize info.
    2591                 $( '#customize-info' ).find( '> .accordion-section-title' ).on( 'click keydown', function( event ) {
     2670                $( '.customize-info' ).find( '> .accordion-section-title .customize-help-toggle' ).on( 'click keydown', function( event ) {
    25922671                        if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    25932672                                return;
    25942673                        }
    25952674                        event.preventDefault(); // Keep this AFTER the key filter above
    25962675
    2597                         var section = $( this ).parent(),
    2598                                 content = section.find( '.accordion-section-content:first' );
     2676                        var section = $( this ).closest( '.accordion-section' ),
     2677                                content = section.find( '.customize-panel-description:first' );
    25992678
    26002679                        if ( section.hasClass( 'cannot-expand' ) ) {
    26012680                                return;
     
    26042683                        if ( section.hasClass( 'open' ) ) {
    26052684                                section.toggleClass( 'open' );
    26062685                                content.slideUp( api.Panel.prototype.defaultExpandedArguments.duration );
     2686                                $( this ).attr( 'aria-expanded', false );
    26072687                        } else {
    26082688                                content.slideDown( api.Panel.prototype.defaultExpandedArguments.duration );
    26092689                                section.toggleClass( 'open' );
     2690                                $( this ).attr( 'aria-expanded', true );
    26102691                        }
    26112692                });
    26122693
  • src/wp-admin/js/customize-widgets.js

    diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js
    index 729e6a8..eb245ea 100644
     
    176176
    177177                        // If the available widgets panel is open and the customize controls are
    178178                        // interacted with (i.e. available widgets panel is blurred) then close the
    179                         // available widgets panel.
    180                         $( '#customize-controls, .customize-overlay-close' ).on( 'click keydown', function( e ) {
     179                        // available widgets panel. Also close on back button click.
     180                        $( '#customize-controls, .customize-overlay-close, #available-widgets .customize-section-title' ).on( 'click keydown', function( e ) {
    181181                                var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
    182182                                if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) {
    183183                                        self.close();
     
    366366                                this.close( { returnFocus: true } );
    367367                        }
    368368
    369                         if ( isTab && ( isShift && isSearchFocused || ! isShift && isLastWidgetFocused ) ) {
     369                        if ( this.currentSidebarControl && isTab && ( isShift && isSearchFocused || ! isShift && isLastWidgetFocused ) ) {
    370370                                this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
    371371                                event.preventDefault();
    372372                        }
     
    12701270
    12711271                        if ( expanded ) {
    12721272
    1273                                 self.expandControlSection();
     1273                                if ( 'undefined' != typeof api.section( self.section ) && ! api.section( self.section ).expanded() ) {
     1274                                        self.expandControlSection();
     1275                                }
    12741276
    12751277                                // Close all other widget controls before expanding this one
    12761278                                api.control.each( function( otherControl ) {
  • 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 98539b0..47c0407 100644
    final class WP_Customize_Manager { 
    6060        protected $customized;
    6161
    6262        /**
    63          * Controls that may be rendered from JS templates.
     63         * Panel types that may be rendered from JS templates.
     64         *
     65         * @since 4.3.0
     66         * @access protected
     67         * @var array
     68         */
     69        protected $registered_panel_types = array();
     70
     71        /**
     72         * Section types that may be rendered from JS templates.
     73         *
     74         * @since 4.3.0
     75         * @access protected
     76         * @var array
     77         */
     78        protected $registered_section_types = array();
     79
     80        /**
     81         * Control types that may be rendered from JS templates.
    6482         *
    6583         * @since 4.1.0
    6684         * @access protected
    final class WP_Customize_Manager { 
    612630                }
    613631
    614632                foreach ( $this->settings as $id => $setting ) {
    615                         $settings['values'][ $id ] = $setting->js_value();
     633                        if ( $setting->check_capabilities() ) {
     634                                $settings['values'][ $id ] = $setting->js_value();
     635                        }
    616636                }
    617                 foreach ( $this->panels as $id => $panel ) {
    618                         $settings['activePanels'][ $id ] = $panel->active();
    619                         foreach ( $panel->sections as $id => $section ) {
    620                                 $settings['activeSections'][ $id ] = $section->active();
     637                foreach ( $this->panels as $panel_id => $panel ) {
     638                        if ( $panel->check_capabilities() ) {
     639                                $settings['activePanels'][ $panel_id ] = $panel->active();
     640                                foreach ( $panel->sections as $section_id => $section ) {
     641                                        if ( $section->check_capabilities() ) {
     642                                                $settings['activeSections'][ $section_id ] = $section->active();
     643                                        }
     644                                }
    621645                        }
    622646                }
    623647                foreach ( $this->sections as $id => $section ) {
    624                         $settings['activeSections'][ $id ] = $section->active();
     648                        if ( $section->check_capabilities() ) {
     649                                $settings['activeSections'][ $id ] = $section->active();
     650                        }
    625651                }
    626652                foreach ( $this->controls as $id => $control ) {
    627                         $settings['activeControls'][ $id ] = $control->active();
     653                        if ( $control->check_capabilities() ) {
     654                                $settings['activeControls'][ $id ] = $control->active();
     655                        }
    628656                }
    629657
    630658                ?>
    final class WP_Customize_Manager { 
    965993        }
    966994
    967995        /**
     996         * Register a customize panel type.
     997         *
     998         * Registered types are eligible to be rendered via JS and created dynamically.
     999         *
     1000         * @since 4.3.0
     1001         * @access public
     1002         *
     1003         * @param string $panel Name of a custom panel which is a subclass of
     1004         *                        {@see WP_Customize_Panel}.
     1005         */
     1006        public function register_panel_type( $panel ) {
     1007                $this->registered_panel_types[] = $panel;
     1008        }
     1009
     1010        /**
     1011         * Render JS templates for all registered panel types.
     1012         *
     1013         * @since 4.3.0
     1014         * @access public
     1015         */
     1016        public function render_panel_templates() {
     1017                foreach ( $this->registered_panel_types as $panel_type ) {
     1018                        $panel = new $panel_type( $this, 'temp', array() );
     1019                        $panel->print_template();
     1020                }
     1021        }
     1022
     1023        /**
    9681024         * Add a customize section.
    9691025         *
    9701026         * @since 3.4.0
    final class WP_Customize_Manager { 
    10061062        }
    10071063
    10081064        /**
     1065         * Register a customize section type.
     1066         *
     1067         * Registered types are eligible to be rendered via JS and created dynamically.
     1068         *
     1069         * @since 4.3.0
     1070         * @access public
     1071         *
     1072         * @param string $section Name of a custom section which is a subclass of
     1073         *                        {@see WP_Customize_Section}.
     1074         */
     1075        public function register_section_type( $section ) {
     1076                $this->registered_section_types[] = $section;
     1077        }
     1078
     1079        /**
     1080         * Render JS templates for all registered section types.
     1081         *
     1082         * @since 4.3.0
     1083         * @access public
     1084         */
     1085        public function render_section_templates() {
     1086                foreach ( $this->registered_section_types as $section_type ) {
     1087                        $section = new $section_type( $this, 'temp', array() );
     1088                        $section->print_template();
     1089                }
     1090        }
     1091
     1092        /**
    10091093         * Add a customize control.
    10101094         *
    10111095         * @since 3.4.0
    final class WP_Customize_Manager { 
    11761260         */
    11771261        public function register_controls() {
    11781262
    1179                 /* Control Types (custom control classes) */
     1263                /* Panel, Section, and Control Types */
     1264                $this->register_panel_type( 'WP_Customize_Panel' );
     1265                $this->register_section_type( 'WP_Customize_Section' );
     1266                $this->register_section_type( 'WP_Customize_Sidebar_Section' );
    11801267                $this->register_control_type( 'WP_Customize_Color_Control' );
    11811268                $this->register_control_type( 'WP_Customize_Media_Control' );
    11821269                $this->register_control_type( 'WP_Customize_Upload_Control' );
  • src/wp-includes/class-wp-customize-panel.php

    diff --git src/wp-includes/class-wp-customize-panel.php src/wp-includes/class-wp-customize-panel.php
    index ee9f846..876723b 100644
    class WP_Customize_Panel { 
    212212         * @return array The array to be exported to the client as JSON.
    213213         */
    214214        public function json() {
    215                 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'type' ) );
     215                $array = wp_array_slice_assoc( (array) $this, array( 'id', 'title', 'description', 'priority', 'type' ) );
    216216                $array['content'] = $this->get_content();
    217217                $array['active'] = $this->active();
    218218                $array['instanceNumber'] = $this->instance_number;
    class WP_Customize_Panel { 
    287287        }
    288288
    289289        /**
    290          * Render the panel container, and then its contents.
     290         * Render the panel container, and then its contents (via `this->render_content()`) in a subclass.
     291         *
     292         * Panel containers are now rendered in JS by default, see {@see WP_Customize_Panel::print_template()}.
    291293         *
    292294         * @since 4.0.0
    293295         * @access protected
    294296         */
    295         protected function render() {
    296                 $classes = 'accordion-section control-section control-panel control-panel-' . $this->type;
     297        protected function render() {}
     298
     299        /**
     300         * Render the panel UI in a subclass.
     301         *
     302         * Panel contents are now rendered in JS by default, see {@see WP_Customize_Panel::print_template()}.
     303         *
     304         * @since 4.1.0
     305         * @access protected
     306         */
     307        protected function render_content() {}
     308
     309        /**
     310         * Render the panel's JS templates.
     311         *
     312         * This function is only run for panel types that have been registered with
     313         * {@see WP_Customize_Manager::register_panel_type()}.
     314         *
     315         * @since 4.3.0
     316         */
     317        public function print_template() {
    297318                ?>
    298                 <li id="accordion-panel-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
     319                <script type="text/html" id="tmpl-customize-panel-<?php echo esc_attr( $this->type ); ?>-content">
     320                        <?php $this->content_template(); ?>
     321                </script>
     322                <script type="text/html" id="tmpl-customize-panel-<?php echo esc_attr( $this->type ); ?>">
     323                        <?php $this->render_template(); ?>
     324                </script>
     325        <?php
     326        }
     327
     328        /**
     329         * An Underscore (JS) template for rendering this panel's container.
     330         *
     331         * Class variables for this panel class are available in the `data` JS object;
     332         * export custom variables by overriding {@see WP_Customize_Panel::json()}.
     333         *
     334         * @see WP_Customize_Panel::print_template()
     335         *
     336         * @since 4.3.0
     337         */
     338        protected function render_template() {
     339                ?>
     340                <li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">
    299341                        <h3 class="accordion-section-title" tabindex="0">
    300                                 <?php echo esc_html( $this->title ); ?>
     342                                {{ data.title }}
    301343                                <span class="screen-reader-text"><?php _e( 'Press return or enter to open this panel' ); ?></span>
    302344                        </h3>
    303                         <ul class="accordion-sub-container control-panel-content">
    304                                 <?php $this->render_content(); ?>
    305                         </ul>
     345                        <ul class="accordion-sub-container control-panel-content"></ul>
    306346                </li>
    307347                <?php
    308348        }
    309349
    310350        /**
    311          * Render the sections that have been added to the panel.
     351         * An Underscore (JS) template for this panel's content (but not its container).
    312352         *
    313          * @since 4.1.0
    314          * @access protected
     353         * Class variables for this panel class are available in the `data` JS object;
     354         * export custom variables by overriding {@see WP_Customize_Panel::json()}.
     355         *
     356         * @see WP_Customize_Panel::print_template()
     357         *
     358         * @since 4.3.0
    315359         */
    316         protected function render_content() {
     360        protected function content_template() {
    317361                ?>
    318                 <li class="panel-meta accordion-section control-section<?php if ( empty( $this->description ) ) { echo ' cannot-expand'; } ?>">
    319                         <div class="accordion-section-title" tabindex="0">
     362                <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>">
     363                        <button class="customize-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button>
     364                        <div class="accordion-section-title">
    320365                                <span class="preview-notice"><?php
    321366                                        /* translators: %s is the site/panel title in the Customizer */
    322                                         echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title">' . esc_html( $this->title ) . '</strong>' );
     367                                        echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
    323368                                ?></span>
     369                                <button class="customize-help-toggle dashicons dashicons-editor-help" tabindex="0" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button>
    324370                        </div>
    325                         <?php if ( ! empty( $this->description ) ) : ?>
    326                                 <div class="accordion-section-content description">
    327                                         <?php echo $this->description; ?>
     371                        <# if ( data.description ) { #>
     372                                <div class="description customize-panel-description">
     373                                        {{{ data.description }}}
    328374                                </div>
    329                         <?php endif; ?>
     375                        <# } #>
    330376                </li>
    331377                <?php
    332378        }
  • src/wp-includes/class-wp-customize-section.php

    diff --git src/wp-includes/class-wp-customize-section.php src/wp-includes/class-wp-customize-section.php
    index a27f22b..dd85772 100644
    class WP_Customize_Section { 
    221221         * @return array The array to be exported to the client as JSON.
    222222         */
    223223        public function json() {
    224                 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'panel', 'type' ) );
     224                $array = wp_array_slice_assoc( (array) $this, array( 'id', 'title', 'description', 'priority', 'panel', 'type' ) );
    225225                $array['content'] = $this->get_content();
    226226                $array['active'] = $this->active();
    227227                $array['instanceNumber'] = $this->instance_number;
     228
     229                if ( $this->panel ) {
     230                        /* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
     231                        $array['customizeAction'] = sprintf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( $this->panel )->title ) );
     232                } else {
     233                        $array['customizeAction'] = __( 'Customizing' );
     234                }
     235
    228236                return $array;
    229237        }
    230238
    class WP_Customize_Section { 
    249257        }
    250258
    251259        /**
    252          * Get the section's content template for insertion into the Customizer pane.
     260         * Get the section's content for insertion into the Customizer pane.
    253261         *
    254262         * @since 4.1.0
    255263         *
    class WP_Customize_Section { 
    295303        }
    296304
    297305        /**
    298          * Render the section, and the controls that have been added to it.
     306         * Render the section UI in a subclass.
     307         *
     308         * Sections are now rendered in JS by default, see {@see WP_Customize_Section::print_template()}.
    299309         *
    300310         * @since 3.4.0
    301311         */
    302         protected function render() {
    303                 $classes = 'accordion-section control-section control-section-' . $this->type;
     312        protected function render() {}
     313
     314        /**
     315         * Render the section's JS template.
     316         *
     317         * This function is only run for section types that have been registered with
     318         * {@see WP_Customize_Manager::register_section_type()}.
     319         *
     320         * @since 4.3.0
     321         */
     322        public function print_template() {
     323        ?>
     324                <script type="text/html" id="tmpl-customize-section-<?php echo $this->type; ?>">
     325                        <?php $this->render_template(); ?>
     326                </script>
     327        <?php
     328        }
     329
     330        /**
     331         * An Underscore (JS) template for rendering this section.
     332         *
     333         * Class variables for this section class are available in the `data` JS object;
     334         * export custom variables by overriding {@see WP_Customize_Section::json()}.
     335         *
     336         * @see WP_Customize_Section::print_template()
     337         *
     338         * @since 4.3.0
     339         */
     340        protected function render_template() {
    304341                ?>
    305                 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
     342                <li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">
    306343                        <h3 class="accordion-section-title" tabindex="0">
    307                                 <?php echo esc_html( $this->title ); ?>
    308                                 <span class="screen-reader-text"><?php _e( 'Press return or enter to expand' ); ?></span>
     344                                {{ data.title }}
     345                                <span class="screen-reader-text"><?php _e( 'Press return or enter to open' ); ?></span>
    309346                        </h3>
    310347                        <ul class="accordion-section-content">
    311                                 <?php if ( ! empty( $this->description ) ) : ?>
    312                                         <li class="customize-section-description-container">
    313                                                 <p class="description customize-section-description"><?php echo $this->description; ?></p>
    314                                         </li>
    315                                 <?php endif; ?>
     348                                <li class="customize-section-description-container">
     349                                        <div class="customize-section-title">
     350                                                <button class="customize-section-back" tabindex="-1">
     351                                                        <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
     352                                                </button>
     353                                                <h3>
     354                                                        <span class="customize-action">
     355                                                                {{{ data.customizeAction }}}
     356                                                        </span>
     357                                                        {{ data.title }}
     358                                                </h3>
     359                                        </div>
     360                                        <# if ( data.description ) { #>
     361                                                <p class="description customize-section-description">{{{ data.description }}}</p>
     362                                        <# } #>
     363                                </li>
    316364                        </ul>
    317365                </li>
    318366                <?php
    class WP_Customize_Themes_Section extends WP_Customize_Section { 
    353401                                <?php
    354402                                if ( $this->manager->is_theme_active() ) {
    355403                                        /* translators: %s: theme name */
    356                                         printf( __( '<span>Active theme</span> %s' ), $this->title );
     404                                        printf( __( '<span class="customize-action">Active theme</span> %s' ), $this->title );
    357405                                } else {
    358406                                        /* translators: %s: theme name */
    359                                         printf( __( '<span>Previewing theme</span> %s' ), $this->title );
     407                                        printf( __( '<span class="customize-action">Previewing theme</span> %s' ), $this->title );
    360408                                }
    361409                                ?>
    362410
    363                                 <button type="button" class="button change-theme"><?php _ex( 'Change', 'theme' ); ?></button>
     411                                <button type="button" class="button change-theme" tabindex="0"><?php _ex( 'Change', 'theme' ); ?></button>
    364412                        </h3>
    365413                        <div class="customize-themes-panel control-panel-content themes-php">
    366                                 <h2>
     414                                <h3 class="accordion-section-title customize-section-title">
     415                                        <span class="customize-action"><?php _e( 'Customizing' ); ?></span>
    367416                                        <?php _e( 'Themes' ); ?>
    368417                                        <span class="title-count theme-count"><?php echo count( $this->controls ) + 1 /* Active theme */; ?></span>
    369                                 </h2>
    370 
     418                                </h3>
    371419                                <h3 class="accordion-section-title customize-section-title">
    372420                                        <?php
    373421                                        if ( $this->manager->is_theme_active() ) {
    374422                                                /* translators: %s: theme name */
    375                                                 printf( __( '<span>Active theme</span> %s' ), $this->title );
     423                                                printf( __( '<span class="customize-action">Active theme</span> %s' ), $this->title );
    376424                                        } else {
    377425                                                /* translators: %s: theme name */
    378                                                 printf( __( '<span>Previewing theme</span> %s' ), $this->title );
     426                                                printf( __( '<span class="customize-action">Previewing theme</span> %s' ), $this->title );
    379427                                        }
    380428                                        ?>
    381429                                        <button type="button" class="button customize-theme"><?php _e( 'Customize' ); ?></button>
  • 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 cfd0541..d4d30b0 100644
    final class WP_Customize_Widgets { 
    681681                ?>
    682682                <div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
    683683                <div id="available-widgets">
     684                        <div class="customize-section-title">
     685                                <button class="customize-section-back" tabindex="-1">
     686                                        <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
     687                                </button>
     688                                <h3>
     689                                        <span class="customize-action"><?php
     690                                                /* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
     691                                                echo sprintf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'widgets' )->title ) );
     692                                        ?></span>
     693                                        <?php _e( 'Add a Widget' ); ?>
     694                                </h3>
     695                        </div>
    684696                        <div id="available-widgets-filter">
    685697                                <label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
    686698                                <input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets&hellip;' ) ?>" />
  • new file tests/phpunit/tests/customize/panel.php

    diff --git tests/phpunit/tests/customize/panel.php tests/phpunit/tests/customize/panel.php
    new file mode 100644
    index 0000000..69fcef8
    - +  
     1<?php
     2
     3/**
     4 * Tests for the WP_Customize_Panel class.
     5 *
     6 * @group customize
     7 */
     8class Tests_WP_Customize_Panel extends WP_UnitTestCase {
     9
     10        /**
     11         * @var WP_Customize_Manager
     12         */
     13        protected $manager;
     14
     15        function setUp() {
     16                parent::setUp();
     17                require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
     18                $GLOBALS['wp_customize'] = new WP_Customize_Manager();
     19                $this->manager = $GLOBALS['wp_customize'];
     20                $this->undefined = new stdClass();
     21        }
     22
     23        function tearDown() {
     24                $this->manager = null;
     25                unset( $GLOBALS['wp_customize'] );
     26                parent::tearDown();
     27        }
     28
     29        /**
     30         * @see WP_Customize_Panel::__construct()
     31         */
     32        function test_construct_default_args() {
     33                $panel = new WP_Customize_Panel( $this->manager, 'foo' );
     34                $this->assertInternalType( 'int', $panel->instance_number );
     35                $this->assertEquals( $this->manager, $panel->manager );
     36                $this->assertEquals( 'foo', $panel->id );
     37                $this->assertEquals( 160, $panel->priority );
     38                $this->assertEquals( 'edit_theme_options', $panel->capability );
     39                $this->assertEquals( '', $panel->theme_supports );
     40                $this->assertEquals( '', $panel->title );
     41                $this->assertEquals( '', $panel->description );
     42                $this->assertEmpty( $panel->sections );
     43                $this->assertEquals( 'default', $panel->type );
     44                $this->assertEquals( array( $panel, 'active_callback' ), $panel->active_callback );
     45        }
     46
     47        /**
     48         * @see WP_Customize_Panel::__construct()
     49         */
     50        function test_construct_custom_args() {
     51                $args = array(
     52                        'priority' => 200,
     53                        'capability' => 'edit_posts',
     54                        'theme_supports' => 'html5',
     55                        'title' => 'Hello World',
     56                        'description' => 'Lorem Ipsum',
     57                        'type' => 'horizontal',
     58                        'active_callback' => '__return_true',
     59                );
     60
     61                $panel = new WP_Customize_Panel( $this->manager, 'foo', $args );
     62                foreach ( $args as $key => $value ) {
     63                        $this->assertEquals( $value, $panel->$key );
     64                }
     65        }
     66
     67        /**
     68         * @see WP_Customize_Panel::__construct()
     69         */
     70        function test_construct_custom_type() {
     71                $panel = new Custom_Panel_Test( $this->manager, 'foo' );
     72                $this->assertEquals( 'titleless', $panel->type );
     73        }
     74
     75        /**
     76         * @see WP_Customize_Panel::active()
     77         * @see WP_Customize_Panel::active_callback()
     78         */
     79        function test_active() {
     80                $panel = new WP_Customize_Panel( $this->manager, 'foo' );
     81                $this->assertTrue( $panel->active() );
     82
     83                $panel = new WP_Customize_Panel( $this->manager, 'foo', array(
     84                        'active_callback' => '__return_false',
     85                ) );
     86                $this->assertFalse( $panel->active() );
     87                add_filter( 'customize_panel_active', array( $this, 'filter_active_test' ), 10, 2 );
     88                $this->assertTrue( $panel->active() );
     89        }
     90
     91        /**
     92         * @param bool $active
     93         * @param WP_Customize_Panel $panel
     94         * @return bool
     95         */
     96        function filter_active_test( $active, $panel ) {
     97                $this->assertFalse( $active );
     98                $this->assertInstanceOf( 'WP_Customize_Panel', $panel );
     99                $active = true;
     100                return $active;
     101        }
     102
     103        /**
     104         * @see WP_Customize_Panel::json()
     105         */
     106        function test_json() {
     107                $args = array(
     108                        'priority' => 200,
     109                        'capability' => 'edit_posts',
     110                        'theme_supports' => 'html5',
     111                        'title' => 'Hello World',
     112                        'description' => 'Lorem Ipsum',
     113                        'type' => 'horizontal',
     114                        'active_callback' => '__return_true',
     115                );
     116                $panel = new WP_Customize_Panel( $this->manager, 'foo', $args );
     117                $data = $panel->json();
     118                $this->assertEquals( 'foo', $data['id'] );
     119                foreach ( array( 'title', 'description', 'priority', 'type' ) as $key ) {
     120                        $this->assertEquals( $args[ $key ], $data[ $key ] );
     121                }
     122                $this->assertEmpty( $data['content'] );
     123                $this->assertTrue( $data['active'] );
     124                $this->assertInternalType( 'int', $data['instanceNumber'] );
     125        }
     126
     127        /**
     128         * @see WP_Customize_Panel::check_capabilities()
     129         */
     130        function test_check_capabilities() {
     131                $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
     132                wp_set_current_user( $user_id );
     133
     134                $panel = new WP_Customize_Panel( $this->manager, 'foo' );
     135                $this->assertTrue( $panel->check_capabilities() );
     136                $old_cap = $panel->capability;
     137                $panel->capability = 'do_not_allow';
     138                $this->assertFalse( $panel->check_capabilities() );
     139                $panel->capability = $old_cap;
     140                $this->assertTrue( $panel->check_capabilities() );
     141                $panel->theme_supports = 'impossible_feature';
     142                $this->assertFalse( $panel->check_capabilities() );
     143        }
     144
     145        /**
     146         * @see WP_Customize_Panel::get_content()
     147         */
     148        function test_get_content() {
     149                $panel = new WP_Customize_Panel( $this->manager, 'foo' );
     150                $this->assertEmpty( $panel->get_content() );
     151        }
     152
     153        /**
     154         * @see WP_Customize_Panel::maybe_render()
     155         */
     156        function test_maybe_render() {
     157                wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
     158                $panel = new WP_Customize_Panel( $this->manager, 'bar' );
     159                $customize_render_panel_count = did_action( 'customize_render_panel' );
     160                add_action( 'customize_render_panel', array( $this, 'action_customize_render_panel_test' ) );
     161                ob_start();
     162                $panel->maybe_render();
     163                $content = ob_get_clean();
     164                $this->assertTrue( $panel->check_capabilities() );
     165                $this->assertEmpty( $content );
     166                $this->assertEquals( $customize_render_panel_count + 1, did_action( 'customize_render_panel' ), 'Unexpected did_action count for customize_render_panel' );
     167                $this->assertEquals( 1, did_action( "customize_render_panel_{$panel->id}" ), "Unexpected did_action count for customize_render_panel_{$panel->id}" );
     168        }
     169
     170        /**
     171         * @see WP_Customize_Panel::maybe_render()
     172         * @param WP_Customize_Panel $panel
     173         */
     174        function action_customize_render_panel_test( $panel ) {
     175                $this->assertInstanceOf( 'WP_Customize_Panel', $panel );
     176        }
     177
     178        /**
     179         * @see WP_Customize_Panel::print_template()
     180         */
     181        function test_print_templates_standard() {
     182                wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
     183
     184                $panel = new WP_Customize_Panel( $this->manager, 'baz' );
     185                ob_start();
     186                $panel->print_template();
     187                $content = ob_get_clean();
     188                $this->assertContains( '<script type="text/html" id="tmpl-customize-panel-default-content">', $content );
     189                $this->assertContains( 'accordion-section-title', $content );
     190                $this->assertContains( 'control-panel-content', $content );
     191                $this->assertContains( '<script type="text/html" id="tmpl-customize-panel-default">', $content );
     192                $this->assertContains( 'accordion-section-content', $content );
     193                $this->assertContains( 'preview-notice', $content );
     194        }
     195
     196        /**
     197         * @see WP_Customize_Panel::print_template()
     198         */
     199        function test_print_templates_custom() {
     200                wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
     201
     202                $panel = new Custom_Panel_Test( $this->manager, 'baz' );
     203                ob_start();
     204                $panel->print_template();
     205                $content = ob_get_clean();
     206                $this->assertContains( '<script type="text/html" id="tmpl-customize-panel-titleless-content">', $content );
     207                $this->assertNotContains( 'accordion-section-title', $content );
     208
     209                $this->assertContains( '<script type="text/html" id="tmpl-customize-panel-titleless">', $content );
     210                $this->assertNotContains( 'preview-notice', $content );
     211        }
     212}
     213
     214require_once ABSPATH . WPINC . '/class-wp-customize-panel.php';
     215class Custom_Panel_Test extends WP_Customize_Panel {
     216        public $type = 'titleless';
     217
     218        protected function render_template() {
     219                ?>
     220                <li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">
     221                        <ul class="accordion-sub-container control-panel-content"></ul>
     222                </li>
     223                <?php
     224        }
     225
     226        protected function content_template() {
     227                ?>
     228                <li class="panel-meta accordion-section control-section<# if ( ! data.description ) { #> cannot-expand<# } #>">
     229                        <# if ( data.description ) { #>
     230                                <div class="accordion-section-content description">
     231                                        {{{ data.description }}}
     232                                </div>
     233                        <# } #>
     234                </li>
     235                <?php
     236        }
     237
     238}
  • new file tests/phpunit/tests/customize/section.php

    diff --git tests/phpunit/tests/customize/section.php tests/phpunit/tests/customize/section.php
    new file mode 100644
    index 0000000..bf5a33f
    - +  
     1<?php
     2
     3/**
     4 * Tests for the WP_Customize_Section class.
     5 *
     6 * @group customize
     7 */
     8class Tests_WP_Customize_Section extends WP_UnitTestCase {
     9
     10        /**
     11         * @var WP_Customize_Manager
     12         */
     13        protected $manager;
     14
     15        function setUp() {
     16                parent::setUp();
     17                require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
     18                $GLOBALS['wp_customize'] = new WP_Customize_Manager();
     19                $this->manager = $GLOBALS['wp_customize'];
     20                $this->undefined = new stdClass();
     21        }
     22
     23        function tearDown() {
     24                $this->manager = null;
     25                unset( $GLOBALS['wp_customize'] );
     26                parent::tearDown();
     27        }
     28
     29        /**
     30         * @see WP_Customize_Section::__construct()
     31         */
     32        function test_construct_default_args() {
     33                $section = new WP_Customize_Section( $this->manager, 'foo' );
     34                $this->assertInternalType( 'int', $section->instance_number );
     35                $this->assertEquals( $this->manager, $section->manager );
     36                $this->assertEquals( 'foo', $section->id );
     37                $this->assertEquals( 160, $section->priority );
     38                $this->assertEquals( 'edit_theme_options', $section->capability );
     39                $this->assertEquals( '', $section->theme_supports );
     40                $this->assertEquals( '', $section->title );
     41                $this->assertEquals( '', $section->description );
     42                $this->assertEmpty( $section->panel );
     43                $this->assertEquals( 'default', $section->type );
     44                $this->assertEquals( array( $section, 'active_callback' ), $section->active_callback );
     45        }
     46
     47        /**
     48         * @see WP_Customize_Section::__construct()
     49         */
     50        function test_construct_custom_args() {
     51                $args = array(
     52                        'priority' => 200,
     53                        'capability' => 'edit_posts',
     54                        'theme_supports' => 'html5',
     55                        'title' => 'Hello World',
     56                        'description' => 'Lorem Ipsum',
     57                        'type' => 'horizontal',
     58                        'active_callback' => '__return_true',
     59                        'panel' => 'bar',
     60                );
     61
     62                $this->manager->add_panel( 'bar' );
     63
     64                $section = new WP_Customize_Section( $this->manager, 'foo', $args );
     65                foreach ( $args as $key => $value ) {
     66                        $this->assertEquals( $value, $section->$key );
     67                }
     68        }
     69
     70        /**
     71         * @see WP_Customize_Section::__construct()
     72         */
     73        function test_construct_custom_type() {
     74                $section = new Custom_Section_Test( $this->manager, 'foo' );
     75                $this->assertEquals( 'titleless', $section->type );
     76        }
     77
     78        /**
     79         * @see WP_Customize_Section::active()
     80         * @see WP_Customize_Section::active_callback()
     81         */
     82        function test_active() {
     83                $section = new WP_Customize_Section( $this->manager, 'foo' );
     84                $this->assertTrue( $section->active() );
     85
     86                $section = new WP_Customize_Section( $this->manager, 'foo', array(
     87                        'active_callback' => '__return_false',
     88                ) );
     89                $this->assertFalse( $section->active() );
     90                add_filter( 'customize_section_active', array( $this, 'filter_active_test' ), 10, 2 );
     91                $this->assertTrue( $section->active() );
     92        }
     93
     94        /**
     95         * @param bool $active
     96         * @param WP_Customize_Section $section
     97         * @return bool
     98         */
     99        function filter_active_test( $active, $section ) {
     100                $this->assertFalse( $active );
     101                $this->assertInstanceOf( 'WP_Customize_Section', $section );
     102                $active = true;
     103                return $active;
     104        }
     105
     106        /**
     107         * @see WP_Customize_Section::json()
     108         */
     109        function test_json() {
     110                $args = array(
     111                        'priority' => 200,
     112                        'capability' => 'edit_posts',
     113                        'theme_supports' => 'html5',
     114                        'title' => 'Hello World',
     115                        'description' => 'Lorem Ipsum',
     116                        'type' => 'horizontal',
     117                        'panel' => 'bar',
     118                        'active_callback' => '__return_true',
     119                );
     120                $section = new WP_Customize_Section( $this->manager, 'foo', $args );
     121                $data = $section->json();
     122                $this->assertEquals( 'foo', $data['id'] );
     123                foreach ( array( 'title', 'description', 'priority', 'panel', 'type' ) as $key ) {
     124                        $this->assertEquals( $args[ $key ], $data[ $key ] );
     125                }
     126                $this->assertEmpty( $data['content'] );
     127                $this->assertTrue( $data['active'] );
     128                $this->assertInternalType( 'int', $data['instanceNumber'] );
     129        }
     130
     131        /**
     132         * @see WP_Customize_Section::check_capabilities()
     133         */
     134        function test_check_capabilities() {
     135                $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
     136                wp_set_current_user( $user_id );
     137
     138                $section = new WP_Customize_Section( $this->manager, 'foo' );
     139                $this->assertTrue( $section->check_capabilities() );
     140                $old_cap = $section->capability;
     141                $section->capability = 'do_not_allow';
     142                $this->assertFalse( $section->check_capabilities() );
     143                $section->capability = $old_cap;
     144                $this->assertTrue( $section->check_capabilities() );
     145                $section->theme_supports = 'impossible_feature';
     146                $this->assertFalse( $section->check_capabilities() );
     147        }
     148
     149        /**
     150         * @see WP_Customize_Section::get_content()
     151         */
     152        function test_get_content() {
     153                $section = new WP_Customize_Section( $this->manager, 'foo' );
     154                $this->assertEmpty( $section->get_content() );
     155        }
     156
     157        /**
     158         * @see WP_Customize_Section::maybe_render()
     159         */
     160        function test_maybe_render() {
     161                wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
     162                $section = new WP_Customize_Section( $this->manager, 'bar' );
     163                $customize_render_section_count = did_action( 'customize_render_section' );
     164                add_action( 'customize_render_section', array( $this, 'action_customize_render_section_test' ) );
     165                ob_start();
     166                $section->maybe_render();
     167                $content = ob_get_clean();
     168                $this->assertTrue( $section->check_capabilities() );
     169                $this->assertEmpty( $content );
     170                $this->assertEquals( $customize_render_section_count + 1, did_action( 'customize_render_section' ), 'Unexpected did_action count for customize_render_section' );
     171                $this->assertEquals( 1, did_action( "customize_render_section_{$section->id}" ), "Unexpected did_action count for customize_render_section_{$section->id}" );
     172        }
     173
     174        /**
     175         * @see WP_Customize_Section::maybe_render()
     176         * @param WP_Customize_Section $section
     177         */
     178        function action_customize_render_section_test( $section ) {
     179                $this->assertInstanceOf( 'WP_Customize_Section', $section );
     180        }
     181
     182        /**
     183         * @see WP_Customize_Section::print_template()
     184         */
     185        function test_print_templates_standard() {
     186                wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
     187
     188                $section = new WP_Customize_Section( $this->manager, 'baz' );
     189                ob_start();
     190                $section->print_template();
     191                $content = ob_get_clean();
     192                $this->assertContains( '<script type="text/html" id="tmpl-customize-section-default">', $content );
     193                $this->assertContains( 'accordion-section-title', $content );
     194                $this->assertContains( 'accordion-section-content', $content );
     195        }
     196
     197        /**
     198         * @see WP_Customize_Section::print_template()
     199         */
     200        function test_print_templates_custom() {
     201                wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
     202
     203                $section = new Custom_Section_Test( $this->manager, 'baz' );
     204                ob_start();
     205                $section->print_template();
     206                $content = ob_get_clean();
     207                $this->assertContains( '<script type="text/html" id="tmpl-customize-section-titleless">', $content );
     208                $this->assertNotContains( 'accordion-section-title', $content );
     209                $this->assertContains( 'accordion-section-content', $content );
     210        }
     211}
     212
     213require_once ABSPATH . WPINC . '/class-wp-customize-section.php';
     214class Custom_Section_Test extends WP_Customize_Section {
     215        public $type = 'titleless';
     216
     217        protected function render_template() {
     218                ?>
     219                <li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">
     220                        <ul class="accordion-section-content">
     221                                <# if ( data.description ) { #>
     222                                        <li class="customize-section-description-container">
     223                                                <p class="description customize-section-description">{{{ data.description }}}</p>
     224                                        </li>
     225                                <# } #>
     226                        </ul>
     227                </li>
     228                <?php
     229        }
     230
     231}
  • tests/qunit/fixtures/customize-settings.js

    diff --git tests/qunit/fixtures/customize-settings.js tests/qunit/fixtures/customize-settings.js
    index 993f767..6feef67 100644
    window._wpCustomizeSettings = { 
    3939                        'description': 'Lorem ipsum',
    4040                        'instanceNumber': 1,
    4141                        'priority': 110,
    42                         'title': 'Lorem Ipsum',
     42                        'title': 'Fixture panel with content',
    4343                        'type': 'default'
     44                },
     45                'fixture-panel-default-templated': {
     46                        'active': true,
     47                        'description': 'Lorem ipsum',
     48                        'instanceNumber': 2,
     49                        'priority': 110,
     50                        'title': 'Fixture default panel using template',
     51                        'type': 'default'
     52                },
     53                'fixture-panel-titleless-templated': {
     54                        'active': true,
     55                        'description': 'Lorem ipsum',
     56                        'instanceNumber': 3,
     57                        'priority': 110,
     58                        'title': 'Fixture titleless panel using template',
     59                        'type': 'titleless'
    4460                }
    4561        },
    4662        'sections': {
    window._wpCustomizeSettings = { 
    5369                        'priority': 20,
    5470                        'title': 'Fixture Section',
    5571                        'type': 'default'
     72                },
     73                'fixture-section-default-templated': {
     74                        'active': true,
     75                        'description': '',
     76                        'instanceNumber': 3,
     77                        'panel': 'fixture-panel',
     78                        'priority': 20,
     79                        'title': 'Fixture default section using template',
     80                        'type': 'default'
     81                },
     82                'fixture-section-titleless-templated': {
     83                        'active': true,
     84                        'description': '',
     85                        'instanceNumber': 4,
     86                        'panel': 'fixture-panel',
     87                        'priority': 20,
     88                        'title': 'Fixture titleless section using template',
     89                        'type': 'titleless'
    5690                }
    5791        },
    5892        'settings': {
  • tests/qunit/index.html

    diff --git tests/qunit/index.html tests/qunit/index.html
    index 55b95fe..1b0e0c1 100644
     
    99                <script src="../../src/wp-includes/js/underscore.min.js"></script>
    1010                <script src="../../src/wp-includes/js/backbone.min.js"></script>
    1111                <script src="../../src/wp-includes/js/zxcvbn.min.js"></script>
     12                <script src="../../src/wp-includes/js/wp-util.js"></script>
    1213
    1314                <!-- QUnit -->
    1415                <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
     
    3940                <script src="wp-includes/js/shortcode.js"></script>
    4041                <script src="wp-admin/js/customize-controls.js"></script>
    4142                <script src="wp-admin/js/customize-controls-utils.js"></script>
     43
     44                <!-- Customizer templates for sections -->
     45                <script type="text/html" id="tmpl-customize-section-default">
     46                        <li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">
     47                                <h3 class="accordion-section-title" tabindex="0">
     48                                        {{ data.title }}
     49                                        <span class="screen-reader-text"><?php _e( 'Press return or enter to expand' ); ?></span>
     50                                </h3>
     51                                <ul class="accordion-section-content">
     52                                        <# if ( data.description ) { #>
     53                                                <li class="customize-section-description-container">
     54                                                        <p class="description customize-section-description">{{{ data.description }}}</p>
     55                                                </li>
     56                                        <# } #>
     57                                </ul>
     58                        </li>
     59                </script>
     60                <script type="text/html" id="tmpl-customize-section-titleless">
     61                        <li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">
     62                                <!-- Notice the lack of an h3 with title displayed inside. -->
     63                                <ul class="accordion-section-content">
     64                                        <# if ( data.description ) { #>
     65                                                <li class="customize-section-description-container">
     66                                                        <p class="description customize-section-description">{{{ data.description }}}</p>
     67                                                </li>
     68                                        <# } #>
     69                                </ul>
     70                        </li>
     71                </script>
     72
     73                <!-- Customizer templates for panels -->
     74                <script type="text/html" id="tmpl-customize-panel-default">
     75                        <li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">
     76                                <h3 class="accordion-section-title" tabindex="0">
     77                                        {{ data.title }}
     78                                        <span class="screen-reader-text"><?php _e( 'Press return or enter to open this panel' ); ?></span>
     79                                </h3>
     80                                <ul class="accordion-sub-container control-panel-content"></ul>
     81                        </li>
     82                </script>
     83                <script type="text/html" id="tmpl-customize-panel-default-content">
     84                        <li class="panel-meta accordion-section control-section<# if ( ! data.description ) { #> cannot-expand<# } #>">
     85                                <div class="accordion-section-title" tabindex="0">
     86                                        <span class="preview-notice"><?php
     87                                                /* translators: %s is the site/panel title in the Customizer */
     88                                                echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
     89                                        ?></span>
     90                                </div>
     91                                <# if ( data.description ) { #>
     92                                        <div class="accordion-section-content description">
     93                                                {{{ data.description }}}
     94                                        </div>
     95                                <# } #>
     96                        </li>
     97                </script>
     98                <script type="text/html" id="tmpl-customize-panel-titleless">
     99                        <li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">
     100                                <!-- Notice the lack of an h3 with title displayed inside. -->
     101                                <ul class="accordion-sub-container control-panel-content"></ul>
     102                        </li>
     103                </script>
     104                <script type="text/html" id="tmpl-customize-panel-titleless-content">
     105                        <li class="panel-meta accordion-section control-section<# if ( ! data.description ) { #> cannot-expand<# } #>">
     106                                <!-- Notice lack of title containing preview notice -->
     107                                <# if ( data.description ) { #>
     108                                        <div class="accordion-section-content description">
     109                                                {{{ data.description }}}
     110                                        </div>
     111                                <# } #>
     112                        </li>
     113                </script>
    42114        </body>
    43115</html>
  • tests/qunit/wp-admin/js/customize-controls.js

    diff --git tests/qunit/wp-admin/js/customize-controls.js tests/qunit/wp-admin/js/customize-controls.js
    index cb8767f..9ab7ce1 100644
    jQuery( window ).load( function (){ 
    9595                equal( control.section(), 'fixture-section' );
    9696        } );
    9797
     98        // Begin sections.
    9899        module( 'Customizer Section in Fixture' );
    99100        test( 'Fixture section exists', function () {
    100101                ok( wp.customize.section.has( 'fixture-section' ) );
    101102        } );
    102103        test( 'Fixture section has control among controls()', function () {
    103104                var section = wp.customize.section( 'fixture-section' );
    104                 equal( section.controls().length, 1 );
    105                 equal( section.controls()[0].id, 'fixture-control' );
     105                ok( -1 !== _.pluck( section.controls(), 'id' ).indexOf( 'fixture-control' ) );
    106106        } );
    107         test( 'Fixture section has control among controls()', function () {
     107        test( 'Fixture section has has expected panel', function () {
    108108                var section = wp.customize.section( 'fixture-section' );
    109109                equal( section.panel(), 'fixture-panel' );
    110110        } );
    111111
     112        module( 'Customizer Default Section with Template in Fixture' );
     113        test( 'Fixture section exists', function () {
     114                ok( wp.customize.section.has( 'fixture-section-default-templated' ) );
     115        } );
     116        test( 'Fixture section has expected content', function () {
     117                var id = 'fixture-section-default-templated', section;
     118                section = wp.customize.section( id );
     119                ok( ! section.params.content );
     120                ok( !! section.container );
     121                ok( section.container.is( '.control-section.control-section-default' ) );
     122                ok( 1 === section.container.find( '> .accordion-section-title' ).length );
     123                ok( 1 === section.container.find( '> .accordion-section-content' ).length );
     124        } );
     125
     126        module( 'Customizer Custom Type (titleless) Section with Template in Fixture' );
     127        test( 'Fixture section exists', function () {
     128                ok( wp.customize.section.has( 'fixture-section-titleless-templated' ) );
     129        } );
     130        test( 'Fixture section has expected content', function () {
     131                var id = 'fixture-section-titleless-templated', section;
     132                section = wp.customize.section( id );
     133                ok( ! section.params.content );
     134                ok( !! section.container );
     135                ok( section.container.is( '.control-section.control-section-titleless' ) );
     136                ok( 0 === section.container.find( '> .accordion-section-title' ).length );
     137                ok( 1 === section.container.find( '> .accordion-section-content' ).length );
     138        } );
     139
     140        // Begin panels.
    112141        module( 'Customizer Panel in Fixture' );
    113142        test( 'Fixture panel exists', function () {
    114143                ok( wp.customize.panel.has( 'fixture-panel' ) );
    115144        } );
    116         test( 'Fixture section has control among controls()', function () {
     145        test( 'Fixture panel has content', function () {
     146                var panel = wp.customize.panel( 'fixture-panel' );
     147                ok( !! panel.params.content );
     148                ok( !! panel.container );
     149        } );
     150        test( 'Fixture panel has section among its sections()', function () {
    117151                var panel = wp.customize.panel( 'fixture-panel' );
    118                 equal( panel.sections().length, 1 );
    119                 equal( panel.sections()[0].id, 'fixture-section' );
     152                ok( -1 !== _.pluck( panel.sections(), 'id' ).indexOf( 'fixture-section' ) );
    120153        } );
    121154        test( 'Panel is not expanded by default', function () {
    122155                var panel = wp.customize.panel( 'fixture-panel' );
    jQuery( window ).load( function (){ 
    138171                ok( panel.expanded() );
    139172        } );
    140173
     174        module( 'Customizer Default Panel with Template in Fixture' );
     175        test( 'Fixture section exists', function () {
     176                ok( wp.customize.panel.has( 'fixture-panel-default-templated' ) );
     177        } );
     178        test( 'Fixture panel has expected content', function () {
     179                var id = 'fixture-panel-default-templated', panel;
     180                panel = wp.customize.panel( id );
     181                ok( ! panel.params.content );
     182                ok( !! panel.container );
     183                ok( panel.container.is( '.control-panel.control-panel-default' ) );
     184                ok( 1 === panel.container.find( '> .accordion-section-title' ).length );
     185                ok( 1 === panel.container.find( '> .control-panel-content' ).length );
     186        } );
     187
     188        module( 'Customizer Custom Type Panel (titleless) with Template in Fixture' );
     189        test( 'Fixture panel exists', function () {
     190                ok( wp.customize.panel.has( 'fixture-panel-titleless-templated' ) );
     191        } );
     192        test( 'Fixture panel has expected content', function () {
     193                var id = 'fixture-panel-titleless-templated', panel;
     194                panel = wp.customize.panel( id );
     195                ok( ! panel.params.content );
     196                ok( !! panel.container );
     197                ok( panel.container.is( '.control-panel.control-panel-titleless' ) );
     198                ok( 0 === panel.container.find( '> .accordion-section-title' ).length );
     199                ok( 1 === panel.container.find( '> .control-panel-content' ).length );
     200        } );
     201
    141202
    142203        module( 'Dynamically-created Customizer Setting Model' );
    143204        settingId = 'new_blogname';
    jQuery( window ).load( function (){ 
    163224        sectionContent = '<li id="accordion-section-mock_title_tagline" class="control-section accordion-section"></li>';
    164225        sectionData = {
    165226                content: sectionContent,
    166                 active: true
     227                active: true,
     228                type: 'default'
    167229        };
    168230
    169231        mockSection = new wp.customize.Section( sectionId, { params: sectionData } );
    jQuery( window ).load( function (){ 
    277339                content: panelContent,
    278340                title: panelTitle,
    279341                description: panelDescription,
    280                 active: true // @todo This should default to true
     342                active: true, // @todo This should default to true
     343                type: 'default'
    281344        };
    282345
    283346        mockPanel = new wp.customize.Panel( panelId, { params: panelData } );