: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
section.deferred.embedded.resolve();
// There is no panel, so embed the section in the root of the customizer.
parentContainer = api.ensure( section.containerPaneParent );
if ( ! section.headContainer.parent().is( parentContainer ) ) {
parentContainer.append( section.headContainer );
if ( ! section.contentContainer.parent().is( section.headContainer ) ) {
section.containerParent.append( section.contentContainer );
section.deferred.embedded.resolve();
section.panel.bind( inject );
inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one.
* Add behaviors for the accordion section.
attachEvents: function () {
var meta, content, section = this;
if ( section.container.hasClass( 'cannot-expand' ) ) {
// Expand/Collapse accordion sections on click.
section.container.find( '.accordion-section-title, .customize-section-back' ).on( 'click keydown', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
event.preventDefault(); // Keep this AFTER the key filter above.
if ( section.expanded() ) {
// This is very similar to what is found for api.Panel.attachEvents().
section.container.find( '.customize-section-title .customize-help-toggle' ).on( 'click', function() {
meta = section.container.find( '.section-meta' );
if ( meta.hasClass( 'cannot-expand' ) ) {
content = meta.find( '.customize-section-description:first' );
content.toggleClass( 'open' );
content.slideToggle( section.defaultExpandedArguments.duration, function() {
content.trigger( 'toggled' );
$( this ).attr( 'aria-expanded', function( i, attr ) {
return 'true' === attr ? 'false' : 'true';
* Return whether this section has any active controls.
isContextuallyActive: function () {
controls = section.controls(),
_( controls ).each( function ( control ) {
if ( control.active() ) {
return ( activeCount !== 0 );
* Get the controls that are associated with this section, sorted by their priority Value.
return this._children( 'section', 'control' );
* Update UI to reflect expanded state.
* @param {boolean} expanded
onChangeExpanded: function ( expanded, args ) {
container = section.headContainer.closest( '.wp-full-overlay-sidebar-content' ),
content = section.contentContainer,
overlay = section.headContainer.closest( '.wp-full-overlay' ),
backBtn = content.find( '.customize-section-back' ),
sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(),
if ( expanded && ! content.hasClass( 'open' ) ) {
expand = args.completeCallback;
section._animateChangeExpanded( function() {
sectionTitle.attr( 'tabindex', '-1' );
backBtn.attr( 'tabindex', '0' );
backBtn.trigger( 'focus' );
content.css( 'top', '' );
container.scrollTop( 0 );
if ( args.completeCallback ) {
content.addClass( 'open' );
overlay.addClass( 'section-open' );
api.state( 'expandedSection' ).set( section );
if ( ! args.allowMultiple ) {
api.section.each( function ( otherSection ) {
if ( otherSection !== section ) {
otherSection.collapse( { duration: args.duration } );
api.panel( section.panel() ).expand({
if ( ! args.allowMultiple ) {
api.panel.each( function( panel ) {
} else if ( ! expanded && content.hasClass( 'open' ) ) {
panel = api.panel( section.panel() );
if ( panel.contentContainer.hasClass( 'skip-transition' ) ) {
section._animateChangeExpanded( function() {
backBtn.attr( 'tabindex', '-1' );
sectionTitle.attr( 'tabindex', '0' );
sectionTitle.trigger( 'focus' );
content.css( 'top', '' );
if ( args.completeCallback ) {
content.removeClass( 'open' );
overlay.removeClass( 'section-open' );
if ( section === api.state( 'expandedSection' ).get() ) {
api.state( 'expandedSection' ).set( false );
if ( args.completeCallback ) {
api.ThemesSection = api.Section.extend(/** @lends wp.customize.ThemesSection.prototype */{
updateCountDebounced: null,
* wp.customize.ThemesSection
* Custom section for themes that loads themes by category, and also
* handles the theme-details view rendering and navigation.
* @constructs wp.customize.ThemesSection
* @augments wp.customize.Section
* @param {string} id - ID.
* @param {Object} options - Options.
initialize: function( id, options ) {
section.headerContainer = $();
section.$window = $( window );
section.$body = $( document.body );
api.Section.prototype.initialize.call( section, id, options );
section.updateCountDebounced = _.debounce( section.updateCount, 500 );
* Embed the section in the DOM when the themes panel is ready.
* Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel.
// Watch for changes to the panel state.
inject = function( panelId ) {
api.panel( panelId, function( panel ) {
// The panel has been registered, wait for it to become ready/initialized.
panel.deferred.embedded.done( function() {
parentContainer = panel.contentContainer;
if ( ! section.headContainer.parent().is( parentContainer ) ) {
parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer );
if ( ! section.contentContainer.parent().is( section.headContainer ) ) {
section.containerParent.append( section.contentContainer );
section.deferred.embedded.resolve();
section.panel.bind( inject );
inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one.
section.overlay = section.container.find( '.theme-overlay' );
section.template = wp.template( 'customize-themes-details-view' );
// Bind global keyboard events.
section.container.on( 'keydown', function( event ) {
if ( ! section.overlay.find( '.theme-wrap' ).is( ':visible' ) ) {
// Pressing the right arrow key fires a theme:next event.
if ( 39 === event.keyCode ) {
// Pressing the left arrow key fires a theme:previous event.
if ( 37 === event.keyCode ) {
// Pressing the escape key fires a theme:collapse event.
if ( 27 === event.keyCode ) {
if ( section.$body.hasClass( 'modal-open' ) ) {
// Escape from the details modal.
// Escape from the infinite scroll list.
section.headerContainer.find( '.customize-themes-section-title' ).focus();
event.stopPropagation(); // Prevent section from being collapsed.
section.renderScreenshots = _.throttle( section.renderScreenshots, 100 );
_.bindAll( section, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' );
* Override Section.isContextuallyActive method.
* Ignore the active states' of the contained theme controls, and just
* use the section's own active state instead. This prevents empty search
* results for theme sections from causing the section to become inactive.
isContextuallyActive: function () {
attachEvents: function () {
var section = this, debounced;
// Expand/Collapse accordion sections on click.
section.container.find( '.customize-section-back' ).on( 'click keydown', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
event.preventDefault(); // Keep this AFTER the key filter above.
section.headerContainer = $( '#accordion-section-' + section.id );
// Expand section/panel. Only collapse when opening another section.
section.headerContainer.on( 'click', '.customize-themes-section-title', function() {
// Toggle accordion filters under section headers.
if ( section.headerContainer.find( '.filter-details' ).length ) {
section.headerContainer.find( '.customize-themes-section-title' )
.toggleClass( 'details-open' )
.attr( 'aria-expanded', function( i, attr ) {
return 'true' === attr ? 'false' : 'true';
section.headerContainer.find( '.filter-details' ).slideToggle( 180 );
if ( ! section.expanded() ) {
// Preview installed themes.
section.container.on( 'click', '.theme-actions .preview-theme', function() {
api.panel( 'themes' ).loadThemePreview( $( this ).data( 'slug' ) );
// Theme navigation in details view.
section.container.on( 'click', '.left', function() {
section.container.on( 'click', '.right', function() {
section.container.on( 'click', '.theme-backdrop, .close', function() {
if ( 'local' === section.params.filter_type ) {
// Filter-search all theme objects loaded in the section.
section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
section.filterSearch( event.currentTarget.value );
} else if ( 'remote' === section.params.filter_type ) {
// Event listeners for remote queries with user-entered terms.
debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search.
section.contentContainer.on( 'input', '.wp-filter-search', function() {
if ( ! api.panel( 'themes' ).expanded() ) {
if ( ! section.expanded() ) {
section.contentContainer.on( 'click', '.filter-group input', function() {
section.filtersChecked();
section.checkTerm( section );
// Toggle feature filters.
section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
var $themeContainer = $( '.customize-themes-full-container' ),
$filterToggle = $( e.currentTarget );
section.filtersHeight = $filterToggle.parents( '.themes-filter-bar' ).next( '.filter-drawer' ).height();
if ( 0 < $themeContainer.scrollTop() ) {
$themeContainer.animate( { scrollTop: 0 }, 400 );
if ( $filterToggle.hasClass( 'open' ) ) {
.attr( 'aria-expanded', function( i, attr ) {
return 'true' === attr ? 'false' : 'true';
.parents( '.themes-filter-bar' ).next( '.filter-drawer' ).slideToggle( 180, 'linear' );
if ( $filterToggle.hasClass( 'open' ) ) {
var marginOffset = 1018 < window.innerWidth ? 50 : 76;
section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + marginOffset );
section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
// Setup section cross-linking.
section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() {
api.section( 'wporg_themes' ).focus();
function updateSelectedState() {
var el = section.headerContainer.find( '.customize-themes-section-title' );
el.toggleClass( 'selected', section.expanded() );
el.attr( 'aria-expanded', section.expanded() ? 'true' : 'false' );
if ( ! section.expanded() ) {
el.removeClass( 'details-open' );
section.expanded.bind( updateSelectedState );
// Move section controls to the themes area.
api.bind( 'ready', function () {
section.contentContainer = section.container.find( '.customize-themes-section' );
section.contentContainer.appendTo( $( '.customize-themes-full-container' ) );
section.container.add( section.headerContainer );
* Update UI to reflect expanded state
* @param {boolean} expanded
* @param {boolean} args.unchanged
* @param {Function} args.completeCallback
onChangeExpanded: function ( expanded, args ) {
// Note: there is a second argument 'args' passed.
container = section.contentContainer.closest( '.customize-themes-full-container' );
// Immediately call the complete callback if there were no changes.
if ( args.completeCallback ) {
// Try to load controls if none are loaded yet.
if ( 0 === section.loaded ) {