Jforrester has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/362144 )

Change subject: Update OOjs UI to v0.22.2
......................................................................

Update OOjs UI to v0.22.2

Release notes:
 
https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v0.22.2

Change-Id: If6a7aa1d924b416fc95831e1a1b26313e6482cbf
---
M composer.json
M composer.lock
M composer/installed.json
M oojs/oojs-ui/History.md
M oojs/oojs-ui/bin/docparser.rb
M oojs/oojs-ui/bin/testsuitegenerator.rb
M oojs/oojs-ui/demos/demo.js
M oojs/oojs-ui/demos/demos.php
M oojs/oojs-ui/demos/index.html
M oojs/oojs-ui/demos/pages/icons.js
M oojs/oojs-ui/demos/pages/widgets.js
M oojs/oojs-ui/demos/styles/demo.css
M oojs/oojs-ui/i18n/jv.json
M oojs/oojs-ui/package.json
M oojs/oojs-ui/php/mixins/TabIndexedElement.php
15 files changed, 433 insertions(+), 204 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/vendor 
refs/changes/44/362144/1

diff --git a/composer.json b/composer.json
index 4a05d97..587d9d8 100644
--- a/composer.json
+++ b/composer.json
@@ -45,7 +45,7 @@
                "monolog/monolog": "1.22.1",
                "mustangostang/spyc": "0.6.2",
                "nmred/kafka-php": "0.1.5",
-               "oojs/oojs-ui": "0.22.1",
+               "oojs/oojs-ui": "0.22.2",
                "oyejorge/less.php": "1.7.0.14",
                "pear/console_getopt": "1.4.1",
                "pear/mail": "1.3.0",
diff --git a/composer.lock b/composer.lock
index 167b31a..daa16e8 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at 
https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file";,
         "This file is @generated automatically"
     ],
-    "content-hash": "52a3bd30d59872778eaf081e7734b7c9",
+    "content-hash": "d5635cfadceb2bfc5954f0657124a504",
     "packages": [
         {
             "name": "composer/semver",
@@ -516,16 +516,16 @@
         },
         {
             "name": "oojs/oojs-ui",
-            "version": "v0.22.1",
+            "version": "v0.22.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/wikimedia/oojs-ui.git";,
-                "reference": "610ed06f7aa8d915e689efad93c45a2d54206031"
+                "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24"
             },
             "dist": {
                 "type": "zip",
-                "url": 
"https://api.github.com/repos/wikimedia/oojs-ui/zipball/610ed06f7aa8d915e689efad93c45a2d54206031";,
-                "reference": "610ed06f7aa8d915e689efad93c45a2d54206031",
+                "url": 
"https://api.github.com/repos/wikimedia/oojs-ui/zipball/ca5f31425138961c4a779d8e0d7674f16dbc7d24";,
+                "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24",
                 "shasum": ""
             },
             "require": {
@@ -591,7 +591,7 @@
             ],
             "description": "Provides library of common widgets, layouts, and 
windows.",
             "homepage": "https://www.mediawiki.org/wiki/OOjs_UI";,
-            "time": "2017-05-31T18:50:29+00:00"
+            "time": "2017-06-28T19:15:29+00:00"
         },
         {
             "name": "oyejorge/less.php",
diff --git a/composer/installed.json b/composer/installed.json
index 9097952..1e05c03 100644
--- a/composer/installed.json
+++ b/composer/installed.json
@@ -2215,18 +2215,67 @@
         ]
     },
     {
-        "name": "oojs/oojs-ui",
-        "version": "v0.22.1",
-        "version_normalized": "0.22.1.0",
+        "name": "wikimedia/css-sanitizer",
+        "version": "v1.0.2",
+        "version_normalized": "1.0.2.0",
         "source": {
             "type": "git",
-            "url": "https://github.com/wikimedia/oojs-ui.git";,
-            "reference": "610ed06f7aa8d915e689efad93c45a2d54206031"
+            "url": "https://github.com/wikimedia/css-sanitizer.git";,
+            "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69"
         },
         "dist": {
             "type": "zip",
-            "url": 
"https://api.github.com/repos/wikimedia/oojs-ui/zipball/610ed06f7aa8d915e689efad93c45a2d54206031";,
-            "reference": "610ed06f7aa8d915e689efad93c45a2d54206031",
+            "url": 
"https://api.github.com/repos/wikimedia/css-sanitizer/zipball/53071d588f7ac20f1467fb5952e968c1f8e2bb69";,
+            "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69",
+            "shasum": ""
+        },
+        "require": {
+            "ext-iconv": "*",
+            "ext-mbstring": "*",
+            "mediawiki/at-ease": "1.1.0",
+            "php": ">=5.5.9",
+            "wikimedia/utfnormal": "1.1.0"
+        },
+        "require-dev": {
+            "jakub-onderka/php-parallel-lint": "0.9.2",
+            "mediawiki/mediawiki-codesniffer": "0.7.2",
+            "phpunit/phpunit": "4.8.31",
+            "wikimedia/testing-access-wrapper": "1.0.0"
+        },
+        "time": "2017-06-13T15:51:12+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "classmap": [
+                "src/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/";,
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "Brad Jorsch",
+                "email": "bjor...@wikimedia.org"
+            }
+        ],
+        "description": "Classes to parse and sanitize CSS",
+        "homepage": "https://www.mediawiki.org/wiki/Css-sanitizer";
+    },
+    {
+        "name": "oojs/oojs-ui",
+        "version": "v0.22.2",
+        "version_normalized": "0.22.2.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/wikimedia/oojs-ui.git";,
+            "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24"
+        },
+        "dist": {
+            "type": "zip",
+            "url": 
"https://api.github.com/repos/wikimedia/oojs-ui/zipball/ca5f31425138961c4a779d8e0d7674f16dbc7d24";,
+            "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24",
             "shasum": ""
         },
         "require": {
@@ -2238,7 +2287,7 @@
             "mediawiki/mediawiki-codesniffer": "0.6.0",
             "phpunit/phpunit": "4.8.21"
         },
-        "time": "2017-05-31T18:50:29+00:00",
+        "time": "2017-06-28T19:15:29+00:00",
         "type": "library",
         "installation-source": "dist",
         "autoload": {
@@ -2294,54 +2343,5 @@
         ],
         "description": "Provides library of common widgets, layouts, and 
windows.",
         "homepage": "https://www.mediawiki.org/wiki/OOjs_UI";
-    },
-    {
-        "name": "wikimedia/css-sanitizer",
-        "version": "v1.0.2",
-        "version_normalized": "1.0.2.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/wikimedia/css-sanitizer.git";,
-            "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69"
-        },
-        "dist": {
-            "type": "zip",
-            "url": 
"https://api.github.com/repos/wikimedia/css-sanitizer/zipball/53071d588f7ac20f1467fb5952e968c1f8e2bb69";,
-            "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69",
-            "shasum": ""
-        },
-        "require": {
-            "ext-iconv": "*",
-            "ext-mbstring": "*",
-            "mediawiki/at-ease": "1.1.0",
-            "php": ">=5.5.9",
-            "wikimedia/utfnormal": "1.1.0"
-        },
-        "require-dev": {
-            "jakub-onderka/php-parallel-lint": "0.9.2",
-            "mediawiki/mediawiki-codesniffer": "0.7.2",
-            "phpunit/phpunit": "4.8.31",
-            "wikimedia/testing-access-wrapper": "1.0.0"
-        },
-        "time": "2017-06-13T15:51:12+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "classmap": [
-                "src/"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/";,
-        "license": [
-            "Apache-2.0"
-        ],
-        "authors": [
-            {
-                "name": "Brad Jorsch",
-                "email": "bjor...@wikimedia.org"
-            }
-        ],
-        "description": "Classes to parse and sanitize CSS",
-        "homepage": "https://www.mediawiki.org/wiki/Css-sanitizer";
     }
 ]
diff --git a/oojs/oojs-ui/History.md b/oojs/oojs-ui/History.md
index 2a800a6..b5bf1a2 100644
--- a/oojs/oojs-ui/History.md
+++ b/oojs/oojs-ui/History.md
@@ -1,4 +1,43 @@
 # OOjs UI Release History
+## v0.22.2 / 2017-06-28
+### Deprecations
+* [DEPRECATING CHANGE] TextInputWidget: Move multi-line support out (Prateek 
Saxena)
+* [DEPRECATING CHANGE] icons: Move and rename 'stripeSideMenu' to 'draggable' 
(Volker E.)
+
+### Features
+* DropdownInputWidget: Unbreak setting 'value' via config options (Bartosz 
Dziewoński)
+* Element: Work around browsers that set fractional scrollTop values (Roan 
Kattouw)
+
+### Styles
+* BookletLayout: Workaround for horizontal scrollbars on menu when editable 
(Bartosz Dziewoński)
+* icons: Let's stop referring to removed icons, hmm? (James D. Forrester)
+* Rewrite all styling for "outline controls" (Bartosz Dziewoński)
+* Apex theme: Align appearance of tags' close icon to WikimediaUI theme 
(Volker E.)
+* Apex theme: Fix HorizontalLayout containing FieldLayouts (Bartosz Dziewoński)
+* WikimediaUI theme: Remove default DraggableElement styling (Ed Sanders)
+* WikimediaUI theme: Use icon instead of indicator in Tag-/CapsuleItemWidget 
(Volker E.)
+* WikimediaUI: Strengthen Radio*Widget's `:checked` state (Volker E.)
+
+### Code
+* MenuSelectWidget: Fix item hiding when menu contents change (Roan Kattouw)
+* MultilineTextInputWidget: Fix autosizing (Bartosz Dziewoński)
+* PopupWidget: Replace CSS with Less comments for smaller dist (Volker E.)
+* SearchInputWidget: Fix ability to clear the input (Bartosz Dziewoński)
+* TabIndexedElement: Fix validation and make consistent in PHP and JS (Bartosz 
Dziewoński)
+* Use javascript-stringify instead of JSON.stringify (Ed Sanders)
+* Apex theme: Fix order of selectors for :first-child FieldLayout (Bartosz 
Dziewoński)
+* demos: Add links to documentation from code examples (Prateek Saxena)
+* demos: Allow linking to specific widgets (Bartosz Dziewoński)
+* demos: Indicate code toggle clearer (Volker E.)
+* demos: Pull out all links to docs/sources to the top of the code (Bartosz 
Dziewoński)
+* demos: Simplify code generation, now that we use javascript-stringify 
(Bartosz Dziewoński)
+* demos: Use URL 'query' part for linking to demo sections rather than URL 
'fragment' (Bartosz Dziewoński)
+* docs: Fix some typos in documentation (Bartosz Dziewoński)
+* docparser: Fix handling for fake trait constructors (Bartosz Dziewoński)
+* docparser: Make matching '(default: ...)' case-insensitive (Bartosz 
Dziewoński)
+* docparser: Tighter check for 'use' statements in PHP (Bartosz Dziewoński)
+
+
 ## v0.22.1 / 2017-05-31
 ### Code
 * WindowManager: Do not use return value of `#closeWindow` as promise (Bartosz 
Dziewoński)
diff --git a/oojs/oojs-ui/bin/docparser.rb b/oojs/oojs-ui/bin/docparser.rb
index 7bdff9c..e995ec8 100644
--- a/oojs/oojs-ui/bin/docparser.rb
+++ b/oojs/oojs-ui/bin/docparser.rb
@@ -22,7 +22,7 @@
 end
 
 def extract_default_from_description item
-       m = item[:description].match(/\(default: (.+?)\)\s*?$/)
+       m = item[:description].match(/\(default: (.+?)\)\s*?$/i)
        return if !m
        # modify `item` in-place
        item[:default] = m[1]
@@ -73,6 +73,7 @@
 
                js_class_constructor = false
                js_class_constructor_desc = ''
+               php_trait_constructor = false
                ignore = false
 
                comment, code_line = d.split '*/'
@@ -223,6 +224,7 @@
                                data[:static] = true if static
                                data[:parent] = cleanup_class_name(parent) if 
parent
                                data[:name] ||= cleanup_class_name(name)
+                               php_trait_constructor = true if kind == :method 
&& data[:name] == 'initialize' + current_class[:name]
                        end
                end
 
@@ -237,7 +239,8 @@
                end
 
                # standardize
-               if data[:name] == '__construct' || js_class_constructor
+               # (also handle fake constructors for traits)
+               if data[:name] == '__construct' || js_class_constructor || 
php_trait_constructor
                        data[:name] = '#constructor'
                end
 
@@ -258,7 +261,7 @@
        # this is evil, assumes we only have one class in a file, but we'd need 
a proper parser to do it better
        if current_class
                current_class[:mixins] +=
-                       text.scan(/[ \t]use (\w+)(?: 
?\{|;)/).flatten.map(&method(:cleanup_class_name))
+                       text.scan(/^[ \t]*use (\w+)(?: 
?\{|;)/).flatten.map(&method(:cleanup_class_name))
        end
 
        output << current_class if current_class
diff --git a/oojs/oojs-ui/bin/testsuitegenerator.rb 
b/oojs/oojs-ui/bin/testsuitegenerator.rb
index bada71b..f91e066 100644
--- a/oojs/oojs-ui/bin/testsuitegenerator.rb
+++ b/oojs/oojs-ui/bin/testsuitegenerator.rb
@@ -57,7 +57,7 @@
                'method' => %w[GET POST],
                'target' => ['_blank'],
                'accessKey' => ['k'],
-               'tabIndex' => [-1, 0, 100],
+               'tabIndex' => [-1, 0, 100, '42'],
                'maxLength' => [100],
                'icon' => ['image'],
                'indicator' => ['down'],
diff --git a/oojs/oojs-ui/demos/demo.js b/oojs/oojs-ui/demos/demo.js
index 3c8dc00..f8905b8 100644
--- a/oojs/oojs-ui/demos/demo.js
+++ b/oojs/oojs-ui/demos/demo.js
@@ -1,8 +1,8 @@
 /* eslint-disable no-console */
-/* globals Prism */
+/* globals Prism, javascriptStringify */
 /**
  * @class
- * @extends {OO.ui.Element}
+ * @extends OO.ui.Element
  *
  * @constructor
  */
@@ -16,7 +16,7 @@
        OO.EventEmitter.call( this );
 
        // Normalization
-       this.normalizeHash();
+       this.normalizeQuery();
 
        // Properties
        this.stylesheetLinks = this.getStylesheetLinks();
@@ -51,10 +51,7 @@
                new OO.ui.ButtonWidget( { label: 'JS' } ).setActive( true ),
                new OO.ui.ButtonWidget( {
                        label: 'PHP',
-                       href: 'demos.php' +
-                               '?page=widgets' +
-                               '&theme=' + this.mode.theme +
-                               '&direction=' + this.mode.direction
+                       href: 'demos.php' + this.getUrlQuery( 
this.getCurrentFactorValues() )
                } )
        ] );
        this.platformSelect = new OO.ui.ButtonSelectWidget().addItems( [
@@ -236,6 +233,19 @@
  */
 Demo.static.defaultPlatform = 'desktop';
 
+/* Static Methods */
+
+/**
+ * Scroll to current fragment identifier. We have to do this manually because 
of the fixed header.
+ */
+Demo.static.scrollToFragment = function () {
+       var elem = document.getElementById( location.hash.slice( 1 ) );
+       if ( elem ) {
+               // The additional '10' is just because it looks nicer.
+               $( window ).scrollTop( $( elem ).offset().top - $( '.demo-menu' 
).outerHeight() - 10 );
+       }
+};
+
 /* Methods */
 
 /**
@@ -279,13 +289,29 @@
                direction = this.directionSelect.getSelectedItem().getData(),
                platform = this.platformSelect.getSelectedItem().getData();
 
-       location.hash = '#' + [ page, theme, direction, platform ].join( '-' );
+       history.pushState( null, document.title, this.getUrlQuery( [ page, 
theme, direction, platform ] ) );
+       $( window ).triggerHandler( 'popstate' );
+};
+
+/**
+ * Get URL query for given factors describing the demo's mode.
+ *
+ * @param {string[]} factors Factors, as returned e.g. by 
#getCurrentFactorValues
+ * @return {string} URL query part, starting with '?'
+ */
+Demo.prototype.getUrlQuery = function ( factors ) {
+       return '?page=' + factors[ 0 ] +
+               '&theme=' + factors[ 1 ] +
+               '&direction=' + factors[ 2 ] +
+               '&platform=' + factors[ 3 ] +
+               // Preserve current URL 'fragment' part
+               location.hash;
 };
 
 /**
  * Get a list of mode factors.
  *
- * Factors are a mapping between symbolic names used in the URL hash and 
internal information used
+ * Factors are a mapping between symbolic names used in the URL query and 
internal information used
  * to act on those symbolic names.
  *
  * Factor lists are in URL order: page, theme, direction, platform. Page 
contains the symbolic
@@ -317,7 +343,7 @@
  * Get a list of default factors.
  *
  * Factor defaults are in URL order: page, theme, direction, platform. Each 
contains a symbolic
- * factor name which should be used as a fallback when the URL hash is missing 
or invalid.
+ * factor name which should be used as a fallback when the URL query is 
missing or invalid.
  *
  * @return {Object[]} List of default factors
  */
@@ -331,18 +357,29 @@
 };
 
 /**
- * Parse the current URL hash into factor values.
+ * Parse the current URL query into factor values.
  *
- * @return {string[]} Factor values in URL order: page, theme, direction
+ * @return {string[]} Factor values in URL order: page, theme, direction, 
platform
  */
 Demo.prototype.getCurrentFactorValues = function () {
-       return location.hash.slice( 1 ).split( '-' );
+       var i, parts, index,
+               factors = this.getDefaultFactorValues(),
+               order = [ 'page', 'theme', 'direction', 'platform' ],
+               query = location.search.slice( 1 ).split( '&' );
+       for ( i = 0; i < query.length; i++ ) {
+               parts = query[ i ].split( '=', 2 );
+               index = order.indexOf( parts[ 0 ] );
+               if ( index !== -1 ) {
+                       factors[ index ] = decodeURIComponent( parts[ 1 ] );
+               }
+       }
+       return factors;
 };
 
 /**
  * Get the current mode.
  *
- * Generated from parsed URL hash values.
+ * Generated from parsed URL query values.
  *
  * @return {Object} List of factor values keyed by factor name
  */
@@ -403,21 +440,43 @@
 };
 
 /**
- * Normalize the URL hash.
+ * Normalize the URL query.
  */
-Demo.prototype.normalizeHash = function () {
-       var i, len, factorValues,
+Demo.prototype.normalizeQuery = function () {
+       var i, len, factorValues, match, valid, factorValue,
                modes = [],
                factors = this.getFactors(),
                defaults = this.getDefaultFactorValues();
 
        factorValues = this.getCurrentFactorValues();
        for ( i = 0, len = factors.length; i < len; i++ ) {
-               modes[ i ] = factors[ i ][ factorValues[ i ] ] !== undefined ? 
factorValues[ i ] : defaults[ i ];
+               factorValue = factorValues[ i ];
+               modes[ i ] = factors[ i ][ factorValue ] !== undefined ? 
factorValue : defaults[ i ];
        }
 
-       // Update hash
-       location.hash = modes.join( '-' );
+       // Backwards-compatibility with old URLs that used the 'fragment' part 
to link to demo sections:
+       // if a fragment is specified and it describes valid factors, turn the 
URL into the new style.
+       match = location.hash.match( /^#(\w+)-(\w+)-(\w+)-(\w+)$/ );
+       if ( match ) {
+               factorValues = [];
+               valid = true;
+               for ( i = 0, len = factors.length; i < len; i++ ) {
+                       factorValue = match[ i + 1 ];
+                       if ( factors[ i ][ factorValue ] !== undefined ) {
+                               factorValues[ i ] = factorValue;
+                       } else {
+                               valid = false;
+                               break;
+                       }
+               }
+               if ( valid ) {
+                       location.hash = '';
+                       modes = factorValues;
+               }
+       }
+
+       // Update query
+       history.replaceState( null, document.title, this.getUrlQuery( modes ) );
 };
 
 /**
@@ -501,19 +560,23 @@
                $log.prop( 'scrollTop', $log.prop( 'scrollHeight' ) );
        }
 
-       function getCode( item ) {
-               var config, isDemoWidget, constructorName, defaultConfig, url, 
params, out,
-                       replaceKeyword = 'replace-',
-                       replaceLater = [];
+       function getCode( item, toplevel ) {
+               var config, defaultConfig, url, params, out, i,
+                       items = [],
+                       demoLinks = [],
+                       docLinks = [];
+
+               function getConstructorName( item ) {
+                       var isDemoWidget = item.constructor.name.indexOf( 
'Demo' ) === 0;
+                       return ( isDemoWidget ? 'Demo.' : 'OO.ui.' ) + 
item.constructor.name.slice( 4 );
+               }
 
                // If no item was passed we shouldn't show a code block
                if ( item === undefined ) {
                        return false;
                }
 
-               isDemoWidget = item.constructor.name.indexOf( 'Demo' ) === 0;
                config = item.initialConfig;
-               constructorName = ( isDemoWidget ? 'Demo.' : 'OO.ui.' ) + 
item.constructor.name.slice( 4 );
 
                // Prevent the default config from being part of the code
                if ( item instanceof OO.ui.ActionFieldLayout ) {
@@ -534,67 +597,71 @@
                        }
                } );
 
-               config = JSON.stringify( config, function ( k, v ) {
-                       if ( v instanceof OO.ui.Element || v instanceof 
OO.ui.HtmlSnippet || v instanceof jQuery || v instanceof Function ) {
-                               replaceLater.push( v );
-                               return replaceKeyword + ( replaceLater.length - 
1 ).toString();
+               config = javascriptStringify( config, function ( obj, indent, 
stringify ) {
+                       if ( obj instanceof Function ) {
+                               // Get function's source code, with extraneous 
indentation removed
+                               return obj.toString().replace( 
/^\t\t\t\t\t\t/gm, '' );
+                       } else if ( obj instanceof jQuery ) {
+                               if ( $.contains( item.$element[ 0 ], obj[ 0 ] ) 
) {
+                                       // If this element appears inside the 
generated widget,
+                                       // assume this was something like 
`$label: $( '<p>Text</p>' )`
+                                       return '$( ' + javascriptStringify( 
obj.prop( 'outerHTML' ) ) + ' )';
+                               } else {
+                                       // Otherwise assume this was something 
like `$overlay: $( '#overlay' )`
+                                       return '$( ' + javascriptStringify( '#' 
+ obj.attr( 'id' ) ) + ' )';
+                               }
+                       } else if ( obj instanceof OO.ui.HtmlSnippet ) {
+                               return 'new OO.ui.HtmlSnippet( ' + 
javascriptStringify( obj.toString() ) + ' )';
+                       } else if ( obj instanceof OO.ui.Element ) {
+                               return getCode( obj );
+                       } else {
+                               return stringify( obj );
                        }
-                       return v;
                }, '\t' );
 
-               // We replace later, because running getCode in place will 
treat the new code
-               // as a string and won't do proper indentation either
-               replaceLater.forEach( function ( obj, i ) {
-                       config = config.replace(
-                               // Match any number of tabs (for indentation) 
and optional object key, followed by our placeholder
-                               new RegExp( '(\t*)("[^"]+?": |)"' + 
replaceKeyword + i + '"' ),
-                               function ( all, indent, objectKey ) {
-                                       var code;
-                                       if ( obj instanceof Function ) {
-                                               // Get function's source code, 
with extraneous indentation removed
-                                               code = obj.toString().replace( 
/^\t\t\t\t\t\t/gm, '' );
-                                       } else if ( obj instanceof jQuery ) {
-                                               if ( $.contains( item.$element[ 
0 ], obj[ 0 ] ) ) {
-                                                       // If this element 
appears inside the generated widget,
-                                                       // assume this was 
something like `$label: $( '<p>Text</p>' )`
-                                                       code = '$( \"' + 
obj.prop( 'outerHTML' ).replace( /'/g, '\\\'' ) + '\" )';
-                                               } else {
-                                                       // Otherwise assume 
this was something like `$overlay: $( '#overlay' )`
-                                                       code = '$( \"#' + 
obj.attr( 'id' ) + '\" )';
-                                               }
-                                       } else if ( obj instanceof 
OO.ui.HtmlSnippet ) {
-                                               code = 'new OO.ui.HtmlSnippet( 
"' + obj.toString() + '" )';
-                                       } else {
-                                               code = getCode( obj );
-                                       }
-                                       // Re-add the indent at the beginning, 
and after every newline
-                                       return indent + objectKey + 
code.replace( /\n/g, '\n' + indent );
-                               }
-                       );
-               } );
-
                // The generated code needs to include different arguments, 
based on the object type
+               items.push( item );
                if ( item instanceof OO.ui.ActionFieldLayout ) {
                        params = getCode( item.fieldWidget ) + ', ' + getCode( 
item.buttonWidget );
+                       items.push( item.fieldWidget );
+                       items.push( item.buttonWidget );
                } else if ( item instanceof OO.ui.FieldLayout ) {
                        params = getCode( item.fieldWidget );
+                       items.push( item.fieldWidget );
                } else {
                        params = '';
                }
                if ( config !== '{}' ) {
                        params += ( params ? ', ' : '' ) + config;
                }
-               out = 'new ' + constructorName + '(' + ( params ? ' ' : '' ) + 
params + ( params ? ' ' : '' ) + ')';
+               out = 'new ' + getConstructorName( item ) + '(' +
+                       ( params ? ' ' : '' ) + params + ( params ? ' ' : '' ) +
+                       ')';
 
-               // The code generated for Demo widgets cannot be copied and used
-               if ( item.constructor.name.indexOf( 'Demo' ) === 0 ) {
-                       url =
-                               
'https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/demos/classes/' 
+
-                               item.constructor.name.slice( 4 ) + '.js';
-                       out = '// See source code:\n// ' + url + '\n' + out;
+               if ( toplevel ) {
+                       for ( i = 0; i < items.length; i++ ) {
+                               item = items[ i ];
+                               // The code generated for Demo widgets cannot 
be copied and used
+                               if ( item.constructor.name.indexOf( 'Demo' ) 
=== 0 ) {
+                                       url =
+                                               
'https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/demos/classes/' 
+
+                                               item.constructor.name.slice( 4 
) + '.js';
+                                       demoLinks.push( url );
+                               } else {
+                                       url = 
'https://doc.wikimedia.org/oojs-ui/master/js/#!/api/' + getConstructorName( 
item );
+                                       url = '[' + url + '](' + url + ')';
+                                       docLinks.push( url );
+                               }
+                       }
                }
 
-               return out;
+               return (
+                       ( docLinks.length ? '// See documentation at: \n// ' : 
'' ) +
+                       docLinks.join( '\n// ' ) + ( docLinks.length ? '\n' : 
'' ) +
+                       ( demoLinks.length ? '// See source code:\n// ' : '' ) +
+                       demoLinks.join( '\n// ' ) + ( demoLinks.length ? '\n' : 
'' ) +
+                       out
+               );
        }
 
        $toggle = $( '<span>' )
@@ -613,9 +680,9 @@
                                        console.log( '[demo]', item );
 
                                        if ( showLayoutCode === true ) {
-                                               code = getCode( item );
+                                               code = getCode( item, true );
                                        } else {
-                                               code = getCode( 
item.fieldWidget );
+                                               code = getCode( 
item.fieldWidget, true );
                                        }
 
                                        if ( code ) {
@@ -670,3 +737,41 @@
 
        return $console;
 };
+
+/**
+ * Build a link to this example.
+ *
+ * @param {OO.ui.Layout} item
+ * @param {OO.ui.FieldsetLayout} parentItem
+ * @return {jQuery} Link interface element
+ */
+Demo.prototype.buildLinkExample = function ( item, parentItem ) {
+       var $linkExample, label, fragment;
+
+       if ( item.$label.text() === '' ) {
+               item = parentItem;
+       }
+       fragment = item.elementId;
+       if ( !fragment ) {
+               label = item.$label.text();
+               fragment = label.replace( /[^\w]+/g, '-' ).replace( /^-|-$/g, 
'' );
+               item.setElementId( fragment );
+       }
+
+       $linkExample = $( '<a>' )
+               .addClass( 'demo-link-example' )
+               .attr( 'title', 'Link to this example' )
+               .attr( 'href', '#' + fragment )
+               .on( 'click', function ( e ) {
+                       // We have to handle this manually in order to call 
.scrollToFragment() even if it's the same
+                       // fragment. Normally, the browser will scroll but not 
fire a 'hashchange' event in this
+                       // situation, and the scroll position will be off 
because of our fixed header.
+                       if ( e.which === OO.ui.MouseButtons.LEFT ) {
+                               location.hash = $( this ).attr( 'href' );
+                               Demo.static.scrollToFragment();
+                               e.preventDefault();
+                       }
+               } );
+
+       return $linkExample;
+};
diff --git a/oojs/oojs-ui/demos/demos.php b/oojs/oojs-ui/demos/demos.php
index 8a779b2..91dd2e1 100644
--- a/oojs/oojs-ui/demos/demos.php
+++ b/oojs/oojs-ui/demos/demos.php
@@ -28,9 +28,9 @@
                ? $_GET['page'] : 'widgets';
 
        $query = [
+               'page' => $page,
                'theme' => $theme,
                'direction' => $direction,
-               'page' => $page,
        ];
 
        $additionalThemeImagesSuffixes = [
@@ -123,7 +123,7 @@
                                        'items' => [
                                                new OOUI\ButtonWidget( [
                                                        'label' => 'JS',
-                                                       'href' => 
".#$page-$theme-$direction",
+                                                       'href' => '.?' . 
http_build_query( $query ),
                                                        'active' => false,
                                                ] ),
                                                new OOUI\ButtonWidget( [
diff --git a/oojs/oojs-ui/demos/index.html b/oojs/oojs-ui/demos/index.html
index cabe444..94c9203 100644
--- a/oojs/oojs-ui/demos/index.html
+++ b/oojs/oojs-ui/demos/index.html
@@ -14,6 +14,7 @@
        <script src="node_modules/oojs/dist/oojs.jquery.js"></script>
        <script src="node_modules/prismjs/prism.js"></script>
        <script 
src="node_modules/prismjs/plugins/autolinker/prism-autolinker.js"></script>
+       <script 
src="node_modules/javascript-stringify/javascript-stringify.js"></script>
        <script src="dist/oojs-ui.js"></script>
        <script src="dist/oojs-ui-wikimediaui.js"></script> <!-- Do not change 
this line or you'll break `grunt add-theme` -->
        <script src="dist/oojs-ui-apex.js"></script>
@@ -52,28 +53,35 @@
        <script src="pages/toolbars.js"></script>
        <script>
                $( function () {
-                       var demo;
+                       var demo, lastQuery = location.search;
 
                        function setup() {
                                var
                                        prevPage = demo ? demo.mode.page : null,
                                        scrollPos = $( window ).scrollTop();
                                if ( demo ) {
+                                       if ( lastQuery === location.search ) {
+                                               return false;
+                                       }
                                        demo.destroy();
                                }
+                               lastQuery = location.search;
                                demo = new Demo();
                                $( 'body' ).append( demo.$element );
                                demo.initialize().done( function () {
-                                       if ( prevPage === demo.mode.page ) {
+                                       if ( prevPage === demo.mode.page && 
scrollPos ) {
                                                // Restore scroll position from 
before we destroyed the demo
                                                $( window ).scrollTop( 
scrollPos );
+                                       } else {
+                                               Demo.static.scrollToFragment();
                                        }
                                } );
                        }
 
                        setup();
 
-                       $( window ).on( 'hashchange', setup );
+                       $( window ).on( 'popstate', setup );
+                       $( window ).on( 'hashchange', 
Demo.static.scrollToFragment );
                } )
        </script>
 </body>
diff --git a/oojs/oojs-ui/demos/pages/icons.js 
b/oojs/oojs-ui/demos/pages/icons.js
index 3ce0269..876dc35 100644
--- a/oojs/oojs-ui/demos/pages/icons.js
+++ b/oojs/oojs-ui/demos/pages/icons.js
@@ -12,7 +12,8 @@
                                'last',
                                'expand',
                                'collapse',
-                               'move'
+                               'move',
+                               'draggable'
                        ],
                        content: [
                                'article',
@@ -175,7 +176,6 @@
                        layout: [
                                'menu',
                                'stripeFlow',
-                               'stripeSideMenu',
                                'stripeSummary',
                                'stripeToC',
                                'viewCompact',
diff --git a/oojs/oojs-ui/demos/pages/widgets.js 
b/oojs/oojs-ui/demos/pages/widgets.js
index 6f30c6b..d4ccc95 100644
--- a/oojs/oojs-ui/demos/pages/widgets.js
+++ b/oojs/oojs-ui/demos/pages/widgets.js
@@ -28,7 +28,7 @@
                        verticalHandledDragItems.push(
                                new Demo.DraggableHandledItemWidget( {
                                        data: 'item' + i,
-                                       icon: 'menu',
+                                       icon: 'draggable',
                                        label: 'Item ' + i
                                } )
                        );
@@ -648,7 +648,7 @@
                } ),
                new OO.ui.FieldsetLayout( {
                        id: 'demo-section-inputs',
-                       label: 'Inputs: TextInput, TextInput (multiline), 
SearchInput, NumberInput',
+                       label: 'Inputs: TextInput, TextInput, 
MultilineTextInput, SearchInput, NumberInput',
                        items: [
                                new OO.ui.FieldLayout(
                                        new OO.ui.TextInputWidget( { value: 
'Text input' } ),
@@ -723,52 +723,47 @@
                                        }
                                ),
                                new OO.ui.FieldLayout(
-                                       new OO.ui.TextInputWidget( {
-                                               multiline: true,
+                                       new OO.ui.MultilineTextInputWidget( {
                                                value: 'Multiline\nMultiline'
                                        } ),
                                        {
-                                               label: 'TextInputWidget 
(multiline)\u200E',
+                                               label: 
'MultilineTextInputWidget\u200E',
                                                align: 'top'
                                        }
                                ),
                                new OO.ui.FieldLayout(
-                                       new OO.ui.TextInputWidget( {
-                                               multiline: true,
+                                       new OO.ui.MultilineTextInputWidget( {
                                                rows: 15,
                                                value: 'Multiline\nMultiline'
                                        } ),
                                        {
-                                               label: 'TextInputWidget 
(multiline, rows=15)\u200E',
+                                               label: 
'MultilineTextInputWidget (rows=15)\u200E',
                                                align: 'top'
                                        }
                                ),
                                new OO.ui.FieldLayout(
-                                       new OO.ui.TextInputWidget( {
-                                               multiline: true,
+                                       new OO.ui.MultilineTextInputWidget( {
                                                autosize: true,
                                                value: 
'Autosize\nAutosize\nAutosize\nAutosize'
                                        } ),
                                        {
-                                               label: 'TextInputWidget 
(autosize)\u200E',
+                                               label: 
'MultilineTextInputWidget (autosize)\u200E',
                                                align: 'top'
                                        }
                                ),
                                new OO.ui.FieldLayout(
-                                       new OO.ui.TextInputWidget( {
-                                               multiline: true,
+                                       new OO.ui.MultilineTextInputWidget( {
                                                rows: 10,
                                                autosize: true,
                                                value: 
'Autosize\nAutosize\nAutosize\nAutosize'
                                        } ),
                                        {
-                                               label: 'TextInputWidget 
(autosize, rows=10)\u200E',
+                                               label: 
'MultilineTextInputWidget (autosize, rows=10)\u200E',
                                                align: 'top'
                                        }
                                ),
                                new OO.ui.FieldLayout(
-                                       new OO.ui.TextInputWidget( {
-                                               multiline: true,
+                                       new OO.ui.MultilineTextInputWidget( {
                                                autosize: true,
                                                icon: 'tag',
                                                indicator: 'alert',
@@ -776,7 +771,7 @@
                                                value: 
'Autosize\nAutosize\nAutosize\nAutosize'
                                        } ),
                                        {
-                                               label: 'TextInputWidget 
(autosize, icon, indicator, label)\u200E',
+                                               label: 
'MultilineTextInputWidget (autosize, icon, indicator, label)\u200E',
                                                align: 'top'
                                        }
                                ),
@@ -2745,6 +2740,7 @@
 
                $.each( fieldsetLayout.getItems(), function ( j, fieldLayout ) {
                        fieldLayout.$element.append(
+                               demo.buildLinkExample( fieldLayout, 
fieldsetLayout instanceof OO.ui.FormLayout ? fieldLayout : fieldsetLayout ),
                                demo.buildConsole( fieldLayout, 'layout', 
'widget', showLayoutCode )
                        );
                } );
diff --git a/oojs/oojs-ui/demos/styles/demo.css 
b/oojs/oojs-ui/demos/styles/demo.css
index 73ae333..34611fe 100644
--- a/oojs/oojs-ui/demos/styles/demo.css
+++ b/oojs/oojs-ui/demos/styles/demo.css
@@ -63,45 +63,82 @@
        box-sizing: border-box;
 }
 
+.demo-link-example,
 .demo-console-toggle {
        float: right;
-       margin-top: -2em;
+       margin-top: -2.5em;
        cursor: pointer;
-       -webkit-transition: background-color 100ms, color 100ms, transform 
100ms;
-       -moz-transition: background-color 100ms, color 100ms, transform 100ms;
-       transition: background-color 100ms, color 100ms, transform 100ms;
+       -webkit-transition: background-color 100ms, color 100ms;
+       -moz-transition: background-color 100ms, color 100ms;
+       transition: background-color 100ms, color 100ms;
+}
+
+.demo-link-example {
+       position: relative;
+       top: 1px;
+       right: 2em;
+       opacity: 0;
+       -webkit-transition: opacity 100ms;
+       -moz-transition: opacity 100ms;
+       transition: opacity 100ms;
+}
+
+.oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-link-example {
+       right: 7.2em;
+       opacity: 1;
 }
 
 .demo-console-expanded .demo-console-toggle {
-       margin-top: -2.5em;
+       margin-top: -3em;
+}
+
+.demo-link-example::after,
+.demo-console-toggle::after {
+       color: #36c;
+       position: relative;
+       display: inline-block;
+       margin-right: -1.2em;
+       padding: 0.703125em 0.9375em 0.625em; /* equals frameless button */
+       border-radius: 2px;
+       line-height: 1.172em;
+       font-weight: bold;
+       text-align: center;
+       -webkit-transition: color 100ms;
+       -moz-transition: color 100ms;
+       transition: color 100ms;
+}
+
+.demo-link-example::after,
+.demo-console-toggle:hover::after {
+       color: #447ff5;
+}
+
+.demo-link-example::after {
+       content: '#';
+}
+
+.demo-container > .oo-ui-fieldsetLayout:first-child 
.oo-ui-fieldLayout:first-child .demo-link-example::after {
+       content: 'Link to this example #';
 }
 
 .demo-console-toggle::after {
-       content: '→';
-       background-color: #f8f9fa;
-       color: #222;
-       position: relative;
-       display: inline-block;
-       width: 1.6em;
-       border-radius: 2px;
-       line-height: 1.6;
-       text-align: center;
-       -webkit-transition: background-color 100ms, color 100ms, transform 
100ms;
-       -moz-transition: background-color 100ms, color 100ms, transform 100ms;
-       transition: background-color 100ms, color 100ms, transform 100ms;
-}
-
-.demo-console-toggle:hover::after,
-.demo-console-expanded .demo-console-toggle::after {
-       background: #fff;
-       color: #444;
+       content: '↓';
 }
 
 .demo-console-expanded .demo-console-toggle::after {
-       -webkit-transform: rotate( -90deg );
-       -moz-transform: rotate( -90deg );
-       -ms-transform: rotate( -90deg );
-       transform: rotate( -90deg );
+       content: '↑';
+}
+
+.oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-console-toggle::after {
+       content: 'Show code ↓';
+}
+
+.oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-console-expanded .demo-console-toggle::after {
+       content: 'Hide code ↑';
+}
+
+.oo-ui-fieldLayout:hover > .demo-link-example {
+       opacity: 1;
 }
 
 .demo-console-collapsed .demo-sample-code,
@@ -276,8 +313,11 @@
        max-width: 50em;
 }
 
+.oo-ui-fieldLayout-align-top.oo-ui-actionFieldLayout .demo-link-example,
 .oo-ui-fieldLayout-align-top.oo-ui-actionFieldLayout .demo-console-toggle,
+.oo-ui-fieldLayout-align-left .demo-link-example,
 .oo-ui-fieldLayout-align-left .demo-console-toggle,
+.oo-ui-fieldLayout-align-right .demo-link-example,
 .oo-ui-fieldLayout-align-right .demo-console-toggle {
        margin-right: -2em;
 }
@@ -344,8 +384,26 @@
                border-width: 1px;
        }
 
+       .demo-console-expanded {
+               width: calc( 100% + 2.5em ); /* equals `100%` + `padding` of 
`.demo-container` above */
+       }
+
+       .demo-link-example,
        .demo-console-toggle {
-               margin-right: -2em;
+               margin-right: -2.5em;
+       }
+
+       .demo-console-expanded .demo-console-toggle {
+               margin-right: 0;
+       }
+
+       .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-link-example,
+       .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-console-toggle {
+               margin-top: 0;
+       }
+
+       .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-console-expanded .demo-console-toggle {
+               margin-top: -0.5em;
        }
 
        @supports ( position: fixed ) {
@@ -380,14 +438,14 @@
                .demo-container.oo-ui-panelLayout {
                        margin-top: 7.5em;
                }
+
+               .demo-console-expanded .demo-console-toggle {
+                       margin-right: 0.5em;
+               }
        }
 }
 
-@media ( min-width: 768px ) {
-       .demo-console-toggle {
-               margin-right: 0;
-       }
-}
+/* @media ( min-width: 768px ) {} */
 
 @media ( min-width: 960px ) {
        body {
@@ -405,6 +463,25 @@
                white-space: nowrap;
        }
 
+       .demo-console-expanded {
+               width: 100%;
+       }
+
+       .demo-link-example,
+       .demo-console-toggle,
+       .demo-console-expanded .demo-console-toggle {
+               margin-right: 0;
+       }
+
+       .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-link-example,
+       .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-console-toggle {
+               margin-top: -2.5em;
+       }
+
+       .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child 
.demo-console-expanded .demo-console-toggle {
+               margin-top: -3em;
+       }
+
        @supports ( position: fixed ) {
                .demo-menu {
                        min-height: 4.5em;
diff --git a/oojs/oojs-ui/i18n/jv.json b/oojs/oojs-ui/i18n/jv.json
index 25aff68..5ade015 100644
--- a/oojs/oojs-ui/i18n/jv.json
+++ b/oojs/oojs-ui/i18n/jv.json
@@ -16,9 +16,9 @@
        "ooui-toolgroup-collapse": "Sacukupé",
        "ooui-dialog-message-accept": "Oké",
        "ooui-dialog-message-reject": "Wurung",
-       "ooui-dialog-process-error": "Ana sing klèru",
+       "ooui-dialog-process-error": "Ana sing salah",
        "ooui-dialog-process-dismiss": "Tutup",
-       "ooui-dialog-process-retry": "Jajal manèh",
+       "ooui-dialog-process-retry": "Jajalen manèh",
        "ooui-dialog-process-continue": "Bacutaké",
        "ooui-selectfile-button-select": "Pilih barkas",
        "ooui-selectfile-not-supported": "Ora bisa milih barkas",
diff --git a/oojs/oojs-ui/package.json b/oojs/oojs-ui/package.json
index 7a18873..fb48d4d 100644
--- a/oojs/oojs-ui/package.json
+++ b/oojs/oojs-ui/package.json
@@ -1,6 +1,6 @@
 {
   "name": "oojs-ui",
-  "version": "0.22.1",
+  "version": "0.22.2",
   "description": "User interface classes built on the OOjs framework.",
   "keywords": [
     "oojs-plugin",
@@ -49,6 +49,7 @@
     "grunt-stylelint": "0.8.0",
     "grunt-svg2png": 
"git://github.com/jdforrester/grunt-svg2png.git#v0.2.7-wmf.1",
     "grunt-tyops": "0.1.0",
+    "javascript-stringify": "1.6.0",
     "karma": "1.1.1",
     "karma-chrome-launcher": "1.0.1",
     "karma-coverage": "1.1.0",
diff --git a/oojs/oojs-ui/php/mixins/TabIndexedElement.php 
b/oojs/oojs-ui/php/mixins/TabIndexedElement.php
index ecf884a..ec88335 100644
--- a/oojs/oojs-ui/php/mixins/TabIndexedElement.php
+++ b/oojs/oojs-ui/php/mixins/TabIndexedElement.php
@@ -22,8 +22,8 @@
 
        /**
         * @param array $config Configuration options
-        * @param number|null $config['tabIndex'] Tab index value. Use 0 to use 
default ordering, use -1 to
-        *   prevent tab focusing, use null to suppress the `tabindex` 
attribute. (default: 0)
+        * @param string|number|null $config['tabIndex'] Tab index value. Use 0 
to use default ordering,
+        *   use -1 to prevent tab focusing, use null to suppress the 
`tabindex` attribute. (default: 0)
         */
        public function initializeTabIndexedElement( array $config = [] ) {
                // Properties
@@ -43,11 +43,11 @@
        /**
         * Set tab index value.
         *
-        * @param number|null $tabIndex Tab index value or null for no tab index
+        * @param string|number|null $tabIndex Tab index value or null for no 
tab index
         * @return $this
         */
        public function setTabIndex( $tabIndex ) {
-               $tabIndex = is_numeric( $tabIndex ) ? $tabIndex : null;
+               $tabIndex = preg_match( '/^-?\d+$/', $tabIndex ) ? 
(int)$tabIndex : null;
 
                if ( $this->tabIndex !== $tabIndex ) {
                        $this->tabIndex = $tabIndex;

-- 
To view, visit https://gerrit.wikimedia.org/r/362144
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: If6a7aa1d924b416fc95831e1a1b26313e6482cbf
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/vendor
Gerrit-Branch: master
Gerrit-Owner: Jforrester <jforres...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to