Ticket #28709: 28709.10.diff
File 28709.10.diff, 72.6 KB (added by , 10 years ago) |
---|
-
src/wp-admin/customize.php
diff --git src/wp-admin/customize.php src/wp-admin/customize.php index 1d79598..50ee0ad 100644
do_action( 'customize_controls_init' ); 53 53 wp_enqueue_script( 'customize-controls' ); 54 54 wp_enqueue_style( 'customize-controls' ); 55 55 56 wp_enqueue_script( 'accordion' );57 58 56 /** 59 57 * Enqueue Customizer control scripts. 60 58 * … … do_action( 'customize_controls_print_scripts' ); 130 128 ?> 131 129 132 130 <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 --> 133 <div class="wp-full-overlay-sidebar-content accordion-container" tabindex="-1">131 <div class="wp-full-overlay-sidebar-content" tabindex="-1"> 134 132 <div id="customize-info" class="accordion-section <?php if ( $cannot_expand ) echo ' cannot-expand'; ?>"> 135 133 <div class="accordion-section-title" aria-label="<?php esc_attr_e( 'Customizer Options' ); ?>" tabindex="0"> 136 134 <span class="preview-notice"><?php … … do_action( 'customize_controls_print_scripts' ); 160 158 <?php endif; ?> 161 159 </div> 162 160 163 <div id="customize-theme-controls"><ul> 164 <?php 165 foreach ( $wp_customize->containers() as $container ) { 166 $container->maybe_render(); 167 } 168 ?> 169 </ul></div> 161 <div id="customize-theme-controls"> 162 <ul><?php // Panels and sections are managed here via JavaScript ?></ul> 163 </div> 170 164 </div> 171 165 </div> 172 166 … … do_action( 'customize_controls_print_scripts' ); 252 246 ), 253 247 'settings' => array(), 254 248 'controls' => array(), 249 'panels' => array(), 250 'sections' => array(), 255 251 'nonce' => array( 256 252 'save' => wp_create_nonce( 'save-customize_' . $wp_customize->get_stylesheet() ), 257 253 'preview' => wp_create_nonce( 'preview-customize_' . $wp_customize->get_stylesheet() ) 258 254 ), 255 'autofocus' => array(), 259 256 ); 260 257 261 258 // Prepare Customize Setting objects to pass to Javascript. … … do_action( 'customize_controls_print_scripts' ); 266 263 ); 267 264 } 268 265 269 // Prepare Customize Control objects to pass to Java script.266 // Prepare Customize Control objects to pass to JavaScript. 270 267 foreach ( $wp_customize->controls() as $id => $control ) { 271 $control->to_json(); 272 $settings['controls'][ $id ] = $control->json; 268 $settings['controls'][ $id ] = $control->json(); 269 } 270 271 // Prepare Customize Section objects to pass to JavaScript. 272 foreach ( $wp_customize->sections() as $id => $section ) { 273 $settings['sections'][ $id ] = $section->json(); 274 } 275 276 // Prepare Customize Panel objects to pass to JavaScript. 277 foreach ( $wp_customize->panels() as $id => $panel ) { 278 $settings['panels'][ $id ] = $panel->json(); 279 foreach ( $panel->sections as $section_id => $section ) { 280 $settings['sections'][ $section_id ] = $section->json(); 281 } 282 } 283 284 // Pass to frontend the Customizer construct being deeplinked 285 if ( isset( $_GET['autofocus'] ) && is_array( $_GET['autofocus'] ) ) { 286 $autofocus = wp_unslash( $_GET['autofocus'] ); 287 foreach ( $autofocus as $type => $id ) { 288 if ( isset( $settings[ $type . 's' ][ $id ] ) ) { 289 $settings['autofocus'][ $type ] = $id; 290 } 291 } 273 292 } 274 293 275 294 ?> -
src/wp-admin/js/accordion.js
diff --git src/wp-admin/js/accordion.js src/wp-admin/js/accordion.js index 6cb1c1c..1769d27 100644
25 25 * 26 26 * Note that any appropriate tags may be used, as long as the above classes are present. 27 27 * 28 * In addition to the standard accordion behavior, this file includes JS for the29 * Customizer's "Panel" functionality.30 *31 28 * @since 3.6.0. 32 29 */ 33 30 … … 46 43 accordionSwitch( $( this ) ); 47 44 }); 48 45 49 // Go back to the top-level Customizer accordion.50 $( '#customize-header-actions' ).on( 'click keydown', '.control-panel-back', function( e ) {51 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key52 return;53 }54 55 e.preventDefault(); // Keep this AFTER the key filter above56 57 panelSwitch( $( '.current-panel' ) );58 });59 46 }); 60 47 61 var sectionContent = $( '.accordion-section-content' );62 63 48 /** 64 49 * Close the current accordion section and open a new one. 65 50 * … … 69 54 function accordionSwitch ( el ) { 70 55 var section = el.closest( '.accordion-section' ), 71 56 siblings = section.closest( '.accordion-container' ).find( '.open' ), 72 content = section.find( sectionContent);57 content = section.find( '.accordion-section-content' ); 73 58 74 59 // This section has no content and cannot be expanded. 75 60 if ( section.hasClass( 'cannot-expand' ) ) { 76 61 return; 77 62 } 78 63 79 // Slide into a sub-panel instead of accordioning (Customizer-specific).80 if ( section.hasClass( 'control-panel' ) ) {81 panelSwitch( section );82 return;83 }84 85 64 if ( section.hasClass( 'open' ) ) { 86 65 section.toggleClass( 'open' ); 87 66 content.toggle( true ).slideToggle( 150 ); 88 67 } else { 89 68 siblings.removeClass( 'open' ); 90 siblings.find( sectionContent).show().slideUp( 150 );69 siblings.find( '.accordion-section-content' ).show().slideUp( 150 ); 91 70 content.toggle( false ).slideToggle( 150 ); 92 71 section.toggleClass( 'open' ); 93 72 } 94 73 } 95 74 96 /**97 * Slide into an accordion sub-panel.98 *99 * For the Customizer-specific panel functionality100 *101 * @param {Object} panel Title element or back button of the accordion panel to toggle.102 * @since 4.0.0103 */104 function panelSwitch( panel ) {105 var position, scroll,106 section = panel.closest( '.accordion-section' ),107 overlay = section.closest( '.wp-full-overlay' ),108 container = section.closest( '.accordion-container' ),109 siblings = container.find( '.open' ),110 topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),111 backBtn = overlay.find( '.control-panel-back' ),112 panelTitle = section.find( '.accordion-section-title' ).first(),113 content = section.find( '.control-panel-content' );114 115 if ( section.hasClass( 'current-panel' ) ) {116 section.toggleClass( 'current-panel' );117 overlay.toggleClass( 'in-sub-panel' );118 content.delay( 180 ).hide( 0, function() {119 content.css( 'margin-top', 'inherit' ); // Reset120 } );121 topPanel.attr( 'tabindex', '0' );122 backBtn.attr( 'tabindex', '-1' );123 panelTitle.focus();124 container.scrollTop( 0 );125 } else {126 // Close all open sections in any accordion level.127 siblings.removeClass( 'open' );128 siblings.find( sectionContent ).show().slideUp( 0 );129 content.show( 0, function() {130 position = content.offset().top;131 scroll = container.scrollTop();132 content.css( 'margin-top', ( 45 - position - scroll ) );133 section.toggleClass( 'current-panel' );134 overlay.toggleClass( 'in-sub-panel' );135 container.scrollTop( 0 );136 } );137 topPanel.attr( 'tabindex', '-1' );138 backBtn.attr( 'tabindex', '0' );139 backBtn.focus();140 }141 }142 143 75 })(jQuery); -
src/wp-admin/js/customize-controls.js
diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js index 363e965..427065f 100644
1 1 /* globals _wpCustomizeHeader, _wpMediaViewsL10n */ 2 2 (function( exports, $ ){ 3 var api = wp.customize;3 var bubbleChildValueChanges, Container, focus, isKeydownButNotEnterEvent, api = wp.customize; 4 4 5 5 /** 6 6 * @constructor … … 31 31 }); 32 32 33 33 /** 34 * Watch all changes to Value properties, and bubble changes to parent Values instance 35 * 36 * @param {wp.customize.Class} instance 37 * @param {Array} properties The names of the Value instances to watch. 38 */ 39 bubbleChildValueChanges = function ( instance, properties ) { 40 $.each( properties, function ( i, key ) { 41 instance[ key ].bind( function ( to, from ) { 42 if ( instance.parent && to !== from ) { 43 instance.parent.trigger( 'change', instance ); 44 } 45 } ); 46 } ); 47 }; 48 49 /** 50 * Expand a panel, section, or control and focus on the first focusable element. 51 * 52 * @param {Object} [params] 53 */ 54 focus = function ( params ) { 55 var construct, completeCallback, focus; 56 construct = this; 57 params = params || {}; 58 focus = function () { 59 construct.container.find( ':focusable:first' ).focus(); 60 construct.container[0].scrollIntoView( true ); 61 }; 62 if ( params.completeCallback ) { 63 completeCallback = params.completeCallback; 64 params.completeCallback = function () { 65 focus(); 66 completeCallback(); 67 }; 68 } else { 69 params.completeCallback = focus; 70 } 71 if ( construct.expand ) { 72 construct.expand( params ); 73 } else { 74 params.completeCallback(); 75 } 76 }; 77 78 /** 79 * Return whether the supplied Event object is for a keydown event but not the Enter key. 80 * 81 * @param {jQuery.Event} event 82 * @returns {boolean} 83 */ 84 isKeydownButNotEnterEvent = function ( event ) { 85 return ( 'keydown' === event.type && 13 !== event.which ); 86 }; 87 88 /** 89 * Base class for Panel and Section 90 * 34 91 * @constructor 35 92 * @augments wp.customize.Class 36 93 */ 37 api.Control = api.Class.extend({ 38 initialize: function( id, options ) { 39 var control = this, 40 nodes, radios, settings; 94 Container = api.Class.extend({ 95 defaultActiveArguments: { duration: 'fast' }, 96 defaultExpandedArguments: { duration: 'fast' }, 97 98 initialize: function ( id, options ) { 99 var container = this; 100 container.id = id; 101 container.params = {}; 102 $.extend( container, options || {} ); 103 container.container = $( container.params.content ); 104 105 container.deferred = { 106 ready: new $.Deferred() 107 }; 108 container.priority = new api.Value(); 109 container.active = new api.Value(); 110 container.activeArgumentsQueue = []; 111 container.expanded = new api.Value(); 112 container.expandedArgumentsQueue = []; 113 114 container.active.bind( function ( active ) { 115 var args = container.activeArgumentsQueue.shift(); 116 args = $.extend( {}, container.defaultActiveArguments, args ); 117 active = ( active && container.isContextuallyActive() ); 118 container.onChangeActive( active, args ); 119 // @todo trigger 'activated' and 'deactivated' events based on the expanded param? 120 }); 121 container.expanded.bind( function ( expanded ) { 122 var args = container.expandedArgumentsQueue.shift(); 123 args = $.extend( {}, container.defaultExpandedArguments, args ); 124 container.onChangeExpanded( expanded, args ); 125 // @todo trigger 'expanded' and 'collapsed' events based on the expanded param? 126 }); 41 127 42 this.params = {}; 43 $.extend( this, options || {} ); 128 container.attachEvents(); 44 129 45 this.id = id; 46 this.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); 47 this.container = $( this.selector ); 48 this.active = new api.Value( this.params.active ); 130 bubbleChildValueChanges( container, [ 'priority', 'active' ] ); 49 131 50 settings = $.map( this.params.settings, function( value ) { 51 return value; 132 container.priority.set( isNaN( container.params.priority ) ? 100 : container.params.priority ); 133 container.active.set( container.params.active ); 134 container.expanded.set( false ); // @todo True if deeplinking? 135 }, 136 137 /** 138 * @abstract 139 */ 140 ready: function() {}, 141 142 /** 143 * Get the child models associated with this parent, sorting them by their priority Value. 144 * 145 * @param {String} parentType 146 * @param {String} childType 147 * @returns {Array} 148 */ 149 _children: function ( parentType, childType ) { 150 var parent = this, 151 children = []; 152 api[ childType ].each( function ( child ) { 153 if ( child[ parentType ].get() === parent.id ) { 154 children.push( child ); 155 } 156 } ); 157 children.sort( function ( a, b ) { 158 return a.priority() - b.priority(); 159 } ); 160 return children; 161 }, 162 163 /** 164 * To override by subclass, to return whether the container has active children. 165 * @abstract 166 */ 167 isContextuallyActive: function () { 168 throw new Error( 'Must override with subclass.' ); 169 }, 170 171 /** 172 * Handle changes to the active state. 173 * This does not change the active state, it merely handles the behavior 174 * for when it does change. 175 * 176 * To override by subclass, update the container's UI to reflect the provided active state. 177 * 178 * @param {Boolean} active 179 * @param {Object} args merged on top of this.defaultActiveArguments 180 */ 181 onChangeActive: function ( active, args ) { 182 var duration = ( 'resolved' === api.previewer.deferred.active.state() ? args.duration : 0 ); 183 if ( active ) { 184 this.container.stop( true, true ).slideDown( duration, args.completeCallback ); 185 } else { 186 this.container.stop( true, true ).slideUp( duration, args.completeCallback ); 187 } 188 }, 189 190 /** 191 * @params {Boolean} active 192 * @param {Object} [params] 193 * @returns {Boolean} false if state already applied 194 */ 195 _toggleActive: function ( active, params ) { 196 var self = this; 197 params = params || {}; 198 if ( ( active && this.active.get() ) || ( ! active && ! this.active.get() ) ) { 199 params.unchanged = true; 200 self.onChangeActive( self.active.get(), params ); 201 return false; 202 } else { 203 params.unchanged = false; 204 this.activeArgumentsQueue.push( params ); 205 this.active.set( active ); 206 return true; 207 } 208 }, 209 210 /** 211 * @param {Object} [params] 212 * @returns {Boolean} false if already active 213 */ 214 activate: function ( params ) { 215 return this._toggleActive( true, params ); 216 }, 217 218 /** 219 * @param {Object} [params] 220 * @returns {Boolean} false if already inactive 221 */ 222 deactivate: function ( params ) { 223 return this._toggleActive( false, params ); 224 }, 225 226 /** 227 * To override by subclass, update the container's UI to reflect the provided active state. 228 * @abstract 229 */ 230 onChangeExpanded: function () { 231 throw new Error( 'Must override with subclass.' ); 232 }, 233 234 /** 235 * @param {Boolean} expanded 236 * @param {Object} [params] 237 * @returns {Boolean} false if state already applied 238 */ 239 _toggleExpanded: function ( expanded, params ) { 240 var self = this; 241 params = params || {}; 242 if ( ( expanded && this.expanded.get() ) || ( ! expanded && ! this.expanded.get() ) ) { 243 params.unchanged = true; 244 self.onChangeExpanded( self.expanded.get(), params ); 245 return false; 246 } else { 247 params.unchanged = false; 248 this.expandedArgumentsQueue.push( params ); 249 this.expanded.set( expanded ); 250 return true; 251 } 252 }, 253 254 /** 255 * @param {Object} [params] 256 * @returns {Boolean} false if already expanded 257 */ 258 expand: function ( params ) { 259 return this._toggleExpanded( true, params ); 260 }, 261 262 /** 263 * @param {Object} [params] 264 * @returns {Boolean} false if already collapsed 265 */ 266 collapse: function ( params ) { 267 return this._toggleExpanded( false, params ); 268 }, 269 270 /** 271 * Bring the container into view and then expand this and bring it into view 272 * @param {Object} [params] 273 */ 274 focus: focus 275 }); 276 277 /** 278 * @constructor 279 * @augments wp.customize.Class 280 */ 281 api.Section = Container.extend({ 282 283 /** 284 * @param {String} id 285 * @param {Array} options 286 */ 287 initialize: function ( id, options ) { 288 var section = this; 289 Container.prototype.initialize.call( section, id, options ); 290 291 section.id = id; 292 section.panel = new api.Value(); 293 section.panel.bind( function ( id ) { 294 $( section.container ).toggleClass( 'control-subsection', !! id ); 52 295 }); 296 section.panel.set( section.params.panel || '' ); 297 bubbleChildValueChanges( section, [ 'panel' ] ); 53 298 54 api.apply( api, settings.concat( function() { 55 var key; 299 section.embed(); 300 section.deferred.ready.done( function () { 301 section.ready(); 302 }); 303 }, 56 304 57 control.settings = {}; 58 for ( key in control.params.settings ) { 59 control.settings[ key ] = api( control.params.settings[ key ] ); 305 /** 306 * Embed the container in the DOM when any parent panel is ready. 307 */ 308 embed: function () { 309 var section = this, inject; 310 311 // Watch for changes to the panel state 312 inject = function ( panelId ) { 313 var parentContainer; 314 if ( panelId ) { 315 // The panel has been supplied, so wait until the panel object is registered 316 api.panel( panelId, function ( panel ) { 317 // The panel has been registered, wait for it to become ready/initialized 318 panel.deferred.ready.done( function () { 319 parentContainer = panel.container.find( 'ul:first' ); 320 if ( ! section.container.parent().is( parentContainer ) ) { 321 parentContainer.append( section.container ); 322 } 323 section.deferred.ready.resolve(); // @todo Better to use `embedded` instead of `ready` 324 }); 325 } ); 326 } else { 327 // There is no panel, so embed the section in the root of the customizer 328 parentContainer = $( '#customize-theme-controls > ul' ); // @todo This should be defined elsewhere, and to be configurable 329 if ( ! section.container.parent().is( parentContainer ) ) { 330 parentContainer.append( section.container ); 331 } 332 section.deferred.ready.resolve(); 60 333 } 334 }; 335 section.panel.bind( inject ); 336 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 337 }, 61 338 62 control.setting = control.settings['default'] || null; 63 control.renderContent( function() { 64 // Don't call ready() until the content has rendered. 65 control.ready(); 339 /** 340 * Add behaviors for the accordion section 341 */ 342 attachEvents: function () { 343 var section = this; 344 345 // Expand/Collapse accordion sections on click. 346 section.container.find( '.accordion-section-title' ).on( 'click keydown', function( e ) { 347 if ( isKeydownButNotEnterEvent( e ) ) { 348 return; 349 } 350 e.preventDefault(); // Keep this AFTER the key filter above 351 352 if ( section.expanded() ) { 353 section.collapse(); 354 } else { 355 section.expand(); 356 } 357 }); 358 }, 359 360 /** 361 * Return whether this section has any active controls. 362 * 363 * @returns {boolean} 364 */ 365 isContextuallyActive: function () { 366 var section = this, 367 controls = section.controls(), 368 activeCount = 0; 369 _( controls ).each( function ( control ) { 370 if ( control.active() ) { 371 activeCount += 1; 372 } 373 } ); 374 return ( activeCount !== 0 ); 375 }, 376 377 /** 378 * Get the controls that are associated with this section, sorted by their priority Value. 379 * 380 * @returns {Array} 381 */ 382 controls: function () { 383 return this._children( 'section', 'control' ); 384 }, 385 386 /** 387 * Update UI to reflect expanded state 388 * 389 * @param {Boolean} expanded 390 * @param {Object} args 391 */ 392 onChangeExpanded: function ( expanded, args ) { 393 var section = this, 394 content = section.container.find( '.accordion-section-content' ), 395 expand; 396 397 if ( expanded ) { 398 399 if ( args.unchanged ) { 400 expand = args.completeCallback; 401 } else { 402 expand = function () { 403 content.stop().slideDown( args.duration, args.completeCallback ); 404 section.container.addClass( 'open' ); 405 }; 406 } 407 408 if ( ! args.allowMultiple ) { 409 api.section.each( function ( otherSection ) { 410 if ( otherSection !== section ) { 411 otherSection.collapse( { duration: args.duration } ); 412 } 413 }); 414 } 415 416 if ( section.panel() ) { 417 api.panel( section.panel() ).expand({ 418 duration: args.duration, 419 completeCallback: expand 420 }); 421 } else { 422 expand(); 423 } 424 425 } else { 426 section.container.removeClass( 'open' ); 427 content.slideUp( args.duration, args.completeCallback ); 428 } 429 } 430 }); 431 432 /** 433 * @constructor 434 * @augments wp.customize.Class 435 */ 436 api.Panel = Container.extend({ 437 initialize: function ( id, options ) { 438 var panel = this; 439 Container.prototype.initialize.call( panel, id, options ); 440 panel.embed(); 441 panel.deferred.ready.done( function () { 442 panel.ready(); 443 }); 444 }, 445 446 /** 447 * Embed the container in the DOM when any parent panel is ready. 448 */ 449 embed: function () { 450 var panel = this, 451 parentContainer = $( '#customize-theme-controls > ul' ); // @todo This should be defined elsewhere, and to be configurable 452 453 if ( ! panel.container.parent().is( parentContainer ) ) { 454 parentContainer.append( panel.container ); 455 } 456 panel.deferred.ready.resolve(); 457 }, 458 459 /** 460 * 461 */ 462 attachEvents: function () { 463 var meta, panel = this; 464 465 // Expand/Collapse accordion sections on click. 466 panel.container.find( '.accordion-section-title' ).on( 'click keydown', function( e ) { 467 if ( isKeydownButNotEnterEvent( e ) ) { 468 return; 469 } 470 e.preventDefault(); // Keep this AFTER the key filter above 471 472 if ( ! panel.expanded() ) { 473 panel.expand(); 474 } 475 }); 476 477 meta = panel.container.find( '.panel-meta:first' ); 478 479 meta.find( '> .accordion-section-title' ).on( 'click keydown', function( e ) { 480 if ( isKeydownButNotEnterEvent( e ) ) { 481 return; 482 } 483 e.preventDefault(); // Keep this AFTER the key filter above 484 485 if ( meta.hasClass( 'cannot-expand' ) ) { 486 return; 487 } 488 489 var content = meta.find( '.accordion-section-content:first' ); 490 if ( meta.hasClass( 'open' ) ) { 491 meta.toggleClass( 'open' ); 492 content.slideUp( panel.defaultExpandedArguments.duration ); 493 } else { 494 content.slideDown( panel.defaultExpandedArguments.duration ); 495 meta.toggleClass( 'open' ); 496 } 497 }); 498 499 }, 500 501 /** 502 * Get the sections that are associated with this panel, sorted by their priority Value. 503 * 504 * @returns {Array} 505 */ 506 sections: function () { 507 return this._children( 'panel', 'section' ); 508 }, 509 510 /** 511 * Return whether this panel has any active sections. 512 * 513 * @returns {boolean} 514 */ 515 isContextuallyActive: function () { 516 var panel = this, 517 sections = panel.sections(), 518 activeCount = 0; 519 _( sections ).each( function ( section ) { 520 if ( section.active() && section.isContextuallyActive() ) { 521 activeCount += 1; 522 } 523 } ); 524 return ( activeCount !== 0 ); 525 }, 526 527 /** 528 * Update UI to reflect expanded state 529 * 530 * @param {Boolean} expanded 531 * @param {Object} args merged with this.defaultExpandedArguments 532 */ 533 onChangeExpanded: function ( expanded, args ) { 534 535 // Immediately call the complete callback if there were no changes 536 if ( args.unchanged ) { 537 if ( args.completeCallback ) { 538 args.completeCallback(); 539 } 540 return; 541 } 542 543 // Note: there is a second argument 'args' passed 544 var position, scroll, 545 panel = this, 546 section = panel.container.closest( '.accordion-section' ), 547 overlay = section.closest( '.wp-full-overlay' ), 548 container = section.closest( '.accordion-container' ), 549 siblings = container.find( '.open' ), 550 topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ), 551 backBtn = overlay.find( '.control-panel-back' ), 552 panelTitle = section.find( '.accordion-section-title' ).first(), 553 content = section.find( '.control-panel-content' ); 554 555 if ( expanded ) { 556 557 // Collapse any sibling sections/panels 558 api.section.each( function ( section ) { 559 if ( ! section.panel() ) { 560 section.collapse( { duration: 0 } ); 561 } 562 }); 563 api.panel.each( function ( otherPanel ) { 564 if ( panel !== otherPanel ) { 565 otherPanel.collapse( { duration: 0 } ); 566 } 567 }); 568 569 content.show( 0, function() { 570 position = content.offset().top; 571 scroll = container.scrollTop(); 572 content.css( 'margin-top', ( 45 - position - scroll ) ); 573 section.addClass( 'current-panel' ); 574 overlay.addClass( 'in-sub-panel' ); 575 container.scrollTop( 0 ); 576 if ( args.completeCallback ) { 577 args.completeCallback(); 578 } 66 579 } ); 67 }) ); 580 topPanel.attr( 'tabindex', '-1' ); 581 backBtn.attr( 'tabindex', '0' ); 582 backBtn.focus(); 583 } else { 584 siblings.removeClass( 'open' ); 585 section.removeClass( 'current-panel' ); 586 overlay.removeClass( 'in-sub-panel' ); 587 content.delay( 180 ).hide( 0, function() { 588 content.css( 'margin-top', 'inherit' ); // Reset 589 if ( args.completeCallback ) { 590 args.completeCallback(); 591 } 592 } ); 593 topPanel.attr( 'tabindex', '0' ); 594 backBtn.attr( 'tabindex', '-1' ); 595 panelTitle.focus(); 596 container.scrollTop( 0 ); 597 } 598 } 599 }); 600 601 /** 602 * @constructor 603 * @augments wp.customize.Class 604 */ 605 api.Control = api.Class.extend({ 606 defaultActiveArguments: { duration: 'fast' }, 607 608 initialize: function( id, options ) { 609 var control = this, 610 nodes, radios, settings; 611 612 control.params = {}; 613 $.extend( control, options || {} ); 614 615 control.id = id; 616 control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); 617 control.templateSelector = 'customize-control-' + control.params.type + '-content'; 618 control.container = control.params.content ? $( control.params.content ) : $( control.selector ); 619 620 control.deferred = { 621 ready: new $.Deferred() 622 }; 623 control.section = new api.Value(); 624 control.priority = new api.Value(); 625 control.active = new api.Value(); 626 control.activeArgumentsQueue = []; 68 627 69 628 control.elements = []; 70 629 71 nodes = this.container.find('[data-customize-setting-link]');630 nodes = control.container.find('[data-customize-setting-link]'); 72 631 radios = {}; 73 632 74 633 nodes.each( function() { 75 var node = $( this),634 var node = $( this ), 76 635 name; 77 636 78 if ( node.is( ':radio') ) {79 name = node.prop( 'name');80 if ( radios[ name ] ) 637 if ( node.is( ':radio' ) ) { 638 name = node.prop( 'name' ); 639 if ( radios[ name ] ) { 81 640 return; 641 } 82 642 83 643 radios[ name ] = true; 84 644 node = nodes.filter( '[name="' + name + '"]' ); 85 645 } 86 646 87 api( node.data( 'customizeSettingLink'), function( setting ) {647 api( node.data( 'customizeSettingLink' ), function( setting ) { 88 648 var element = new api.Element( node ); 89 649 control.elements.push( element ); 90 650 element.sync( setting ); … … 93 653 }); 94 654 95 655 control.active.bind( function ( active ) { 96 control.toggle( active ); 656 var args = control.activeArgumentsQueue.shift(); 657 args = $.extend( {}, control.defaultActiveArguments, args ); 658 control.onChangeActive( active, args ); 97 659 } ); 98 control.toggle( control.active() ); 660 661 control.section.set( control.params.section ); 662 control.priority.set( isNaN( control.params.priority ) ? 10 : control.params.priority ); 663 control.active.set( control.params.active ); 664 665 bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] ); 666 667 // Associate this control with its settings when they are created 668 settings = $.map( control.params.settings, function( value ) { 669 return value; 670 }); 671 api.apply( api, settings.concat( function () { 672 var key; 673 674 control.settings = {}; 675 for ( key in control.params.settings ) { 676 control.settings[ key ] = api( control.params.settings[ key ] ); 677 } 678 679 control.setting = control.settings['default'] || null; 680 681 control.embed(); 682 }) ); 683 684 control.deferred.ready.done( function () { 685 control.ready(); 686 }); 687 }, 688 689 /** 690 * 691 */ 692 embed: function () { 693 var control = this, 694 inject; 695 696 // Watch for changes to the section state 697 inject = function ( sectionId ) { 698 var parentContainer; 699 if ( ! sectionId ) { // @todo allow a control to be embeded without a section, for instance a control embedded in the frontend 700 return; 701 } 702 // Wait for the section to be registered 703 api.section( sectionId, function ( section ) { 704 // Wait for the section to be ready/initialized 705 section.deferred.ready.done( function () { 706 parentContainer = section.container.find( 'ul:first' ); 707 if ( ! control.container.parent().is( parentContainer ) ) { 708 parentContainer.append( control.container ); 709 control.renderContent(); 710 } 711 control.deferred.ready.resolve(); // @todo Better to use `embedded` instead of `ready` 712 }); 713 }); 714 }; 715 control.section.bind( inject ); 716 inject( control.section.get() ); 99 717 }, 100 718 101 719 /** … … 104 722 ready: function() {}, 105 723 106 724 /** 107 * Callback for change to the control's active state.725 * Normal controls do not expand, so just expand its parent 108 726 * 109 * Override function for custom behavior for the control being active/inactive. 727 * @param {Object} [params] 728 */ 729 expand: function ( params ) { 730 api.section( this.section() ).expand( params ); 731 }, 732 733 /** 734 * Bring the containing section and panel into view and then this control into view, focusing on the first input 735 */ 736 focus: focus, 737 738 /** 739 * Update UI in response to a change in the control's active state. 740 * This does not change the active state, it merely handles the behavior 741 * for when it does change. 110 742 * 111 743 * @param {Boolean} active 744 * @param {Object} args merged on top of this.defaultActiveArguments 112 745 */ 113 toggle: function ( active) {746 onChangeActive: function ( active, args ) { 114 747 if ( active ) { 115 this.container.slideDown( );748 this.container.slideDown( args.duration, args.completeCallback ); 116 749 } else { 117 this.container.slideUp( );750 this.container.slideUp( args.duration, args.completeCallback ); 118 751 } 119 752 }, 120 753 754 /** 755 * @deprecated alias of onChangeActive 756 */ 757 toggle: function ( active ) { 758 return this.onChangeActive( active, this.defaultActiveArguments ); 759 }, 760 761 /** 762 * Shorthand way to enable the active state. 763 * 764 * @param {Object} [params] 765 * @returns {Boolean} false if already active 766 */ 767 activate: Container.prototype.activate, 768 769 /** 770 * Shorthand way to disable the active state. 771 * 772 * @param {Object} [params] 773 * @returns {Boolean} false if already inactive 774 */ 775 deactivate: Container.prototype.deactivate, 776 121 777 dropdownInit: function() { 122 778 var control = this, 123 779 statuses = this.container.find('.dropdown-status'), … … 132 788 133 789 // Support the .dropdown class to open/close complex elements 134 790 this.container.on( 'click keydown', '.dropdown', function( event ) { 135 if ( event.type === 'keydown' && 13 !== event.which ) // enter791 if ( isKeydownButNotEnterEvent( event ) ) { 136 792 return; 793 } 137 794 138 795 event.preventDefault(); 139 796 … … 157 814 /** 158 815 * Render the control from its JS template, if it exists. 159 816 * 160 * The control's container must alrea sy exist in the DOM.817 * The control's container must already exist in the DOM. 161 818 */ 162 renderContent: function ( callback) {819 renderContent: function () { 163 820 var template, 164 selector = 'customize-control-' + this.params.type + '-content';821 control = this; 165 822 166 callback = callback || function(){}; 167 if ( 0 !== $( '#tmpl-' + selector ).length ) { 168 template = wp.template( selector ); 169 if ( template && this.container ) { 170 this.container.append( template( this.params ) ); 823 if ( 0 !== $( '#tmpl-' + control.templateSelector ).length ) { 824 template = wp.template( control.templateSelector ); 825 if ( template && control.container ) { 826 control.container.append( template( control.params ) ); 171 827 } 172 828 } 173 callback();174 829 } 175 830 }); 176 831 … … 234 889 235 890 this.remover = this.container.find('.remove'); 236 891 this.remover.on( 'click keydown', function( event ) { 237 if ( event.type === 'keydown' && 13 !== event.which ) // enter892 if ( isKeydownButNotEnterEvent( event ) ) { 238 893 return; 894 } 239 895 240 896 control.setting.set( control.params.removed ); 241 897 event.preventDefault(); … … 306 962 307 963 // Bind tab switch events 308 964 this.library.children('ul').on( 'click keydown', 'li', function( event ) { 309 if ( event.type === 'keydown' && 13 !== event.which ) // enter965 if ( isKeydownButNotEnterEvent( event ) ) { 310 966 return; 967 } 311 968 312 969 var id = $(this).data('customizeTab'), 313 970 tab = control.tabs[ id ]; … … 324 981 325 982 // Bind events to switch image urls. 326 983 this.library.on( 'click keydown', 'a', function( event ) { 327 if ( event.type === 'keydown' && 13 !== event.which ) // enter984 if ( isKeydownButNotEnterEvent( event ) ) { 328 985 return; 986 } 329 987 330 988 var value = $(this).data('customizeImageValue'); 331 989 … … 597 1255 598 1256 // Create the collection of Control objects. 599 1257 api.control = new api.Values({ defaultConstructor: api.Control }); 1258 api.section = new api.Values({ defaultConstructor: api.Section }); 1259 api.panel = new api.Values({ defaultConstructor: api.Panel }); 600 1260 601 1261 /** 602 1262 * @constructor … … 632 1292 loaded = false, 633 1293 ready = false; 634 1294 635 if ( this._ready ) 1295 if ( this._ready ) { 636 1296 this.unbind( 'ready', this._ready ); 1297 } 637 1298 638 1299 this._ready = function() { 639 1300 ready = true; 640 1301 641 if ( loaded ) 1302 if ( loaded ) { 642 1303 deferred.resolveWith( self ); 1304 } 643 1305 }; 644 1306 645 1307 this.bind( 'ready', this._ready ); 646 1308 647 1309 this.bind( 'ready', function ( data ) { 648 if ( ! data || ! data.activeControls) {1310 if ( ! data ) { 649 1311 return; 650 1312 } 651 1313 652 $.each( data.activeControls, function ( id, active ) { 653 var control = api.control( id ); 654 if ( control ) { 655 control.active( active ); 1314 var constructs = { 1315 panel: data.activePanels, 1316 section: data.activeSections, 1317 control: data.activeControls 1318 }; 1319 1320 $.each( constructs, function ( type, activeConstructs ) { 1321 if ( activeConstructs ) { 1322 $.each( activeConstructs, function ( id, active ) { 1323 var construct = api[ type ]( id ); 1324 if ( construct ) { 1325 construct.active( active ); 1326 } 1327 } ); 656 1328 } 657 1329 } ); 1330 658 1331 } ); 659 1332 660 1333 this.request = $.ajax( this.previewUrl(), { … … 676 1349 677 1350 // Check if the location response header differs from the current URL. 678 1351 // If so, the request was redirected; try loading the requested page. 679 if ( location && location != self.previewUrl() ) {1352 if ( location && location !== self.previewUrl() ) { 680 1353 deferred.rejectWith( self, [ 'redirect', location ] ); 681 1354 return; 682 1355 } … … 803 1476 rscheme = /^https?/; 804 1477 805 1478 $.extend( this, options || {} ); 1479 this.deferred = { 1480 active: $.Deferred() 1481 }; 806 1482 807 1483 /* 808 1484 * Wrap this.refresh to prevent it from hammering the servers: … … 934 1610 self.targetWindow( this.targetWindow() ); 935 1611 self.channel( this.channel() ); 936 1612 1613 self.deferred.active.resolve(); 937 1614 self.send( 'active' ); 938 1615 }); 939 1616 … … 1001 1678 image: api.ImageControl, 1002 1679 header: api.HeaderControl 1003 1680 }; 1681 api.panelConstructor = {}; 1682 api.sectionConstructor = {}; 1004 1683 1005 1684 $( function() { 1006 1685 api.settings = window._wpCustomizeSettings; … … 1031 1710 } 1032 1711 }); 1033 1712 1713 // Expand/Collapse the main customizer customize info 1714 $( '#customize-info' ).find( '> .accordion-section-title' ).on( 'click keydown', function( e ) { 1715 if ( isKeydownButNotEnterEvent( e ) ) { 1716 return; 1717 } 1718 e.preventDefault(); // Keep this AFTER the key filter above 1719 1720 var section = $( this ).parent(), 1721 content = section.find( '.accordion-section-content:first' ); 1722 1723 if ( section.hasClass( 'cannot-expand' ) ) { 1724 return; 1725 } 1726 1727 if ( section.hasClass( 'open' ) ) { 1728 section.toggleClass( 'open' ); 1729 content.slideUp( api.Panel.prototype.defaultExpandedArguments.duration ); 1730 } else { 1731 content.slideDown( api.Panel.prototype.defaultExpandedArguments.duration ); 1732 section.toggleClass( 'open' ); 1733 } 1734 }); 1735 1034 1736 // Initialize Previewer 1035 1737 api.previewer = new api.Previewer({ 1036 1738 container: '#customize-preview', … … 1124 1826 $.extend( this.nonce, nonce ); 1125 1827 }); 1126 1828 1829 // Create Settings 1127 1830 $.each( api.settings.settings, function( id, data ) { 1128 1831 api.create( id, id, data.value, { 1129 1832 transport: data.transport, … … 1131 1834 } ); 1132 1835 }); 1133 1836 1837 // Create Panels 1838 $.each( api.settings.panels, function ( id, data ) { 1839 var constructor = api.panelConstructor[ data.type ] || api.Panel, 1840 panel; 1841 1842 panel = new constructor( id, { 1843 params: data 1844 } ); 1845 api.panel.add( id, panel ); 1846 }); 1847 1848 // Create Sections 1849 $.each( api.settings.sections, function ( id, data ) { 1850 var constructor = api.sectionConstructor[ data.type ] || api.Section, 1851 section; 1852 1853 section = new constructor( id, { 1854 params: data 1855 } ); 1856 api.section.add( id, section ); 1857 }); 1858 1859 // Create Controls 1134 1860 $.each( api.settings.controls, function( id, data ) { 1135 1861 var constructor = api.controlConstructor[ data.type ] || api.Control, 1136 1862 control; 1137 1863 1138 control = api.control.add( id,new constructor( id, {1864 control = new constructor( id, { 1139 1865 params: data, 1140 1866 previewer: api.previewer 1141 } ) ); 1867 } ); 1868 api.control.add( id, control ); 1142 1869 }); 1143 1870 1871 // Focus the autofocused element 1872 _.each( [ 'panel', 'section', 'control' ], function ( type ) { 1873 var instance, id = api.settings.autofocus[ type ]; 1874 if ( id && api[ type ]( id ) ) { 1875 instance = api[ type ]( id ); 1876 // Wait until the element is embedded in the DOM 1877 instance.deferred.ready.done( function () { 1878 // Wait until the preview has activated and so active panels, sections, controls have been set 1879 api.previewer.deferred.active.done( function () { 1880 instance.focus(); 1881 }); 1882 }); 1883 } 1884 }); 1885 1886 /** 1887 * Sort panels, sections, controls by priorities. Hide empty sections and panels. 1888 */ 1889 api.reflowPaneContents = _.bind( function () { 1890 1891 var appendContainer, activeElement, rootNodes = []; 1892 1893 if ( document.activeElement ) { 1894 activeElement = $( document.activeElement ); 1895 } 1896 1897 api.panel.each( function ( panel ) { 1898 var sections = panel.sections(); 1899 rootNodes.push( panel ); 1900 appendContainer = panel.container.find( 'ul:first' ); 1901 // @todo Skip doing any DOM manipulation if the ordering is already correct 1902 _( sections ).each( function ( section ) { 1903 appendContainer.append( section.container ); 1904 } ); 1905 } ); 1906 1907 api.section.each( function ( section ) { 1908 var controls = section.controls(); 1909 if ( ! section.panel() ) { 1910 rootNodes.push( section ); 1911 } 1912 appendContainer = section.container.find( 'ul:first' ); 1913 // @todo Skip doing any DOM manipulation if the ordering is already correct 1914 _( controls ).each( function ( control ) { 1915 appendContainer.append( control.container ); 1916 } ); 1917 } ); 1918 1919 // Sort the root elements 1920 rootNodes.sort( function ( a, b ) { 1921 return a.priority() - b.priority(); 1922 } ); 1923 appendContainer = $( '#customize-theme-controls > ul' ); 1924 // @todo Skip doing any DOM manipulation if the ordering is already correct 1925 _( rootNodes ).each( function ( rootNode ) { 1926 appendContainer.append( rootNode.container ); 1927 } ); 1928 1929 // Now re-trigger the active Value callbacks to that the panels and sections can decide whether they can be rendered 1930 api.panel.each( function ( panel ) { 1931 var value = panel.active(); 1932 panel.active.callbacks.fireWith( panel.active, [ value, value ] ); 1933 } ); 1934 api.section.each( function ( section ) { 1935 var value = section.active(); 1936 section.active.callbacks.fireWith( section.active, [ value, value ] ); 1937 } ); 1938 1939 if ( activeElement ) { 1940 activeElement.focus(); 1941 } 1942 }, api ); 1943 api.reflowPaneContents = _.debounce( api.reflowPaneContents, 100 ); 1944 $( [ api.panel, api.section, api.control ] ).each( function ( i, values ) { 1945 values.bind( 'add', api.reflowPaneContents ); 1946 values.bind( 'change', api.reflowPaneContents ); 1947 values.bind( 'remove', api.reflowPaneContents ); 1948 } ); 1949 api.bind( 'ready', api.reflowPaneContents ); 1950 1144 1951 // Check if preview url is valid and load the preview frame. 1145 1952 if ( api.previewer.previewUrl() ) { 1146 1953 api.previewer.refresh(); … … 1205 2012 event.preventDefault(); 1206 2013 }); 1207 2014 2015 // Go back to the top-level Customizer accordion. 2016 $( '#customize-header-actions' ).on( 'click keydown', '.control-panel-back', function( e ) { 2017 if ( isKeydownButNotEnterEvent( e ) ) { 2018 return; 2019 } 2020 2021 e.preventDefault(); // Keep this AFTER the key filter above 2022 api.panel.each( function ( panel ) { 2023 panel.collapse(); 2024 }); 2025 }); 2026 1208 2027 closeBtn.keydown( function( event ) { 1209 2028 if ( 9 === event.which ) // tab 1210 2029 return; … … 1219 2038 }); 1220 2039 1221 2040 $('.collapse-sidebar').on( 'click keydown', function( event ) { 1222 if ( event.type === 'keydown' && 13 !== event.which ) // enter2041 if ( isKeydownButNotEnterEvent( event ) ) { 1223 2042 return; 2043 } 1224 2044 1225 2045 overlay.toggleClass( 'collapsed' ).toggleClass( 'expanded' ); 1226 2046 event.preventDefault(); -
src/wp-admin/js/customize-widgets.js
diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js index 6be9a08..8112f3e 100644
404 404 * @augments wp.customize.Control 405 405 */ 406 406 api.Widgets.WidgetControl = api.Control.extend({ 407 defaultExpandedArguments: { 408 duration: 'fast' 409 }, 410 411 initialize: function ( id, options ) { 412 var control = this; 413 api.Control.prototype.initialize.call( control, id, options ); 414 control.expanded = new api.Value(); 415 control.expandedArgumentsQueue = []; 416 control.expanded.bind( function ( expanded ) { 417 var args = control.expandedArgumentsQueue.shift(); 418 args = $.extend( {}, control.defaultExpandedArguments, args ); 419 control.onChangeExpanded( expanded, args ); 420 }); 421 control.expanded.set( false ); 422 }, 423 407 424 /** 408 425 * Set up the control 409 426 */ … … 529 546 if ( sidebarWidgetsControl.isReordering ) { 530 547 return; 531 548 } 532 self. toggleForm();549 self.expanded( ! self.expanded() ); 533 550 } ); 534 551 535 552 $closeBtn = this.container.find( '.widget-control-close' ); 536 553 $closeBtn.on( 'click', function( e ) { 537 554 e.preventDefault(); 538 self.collapse Form();555 self.collapse(); 539 556 self.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility 540 557 } ); 541 558 }, … … 777 794 * Overrides api.Control.toggle() 778 795 * 779 796 * @param {Boolean} active 797 * @param {Object} args 780 798 */ 781 toggle: function ( active ) { 799 onChangeActive: function ( active, args ) { 800 // Note: there is a second 'args' parameter being passed, merged on top of this.defaultActiveArguments 782 801 this.container.toggleClass( 'widget-rendered', active ); 802 if ( args.completeCallback ) { 803 args.completeCallback(); 804 } 783 805 }, 784 806 785 807 /** … … 1101 1123 * Expand the accordion section containing a control 1102 1124 */ 1103 1125 expandControlSection: function() { 1104 var $section = this.container.closest( '.accordion-section' ); 1105 1106 if ( ! $section.hasClass( 'open' ) ) { 1107 $section.find( '.accordion-section-title:first' ).trigger( 'click' ); 1108 } 1126 api.Control.prototype.expand.call( this ); 1109 1127 }, 1110 1128 1111 1129 /** 1130 * @param {Boolean} expanded 1131 * @param {Object} [params] 1132 * @returns {Boolean} false if state already applied 1133 */ 1134 _toggleExpanded: api.Section.prototype._toggleExpanded, 1135 1136 /** 1137 * @param {Object} [params] 1138 * @returns {Boolean} false if already expanded 1139 */ 1140 expand: api.Section.prototype.expand, 1141 1142 /** 1112 1143 * Expand the widget form control 1144 * 1145 * @deprecated alias of expand() 1113 1146 */ 1114 1147 expandForm: function() { 1115 this. toggleForm( true);1148 this.expand(); 1116 1149 }, 1117 1150 1118 1151 /** 1152 * @param {Object} [params] 1153 * @returns {Boolean} false if already collapsed 1154 */ 1155 collapse: api.Section.prototype.collapse, 1156 1157 /** 1119 1158 * Collapse the widget form control 1159 * 1160 * @deprecated alias of expand() 1120 1161 */ 1121 1162 collapseForm: function() { 1122 this. toggleForm( false);1163 this.collapse(); 1123 1164 }, 1124 1165 1125 1166 /** 1126 1167 * Expand or collapse the widget control 1127 1168 * 1169 * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide ) 1170 * 1128 1171 * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility 1129 1172 */ 1130 1173 toggleForm: function( showOrHide ) { 1131 var self = this, $widget, $inside, complete;1132 1133 $widget = this.container.find( 'div.widget:first' );1134 $inside = $widget.find( '.widget-inside:first' );1135 1174 if ( typeof showOrHide === 'undefined' ) { 1136 showOrHide = ! $inside.is( ':visible');1175 showOrHide = ! this.expanded(); 1137 1176 } 1177 this.expanded( showOrHide ); 1178 }, 1138 1179 1139 // Already expanded or collapsed, so noop 1140 if ( $inside.is( ':visible' ) === showOrHide ) { 1180 /** 1181 * Respond to change in the expanded state. 1182 * 1183 * @param {Boolean} expanded 1184 * @param {Object} args merged on top of this.defaultActiveArguments 1185 */ 1186 onChangeExpanded: function ( expanded, args ) { 1187 var self = this, $widget, $inside, complete, prevComplete; 1188 1189 // If the expanded state is unchanged only manipulate container expanded states 1190 if ( args.unchanged ) { 1191 if ( expanded ) { 1192 api.Control.prototype.expand.call( self, { 1193 completeCallback: args.completeCallback 1194 }); 1195 } 1141 1196 return; 1142 1197 } 1143 1198 1144 if ( showOrHide ) { 1199 $widget = this.container.find( 'div.widget:first' ); 1200 $inside = $widget.find( '.widget-inside:first' ); 1201 1202 if ( expanded ) { 1203 1204 self.expandControlSection(); 1205 1145 1206 // Close all other widget controls before expanding this one 1146 1207 api.control.each( function( otherControl ) { 1147 1208 if ( self.params.type === otherControl.params.type && self !== otherControl ) { 1148 otherControl.collapse Form();1209 otherControl.collapse(); 1149 1210 } 1150 1211 } ); 1151 1212 … … 1154 1215 self.container.addClass( 'expanded' ); 1155 1216 self.container.trigger( 'expanded' ); 1156 1217 }; 1218 if ( args.completeCallback ) { 1219 prevComplete = complete; 1220 complete = function () { 1221 prevComplete(); 1222 args.completeCallback(); 1223 }; 1224 } 1157 1225 1158 1226 if ( self.params.is_wide ) { 1159 $inside.fadeIn( 'fast', complete );1227 $inside.fadeIn( args.duration, complete ); 1160 1228 } else { 1161 $inside.slideDown( 'fast', complete );1229 $inside.slideDown( args.duration, complete ); 1162 1230 } 1163 1231 1164 1232 self.container.trigger( 'expand' ); 1165 1233 self.container.addClass( 'expanding' ); 1166 1234 } else { 1235 1167 1236 complete = function() { 1168 1237 self.container.removeClass( 'collapsing' ); 1169 1238 self.container.removeClass( 'expanded' ); 1170 1239 self.container.trigger( 'collapsed' ); 1171 1240 }; 1241 if ( args.completeCallback ) { 1242 prevComplete = complete; 1243 complete = function () { 1244 prevComplete(); 1245 args.completeCallback(); 1246 }; 1247 } 1172 1248 1173 1249 self.container.trigger( 'collapse' ); 1174 1250 self.container.addClass( 'collapsing' ); 1175 1251 1176 1252 if ( self.params.is_wide ) { 1177 $inside.fadeOut( 'fast', complete );1253 $inside.fadeOut( args.duration, complete ); 1178 1254 } else { 1179 $inside.slideUp( 'fast', function() {1255 $inside.slideUp( args.duration, function() { 1180 1256 $widget.css( { width:'', margin:'' } ); 1181 1257 complete(); 1182 1258 } ); … … 1185 1261 }, 1186 1262 1187 1263 /** 1188 * Expand the containing sidebar section, expand the form, and focus on1189 * the first input in the control1190 */1191 focus: function() {1192 this.expandControlSection();1193 this.expandForm();1194 this.container.find( '.widget-content :focusable:first' ).focus();1195 },1196 1197 /**1198 1264 * Get the position (index) of the widget in the containing sidebar 1199 1265 * 1200 1266 * @returns {Number} … … 1304 1370 * @augments wp.customize.Control 1305 1371 */ 1306 1372 api.Widgets.SidebarControl = api.Control.extend({ 1373 1307 1374 /** 1308 1375 * Set up the control 1309 1376 */ … … 1325 1392 registeredSidebar = api.Widgets.registeredSidebars.get( this.params.sidebar_id ); 1326 1393 1327 1394 this.setting.bind( function( newWidgetIds, oldWidgetIds ) { 1328 var widgetFormControls, $sidebarWidgetsAddControl, finalControlContainers, removedWidgetIds;1395 var widgetFormControls, removedWidgetIds, priority; 1329 1396 1330 1397 removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds ); 1331 1398 … … 1350 1417 widgetFormControls.sort( function( a, b ) { 1351 1418 var aIndex = _.indexOf( newWidgetIds, a.params.widget_id ), 1352 1419 bIndex = _.indexOf( newWidgetIds, b.params.widget_id ); 1420 return aIndex - bIndex; 1421 }); 1353 1422 1354 if ( aIndex === bIndex ) { 1355 return 0; 1356 } 1357 1358 return aIndex < bIndex ? -1 : 1; 1359 } ); 1360 1361 // Append the controls to put them in the right order 1362 finalControlContainers = _( widgetFormControls ).map( function( widgetFormControls ) { 1363 return widgetFormControls.container[0]; 1364 } ); 1365 1366 $sidebarWidgetsAddControl = self.$sectionContent.find( '.customize-control-sidebar_widgets' ); 1367 $sidebarWidgetsAddControl.before( finalControlContainers ); 1423 priority = 0; 1424 _( widgetFormControls ).each( function ( control ) { 1425 control.priority( priority ); 1426 control.section( self.section() ); 1427 priority += 1; 1428 }); 1429 self.priority( priority ); // Make sure sidebar control remains at end 1368 1430 1369 1431 // Re-sort widget form controls (including widgets form other sidebars newly moved here) 1370 1432 self._applyCardinalOrderClassNames(); … … 1434 1496 // Update the model with whether or not the sidebar is rendered 1435 1497 self.active.bind( function ( active ) { 1436 1498 registeredSidebar.set( 'is_rendered', active ); 1499 api.section( self.section.get() ).active( active ); 1437 1500 } ); 1438 }, 1439 1440 /** 1441 * Show the sidebar section when it becomes visible. 1442 * 1443 * Overrides api.Control.toggle() 1444 * 1445 * @param {Boolean} active 1446 */ 1447 toggle: function ( active ) { 1448 var $section, sectionSelector; 1449 1450 sectionSelector = '#accordion-section-sidebar-widgets-' + this.params.sidebar_id; 1451 $section = $( sectionSelector ); 1452 1453 if ( active ) { 1454 $section.stop().slideDown( function() { 1455 $( this ).css( 'height', 'auto' ); // so that the .accordion-section-content won't overflow 1456 } ); 1457 1458 } else { 1459 // Make sure that hidden sections get closed first 1460 if ( $section.hasClass( 'open' ) ) { 1461 // it would be nice if accordionSwitch() in accordion.js was public 1462 $section.find( '.accordion-section-title' ).trigger( 'click' ); 1463 } 1464 1465 $section.stop().slideUp(); 1466 } 1501 api.section( self.section.get() ).active( self.active() ); 1467 1502 }, 1468 1503 1469 1504 /** … … 1500 1535 this.$controlSection.find( '.accordion-section-title' ).droppable({ 1501 1536 accept: '.customize-control-widget_form', 1502 1537 over: function() { 1503 if ( ! self.$controlSection.hasClass( 'open' ) ) { 1504 self.$controlSection.addClass( 'open' ); 1505 self.$sectionContent.toggle( false ).slideToggle( 150, function() { 1506 self.$sectionContent.sortable( 'refreshPositions' ); 1507 } ); 1508 } 1538 var section = api.section( self.section.get() ); 1539 section.expand({ 1540 allowMultiple: true, // Prevent the section being dragged from to be collapsed 1541 completeCallback: function () { 1542 // @todo It is not clear when refreshPositions should be called on which sections, or if it is even needed 1543 api.section.each( function ( otherSection ) { 1544 if ( otherSection.container.find( '.customize-control-sidebar_widgets' ).length ) { 1545 otherSection.container.find( '.accordion-section-content:first' ).sortable( 'refreshPositions' ); 1546 } 1547 } ); 1548 } 1549 }); 1509 1550 } 1510 1551 }); 1511 1552 … … 1548 1589 * Add classes to the widget_form controls to assist with styling 1549 1590 */ 1550 1591 _applyCardinalOrderClassNames: function() { 1551 this.$sectionContent.find( '.customize-control-widget_form' ) 1552 .removeClass( 'first-widget' ) 1553 .removeClass( 'last-widget' ) 1554 .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 ); 1592 var widgetControls = []; 1593 _.each( this.setting(), function ( widgetId ) { 1594 var widgetControl = api.Widgets.getWidgetFormControlForWidget( widgetId ); 1595 if ( widgetControl ) { 1596 widgetControls.push( widgetControl ); 1597 } 1598 }); 1599 1600 if ( ! widgetControls.length ) { 1601 return; 1602 } 1555 1603 1556 this.$sectionContent.find( '.customize-control-widget_form:first' ) 1604 $( widgetControls ).each( function () { 1605 $( this.container ) 1606 .removeClass( 'first-widget' ) 1607 .removeClass( 'last-widget' ) 1608 .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 ); 1609 }); 1610 1611 _.first( widgetControls ).container 1557 1612 .addClass( 'first-widget' ) 1558 1613 .find( '.move-widget-up' ).prop( 'tabIndex', -1 ); 1559 1614 1560 this.$sectionContent.find( '.customize-control-widget_form:last' )1615 _.last( widgetControls ).container 1561 1616 .addClass( 'last-widget' ) 1562 1617 .find( '.move-widget-down' ).prop( 'tabIndex', -1 ); 1563 1618 }, … … 1571 1626 * Enable/disable the reordering UI 1572 1627 * 1573 1628 * @param {Boolean} showOrHide to enable/disable reordering 1629 * 1630 * @todo We should have a reordering state instead and rename this to onChangeReordering 1574 1631 */ 1575 1632 toggleReordering: function( showOrHide ) { 1576 1633 showOrHide = Boolean( showOrHide ); … … 1584 1641 1585 1642 if ( showOrHide ) { 1586 1643 _( this.getWidgetFormControls() ).each( function( formControl ) { 1587 formControl.collapse Form();1644 formControl.collapse(); 1588 1645 } ); 1589 1646 1590 1647 this.$sectionContent.find( '.first-widget .move-widget' ).focus(); … … 1619 1676 * @returns {object|false} widget_form control instance, or false on error 1620 1677 */ 1621 1678 addWidget: function( widgetId ) { 1622 var self = this, controlHtml, $widget, controlType = 'widget_form', $control, controlConstructor,1679 var self = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor, 1623 1680 parsedWidgetId = parseWidgetId( widgetId ), 1624 1681 widgetNumber = parsedWidgetId.number, 1625 1682 widgetIdBase = parsedWidgetId.id_base, … … 1651 1708 1652 1709 $widget = $( controlHtml ); 1653 1710 1654 $control= $( '<li/>' )1711 controlContainer = $( '<li/>' ) 1655 1712 .addClass( 'customize-control' ) 1656 1713 .addClass( 'customize-control-' + controlType ) 1657 1714 .append( $widget ); 1658 1715 1659 1716 // Remove icon which is visible inside the panel 1660 $control.find( '> .widget-icon' ).remove();1717 controlContainer.find( '> .widget-icon' ).remove(); 1661 1718 1662 1719 if ( widget.get( 'is_multi' ) ) { 1663 $control.find( 'input[name="widget_number"]' ).val( widgetNumber );1664 $control.find( 'input[name="multi_number"]' ).val( widgetNumber );1720 controlContainer.find( 'input[name="widget_number"]' ).val( widgetNumber ); 1721 controlContainer.find( 'input[name="multi_number"]' ).val( widgetNumber ); 1665 1722 } 1666 1723 1667 widgetId = $control.find( '[name="widget-id"]' ).val();1724 widgetId = controlContainer.find( '[name="widget-id"]' ).val(); 1668 1725 1669 $control.hide(); // to be slid-down below1726 controlContainer.hide(); // to be slid-down below 1670 1727 1671 1728 settingId = 'widget_' + widget.get( 'id_base' ); 1672 1729 if ( widget.get( 'is_multi' ) ) { 1673 1730 settingId += '[' + widgetNumber + ']'; 1674 1731 } 1675 $control.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); 1676 1677 this.container.after( $control ); 1732 controlContainer.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); 1678 1733 1679 1734 // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) 1680 1735 isExistingWidget = api.has( settingId ); … … 1692 1747 settings: { 1693 1748 'default': settingId 1694 1749 }, 1750 content: controlContainer, 1695 1751 sidebar_id: self.params.sidebar_id, 1696 1752 widget_id: widgetId, 1697 1753 widget_id_base: widget.get( 'id_base' ), … … 1731 1787 this.setting( sidebarWidgets ); 1732 1788 } 1733 1789 1734 $control.slideDown( function() {1790 controlContainer.slideDown( function() { 1735 1791 if ( isExistingWidget ) { 1736 widgetFormControl.expand Form();1792 widgetFormControl.expand(); 1737 1793 widgetFormControl.updateWidget( { 1738 1794 instance: widgetFormControl.setting(), 1739 1795 complete: function( error ) { -
src/wp-includes/class-wp-customize-control.php
diff --git src/wp-includes/class-wp-customize-control.php src/wp-includes/class-wp-customize-control.php index cbb7cdd..8f24601 100644
class WP_Customize_Control { 74 74 public $input_attrs = array(); 75 75 76 76 /** 77 * @deprecated It is better to just call the json() method 77 78 * @access public 78 79 * @var array 79 80 */ … … class WP_Customize_Control { 218 219 } 219 220 220 221 $this->json['type'] = $this->type; 222 $this->json['priority'] = $this->priority; 223 $this->json['active'] = $this->active(); 224 $this->json['section'] = $this->section; 225 $this->json['content'] = $this->get_content(); 221 226 $this->json['label'] = $this->label; 222 227 $this->json['description'] = $this->description; 223 $this->json['active'] = $this->active(); 228 } 229 230 /** 231 * Get the data to export to the client via JSON. 232 * 233 * @since 4.1.0 234 * 235 * @return array 236 */ 237 public function json() { 238 $this->to_json(); 239 return $this->json; 224 240 } 225 241 226 242 /** … … class WP_Customize_Control { 244 260 } 245 261 246 262 /** 263 * Get the control's content for insertion into the Customizer pane. 264 * 265 * @since 4.1.0 266 * 267 * @return string 268 */ 269 public final function get_content() { 270 ob_start(); 271 $this->maybe_render(); 272 $template = trim( ob_get_contents() ); 273 ob_end_clean(); 274 return $template; 275 } 276 277 /** 247 278 * Check capabilities and render the control. 248 279 * 249 280 * @since 3.4.0 … … class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control { 1071 1102 /** 1072 1103 * Widget Area Customize Control Class 1073 1104 * 1105 * @since 3.9.0 1074 1106 */ 1075 1107 class WP_Widget_Area_Customize_Control extends WP_Customize_Control { 1076 1108 public $type = 'sidebar_widgets'; … … class WP_Widget_Area_Customize_Control extends WP_Customize_Control { 1112 1144 1113 1145 /** 1114 1146 * Widget Form Customize Control Class 1147 * 1148 * @since 3.9.0 1115 1149 */ 1116 1150 class WP_Widget_Form_Customize_Control extends WP_Customize_Control { 1117 1151 public $type = 'widget_form'; -
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 bcbf127..4d4e73e 100644
final class WP_Customize_Manager { 498 498 $settings = array( 499 499 'values' => array(), 500 500 'channel' => wp_unslash( $_POST['customize_messenger_channel'] ), 501 'activePanels' => array(), 502 'activeSections' => array(), 501 503 'activeControls' => array(), 502 504 ); 503 505 … … final class WP_Customize_Manager { 511 513 foreach ( $this->settings as $id => $setting ) { 512 514 $settings['values'][ $id ] = $setting->js_value(); 513 515 } 516 foreach ( $this->panels as $id => $panel ) { 517 $settings['activePanels'][ $id ] = $panel->active(); 518 } 519 foreach ( $this->sections as $id => $section ) { 520 $settings['activeSections'][ $id ] = $section->active(); 521 } 514 522 foreach ( $this->controls as $id => $control ) { 515 523 $settings['activeControls'][ $id ] = $control->active(); 516 524 } … … final class WP_Customize_Manager { 911 919 912 920 if ( ! $section->panel ) { 913 921 // Top-level section. 914 $sections[ ] = $section;922 $sections[ $section->id ] = $section; 915 923 } else { 916 924 // This section belongs to a panel. 917 925 if ( isset( $this->panels [ $section->panel ] ) ) { 918 $this->panels[ $section->panel ]->sections[ ] = $section;926 $this->panels[ $section->panel ]->sections[ $section->id ] = $section; 919 927 } 920 928 } 921 929 } … … final class WP_Customize_Manager { 932 940 continue; 933 941 } 934 942 935 u sort( $panel->sections, array( $this, '_cmp_priority' ) );936 $panels[ ] = $panel;943 uasort( $panel->sections, array( $this, '_cmp_priority' ) ); 944 $panels[ $panel->id ] = $panel; 937 945 } 938 946 $this->panels = $panels; 939 947 -
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 f289cb7..201c4b9 100644
class WP_Customize_Panel { 83 83 public $sections; 84 84 85 85 /** 86 * @since 4.1.0 87 * @access public 88 * @var string 89 */ 90 public $type; 91 92 /** 93 * Callback. 94 * 95 * @since 4.1.0 96 * @access public 97 * 98 * @see WP_Customize_Section::active() 99 * 100 * @var callable Callback is called with one argument, the instance of 101 * WP_Customize_Section, and returns bool to indicate whether 102 * the section is active (such as it relates to the URL 103 * currently being previewed). 104 */ 105 public $active_callback = ''; 106 107 /** 86 108 * Constructor. 87 109 * 88 110 * Any supplied $args override class property defaults. … … class WP_Customize_Panel { 103 125 104 126 $this->manager = $manager; 105 127 $this->id = $id; 128 if ( empty( $this->active_callback ) ) { 129 $this->active_callback = array( $this, 'active_callback' ); 130 } 106 131 107 132 $this->sections = array(); // Users cannot customize the $sections array. 108 133 … … class WP_Customize_Panel { 110 135 } 111 136 112 137 /** 138 * Check whether panel is active to current Customizer preview. 139 * 140 * @since 4.1.0 141 * @access public 142 * 143 * @return bool Whether the panel is active to the current preview. 144 */ 145 public final function active() { 146 $panel = $this; 147 $active = call_user_func( $this->active_callback, $this ); 148 149 /** 150 * Filter response of WP_Customize_Panel::active(). 151 * 152 * @since 4.1.0 153 * 154 * @param bool $active Whether the Customizer panel is active. 155 * @param WP_Customize_Panel $panel WP_Customize_Panel instance. 156 */ 157 $active = apply_filters( 'customize_panel_active', $active, $panel ); 158 159 return $active; 160 } 161 162 /** 163 * Default callback used when invoking WP_Customize_Panel::active(). 164 * 165 * Subclasses can override this with their specific logic, or they may 166 * provide an 'active_callback' argument to the constructor. 167 * 168 * @since 4.1.0 169 * @access public 170 * 171 * @return bool Always true. 172 */ 173 public function active_callback() { 174 return true; 175 } 176 177 /** 178 * Gather the parameters passed to client JavaScript via JSON. 179 * 180 * @since 4.1.0 181 * 182 * @return array The array to be exported to the client as JSON 183 */ 184 public function json() { 185 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'type' ) ); 186 $array['content'] = $this->get_content(); 187 $array['active'] = $this->active(); 188 return $array; 189 } 190 191 /** 113 192 * Checks required user capabilities and whether the theme has the 114 193 * feature support required by the panel. 115 194 * … … class WP_Customize_Panel { 130 209 } 131 210 132 211 /** 212 * Get the panel's content template for insertion into the Customizer pane. 213 * 214 * @since 4.1.0 215 * 216 * @return string 217 */ 218 public final function get_content() { 219 ob_start(); 220 $this->maybe_render(); 221 $template = trim( ob_get_contents() ); 222 ob_end_clean(); 223 return $template; 224 } 225 226 /** 133 227 * Check capabilities and render the panel. 134 228 * 135 229 * @since 4.0.0 … … class WP_Customize_Panel { 189 283 */ 190 284 protected function render_content() { 191 285 ?> 192 <li class=" accordion-section control-section<?php if ( empty( $this->description ) ) echo ' cannot-expand';?>">286 <li class="panel-meta accordion-section control-section<?php if ( empty( $this->description ) ) { echo ' cannot-expand'; } ?>"> 193 287 <div class="accordion-section-title" tabindex="0"> 194 288 <span class="preview-notice"><?php 195 289 /* translators: %s is the site/panel title in the Customizer */ … … class WP_Customize_Panel { 203 297 <?php endif; ?> 204 298 </li> 205 299 <?php 206 foreach ( $this->sections as $section ) {207 $section->maybe_render();208 }209 300 } 210 301 } -
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 d740ddb..3553285 100644
class WP_Customize_Section { 92 92 public $controls; 93 93 94 94 /** 95 * @since 4.1.0 96 * @access public 97 * @var string 98 */ 99 public $type; 100 101 /** 102 * Callback. 103 * 104 * @since 4.1.0 105 * @access public 106 * 107 * @see WP_Customize_Section::active() 108 * 109 * @var callable Callback is called with one argument, the instance of 110 * WP_Customize_Section, and returns bool to indicate whether 111 * the section is active (such as it relates to the URL 112 * currently being previewed). 113 */ 114 public $active_callback = ''; 115 116 /** 95 117 * Constructor. 96 118 * 97 119 * Any supplied $args override class property defaults. … … class WP_Customize_Section { 105 127 public function __construct( $manager, $id, $args = array() ) { 106 128 $keys = array_keys( get_object_vars( $this ) ); 107 129 foreach ( $keys as $key ) { 108 if ( isset( $args[ $key ] ) ) 130 if ( isset( $args[ $key ] ) ) { 109 131 $this->$key = $args[ $key ]; 132 } 110 133 } 111 134 112 135 $this->manager = $manager; 113 136 $this->id = $id; 137 if ( empty( $this->active_callback ) ) { 138 $this->active_callback = array( $this, 'active_callback' ); 139 } 114 140 115 141 $this->controls = array(); // Users cannot customize the $controls array. 116 142 … … class WP_Customize_Section { 118 144 } 119 145 120 146 /** 147 * Check whether section is active to current Customizer preview. 148 * 149 * @since 4.1.0 150 * @access public 151 * 152 * @return bool Whether the section is active to the current preview. 153 */ 154 public final function active() { 155 $section = $this; 156 $active = call_user_func( $this->active_callback, $this ); 157 158 /** 159 * Filter response of WP_Customize_Section::active(). 160 * 161 * @since 4.1.0 162 * 163 * @param bool $active Whether the Customizer section is active. 164 * @param WP_Customize_Section $section WP_Customize_Section instance. 165 */ 166 $active = apply_filters( 'customize_section_active', $active, $section ); 167 168 return $active; 169 } 170 171 /** 172 * Default callback used when invoking WP_Customize_Section::active(). 173 * 174 * Subclasses can override this with their specific logic, or they may 175 * provide an 'active_callback' argument to the constructor. 176 * 177 * @since 4.1.0 178 * @access public 179 * 180 * @return bool Always true. 181 */ 182 public function active_callback() { 183 return true; 184 } 185 186 /** 187 * Gather the parameters passed to client JavaScript via JSON. 188 * 189 * @since 4.1.0 190 * 191 * @return array The array to be exported to the client as JSON 192 */ 193 public function json() { 194 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'panel', 'type' ) ); 195 $array['content'] = $this->get_content(); 196 $array['active'] = $this->active(); 197 return $array; 198 } 199 200 /** 121 201 * Checks required user capabilities and whether the theme has the 122 202 * feature support required by the section. 123 203 * … … class WP_Customize_Section { 126 206 * @return bool False if theme doesn't support the section or user doesn't have the capability. 127 207 */ 128 208 public final function check_capabilities() { 129 if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) 209 if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) { 130 210 return false; 211 } 131 212 132 if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) ) 213 if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) ) { 133 214 return false; 215 } 134 216 135 217 return true; 136 218 } 137 219 138 220 /** 221 * Get the section's content template for insertion into the Customizer pane. 222 * 223 * @since 4.1.0 224 * 225 * @return string 226 */ 227 public final function get_content() { 228 ob_start(); 229 $this->maybe_render(); 230 $template = trim( ob_get_contents() ); 231 ob_end_clean(); 232 return $template; 233 } 234 235 /** 139 236 * Check capabilities and render the section. 140 237 * 141 238 * @since 3.4.0 142 239 */ 143 240 public final function maybe_render() { 144 if ( ! $this->check_capabilities() ) 241 if ( ! $this->check_capabilities() ) { 145 242 return; 243 } 146 244 147 245 /** 148 246 * Fires before rendering a Customizer section. … … class WP_Customize_Section { 172 270 */ 173 271 protected function render() { 174 272 $classes = 'control-section accordion-section'; 175 if ( $this->panel ) {176 $classes .= ' control-subsection';177 }178 273 ?> 179 274 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>"> 180 275 <h3 class="accordion-section-title" tabindex="0"> … … class WP_Customize_Section { 183 278 </h3> 184 279 <ul class="accordion-section-content"> 185 280 <?php if ( ! empty( $this->description ) ) : ?> 186 <li><p class="description customize-section-description"><?php echo $this->description; ?></p></li> 281 <li class="customize-section-description-container"> 282 <p class="description customize-section-description"><?php echo $this->description; ?></p> 283 </li> 187 284 <?php endif; ?> 188 <?php189 foreach ( $this->controls as $control )190 $control->maybe_render();191 ?>192 285 </ul> 193 286 </li> 194 287 <?php -
src/wp-includes/js/customize-base.js
diff --git src/wp-includes/js/customize-base.js src/wp-includes/js/customize-base.js index d2488dd..6ea2822 100644
window.wp = window.wp || {}; 184 184 to = this.validate( to ); 185 185 186 186 // Bail if the sanitized value is null or unchanged. 187 if ( null === to || _.isEqual( from, to ) ) 187 if ( null === to || _.isEqual( from, to ) ) { 188 188 return this; 189 } 189 190 190 191 this._value = to; 191 192 this._dirty = true; -
src/wp-includes/js/customize-preview.js
diff --git src/wp-includes/js/customize-preview.js src/wp-includes/js/customize-preview.js index 6da26f4..1a82565 100644
107 107 }); 108 108 109 109 preview.send( 'ready', { 110 activePanels: api.settings.activePanels, 111 activeSections: api.settings.activeSections, 110 112 activeControls: api.settings.activeControls 111 113 } ); 112 114 -
tests/qunit/index.html
diff --git tests/qunit/index.html tests/qunit/index.html index c5afd52..ce11144 100644
8 8 <script src="../../src/wp-includes/js/underscore.min.js"></script> 9 9 <script src="../../src/wp-includes/js/backbone.min.js"></script> 10 10 <script src="../../src/wp-includes/js/zxcvbn.min.js"></script> 11 11 12 12 <!-- QUnit --> 13 13 <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" /> 14 14 <script src="vendor/qunit.js"></script> … … 28 28 29 29 <!-- Tested files --> 30 30 <script src="../../src/wp-admin/js/password-strength-meter.js"></script> 31 <script src="../../src/wp-includes/js/customize-base.js"></script> 31 32 <script src="../../src/wp-includes/js/customize-models.js"></script> 32 33 <script src="../../src/wp-includes/js/shortcode.js"></script> 33 34 34 35 <!-- Unit tests --> 35 36 <script src="wp-admin/js/password-strength-meter.js"></script> 37 <script src="wp-admin/js/customize-base.js"></script> 36 38 <script src="wp-admin/js/customize-header.js"></script> 37 39 <script src="wp-includes/js/shortcode.js"></script> 38 40 </div> 39 41 </body> 40 42 </html> 41 -
new file tests/qunit/wp-admin/js/customize-base.js
diff --git tests/qunit/wp-admin/js/customize-base.js tests/qunit/wp-admin/js/customize-base.js new file mode 100644 index 0000000..1235bd5
- + 1 /* global wp, sinon */ 2 3 jQuery( function( $ ) { 4 var FooSuperClass, BarSubClass, foo, bar; 5 6 module( 'Customize Base: Class' ); 7 8 FooSuperClass = wp.customize.Class.extend( 9 { 10 initialize: function ( instanceProps ) { 11 $.extend( this, instanceProps || {} ); 12 }, 13 protoProp: 'protoPropValue' 14 }, 15 { 16 staticProp: 'staticPropValue' 17 } 18 ); 19 test( 'FooSuperClass is a function ', function () { 20 equal( typeof FooSuperClass, 'function' ); 21 }); 22 test( 'FooSuperClass prototype has protoProp', function () { 23 equal( FooSuperClass.prototype.protoProp, 'protoPropValue' ); 24 }); 25 test( 'FooSuperClass does not have protoProp', function () { 26 equal( typeof FooSuperClass.protoProp, 'undefined' ); 27 }); 28 test( 'FooSuperClass has staticProp', function () { 29 equal( FooSuperClass.staticProp, 'staticPropValue' ); 30 }); 31 test( 'FooSuperClass prototype does not have staticProp', function () { 32 equal( typeof FooSuperClass.prototype.staticProp, 'undefined' ); 33 }); 34 35 foo = new FooSuperClass( { instanceProp: 'instancePropValue' } ); 36 test( 'FooSuperClass instance foo extended Class', function () { 37 equal( foo.extended( wp.customize.Class ), true ); 38 }); 39 test( 'foo instance has protoProp', function () { 40 equal( foo.protoProp, 'protoPropValue' ); 41 }); 42 test( 'foo instance does not have staticProp', function () { 43 equal( typeof foo.staticProp, 'undefined' ); 44 }); 45 test( 'FooSuperClass instance foo ran initialize() and has supplied instanceProp', function () { 46 equal( foo.instanceProp, 'instancePropValue' ); 47 }); 48 49 // @todo Test Class.constructor() manipulation 50 // @todo Test Class.applicator? 51 // @todo do we test object.instance? 52 53 54 module( 'Customize Base: Subclass' ); 55 56 BarSubClass = FooSuperClass.extend( 57 { 58 initialize: function ( instanceProps ) { 59 FooSuperClass.prototype.initialize.call( this, instanceProps ); 60 this.subInstanceProp = 'subInstancePropValue'; 61 }, 62 subProtoProp: 'subProtoPropValue' 63 }, 64 { 65 subStaticProp: 'subStaticPropValue' 66 } 67 ); 68 test( 'BarSubClass prototype has subProtoProp', function () { 69 equal( BarSubClass.prototype.subProtoProp, 'subProtoPropValue' ); 70 }); 71 test( 'BarSubClass prototype has parent FooSuperClass protoProp', function () { 72 equal( BarSubClass.prototype.protoProp, 'protoPropValue' ); 73 }); 74 75 bar = new BarSubClass( { instanceProp: 'instancePropValue' } ); 76 test( 'BarSubClass instance bar its initialize() and parent initialize() run', function () { 77 equal( bar.instanceProp, 'instancePropValue' ); 78 equal( bar.subInstanceProp, 'subInstancePropValue' ); 79 }); 80 81 test( 'BarSubClass instance bar extended FooSuperClass', function () { 82 equal( bar.extended( FooSuperClass ), true ); 83 }); 84 85 });