* @since 4.1.0
*
* @param {Object} [params]
- * @param {Callback} [params.completeCallback]
+ * @param {Function} [params.completeCallback]
*/
focus = function ( params ) {
- var construct, completeCallback, focus;
+ var construct, completeCallback, focus, focusElement;
construct = this;
params = params || {};
focus = function () {
focusContainer = construct.container;
}
- // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
- focusContainer.find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ).first().focus();
+ focusElement = focusContainer.find( '.control-focus:first' );
+ if ( 0 === focusElement.length ) {
+ // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
+ focusElement = focusContainer.find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ).first();
+ }
+ focusElement.focus();
};
if ( params.completeCallback ) {
completeCallback = params.completeCallback;
// Fix the height after browser resize.
$( window ).on( 'resize.customizer-section', _.debounce( resizeContentHeight, 100 ) );
- section._recalculateTopMargin();
+ setTimeout( _.bind( section._recalculateTopMargin, section ), 0 );
};
}
settings = $.map( control.params.settings, function( value ) {
return value;
});
- api.apply( api, settings.concat( function () {
- var key;
+ if ( 0 === settings.length ) {
+ control.setting = null;
control.settings = {};
- for ( key in control.params.settings ) {
- control.settings[ key ] = api( control.params.settings[ key ] );
- }
+ control.embed();
+ } else {
+ api.apply( api, settings.concat( function() {
+ var key;
- control.setting = control.settings['default'] || null;
+ control.settings = {};
+ for ( key in control.params.settings ) {
+ control.settings[ key ] = api( control.params.settings[ key ] );
+ }
- control.embed();
- }) );
+ control.setting = control.settings['default'] || null;
+
+ control.embed();
+ }) );
+ }
// After the control is embedded on the page, invoke the "ready" method.
control.deferred.embedded.done( function () {
// Watch for changes to the section state
inject = function ( sectionId ) {
var parentContainer;
- if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the frontend
+ if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the front end.
return;
}
// Wait for the section to be registered
control.pausePlayer();
});
- // Re-render whenever the control's setting changes.
- control.setting.bind( function () { control.renderContent(); } );
+ control.setting.bind( function( value ) {
+
+ // Send attachment information to the preview for possible use in `postMessage` transport.
+ wp.media.attachment( value ).fetch().done( function() {
+ wp.customize.previewer.send( control.setting.id + '-attachment-data', this.attributes );
+ } );
+
+ // Re-render whenever the control's setting changes.
+ control.renderContent();
+ } );
},
pausePlayer: function () {
xInit = parseInt( control.params.width, 10 ),
yInit = parseInt( control.params.height, 10 ),
ratio = xInit / yInit,
- xImg = realWidth,
- yImg = realHeight,
+ xImg = xInit,
+ yImg = yInit,
x1, y1, imgSelectOptions;
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
- if ( xImg / yImg > ratio ) {
- yInit = yImg;
+ if ( realWidth / realHeight > ratio ) {
+ yInit = realHeight;
xInit = yInit * ratio;
} else {
- xInit = xImg;
+ xInit = realWidth;
yInit = xInit / ratio;
}
- x1 = ( xImg - xInit ) / 2;
- y1 = ( yImg - yInit ) / 2;
+ x1 = ( realWidth - xInit ) / 2;
+ y1 = ( realHeight - yInit ) / 2;
imgSelectOptions = {
handles: true,
persistent: true,
imageWidth: realWidth,
imageHeight: realHeight,
+ minWidth: xImg > xInit ? xInit : xImg,
+ minHeight: yImg > yInit ? yInit : yImg,
x1: x1,
y1: y1,
x2: xInit + x1,
if ( flexHeight === false && flexWidth === false ) {
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
}
- if ( flexHeight === false ) {
- imgSelectOptions.maxHeight = yInit;
+
+ if ( true === flexHeight ) {
+ delete imgSelectOptions.minHeight;
+ imgSelectOptions.maxWidth = realWidth;
}
- if ( flexWidth === false ) {
- imgSelectOptions.maxWidth = xInit;
+
+ if ( true === flexWidth ) {
+ delete imgSelectOptions.minWidth;
+ imgSelectOptions.maxHeight = realHeight;
}
return imgSelectOptions;
api.HeaderTool.UploadsList,
api.HeaderTool.DefaultsList
]);
+
+ // Ensure custom-header-crop Ajax requests bootstrap the Customizer to activate the previewed theme.
+ wp.media.controller.Cropper.prototype.defaults.doCropArgs.wp_customize = 'on';
+ wp.media.controller.Cropper.prototype.defaults.doCropArgs.theme = api.settings.theme.stylesheet;
},
/**
iframe = $( '<iframe />', { 'src': self.previewUrl(), 'title': api.l10n.previewIframeTitle } ).hide();
iframe.appendTo( self.container );
- iframe.load( function() {
+ iframe.on( 'load', function() {
self.triedLogin = true;
iframe.remove();
// Limit the URL to internal, front-end links.
//
- // If the frontend and the admin are served from the same domain, load the
+ // If the front end and the admin are served from the same domain, load the
// preview over ssl if the Customizer is being loaded over ssl. This avoids
- // insecure content warnings. This is not attempted if the admin and frontend
- // are on different domains to avoid the case where the frontend doesn't have
+ // insecure content warnings. This is not attempted if the admin and front end
+ // are on different domains to avoid the case where the front end doesn't have
// ssl certs.
this.add( 'previewUrl', params.previewUrl ).setter( function( to ) {
overlay = body.children( '.wp-full-overlay' ),
title = $( '#customize-info .panel-title.site-title' ),
closeBtn = $( '.customize-controls-close' ),
- saveBtn = $( '#save' );
+ saveBtn = $( '#save' ),
+ footerActions = $( '#customize-footer-actions' );
// Prevent the form from saving when enter is pressed on an input or select element.
$('#customize-controls').on( 'keydown', function( e ) {
value._dirty = false;
} );
+ api.previewer.send( 'saved', response );
+
api.trigger( 'saved', response );
} );
};
api.bind( 'nonce-refresh', function( nonce ) {
$.extend( api.settings.nonce, nonce );
$.extend( api.previewer.nonce, nonce );
+ api.previewer.send( 'nonce-refresh', nonce );
});
// Create Settings
});
// Focus the autofocused element
- _.each( [ 'panel', 'section', 'control' ], function ( type ) {
- var instance, id = api.settings.autofocus[ type ];
- if ( id && api[ type ]( id ) ) {
- instance = api[ type ]( id );
- // Wait until the element is embedded in the DOM
- instance.deferred.embedded.done( function () {
- // Wait until the preview has activated and so active panels, sections, controls have been set
- api.previewer.deferred.active.done( function () {
+ _.each( [ 'panel', 'section', 'control' ], function( type ) {
+ var id = api.settings.autofocus[ type ];
+ if ( ! id ) {
+ return;
+ }
+
+ /*
+ * Defer focus until:
+ * 1. The panel, section, or control exists (especially for dynamically-created ones).
+ * 2. The instance is embedded in the document (and so is focusable).
+ * 3. The preview has finished loading so that the active states have been set.
+ */
+ api[ type ]( id, function( instance ) {
+ instance.deferred.embedded.done( function() {
+ api.previewer.deferred.active.done( function() {
instance.focus();
});
});
- }
+ });
});
/**
event.preventDefault();
});
+ // Previewed device bindings.
+ api.previewedDevice = new api.Value();
+
+ // Set the default device.
+ api.bind( 'ready', function() {
+ _.find( api.settings.previewableDevices, function( value, key ) {
+ if ( true === value['default'] ) {
+ api.previewedDevice.set( key );
+ return true;
+ }
+ } );
+ } );
+
+ // Set the toggled device.
+ footerActions.find( '.devices button' ).on( 'click', function( event ) {
+ api.previewedDevice.set( $( event.currentTarget ).data( 'device' ) );
+ });
+
+ // Bind device changes.
+ api.previewedDevice.bind( function( newDevice ) {
+ var overlay = $( '.wp-full-overlay' ),
+ devices = '';
+
+ footerActions.find( '.devices button' )
+ .removeClass( 'active' )
+ .attr( 'aria-pressed', false );
+
+ footerActions.find( '.devices .preview-' + newDevice )
+ .addClass( 'active' )
+ .attr( 'aria-pressed', true );
+
+ $.each( api.settings.previewableDevices, function( device ) {
+ devices += ' preview-' + device;
+ } );
+
+ overlay
+ .removeClass( devices )
+ .addClass( 'preview-' + newDevice );
+ } );
+
// Bind site title display to the corresponding field.
if ( title.length ) {
- $( '#customize-control-blogname input' ).on( 'input', function() {
- title.text( this.value );
+ api( 'blogname', function( setting ) {
+ var updateTitle = function() {
+ title.text( $.trim( setting() ) || api.l10n.untitledBlogName );
+ };
+ setting.bind( updateTitle );
+ updateTitle();
} );
}
});
});
+ // Focus on the control that is associated with the given setting.
+ api.previewer.bind( 'focus-control-for-setting', function( settingId ) {
+ var matchedControl;
+ api.control.each( function( control ) {
+ var settingIds = _.pluck( control.settings, 'id' );
+ if ( -1 !== _.indexOf( settingIds, settingId ) ) {
+ matchedControl = control;
+ }
+ } );
+
+ if ( matchedControl ) {
+ matchedControl.focus();
+ }
+ } );
+
+ // Refresh the preview when it requests.
+ api.previewer.bind( 'refresh', function() {
+ api.previewer.refresh();
+ });
+
api.trigger( 'ready' );
// Make sure left column gets focus