From 82441864af51854cc346d5d249bfa5c40673eac0 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 24 Dec 2016 18:38:16 -0800 Subject: [PATCH] Implement WP Core Trac #39389: Scroll widget partial into view when control expanded or setting modified --- js/trac-39389-controls.js | 39 +++++++++++++++ js/trac-39389-preview.js | 88 +++++++++++++++++++++++++++++++++ php/class-js-widgets-plugin.php | 16 ++++++ 3 files changed, 143 insertions(+) create mode 100644 js/trac-39389-controls.js create mode 100644 js/trac-39389-preview.js diff --git a/js/trac-39389-controls.js b/js/trac-39389-controls.js new file mode 100644 index 0000000..9ae9fdb --- /dev/null +++ b/js/trac-39389-controls.js @@ -0,0 +1,39 @@ +/* global wp */ +/* eslint-disable strict */ +/* eslint consistent-this: [ "error", "partial" ] */ + +(function( api ) { + 'use strict'; + + var component = { + + /** + * Init component. + * + * @returns {void} + */ + init: function init() { + api.control.each( component.handleControlAddition ); + api.control.bind( 'add', component.handleControlAddition ); + }, + + /** + * Handle control addition. + * + * @param {wp.customize.Control} control Control. + * @returns {void} + */ + handleControlAddition: function handleControlAddition( control ) { + if ( ! control.extended( api.Widgets.WidgetControl ) ) { + return; + } + control.expanded.bind( function handleControlExpandedChange( isExpanded ) { + if ( isExpanded ) { + api.previewer.send( 'scroll-setting-related-partial-into-view', control.setting.id ); + } + } ); + } + }; + + api.bind( 'ready', component.init ); +} )( wp.customize ); diff --git a/js/trac-39389-preview.js b/js/trac-39389-preview.js new file mode 100644 index 0000000..ceea9f4 --- /dev/null +++ b/js/trac-39389-preview.js @@ -0,0 +1,88 @@ +/* global wp, jQuery */ +/* eslint-disable strict */ +/* eslint consistent-this: [ "error", "partial" ] */ +/* eslint-disable complexity */ + +(function( api, WidgetPartial, $ ) { + 'use strict'; + + var wrappedPreparePlacement, wrappedRenderContent; + + if ( ! WidgetPartial || WidgetPartial.prototype.scrollIntoView ) { + return; + } + + /** + * Scroll a widget placement container into view. + * + * @since 4.8.0 + * + * @param {Placement} [placement] Placement, if not provided then the first found placement will be used. + * @returns {void} + */ + WidgetPartial.prototype.scrollIntoView = function scrollIntoView( placement ) { + var partial = this, container, docViewTop, docViewBottom, elemTop, elemBottom, selectedPlacement; + selectedPlacement = placement || partial.placements()[0]; + if ( ! selectedPlacement ) { + return; + } + container = $( selectedPlacement.container ); + if ( ! container[0] ) { + return; + } + if ( container[0].scrollIntoViewIfNeeded ) { + container[0].scrollIntoViewIfNeeded(); + } else { + + // Props http://stackoverflow.com/a/488073/93579 + docViewTop = $( window ).scrollTop(); + docViewBottom = docViewTop + $( window ).height(); + elemTop = container.offset().top; + elemBottom = elemTop + container.height(); + if ( elemBottom > docViewBottom || elemTop < docViewTop ) { + container[0].scrollIntoView( elemTop < docViewTop ); + } + } + }; + + wrappedPreparePlacement = WidgetPartial.prototype.preparePlacement; + wrappedRenderContent = WidgetPartial.prototype.renderContent; + + /** + * Prepare container for selective refresh. + * + * @inheritDoc + */ + WidgetPartial.prototype.preparePlacement = function preparePlacement( placement ) { + var partial = this; + wrappedPreparePlacement.call( partial, placement ); + partial.scrollIntoView( placement ); + }; + + /** + * Apply the addedContent in the placement to the document. + * + * @inheritDoc + */ + WidgetPartial.prototype.renderContent = function renderContent( placement ) { + var partial = this, retVal; + retVal = wrappedRenderContent.call( partial, placement ); + partial.scrollIntoView( placement ); + return retVal; + }; + + api.bind( 'preview-ready', function() { + api.preview.bind( 'scroll-setting-related-partial-into-view', function( settingId ) { + var relatedPartials = []; + api.selectiveRefresh.partial.each( function partialIterate( iteratedPartial ) { + if ( -1 !== iteratedPartial.params.settings.indexOf( settingId ) && iteratedPartial.scrollIntoView ) { + relatedPartials.push( iteratedPartial ); + } + } ); + if ( relatedPartials[0] ) { + relatedPartials[0].scrollIntoView(); + } + } ); + } ); + +} )( wp.customize, wp.customize.widgetsPreview.WidgetPartial, jQuery ); diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 328ea76..ab23eb9 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -146,6 +146,16 @@ public function register_scripts( WP_Scripts $wp_scripts ) { $deps = array( 'customize-widgets', $this->script_handles['control-form'] ); $wp_scripts->add( $this->script_handles['js-widgets'], $src, $deps, $this->version ); + $this->script_handles['trac-39389-controls'] = 'js-widgets-trac-39389-controls'; + $src = $plugin_dir_url . 'js/trac-39389-controls.js'; + $deps = array( 'customize-widgets' ); + $wp_scripts->add( $this->script_handles['trac-39389-controls'], $src, $deps, $this->version ); + + $this->script_handles['trac-39389-preview'] = 'js-widgets-trac-39389-preview'; + $src = $plugin_dir_url . 'js/trac-39389-preview.js'; + $deps = array( 'customize-preview-widgets' ); + $wp_scripts->add( $this->script_handles['trac-39389-preview'], $src, $deps, $this->version ); + // Register scripts for widgets. foreach ( $wp_widget_factory->widgets as $widget ) { if ( $widget instanceof WP_JS_Widget ) { @@ -231,6 +241,8 @@ function enqueue_pane_scripts() { $widget->enqueue_control_scripts(); } } + + wp_enqueue_script( $this->script_handles['trac-39389-controls'] ); } /** @@ -246,6 +258,10 @@ function enqueue_frontend_scripts() { $widget->enqueue_frontend_scripts(); } } + + if ( is_customize_preview() ) { + wp_enqueue_script( $this->script_handles['trac-39389-preview'] ); + } } /**