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 partial.addEditShortcutToPlacement( placement, $shortcut );
125 $shortcut.on( 'click', function( event ) {
126 event.preventDefault();
127 event.stopPropagation();
128 partial.showControl();
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, menuSlug;
317 settingId = _.first( partial.settings() );
319 if ( partial.getType() === 'nav_menu' ) {
320 menuSlug = partial.params.navMenuArgs.theme_location;
322 settingId = 'nav_menu_locations[' + menuSlug + ']';
325 api.preview.send( 'focus-control-for-setting', settingId );
329 * Prepare container for selective refresh.
333 * @param {Placement} placement
335 preparePlacement: function( placement ) {
336 $( placement.container ).addClass( 'customize-partial-refreshing' );
340 * Reference to the pending promise returned from self.requestPartial().
345 _pendingRefreshPromise: null,
348 * Request the new partial and render it into the placements.
352 * @this {wp.customize.selectiveRefresh.Partial}
353 * @return {jQuery.Promise}
355 refresh: function() {
356 var partial = this, refreshPromise;
358 refreshPromise = self.requestPartial( partial );
360 if ( ! partial._pendingRefreshPromise ) {
361 _.each( partial.placements(), function( placement ) {
362 partial.preparePlacement( placement );
365 refreshPromise.done( function( placements ) {
366 _.each( placements, function( placement ) {
367 partial.renderContent( placement );
371 refreshPromise.fail( function( data, placements ) {
372 partial.fallback( data, placements );
375 // Allow new request when this one finishes.
376 partial._pendingRefreshPromise = refreshPromise;
377 refreshPromise.always( function() {
378 partial._pendingRefreshPromise = null;
382 return refreshPromise;
386 * Apply the addedContent in the placement to the document.
388 * Note the placement object will have its container and removedNodes
389 * properties updated.
393 * @param {Placement} placement
394 * @param {Element|jQuery} [placement.container] - This param will be empty if there was no element matching the selector.
395 * @param {string|object|boolean} placement.addedContent - Rendered HTML content, a data object for JS templates to render, or false if no render.
396 * @param {object} [placement.context] - Optional context information about the container.
397 * @returns {boolean} Whether the rendering was successful and the fallback was not invoked.
399 renderContent: function( placement ) {
400 var partial = this, content, newContainerElement;
401 if ( ! placement.container ) {
402 partial.fallback( new Error( 'no_container' ), [ placement ] );
405 placement.container = $( placement.container );
406 if ( false === placement.addedContent ) {
407 partial.fallback( new Error( 'missing_render' ), [ placement ] );
411 // Currently a subclass needs to override renderContent to handle partials returning data object.
412 if ( ! _.isString( placement.addedContent ) ) {
413 partial.fallback( new Error( 'non_string_content' ), [ placement ] );
417 /* jshint ignore:start */
418 self.orginalDocumentWrite = document.write;
419 document.write = function() {
420 throw new Error( self.data.l10n.badDocumentWrite );
422 /* jshint ignore:end */
424 content = placement.addedContent;
425 if ( wp.emoji && wp.emoji.parse && ! $.contains( document.head, placement.container[0] ) ) {
426 content = wp.emoji.parse( content );
429 if ( partial.params.containerInclusive ) {
431 // Note that content may be an empty string, and in this case jQuery will just remove the oldContainer
432 newContainerElement = $( content );
434 // Merge the new context on top of the old context.
435 placement.context = _.extend(
437 newContainerElement.data( 'customize-partial-placement-context' ) || {}
439 newContainerElement.data( 'customize-partial-placement-context', placement.context );
441 placement.removedNodes = placement.container;
442 placement.container = newContainerElement;
443 placement.removedNodes.replaceWith( placement.container );
444 placement.container.attr( 'title', self.data.l10n.shiftClickToEdit );
446 placement.removedNodes = document.createDocumentFragment();
447 while ( placement.container[0].firstChild ) {
448 placement.removedNodes.appendChild( placement.container[0].firstChild );
451 placement.container.html( content );
454 placement.container.removeClass( 'customize-render-content-error' );
456 if ( 'undefined' !== typeof console && console.error ) {
457 console.error( partial.id, error );
460 /* jshint ignore:start */
461 document.write = self.orginalDocumentWrite;
462 self.orginalDocumentWrite = null;
463 /* jshint ignore:end */
465 partial.createEditShortcutForPlacement( placement );
466 placement.container.removeClass( 'customize-partial-refreshing' );
468 // Prevent placement container from being being re-triggered as being rendered among nested partials.
469 placement.container.data( 'customize-partial-content-rendered', true );
472 * Announce when a partial's placement has been rendered so that dynamic elements can be re-built.
474 self.trigger( 'partial-content-rendered', placement );
479 * Handle fail to render partial.
481 * The first argument is either the failing jqXHR or an Error object, and the second argument is the array of containers.
485 fallback: function() {
487 if ( partial.params.fallbackRefresh ) {
488 self.requestFullRefresh();
494 * A Placement for a Partial.
496 * A partial placement is the actual physical representation of a partial for a given context.
497 * It also may have information in relation to how a placement may have just changed.
498 * The placement is conceptually similar to a DOM Range or MutationRecord.
501 * @augments wp.customize.Class
504 self.Placement = Placement = api.Class.extend({
507 * The partial with which the container is associated.
509 * @param {wp.customize.selectiveRefresh.Partial}
514 * DOM element which contains the placement's contents.
516 * This will be null if the startNode and endNode do not point to the same
517 * DOM element, such as in the case of a sidebar partial.
518 * This container element itself will be replaced for partials that
519 * have containerInclusive param defined as true.
524 * DOM node for the initial boundary of the placement.
526 * This will normally be the same as endNode since most placements appear as elements.
527 * This is primarily useful for widget sidebars which do not have intrinsic containers, but
528 * for which an HTML comment is output before to mark the starting position.
533 * DOM node for the terminal boundary of the placement.
535 * This will normally be the same as startNode since most placements appear as elements.
536 * This is primarily useful for widget sidebars which do not have intrinsic containers, but
537 * for which an HTML comment is output before to mark the ending position.
544 * This provides information about the placement which is included in the request
545 * in order to render the partial properly.
552 * The content for the partial when refreshed.
559 * DOM node(s) removed when the partial is refreshed.
561 * If the partial is containerInclusive, then the removedNodes will be
562 * the single Element that was the partial's former placement. If the
563 * partial is not containerInclusive, then the removedNodes will be a
564 * documentFragment containing the nodes removed.
566 * @param {Element|DocumentFragment}
575 * @param {object} args
576 * @param {Partial} args.partial
577 * @param {jQuery|Element} [args.container]
578 * @param {Node} [args.startNode]
579 * @param {Node} [args.endNode]
580 * @param {object} [args.context]
581 * @param {string} [args.addedContent]
582 * @param {jQuery|DocumentFragment} [args.removedNodes]
584 initialize: function( args ) {
585 var placement = this;
587 args = _.extend( {}, args || {} );
588 if ( ! args.partial || ! args.partial.extended( Partial ) ) {
589 throw new Error( 'Missing partial' );
591 args.context = args.context || {};
592 if ( args.container ) {
593 args.container = $( args.container );
596 _.extend( placement, args );
602 * Mapping of type names to Partial constructor subclasses.
606 * @type {Object.<string, wp.customize.selectiveRefresh.Partial>}
608 self.partialConstructor = {};
610 self.partial = new api.Values({ defaultConstructor: Partial });
613 * Get the POST vars for a Customizer preview request.
616 * @see wp.customize.previewer.query()
620 self.getCustomizeQuery = function() {
621 var dirtyCustomized = {};
622 api.each( function( value, key ) {
623 if ( value._dirty ) {
624 dirtyCustomized[ key ] = value();
630 nonce: api.settings.nonce.preview,
631 customize_theme: api.settings.theme.stylesheet,
632 customized: JSON.stringify( dirtyCustomized ),
633 customize_changeset_uuid: api.settings.changeset.uuid
638 * Currently-requested partials and their associated deferreds.
641 * @type {Object<string, { deferred: jQuery.Promise, partial: wp.customize.selectiveRefresh.Partial }>}
643 self._pendingPartialRequests = {};
646 * Timeout ID for the current requesr, or null if no request is current.
649 * @type {number|null}
652 self._debouncedTimeoutId = null;
655 * Current jqXHR for the request to the partials.
658 * @type {jQuery.jqXHR|null}
661 self._currentRequest = null;
664 * Request full page refresh.
666 * When selective refresh is embedded in the context of front-end editing, this request
667 * must fail or else changes will be lost, unless transactions are implemented.
671 self.requestFullRefresh = function() {
672 api.preview.send( 'refresh' );
676 * Request a re-rendering of a partial.
680 * @param {wp.customize.selectiveRefresh.Partial} partial
681 * @return {jQuery.Promise}
683 self.requestPartial = function( partial ) {
686 if ( self._debouncedTimeoutId ) {
687 clearTimeout( self._debouncedTimeoutId );
688 self._debouncedTimeoutId = null;
690 if ( self._currentRequest ) {
691 self._currentRequest.abort();
692 self._currentRequest = null;
695 partialRequest = self._pendingPartialRequests[ partial.id ];
696 if ( ! partialRequest || 'pending' !== partialRequest.deferred.state() ) {
698 deferred: $.Deferred(),
701 self._pendingPartialRequests[ partial.id ] = partialRequest;
704 // Prevent leaking partial into debounced timeout callback.
707 self._debouncedTimeoutId = setTimeout(
709 var data, partialPlacementContexts, partialsPlacements, request;
711 self._debouncedTimeoutId = null;
712 data = self.getCustomizeQuery();
715 * It is key that the containers be fetched exactly at the point of the request being
716 * made, because the containers need to be mapped to responses by array indices.
718 partialsPlacements = {};
720 partialPlacementContexts = {};
722 _.each( self._pendingPartialRequests, function( pending, partialId ) {
723 partialsPlacements[ partialId ] = pending.partial.placements();
724 if ( ! self.partial.has( partialId ) ) {
725 pending.deferred.rejectWith( pending.partial, [ new Error( 'partial_removed' ), partialsPlacements[ partialId ] ] );
728 * Note that this may in fact be an empty array. In that case, it is the responsibility
729 * of the Partial subclass instance to know where to inject the response, or else to
730 * just issue a refresh (default behavior). The data being returned with each container
731 * is the context information that may be needed to render certain partials, such as
732 * the contained sidebar for rendering widgets or what the nav menu args are for a menu.
734 partialPlacementContexts[ partialId ] = _.map( partialsPlacements[ partialId ], function( placement ) {
735 return placement.context || {};
740 data.partials = JSON.stringify( partialPlacementContexts );
741 data[ self.data.renderQueryVar ] = '1';
743 request = self._currentRequest = wp.ajax.send( null, {
745 url: api.settings.url.self
748 request.done( function( data ) {
751 * Announce the data returned from a request to render partials.
753 * The data is filtered on the server via customize_render_partials_response
754 * so plugins can inject data from the server to be utilized
755 * on the client via this event. Plugins may use this filter
756 * to communicate script and style dependencies that need to get
757 * injected into the page to support the rendered partials.
758 * This is similar to the 'saved' event.
760 self.trigger( 'render-partials-response', data );
762 // Relay errors (warnings) captured during rendering and relay to console.
763 if ( data.errors && 'undefined' !== typeof console && console.warn ) {
764 _.each( data.errors, function( error ) {
765 console.warn( error );
770 * Note that data is an array of items that correspond to the array of
771 * containers that were submitted in the request. So we zip up the
772 * array of containers with the array of contents for those containers,
773 * and send them into .
775 _.each( self._pendingPartialRequests, function( pending, partialId ) {
776 var placementsContents;
777 if ( ! _.isArray( data.contents[ partialId ] ) ) {
778 pending.deferred.rejectWith( pending.partial, [ new Error( 'unrecognized_partial' ), partialsPlacements[ partialId ] ] );
780 placementsContents = _.map( data.contents[ partialId ], function( content, i ) {
781 var partialPlacement = partialsPlacements[ partialId ][ i ];
782 if ( partialPlacement ) {
783 partialPlacement.addedContent = content;
785 partialPlacement = new Placement( {
786 partial: pending.partial,
787 addedContent: content
790 return partialPlacement;
792 pending.deferred.resolveWith( pending.partial, [ placementsContents ] );
795 self._pendingPartialRequests = {};
798 request.fail( function( data, statusText ) {
801 * Ignore failures caused by partial.currentRequest.abort()
802 * The pending deferreds will remain in self._pendingPartialRequests
803 * for re-use with the next request.
805 if ( 'abort' === statusText ) {
809 _.each( self._pendingPartialRequests, function( pending, partialId ) {
810 pending.deferred.rejectWith( pending.partial, [ data, partialsPlacements[ partialId ] ] );
812 self._pendingPartialRequests = {};
815 api.settings.timeouts.selectiveRefresh
818 return partialRequest.deferred.promise();
822 * Add partials for any nav menu container elements in the document.
824 * This method may be called multiple times. Containers that already have been
825 * seen will be skipped.
829 * @param {jQuery|HTMLElement} [rootElement]
830 * @param {object} [options]
831 * @param {boolean=true} [options.triggerRendered]
833 self.addPartials = function( rootElement, options ) {
834 var containerElements;
835 if ( ! rootElement ) {
836 rootElement = document.documentElement;
838 rootElement = $( rootElement );
841 triggerRendered: true
846 containerElements = rootElement.find( '[data-customize-partial-id]' );
847 if ( rootElement.is( '[data-customize-partial-id]' ) ) {
848 containerElements = containerElements.add( rootElement );
850 containerElements.each( function() {
851 var containerElement = $( this ), partial, id, Constructor, partialOptions, containerContext;
852 id = containerElement.data( 'customize-partial-id' );
856 containerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
858 partial = self.partial( id );
860 partialOptions = containerElement.data( 'customize-partial-options' ) || {};
861 partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
862 Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
863 partial = new Constructor( id, partialOptions );
864 self.partial.add( partial.id, partial );
868 * Only trigger renders on (nested) partials that have been not been
869 * handled yet. An example where this would apply is a nav menu
870 * embedded inside of a custom menu widget. When the widget's title
871 * is updated, the entire widget will re-render and then the event
872 * will be triggered for the nested nav menu to do any initialization.
874 if ( options.triggerRendered && ! containerElement.data( 'customize-partial-content-rendered' ) ) {
877 * Announce when a partial's nested placement has been re-rendered.
879 self.trigger( 'partial-content-rendered', new Placement( {
881 context: containerContext,
882 container: containerElement
885 containerElement.data( 'customize-partial-content-rendered', true );
889 api.bind( 'preview-ready', function() {
890 var handleSettingChange, watchSettingChange, unwatchSettingChange;
892 _.extend( self.data, _customizePartialRefreshExports );
894 // Create the partial JS models.
895 _.each( self.data.partials, function( data, id ) {
896 var Constructor, partial = self.partial( id );
898 Constructor = self.partialConstructor[ data.type ] || self.Partial;
899 partial = new Constructor( id, { params: data } );
900 self.partial.add( id, partial );
902 _.extend( partial.params, data );
907 * Handle change to a setting.
909 * Note this is largely needed because adding a 'change' event handler to wp.customize
910 * will only include the changed setting object as an argument, not including the
911 * new value or the old value.
914 * @this {wp.customize.Setting}
916 * @param {*|null} newValue New value, or null if the setting was just removed.
917 * @param {*|null} oldValue Old value, or null if the setting was just added.
919 handleSettingChange = function( newValue, oldValue ) {
921 self.partial.each( function( partial ) {
922 if ( partial.isRelatedSetting( setting, newValue, oldValue ) ) {
929 * Trigger the initial change for the added setting, and watch for changes.
932 * @this {wp.customize.Values}
934 * @param {wp.customize.Setting} setting
936 watchSettingChange = function( setting ) {
937 handleSettingChange.call( setting, setting(), null );
938 setting.bind( handleSettingChange );
942 * Trigger the final change for the removed setting, and unwatch for changes.
945 * @this {wp.customize.Values}
947 * @param {wp.customize.Setting} setting
949 unwatchSettingChange = function( setting ) {
950 handleSettingChange.call( setting, null, setting() );
951 setting.unbind( handleSettingChange );
954 api.bind( 'add', watchSettingChange );
955 api.bind( 'remove', unwatchSettingChange );
956 api.each( function( setting ) {
957 setting.bind( handleSettingChange );
960 // Add (dynamic) initial partials that are declared via data-* attributes.
961 self.addPartials( document.documentElement, {
962 triggerRendered: false
965 // Add new dynamic partials when the document changes.
966 if ( 'undefined' !== typeof MutationObserver ) {
967 self.mutationObserver = new MutationObserver( function( mutations ) {
968 _.each( mutations, function( mutation ) {
969 self.addPartials( $( mutation.target ) );
972 self.mutationObserver.observe( document.documentElement, {
979 * Handle rendering of partials.
981 * @param {api.selectiveRefresh.Placement} placement
983 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
984 if ( placement.container ) {
985 self.addPartials( placement.container );
990 * Handle setting validities in partial refresh response.
992 * @param {object} data Response data.
993 * @param {object} data.setting_validities Setting validities.
995 api.selectiveRefresh.bind( 'render-partials-response', function handleSettingValiditiesResponse( data ) {
996 if ( data.setting_validities ) {
997 api.preview.send( 'selective-refresh-setting-validities', data.setting_validities );
1001 api.preview.bind( 'edit-shortcut-visibility', function( visibility ) {
1002 api.selectiveRefresh.editShortcutVisibility.set( visibility );
1004 api.selectiveRefresh.editShortcutVisibility.bind( function( visibility ) {
1005 var body = $( document.body ), shouldAnimateHide;
1007 shouldAnimateHide = ( 'hidden' === visibility && body.hasClass( 'customize-partial-edit-shortcuts-shown' ) && ! body.hasClass( 'customize-partial-edit-shortcuts-hidden' ) );
1008 body.toggleClass( 'customize-partial-edit-shortcuts-hidden', shouldAnimateHide );
1009 body.toggleClass( 'customize-partial-edit-shortcuts-shown', 'visible' === visibility );
1012 api.preview.bind( 'active', function() {
1014 // Make all partials ready.
1015 self.partial.each( function( partial ) {
1016 partial.deferred.ready.resolve();
1019 // Make all partials added henceforth as ready upon add.
1020 self.partial.bind( 'add', function( partial ) {
1021 partial.deferred.ready.resolve();
1028 }( jQuery, wp.customize ) );