1 /* global jQuery, JSON, _customizePartialRefreshExports, console */
3 wp.customize.selectiveRefresh = ( function( $, api ) {
5 var self, Partial, Placement;
9 editShortcutVisibility: new api.Value(),
20 _.extend( self, api.Events );
23 * A Customizer Partial.
25 * A partial provides a rendering of one or more settings according to a template.
27 * @see PHP class WP_Customize_Partial.
30 * @augments wp.customize.Class
33 * @param {string} id Unique identifier for the control instance.
34 * @param {object} options Options hash for the control instance.
35 * @param {object} options.params
36 * @param {string} options.params.type Type of partial (e.g. nav_menu, widget, etc)
37 * @param {string} options.params.selector jQuery selector to find the container element in the page.
38 * @param {array} options.params.settings The IDs for the settings the partial relates to.
39 * @param {string} options.params.primarySetting The ID for the primary setting the partial renders.
40 * @param {bool} options.params.fallbackRefresh Whether to refresh the entire preview in case of a partial refresh failure.
42 Partial = self.Partial = api.Class.extend({
51 * @param {string} id - Partial ID.
52 * @param {Object} options
53 * @param {Object} options.params
55 initialize: function( id, options ) {
57 options = options || {};
60 partial.params = _.extend(
65 containerInclusive: false,
66 fallbackRefresh: true // Note this needs to be false in a front-end editing context.
71 partial.deferred = {};
72 partial.deferred.ready = $.Deferred();
74 partial.deferred.ready.done( function() {
86 _.each( partial.placements(), function( placement ) {
87 $( placement.container ).attr( 'title', self.data.l10n.shiftClickToEdit );
88 partial.createEditShortcutForPlacement( placement );
90 $( document ).on( 'click', partial.params.selector, function( e ) {
95 _.each( partial.placements(), function( placement ) {
96 if ( $( placement.container ).is( e.currentTarget ) ) {
97 partial.showControl();
104 * Create and show the edit shortcut for a given partial placement container.
109 * @param {Placement} placement The placement container element.
112 createEditShortcutForPlacement: function( placement ) {
113 var partial = this, $shortcut, $placementContainer, illegalAncestorSelector, illegalContainerSelector;
114 if ( ! placement.container ) {
117 $placementContainer = $( placement.container );
118 illegalAncestorSelector = 'head';
119 illegalContainerSelector = 'area, audio, base, bdi, bdo, br, button, canvas, col, colgroup, command, datalist, embed, head, hr, html, iframe, img, input, keygen, label, link, map, math, menu, meta, noscript, object, optgroup, option, param, progress, rp, rt, ruby, script, select, source, style, svg, table, tbody, textarea, tfoot, thead, title, tr, track, video, wbr';
120 if ( ! $placementContainer.length || $placementContainer.is( illegalContainerSelector ) || $placementContainer.closest( illegalAncestorSelector ).length ) {
123 $shortcut = partial.createEditShortcut();
124 $shortcut.on( 'click', function( event ) {
125 event.preventDefault();
126 event.stopPropagation();
127 partial.showControl();
129 partial.addEditShortcutToPlacement( placement, $shortcut );
133 * Add an edit shortcut to the placement container.
138 * @param {Placement} placement The placement for the partial.
139 * @param {jQuery} $editShortcut The shortcut element as a jQuery object.
142 addEditShortcutToPlacement: function( placement, $editShortcut ) {
143 var $placementContainer = $( placement.container );
144 $placementContainer.prepend( $editShortcut );
145 if ( ! $placementContainer.is( ':visible' ) || 'none' === $placementContainer.css( 'display' ) ) {
146 $editShortcut.addClass( 'customize-partial-edit-shortcut-hidden' );
151 * Return the unique class name for the edit shortcut button for this partial.
156 * @return {string} Partial ID converted into a class name for use in shortcut.
158 getEditShortcutClassName: function() {
159 var partial = this, cleanId;
160 cleanId = partial.id.replace( /]/g, '' ).replace( /\[/g, '-' );
161 return 'customize-partial-edit-shortcut-' + cleanId;
165 * Return the appropriate translated string for the edit shortcut button.
170 * @return {string} Tooltip for edit shortcut.
172 getEditShortcutTitle: function() {
173 var partial = this, l10n = self.data.l10n;
174 switch ( partial.getType() ) {
176 return l10n.clickEditWidget;
178 return l10n.clickEditTitle;
179 case 'blogdescription':
180 return l10n.clickEditTitle;
182 return l10n.clickEditMenu;
184 return l10n.clickEditMisc;
189 * Return the type of this partial
191 * Will use `params.type` if set, but otherwise will try to infer type from settingId.
196 * @return {string} Type of partial derived from type param or the related setting ID.
198 getType: function() {
199 var partial = this, settingId;
200 settingId = partial.params.primarySetting || _.first( partial.settings() ) || 'unknown';
201 if ( partial.params.type ) {
202 return partial.params.type;
204 if ( settingId.match( /^nav_menu_instance\[/ ) ) {
207 if ( settingId.match( /^widget_.+\[\d+]$/ ) ) {
214 * Create an edit shortcut button for this partial.
219 * @return {jQuery} The edit shortcut button element.
221 createEditShortcut: function() {
222 var partial = this, shortcutTitle, $buttonContainer, $button, $image;
223 shortcutTitle = partial.getEditShortcutTitle();
224 $buttonContainer = $( '<span>', {
225 'class': 'customize-partial-edit-shortcut ' + partial.getEditShortcutClassName()
227 $button = $( '<button>', {
228 'aria-label': shortcutTitle,
229 'title': shortcutTitle,
230 'class': 'customize-partial-edit-shortcut-button'
232 $image = $( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13.89 3.39l2.71 2.72c.46.46.42 1.24.03 1.64l-8.01 8.02-5.56 1.16 1.16-5.58s7.6-7.63 7.99-8.03c.39-.39 1.22-.39 1.68.07zm-2.73 2.79l-5.59 5.61 1.11 1.11 5.54-5.65zm-2.97 8.23l5.58-5.6-1.07-1.08-5.59 5.6z"/></svg>' );
233 $button.append( $image );
234 $buttonContainer.append( $button );
235 return $buttonContainer;
239 * Find all placements for this partial int he document.
243 * @return {Array.<Placement>}
245 placements: function() {
246 var partial = this, selector;
248 selector = partial.params.selector || '';
252 selector += '[data-customize-partial-id="' + partial.id + '"]'; // @todo Consider injecting customize-partial-id-${id} classnames instead.
254 return $( selector ).map( function() {
255 var container = $( this ), context;
257 context = container.data( 'customize-partial-placement-context' );
258 if ( _.isString( context ) && '{' === context.substr( 0, 1 ) ) {
259 throw new Error( 'context JSON parse error' );
262 return new Placement( {
264 container: container,
271 * Get list of setting IDs related to this partial.
277 settings: function() {
279 if ( partial.params.settings && 0 !== partial.params.settings.length ) {
280 return partial.params.settings;
281 } else if ( partial.params.primarySetting ) {
282 return [ partial.params.primarySetting ];
284 return [ partial.id ];
289 * Return whether the setting is related to the partial.
293 * @param {wp.customize.Value|string} setting ID or object for setting.
294 * @return {boolean} Whether the setting is related to the partial.
296 isRelatedSetting: function( setting /*... newValue, oldValue */ ) {
298 if ( _.isString( setting ) ) {
299 setting = api( setting );
304 return -1 !== _.indexOf( partial.settings(), setting.id );
308 * Show the control to modify this partial's setting(s).
310 * This may be overridden for inline editing.
314 showControl: function() {
315 var partial = this, settingId = partial.params.primarySetting;
317 settingId = _.first( partial.settings() );
319 if ( partial.getType() === 'nav_menu' ) {
320 if ( partial.params.navMenuArgs.theme_location ) {
321 settingId = 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']';
322 } else if ( partial.params.navMenuArgs.menu ) {
323 settingId = 'nav_menu[' + String( partial.params.navMenuArgs.menu ) + ']';
326 api.preview.send( 'focus-control-for-setting', settingId );
330 * Prepare container for selective refresh.
334 * @param {Placement} placement
336 preparePlacement: function( placement ) {
337 $( placement.container ).addClass( 'customize-partial-refreshing' );
341 * Reference to the pending promise returned from self.requestPartial().
346 _pendingRefreshPromise: null,
349 * Request the new partial and render it into the placements.
353 * @this {wp.customize.selectiveRefresh.Partial}
354 * @return {jQuery.Promise}
356 refresh: function() {
357 var partial = this, refreshPromise;
359 refreshPromise = self.requestPartial( partial );
361 if ( ! partial._pendingRefreshPromise ) {
362 _.each( partial.placements(), function( placement ) {
363 partial.preparePlacement( placement );
366 refreshPromise.done( function( placements ) {
367 _.each( placements, function( placement ) {
368 partial.renderContent( placement );
372 refreshPromise.fail( function( data, placements ) {
373 partial.fallback( data, placements );
376 // Allow new request when this one finishes.
377 partial._pendingRefreshPromise = refreshPromise;
378 refreshPromise.always( function() {
379 partial._pendingRefreshPromise = null;
383 return refreshPromise;
387 * Apply the addedContent in the placement to the document.
389 * Note the placement object will have its container and removedNodes
390 * properties updated.
394 * @param {Placement} placement
395 * @param {Element|jQuery} [placement.container] - This param will be empty if there was no element matching the selector.
396 * @param {string|object|boolean} placement.addedContent - Rendered HTML content, a data object for JS templates to render, or false if no render.
397 * @param {object} [placement.context] - Optional context information about the container.
398 * @returns {boolean} Whether the rendering was successful and the fallback was not invoked.
400 renderContent: function( placement ) {
401 var partial = this, content, newContainerElement;
402 if ( ! placement.container ) {
403 partial.fallback( new Error( 'no_container' ), [ placement ] );
406 placement.container = $( placement.container );
407 if ( false === placement.addedContent ) {
408 partial.fallback( new Error( 'missing_render' ), [ placement ] );
412 // Currently a subclass needs to override renderContent to handle partials returning data object.
413 if ( ! _.isString( placement.addedContent ) ) {
414 partial.fallback( new Error( 'non_string_content' ), [ placement ] );
418 /* jshint ignore:start */
419 self.orginalDocumentWrite = document.write;
420 document.write = function() {
421 throw new Error( self.data.l10n.badDocumentWrite );
423 /* jshint ignore:end */
425 content = placement.addedContent;
426 if ( wp.emoji && wp.emoji.parse && ! $.contains( document.head, placement.container[0] ) ) {
427 content = wp.emoji.parse( content );
430 if ( partial.params.containerInclusive ) {
432 // Note that content may be an empty string, and in this case jQuery will just remove the oldContainer
433 newContainerElement = $( content );
435 // Merge the new context on top of the old context.
436 placement.context = _.extend(
438 newContainerElement.data( 'customize-partial-placement-context' ) || {}
440 newContainerElement.data( 'customize-partial-placement-context', placement.context );
442 placement.removedNodes = placement.container;
443 placement.container = newContainerElement;
444 placement.removedNodes.replaceWith( placement.container );
445 placement.container.attr( 'title', self.data.l10n.shiftClickToEdit );
447 placement.removedNodes = document.createDocumentFragment();
448 while ( placement.container[0].firstChild ) {
449 placement.removedNodes.appendChild( placement.container[0].firstChild );
452 placement.container.html( content );
455 placement.container.removeClass( 'customize-render-content-error' );
457 if ( 'undefined' !== typeof console && console.error ) {
458 console.error( partial.id, error );
461 /* jshint ignore:start */
462 document.write = self.orginalDocumentWrite;
463 self.orginalDocumentWrite = null;
464 /* jshint ignore:end */
466 partial.createEditShortcutForPlacement( placement );
467 placement.container.removeClass( 'customize-partial-refreshing' );
469 // Prevent placement container from being being re-triggered as being rendered among nested partials.
470 placement.container.data( 'customize-partial-content-rendered', true );
473 * Announce when a partial's placement has been rendered so that dynamic elements can be re-built.
475 self.trigger( 'partial-content-rendered', placement );
480 * Handle fail to render partial.
482 * The first argument is either the failing jqXHR or an Error object, and the second argument is the array of containers.
486 fallback: function() {
488 if ( partial.params.fallbackRefresh ) {
489 self.requestFullRefresh();
495 * A Placement for a Partial.
497 * A partial placement is the actual physical representation of a partial for a given context.
498 * It also may have information in relation to how a placement may have just changed.
499 * The placement is conceptually similar to a DOM Range or MutationRecord.
502 * @augments wp.customize.Class
505 self.Placement = Placement = api.Class.extend({
508 * The partial with which the container is associated.
510 * @param {wp.customize.selectiveRefresh.Partial}
515 * DOM element which contains the placement's contents.
517 * This will be null if the startNode and endNode do not point to the same
518 * DOM element, such as in the case of a sidebar partial.
519 * This container element itself will be replaced for partials that
520 * have containerInclusive param defined as true.
525 * DOM node for the initial boundary of the placement.
527 * This will normally be the same as endNode since most placements appear as elements.
528 * This is primarily useful for widget sidebars which do not have intrinsic containers, but
529 * for which an HTML comment is output before to mark the starting position.
534 * DOM node for the terminal boundary of the placement.
536 * This will normally be the same as startNode since most placements appear as elements.
537 * This is primarily useful for widget sidebars which do not have intrinsic containers, but
538 * for which an HTML comment is output before to mark the ending position.
545 * This provides information about the placement which is included in the request
546 * in order to render the partial properly.
553 * The content for the partial when refreshed.
560 * DOM node(s) removed when the partial is refreshed.
562 * If the partial is containerInclusive, then the removedNodes will be
563 * the single Element that was the partial's former placement. If the
564 * partial is not containerInclusive, then the removedNodes will be a
565 * documentFragment containing the nodes removed.
567 * @param {Element|DocumentFragment}
576 * @param {object} args
577 * @param {Partial} args.partial
578 * @param {jQuery|Element} [args.container]
579 * @param {Node} [args.startNode]
580 * @param {Node} [args.endNode]
581 * @param {object} [args.context]
582 * @param {string} [args.addedContent]
583 * @param {jQuery|DocumentFragment} [args.removedNodes]
585 initialize: function( args ) {
586 var placement = this;
588 args = _.extend( {}, args || {} );
589 if ( ! args.partial || ! args.partial.extended( Partial ) ) {
590 throw new Error( 'Missing partial' );
592 args.context = args.context || {};
593 if ( args.container ) {
594 args.container = $( args.container );
597 _.extend( placement, args );
603 * Mapping of type names to Partial constructor subclasses.
607 * @type {Object.<string, wp.customize.selectiveRefresh.Partial>}
609 self.partialConstructor = {};
611 self.partial = new api.Values({ defaultConstructor: Partial });
614 * Get the POST vars for a Customizer preview request.
617 * @see wp.customize.previewer.query()
621 self.getCustomizeQuery = function() {
622 var dirtyCustomized = {};
623 api.each( function( value, key ) {
624 if ( value._dirty ) {
625 dirtyCustomized[ key ] = value();
631 nonce: api.settings.nonce.preview,
632 customize_theme: api.settings.theme.stylesheet,
633 customized: JSON.stringify( dirtyCustomized ),
634 customize_changeset_uuid: api.settings.changeset.uuid
639 * Currently-requested partials and their associated deferreds.
642 * @type {Object<string, { deferred: jQuery.Promise, partial: wp.customize.selectiveRefresh.Partial }>}
644 self._pendingPartialRequests = {};
647 * Timeout ID for the current requesr, or null if no request is current.
650 * @type {number|null}
653 self._debouncedTimeoutId = null;
656 * Current jqXHR for the request to the partials.
659 * @type {jQuery.jqXHR|null}
662 self._currentRequest = null;
665 * Request full page refresh.
667 * When selective refresh is embedded in the context of front-end editing, this request
668 * must fail or else changes will be lost, unless transactions are implemented.
672 self.requestFullRefresh = function() {
673 api.preview.send( 'refresh' );
677 * Request a re-rendering of a partial.
681 * @param {wp.customize.selectiveRefresh.Partial} partial
682 * @return {jQuery.Promise}
684 self.requestPartial = function( partial ) {
687 if ( self._debouncedTimeoutId ) {
688 clearTimeout( self._debouncedTimeoutId );
689 self._debouncedTimeoutId = null;
691 if ( self._currentRequest ) {
692 self._currentRequest.abort();
693 self._currentRequest = null;
696 partialRequest = self._pendingPartialRequests[ partial.id ];
697 if ( ! partialRequest || 'pending' !== partialRequest.deferred.state() ) {
699 deferred: $.Deferred(),
702 self._pendingPartialRequests[ partial.id ] = partialRequest;
705 // Prevent leaking partial into debounced timeout callback.
708 self._debouncedTimeoutId = setTimeout(
710 var data, partialPlacementContexts, partialsPlacements, request;
712 self._debouncedTimeoutId = null;
713 data = self.getCustomizeQuery();
716 * It is key that the containers be fetched exactly at the point of the request being
717 * made, because the containers need to be mapped to responses by array indices.
719 partialsPlacements = {};
721 partialPlacementContexts = {};
723 _.each( self._pendingPartialRequests, function( pending, partialId ) {
724 partialsPlacements[ partialId ] = pending.partial.placements();
725 if ( ! self.partial.has( partialId ) ) {
726 pending.deferred.rejectWith( pending.partial, [ new Error( 'partial_removed' ), partialsPlacements[ partialId ] ] );
729 * Note that this may in fact be an empty array. In that case, it is the responsibility
730 * of the Partial subclass instance to know where to inject the response, or else to
731 * just issue a refresh (default behavior). The data being returned with each container
732 * is the context information that may be needed to render certain partials, such as
733 * the contained sidebar for rendering widgets or what the nav menu args are for a menu.
735 partialPlacementContexts[ partialId ] = _.map( partialsPlacements[ partialId ], function( placement ) {
736 return placement.context || {};
741 data.partials = JSON.stringify( partialPlacementContexts );
742 data[ self.data.renderQueryVar ] = '1';
744 request = self._currentRequest = wp.ajax.send( null, {
746 url: api.settings.url.self
749 request.done( function( data ) {
752 * Announce the data returned from a request to render partials.
754 * The data is filtered on the server via customize_render_partials_response
755 * so plugins can inject data from the server to be utilized
756 * on the client via this event. Plugins may use this filter
757 * to communicate script and style dependencies that need to get
758 * injected into the page to support the rendered partials.
759 * This is similar to the 'saved' event.
761 self.trigger( 'render-partials-response', data );
763 // Relay errors (warnings) captured during rendering and relay to console.
764 if ( data.errors && 'undefined' !== typeof console && console.warn ) {
765 _.each( data.errors, function( error ) {
766 console.warn( error );
771 * Note that data is an array of items that correspond to the array of
772 * containers that were submitted in the request. So we zip up the
773 * array of containers with the array of contents for those containers,
774 * and send them into .
776 _.each( self._pendingPartialRequests, function( pending, partialId ) {
777 var placementsContents;
778 if ( ! _.isArray( data.contents[ partialId ] ) ) {
779 pending.deferred.rejectWith( pending.partial, [ new Error( 'unrecognized_partial' ), partialsPlacements[ partialId ] ] );
781 placementsContents = _.map( data.contents[ partialId ], function( content, i ) {
782 var partialPlacement = partialsPlacements[ partialId ][ i ];
783 if ( partialPlacement ) {
784 partialPlacement.addedContent = content;
786 partialPlacement = new Placement( {
787 partial: pending.partial,
788 addedContent: content
791 return partialPlacement;
793 pending.deferred.resolveWith( pending.partial, [ placementsContents ] );
796 self._pendingPartialRequests = {};
799 request.fail( function( data, statusText ) {
802 * Ignore failures caused by partial.currentRequest.abort()
803 * The pending deferreds will remain in self._pendingPartialRequests
804 * for re-use with the next request.
806 if ( 'abort' === statusText ) {
810 _.each( self._pendingPartialRequests, function( pending, partialId ) {
811 pending.deferred.rejectWith( pending.partial, [ data, partialsPlacements[ partialId ] ] );
813 self._pendingPartialRequests = {};
816 api.settings.timeouts.selectiveRefresh
819 return partialRequest.deferred.promise();
823 * Add partials for any nav menu container elements in the document.
825 * This method may be called multiple times. Containers that already have been
826 * seen will be skipped.
830 * @param {jQuery|HTMLElement} [rootElement]
831 * @param {object} [options]
832 * @param {boolean=true} [options.triggerRendered]
834 self.addPartials = function( rootElement, options ) {
835 var containerElements;
836 if ( ! rootElement ) {
837 rootElement = document.documentElement;
839 rootElement = $( rootElement );
842 triggerRendered: true
847 containerElements = rootElement.find( '[data-customize-partial-id]' );
848 if ( rootElement.is( '[data-customize-partial-id]' ) ) {
849 containerElements = containerElements.add( rootElement );
851 containerElements.each( function() {
852 var containerElement = $( this ), partial, id, Constructor, partialOptions, containerContext;
853 id = containerElement.data( 'customize-partial-id' );
857 containerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
859 partial = self.partial( id );
861 partialOptions = containerElement.data( 'customize-partial-options' ) || {};
862 partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
863 Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
864 partial = new Constructor( id, partialOptions );
865 self.partial.add( partial.id, partial );
869 * Only trigger renders on (nested) partials that have been not been
870 * handled yet. An example where this would apply is a nav menu
871 * embedded inside of a custom menu widget. When the widget's title
872 * is updated, the entire widget will re-render and then the event
873 * will be triggered for the nested nav menu to do any initialization.
875 if ( options.triggerRendered && ! containerElement.data( 'customize-partial-content-rendered' ) ) {
878 * Announce when a partial's nested placement has been re-rendered.
880 self.trigger( 'partial-content-rendered', new Placement( {
882 context: containerContext,
883 container: containerElement
886 containerElement.data( 'customize-partial-content-rendered', true );
890 api.bind( 'preview-ready', function() {
891 var handleSettingChange, watchSettingChange, unwatchSettingChange;
893 _.extend( self.data, _customizePartialRefreshExports );
895 // Create the partial JS models.
896 _.each( self.data.partials, function( data, id ) {
897 var Constructor, partial = self.partial( id );
899 Constructor = self.partialConstructor[ data.type ] || self.Partial;
900 partial = new Constructor( id, { params: data } );
901 self.partial.add( id, partial );
903 _.extend( partial.params, data );
908 * Handle change to a setting.
910 * Note this is largely needed because adding a 'change' event handler to wp.customize
911 * will only include the changed setting object as an argument, not including the
912 * new value or the old value.
915 * @this {wp.customize.Setting}
917 * @param {*|null} newValue New value, or null if the setting was just removed.
918 * @param {*|null} oldValue Old value, or null if the setting was just added.
920 handleSettingChange = function( newValue, oldValue ) {
922 self.partial.each( function( partial ) {
923 if ( partial.isRelatedSetting( setting, newValue, oldValue ) ) {
930 * Trigger the initial change for the added setting, and watch for changes.
933 * @this {wp.customize.Values}
935 * @param {wp.customize.Setting} setting
937 watchSettingChange = function( setting ) {
938 handleSettingChange.call( setting, setting(), null );
939 setting.bind( handleSettingChange );
943 * Trigger the final change for the removed setting, and unwatch for changes.
946 * @this {wp.customize.Values}
948 * @param {wp.customize.Setting} setting
950 unwatchSettingChange = function( setting ) {
951 handleSettingChange.call( setting, null, setting() );
952 setting.unbind( handleSettingChange );
955 api.bind( 'add', watchSettingChange );
956 api.bind( 'remove', unwatchSettingChange );
957 api.each( function( setting ) {
958 setting.bind( handleSettingChange );
961 // Add (dynamic) initial partials that are declared via data-* attributes.
962 self.addPartials( document.documentElement, {
963 triggerRendered: false
966 // Add new dynamic partials when the document changes.
967 if ( 'undefined' !== typeof MutationObserver ) {
968 self.mutationObserver = new MutationObserver( function( mutations ) {
969 _.each( mutations, function( mutation ) {
970 self.addPartials( $( mutation.target ) );
973 self.mutationObserver.observe( document.documentElement, {
980 * Handle rendering of partials.
982 * @param {api.selectiveRefresh.Placement} placement
984 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
985 if ( placement.container ) {
986 self.addPartials( placement.container );
991 * Handle setting validities in partial refresh response.
993 * @param {object} data Response data.
994 * @param {object} data.setting_validities Setting validities.
996 api.selectiveRefresh.bind( 'render-partials-response', function handleSettingValiditiesResponse( data ) {
997 if ( data.setting_validities ) {
998 api.preview.send( 'selective-refresh-setting-validities', data.setting_validities );
1002 api.preview.bind( 'edit-shortcut-visibility', function( visibility ) {
1003 api.selectiveRefresh.editShortcutVisibility.set( visibility );
1005 api.selectiveRefresh.editShortcutVisibility.bind( function( visibility ) {
1006 var body = $( document.body ), shouldAnimateHide;
1008 shouldAnimateHide = ( 'hidden' === visibility && body.hasClass( 'customize-partial-edit-shortcuts-shown' ) && ! body.hasClass( 'customize-partial-edit-shortcuts-hidden' ) );
1009 body.toggleClass( 'customize-partial-edit-shortcuts-hidden', shouldAnimateHide );
1010 body.toggleClass( 'customize-partial-edit-shortcuts-shown', 'visible' === visibility );
1013 api.preview.bind( 'active', function() {
1015 // Make all partials ready.
1016 self.partial.each( function( partial ) {
1017 partial.deferred.ready.resolve();
1020 // Make all partials added henceforth as ready upon add.
1021 self.partial.bind( 'add', function( partial ) {
1022 partial.deferred.ready.resolve();
1029 }( jQuery, wp.customize ) );