jenkins-bot has submitted this change and it was merged.
Change subject: Upstream `isElementInViewport` from MobileFrontend
......................................................................
Upstream `isElementInViewport` from MobileFrontend
As a useful utility function, we've copied this method several times
across multiple extensions, which is a pretty good sign it should
actually live in core.
Changes:
* Add `mediawiki.viewport` module
* Rewrite method to be more robust and accept any viewport
* Add `mw.viewport` to jsduck categories file
* Add method for checking if an element is close to the viewport
* Add unit tests
Bug: T124317
Change-Id: I38eec4f1e568f51e7e212b2b3f10b8da8d36f316
---
M maintenance/jsduck/categories.json
M resources/Resources.php
A resources/src/mediawiki/mediawiki.viewport.js
M tests/qunit/QUnitTestResources.php
A tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js
5 files changed, 187 insertions(+), 1 deletion(-)
Approvals:
Jdlrobson: Looks good to me, approved
jenkins-bot: Verified
diff --git a/maintenance/jsduck/categories.json
b/maintenance/jsduck/categories.json
index 41b56f6..d9e2c50 100644
--- a/maintenance/jsduck/categories.json
+++ b/maintenance/jsduck/categories.json
@@ -32,7 +32,8 @@
"mw.util",
"mw.plugin.*",
"mw.cookie",
- "mw.experiments"
+ "mw.experiments",
+ "mw.viewport"
]
},
{
diff --git a/resources/Resources.php b/resources/Resources.php
index 1179a9a..8b6b559 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -1334,6 +1334,11 @@
'position' => 'top', // For $wgPreloadJavaScriptMwUtil
'targets' => [ 'desktop', 'mobile' ],
],
+ 'mediawiki.viewport' => [
+ 'scripts' => 'resources/src/mediawiki/mediawiki.viewport.js',
+ 'position' => 'top',
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'mediawiki.checkboxtoggle' => [
'scripts' =>
'resources/src/mediawiki/mediawiki.checkboxtoggle.js',
'position' => 'top',
diff --git a/resources/src/mediawiki/mediawiki.viewport.js
b/resources/src/mediawiki/mediawiki.viewport.js
new file mode 100644
index 0000000..aa9dd05
--- /dev/null
+++ b/resources/src/mediawiki/mediawiki.viewport.js
@@ -0,0 +1,89 @@
+( function ( mw, $ ) {
+ 'use strict';
+
+ /**
+ * Utility library for viewport-related functions
+ *
+ * Notable references:
+ * - https://github.com/tuupola/jquery_lazyload
+ * - https://github.com/luis-almeida/unveil
+ *
+ * @class mw.viewport
+ * @singleton
+ */
+ var viewport = {
+
+ /**
+ * This is a private method pulled inside the module for
testing purposes.
+ *
+ * @ignore
+ * @private
+ */
+ makeViewportFromWindow: function () {
+ var $window = $( window ),
+ scrollTop = $window.scrollTop(),
+ scrollLeft = $window.scrollLeft();
+
+ return {
+ top: scrollTop,
+ left: scrollLeft,
+ right: scrollLeft + $window.width(),
+ bottom: ( window.innerHeight ?
window.innerHeight : $window.height() ) + scrollTop
+ };
+ },
+
+ /**
+ * Check if any part of a given element is in a given viewport
+ *
+ * @method
+ * @param {HTMLElement} el Element that's being tested
+ * @param {Object} [rectangle] Viewport to test against;
structured as such:
+ *
+ * var rectangle = {
+ * top: topEdge,
+ * left: leftEdge,
+ * right: rightEdge,
+ * bottom: bottomEdge
+ * }
+ * Defaults to viewport made from `window`.
+ *
+ * @return {boolean}
+ */
+ isElementInViewport: function ( el, rectangle ) {
+ var elRect = el.getBoundingClientRect(),
+ viewport = rectangle ||
this.makeViewportFromWindow();
+
+ return (
+ ( viewport.bottom >= elRect.top ) &&
+ ( viewport.right >= elRect.left ) &&
+ ( viewport.top <= elRect.top + elRect.height )
&&
+ ( viewport.left <= elRect.left + elRect.width )
+ );
+ },
+
+ /**
+ * Check if an element is a given threshold away in any
direction from a given viewport
+ *
+ * @method
+ * @param {HTMLElement} el Element that's being tested
+ * @param {number} [threshold] Pixel distance considered
"close". Must be a positive number.
+ * Defaults to 50.
+ * @param {Object} [rectangle] Viewport to test against.
+ * Defaults to viewport made from `window`.
+ * @return {boolean}
+ */
+ isElementCloseToViewport: function ( el, threshold, rectangle )
{
+ var viewport = rectangle ? $.extend( {}, rectangle ) :
this.makeViewportFromWindow();
+ threshold = threshold || 50 ;
+
+ viewport.top -= threshold;
+ viewport.left -= threshold;
+ viewport.right += threshold;
+ viewport.bottom += threshold;
+ return this.isElementInViewport( el, viewport );
+ }
+
+ };
+
+ mw.viewport = viewport;
+}( mediaWiki, jQuery ) );
diff --git a/tests/qunit/QUnitTestResources.php
b/tests/qunit/QUnitTestResources.php
index a2dead6..310268f 100644
--- a/tests/qunit/QUnitTestResources.php
+++ b/tests/qunit/QUnitTestResources.php
@@ -80,6 +80,7 @@
'tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js',
+
'tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js',
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js',
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js',
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js',
@@ -130,6 +131,7 @@
'mediawiki.template.mustache',
'mediawiki.template',
'mediawiki.util',
+ 'mediawiki.viewport',
'mediawiki.special.recentchanges',
'mediawiki.language',
'mediawiki.cldr',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js
b/tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js
new file mode 100644
index 0000000..61391d8
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js
@@ -0,0 +1,89 @@
+( function ( mw, $ ) {
+
+ // Simulate square element with 20px long edges placed at (20, 20) on
the page
+ var
+ DEFAULT_VIEWPORT = {
+ top: 0,
+ left: 0,
+ right: 100,
+ bottom: 100
+ };
+
+ QUnit.module( 'mediawiki.viewport', QUnit.newMwEnvironment( {
+ setup: function () {
+ this.el = $( '<div />' )
+ .appendTo( '#qunit-fixture' )
+ .width( 20 )
+ .height( 20 )
+ .offset( {
+ top: 20,
+ left: 20
+ } )
+ .get( 0 );
+ this.sandbox.stub( mw.viewport,
'makeViewportFromWindow' )
+ .returns( DEFAULT_VIEWPORT );
+ }
+ } ) );
+
+ QUnit.test( 'isElementInViewport', 6, function ( assert ) {
+ var viewport = $.extend( {}, DEFAULT_VIEWPORT );
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when the element is fully
enclosed in the viewport' );
+
+ viewport.right = 20;
+ viewport.bottom = 20;
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when only the top-left of the
element is within the viewport' );
+
+ viewport.top = 40;
+ viewport.left = 40;
+ viewport.right = 50;
+ viewport.bottom = 50;
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when only the bottom-right is
within the viewport' );
+
+ viewport.top = 30;
+ viewport.left = 30;
+ viewport.right = 35;
+ viewport.bottom = 35;
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when the element encapsulates
the viewport' );
+
+ viewport.top = 0;
+ viewport.left = 0;
+ viewport.right = 19;
+ viewport.bottom = 19;
+ assert.notOk( mw.viewport.isElementInViewport( this.el,
viewport ),
+ 'It should return false when the element is not within
the viewport' );
+
+ assert.ok( mw.viewport.isElementInViewport( this.el ),
+ 'It should default to the window object if no viewport
is given' );
+ } );
+
+ QUnit.test( 'isElementCloseToViewport', 3, function ( assert ) {
+ var
+ viewport = {
+ top: 90,
+ left: 90,
+ right: 100,
+ bottom: 100
+ },
+ distantElement = $( '<div />' )
+ .appendTo( '#qunit-fixture' )
+ .width( 20 )
+ .height( 20 )
+ .offset( {
+ top: 220,
+ left: 20
+ } )
+ .get( 0 );
+
+ assert.ok( mw.viewport.isElementCloseToViewport( this.el, 60,
viewport ),
+ 'It should return true when the element is within the
given threshold away' );
+ assert.notOk( mw.viewport.isElementCloseToViewport( this.el,
20, viewport ),
+ 'It should return false when the element is further
than the given threshold away' );
+ assert.notOk( mw.viewport.isElementCloseToViewport(
distantElement ),
+ 'It should default to a threshold of 50px and the
window\'s viewport' );
+ } );
+
+}( mediaWiki, jQuery ) );
--
To view, visit https://gerrit.wikimedia.org/r/267058
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I38eec4f1e568f51e7e212b2b3f10b8da8d36f316
Gerrit-PatchSet: 15
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Jhobs <[email protected]>
Gerrit-Reviewer: Bmansurov <[email protected]>
Gerrit-Reviewer: Edokter <[email protected]>
Gerrit-Reviewer: Florianschmidtwelzow <[email protected]>
Gerrit-Reviewer: Jack Phoenix <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Jhobs <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Phuedx <[email protected]>
Gerrit-Reviewer: TheDJ <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits