From 6f7d145fdb6c2c2e52fcab10a6c4716cab43a41e Mon Sep 17 00:00:00 2001 From: "James D. Forrester" Date: Thu, 21 Mar 2019 09:15:22 -0700 Subject: [PATCH] Update OOUI to v0.31.1 Release notes: https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v0.31.1 Bug: T163135 Bug: T170263 Bug: T184835 Bug: T209716 Bug: T214748 Bug: T216071 Bug: T216780 Bug: T218409 Bug: T218722 Bug: T218753 Depends-On: I79088caef4a34b3f6ae06bff25213652a079854a Change-Id: I79088caef4a34b3f6ae06bff25213652a079854b --- RELEASE-NOTES-1.33 | 2 +- composer.json | 2 +- maintenance/resources/foreign-resources.yaml | 4 +- resources/lib/ooui/History.md | 41 ++ resources/lib/ooui/i18n/fa.json | 1 + resources/lib/ooui/oojs-ui-apex.js | 4 +- resources/lib/ooui/oojs-ui-core-apex.css | 101 +++- .../lib/ooui/oojs-ui-core-wikimediaui.css | 103 +++- resources/lib/ooui/oojs-ui-core.js | 434 ++++++++++++++--- resources/lib/ooui/oojs-ui-core.js.map.json | 2 +- resources/lib/ooui/oojs-ui-toolbars-apex.css | 4 +- .../lib/ooui/oojs-ui-toolbars-wikimediaui.css | 4 +- resources/lib/ooui/oojs-ui-toolbars.js | 4 +- resources/lib/ooui/oojs-ui-widgets-apex.css | 248 ++-------- .../lib/ooui/oojs-ui-widgets-wikimediaui.css | 255 ++-------- resources/lib/ooui/oojs-ui-widgets.js | 438 ++++++++---------- .../lib/ooui/oojs-ui-widgets.js.map.json | 2 +- resources/lib/ooui/oojs-ui-wikimediaui.js | 4 +- resources/lib/ooui/oojs-ui-windows-apex.css | 7 +- .../lib/ooui/oojs-ui-windows-wikimediaui.css | 4 +- resources/lib/ooui/oojs-ui-windows.js | 4 +- resources/lib/ooui/themes/apex/textures.json | 10 +- .../icons/folderPlaceholder-ltr-invert.png | Bin 120 -> 121 bytes .../icons/folderPlaceholder-ltr-invert.svg | 2 +- .../folderPlaceholder-ltr-progressive.png | Bin 127 -> 129 bytes .../folderPlaceholder-ltr-progressive.svg | 2 +- .../images/icons/folderPlaceholder-ltr.png | Bin 118 -> 119 bytes .../images/icons/folderPlaceholder-ltr.svg | 2 +- .../icons/folderPlaceholder-rtl-invert.png | Bin 115 -> 119 bytes .../icons/folderPlaceholder-rtl-invert.svg | 2 +- .../folderPlaceholder-rtl-progressive.png | Bin 121 -> 126 bytes .../folderPlaceholder-rtl-progressive.svg | 2 +- .../images/icons/folderPlaceholder-rtl.png | Bin 112 -> 115 bytes .../images/icons/folderPlaceholder-rtl.svg | 2 +- .../images/icons/imageLayoutBasic-invert.svg | 2 +- .../icons/imageLayoutBasic-progressive.svg | 2 +- .../images/icons/imageLayoutBasic.svg | 2 +- .../images/icons/imageLayoutFrame-invert.svg | 2 +- .../icons/imageLayoutFrame-progressive.svg | 2 +- .../images/icons/imageLayoutFrame.svg | 2 +- .../icons/imageLayoutFrameless-invert.svg | 2 +- .../imageLayoutFrameless-progressive.svg | 2 +- .../images/icons/imageLayoutFrameless.svg | 2 +- .../icons/imageLayoutThumbnail-invert.svg | 2 +- .../imageLayoutThumbnail-progressive.svg | 2 +- .../images/icons/imageLayoutThumbnail.svg | 2 +- .../images/icons/settings-invert.png | Bin 306 -> 262 bytes .../images/icons/settings-invert.svg | 2 +- .../images/icons/settings-progressive.png | Bin 404 -> 338 bytes .../images/icons/settings-progressive.svg | 2 +- .../wikimediaui/images/icons/settings.png | Bin 302 -> 258 bytes .../wikimediaui/images/icons/settings.svg | 2 +- .../images/icons/speechBubble-ltr-invert.png | Bin 119 -> 118 bytes .../images/icons/speechBubble-ltr-invert.svg | 2 +- .../icons/speechBubble-ltr-progressive.png | Bin 127 -> 129 bytes .../icons/speechBubble-ltr-progressive.svg | 2 +- .../images/icons/speechBubble-ltr.png | Bin 116 -> 117 bytes .../images/icons/speechBubble-ltr.svg | 2 +- .../images/icons/speechBubble-rtl-invert.png | Bin 118 -> 118 bytes .../images/icons/speechBubble-rtl-invert.svg | 2 +- .../icons/speechBubble-rtl-progressive.png | Bin 130 -> 130 bytes .../icons/speechBubble-rtl-progressive.svg | 2 +- .../images/icons/speechBubble-rtl.png | Bin 118 -> 118 bytes .../images/icons/speechBubble-rtl.svg | 2 +- .../images/icons/speechBubbles-ltr-invert.png | Bin 156 -> 157 bytes .../images/icons/speechBubbles-ltr-invert.svg | 2 +- .../icons/speechBubbles-ltr-progressive.png | Bin 173 -> 179 bytes .../icons/speechBubbles-ltr-progressive.svg | 2 +- .../images/icons/speechBubbles-ltr.png | Bin 153 -> 156 bytes .../images/icons/speechBubbles-ltr.svg | 2 +- .../images/icons/speechBubbles-rtl-invert.png | Bin 154 -> 159 bytes .../images/icons/speechBubbles-rtl-invert.svg | 2 +- .../icons/speechBubbles-rtl-progressive.png | Bin 177 -> 184 bytes .../icons/speechBubbles-rtl-progressive.svg | 2 +- .../images/icons/speechBubbles-rtl.png | Bin 153 -> 156 bytes .../images/icons/speechBubbles-rtl.svg | 2 +- .../icons/tableMoveColumnAfter-invert.svg | 2 +- .../tableMoveColumnAfter-progressive.svg | 2 +- .../images/icons/tableMoveColumnAfter.svg | 2 +- .../icons/tableMoveColumnBefore-invert.svg | 2 +- .../tableMoveColumnBefore-progressive.svg | 2 +- .../images/icons/tableMoveColumnBefore.svg | 2 +- .../images/icons/tableMoveRowAfter-invert.svg | 2 +- .../icons/tableMoveRowAfter-progressive.svg | 2 +- .../images/icons/tableMoveRowAfter.svg | 2 +- .../icons/tableMoveRowBefore-invert.svg | 2 +- .../icons/tableMoveRowBefore-progressive.svg | 2 +- .../images/icons/tableMoveRowBefore.svg | 2 +- .../wikimediaui/images/icons/web-invert.png | Bin 127 -> 127 bytes .../wikimediaui/images/icons/web-invert.svg | 2 +- .../images/icons/web-progressive.png | Bin 143 -> 142 bytes .../images/icons/web-progressive.svg | 2 +- .../themes/wikimediaui/images/icons/web.png | Bin 124 -> 124 bytes .../themes/wikimediaui/images/icons/web.svg | 2 +- .../images/textures/transparency.svg | 2 +- .../lib/ooui/themes/wikimediaui/textures.json | 10 +- 96 files changed, 972 insertions(+), 814 deletions(-) diff --git a/RELEASE-NOTES-1.33 b/RELEASE-NOTES-1.33 index 646a9c24f0..1d4fd40013 100644 --- a/RELEASE-NOTES-1.33 +++ b/RELEASE-NOTES-1.33 @@ -109,7 +109,7 @@ For notes on 1.32.x and older releases, see HISTORY. * Added jakub-onderka/php-console-highlighter 0.3.2 explicitly (dev-only). ==== Changed external libraries ==== -* Updated OOUI from v0.29.2 to v0.31.0. +* Updated OOUI from v0.29.2 to v0.31.1. * Updated OOjs Router from pre-release to v0.2.0. * Updated moment from v2.19.3 to v2.24.0. * Updated wikimedia/xmp-reader from 0.6.0 to 0.6.2. diff --git a/composer.json b/composer.json index 8c9dc3f3c5..396d220a05 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ext-xml": "*", "guzzlehttp/guzzle": "6.3.3", "liuggio/statsd-php-client": "1.0.18", - "oojs/oojs-ui": "0.31.0", + "oojs/oojs-ui": "0.31.1", "pear/mail": "1.4.1", "pear/mail_mime": "1.10.2", "pear/net_smtp": "1.8.1", diff --git a/maintenance/resources/foreign-resources.yaml b/maintenance/resources/foreign-resources.yaml index 3ecd12e8a3..d4458aa905 100644 --- a/maintenance/resources/foreign-resources.yaml +++ b/maintenance/resources/foreign-resources.yaml @@ -163,8 +163,8 @@ oojs-router: ooui: type: tar - src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.31.0.tgz - integrity: sha384-kmMOvTjLZbr0Nd1iiV61KSDevnZffuY0jpr7Wjoo61HrJY53b9SacjQqebcNzTEK + src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.31.1.tgz + integrity: sha384-M9KdU6u02zSKCVczcw6YJmSvFLhdeagNg9CPhizYVqrybL8bamrF5u6YfrFGEyiv dest: # Main stuff package/dist/oojs-ui-core.js{,.map.json}: diff --git a/resources/lib/ooui/History.md b/resources/lib/ooui/History.md index 9ade84c228..a8e0519779 100644 --- a/resources/lib/ooui/History.md +++ b/resources/lib/ooui/History.md @@ -1,4 +1,45 @@ # OOUI Release History +## v0.31.1 / 2019-03-21 +### Deprecations +* [DEPRECATING CHANGE] core: Remove unused Date.now fallback (Timo Tijhof) +* [DEPRECATING CHANGE] textures: Deprecate 'pending.gif' (Volker E.) +* [DEPRECATING CHANGE] textures: Deprecate unused 'transparency' (Volker E.) + +### Features +* MenuTagMultiselectWidget: `hideOnChoose` should be set to false (Moriel Schottlender) +* MenuTagMultiselectWidget: `highlightOnFilter` only if not `allowArbitrary` (Moriel Schottlender) +* MenuTagMultiselectWidget: Fix highlight and scrolling to item behavior (Moriel Schottlender) +* SearchInputWidget: Use click handler for indicator (Ed Sanders) +* SelectWidget: Allow multiselect mode, add to MenuTagMultiselectWidget (Moriel Schottlender) +* SelectFileWidget: Support a button-only mode (Ed Sanders) +* SelectFileWidget: Suppress misleading browser default tooltips (Bartosz Dziewoński) +* SelectFileInputWidget: Create as a super-class of SelectFileWidget (Ed Sanders) +* SelectFileInputWidget: Allow button config to be passed (Ed Sanders) +* TagMultiselectWidget: Edit by item label, not data (Moriel Schottlender) + +### Styles +* Separate SelectFileWidget and SelectFileInputWidget styles (Ed Sanders) +* themes: Provide `background` needed for PendingElement on inputs (Volker E.) +* themes: Replace 'pending.gif' with CSS animation (Volker E.) +* icons: Manually rewrite paths of tableMove….svg icons (Thiemo Kreuz) +* icons: Recreate settings.svg icon with shorter syntax (Thiemo Kreuz) +* icons: Remove invisible parts from web.svg icon (Thiemo Kreuz) +* icons: Remove unused dotted borders from imageLayout….svg icons (Thiemo Kreuz) +* icons: Use rounded elements to optimize some SVG icons (Thiemo Kreuz) + +### Code +* MenuSectionOptionWidget: Avoid select events (Gabriel Birke) +* SelectFileInputWidget: Rewrite as an ActionFieldLayout (Ed Sanders) +* testsuitegenerator: Reduce PHP test count by 40% (Ed Sanders) +* testsuitegenerator: Reduce some code duplication (Bartosz Dziewoński) +* testsuitegenerator: Use normal methods more instead of lambdas (Bartosz Dziewoński) +* docs: Clarify some types in documentation (Bartosz Dziewoński) +* docs: Fix missing `;` and typos in documentation examples (Volker E.) +* demos: Make demo toolbar narrower (Ed Sanders) +* build: Specify library entry (Stephen Niedzielski) +* Grunt: Add a quick-build-code task for JS-only quick builds (Ed Sanders) + + ## v0.31.0 / 2019-03-13 ### Breaking changes * [BREAKING CHANGE] Remove FlaggedElement from InputWidget (Ed Sanders) diff --git a/resources/lib/ooui/i18n/fa.json b/resources/lib/ooui/i18n/fa.json index e6e44637b7..ae6171da2f 100644 --- a/resources/lib/ooui/i18n/fa.json +++ b/resources/lib/ooui/i18n/fa.json @@ -31,6 +31,7 @@ "ooui-dialog-process-dismiss": "رد", "ooui-dialog-process-retry": "دوباره امتحان کنید", "ooui-dialog-process-continue": "ادامه", + "ooui-combobox-button-label": "پایین‌رونده برای جعبهٔ پایین‌رونده", "ooui-selectfile-button-select": "یک فایل انتخاب کنید", "ooui-selectfile-not-supported": "انتخاب پرونده پشتیبانی نمی‌شود", "ooui-selectfile-placeholder": "هیچ پرونده‌ای انتخاب نشده است", diff --git a/resources/lib/ooui/oojs-ui-apex.js b/resources/lib/ooui/oojs-ui-apex.js index 8847ef2355..dd6cb81ab3 100644 --- a/resources/lib/ooui/oojs-ui-apex.js +++ b/resources/lib/ooui/oojs-ui-apex.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.1 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-14T00:52:20Z + * Date: 2019-03-21T15:54:37Z */ ( function ( OO ) { diff --git a/resources/lib/ooui/oojs-ui-core-apex.css b/resources/lib/ooui/oojs-ui-core-apex.css index c298481ae9..b7b5f76f68 100644 --- a/resources/lib/ooui/oojs-ui-core-apex.css +++ b/resources/lib/ooui/oojs-ui-core-apex.css @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.1 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-14T00:52:27Z + * Date: 2019-03-21T15:54:46Z */ .oo-ui-element-hidden { display: none !important; @@ -345,7 +345,38 @@ } .oo-ui-pendingElement-pending { - background-image: /* @embed */ url(themes/wikimediaui/images/textures/pending.gif); + background-color: #ddd; + background-image: -webkit-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-size: 1.5625em 1.5625em; + -webkit-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + -moz-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + animation: oo-ui-pendingElement-stripes 650ms linear infinite; +} +@-webkit-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.5625em 0; + } + 100% { + background-position: 0 0; + } +} +@-moz-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.5625em 0; + } + 100% { + background-position: 0 0; + } +} +@keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.5625em 0; + } + 100% { + background-position: 0 0; + } } @@ -1204,7 +1235,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { line-height: 1.275; } .oo-ui-textInputWidget .oo-ui-pendingElement-pending { - background-color: transparent; + background-color: #ddd; } .oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon, .oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator { @@ -1659,6 +1690,68 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { border-left-width: 0; } +.oo-ui-selectFileInputWidget { + width: 100%; + max-width: 50em; + margin-right: 0.5em; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button { + position: relative; + overflow: hidden; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + opacity: 0; + z-index: 1; + cursor: pointer; + padding-top: 100px; +} +.oo-ui-selectFileInputWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] { + display: none; +} +.oo-ui-selectFileInputWidget-info > .oo-ui-inputWidget-input { + pointer-events: none; +} +.oo-ui-selectFileInputWidget-label { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + text-overflow: ellipsis; +} +.oo-ui-selectFileInputWidget-clearButton { + position: absolute; + z-index: 2; +} +.oo-ui-selectFileInputWidget-empty .oo-ui-selectFileInputWidget-clearButton { + display: none; +} +.oo-ui-selectFileInputWidget-empty.oo-ui-widget-enabled .oo-ui-selectFileInputWidget-label { + cursor: default; +} +.oo-ui-selectFileInputWidget:last-child { + margin-right: 0; +} +.oo-ui-selectFileInputWidget-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + left: 0.5em; + right: 2.175em; + line-height: 2.3em; + margin: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + .oo-ui-defaultOverlay { position: absolute; top: 0; diff --git a/resources/lib/ooui/oojs-ui-core-wikimediaui.css b/resources/lib/ooui/oojs-ui-core-wikimediaui.css index 6766494240..344a9fcce6 100644 --- a/resources/lib/ooui/oojs-ui-core-wikimediaui.css +++ b/resources/lib/ooui/oojs-ui-core-wikimediaui.css @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.1 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-14T00:52:27Z + * Date: 2019-03-21T15:54:46Z */ .oo-ui-element-hidden { display: none !important; @@ -462,7 +462,38 @@ } .oo-ui-pendingElement-pending { - background-image: /* @embed */ url(themes/wikimediaui/images/textures/pending.gif); + background-color: #eaecf0; + background-image: -webkit-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-size: 1.42857143em 1.42857143em; + -webkit-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + -moz-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + animation: oo-ui-pendingElement-stripes 650ms linear infinite; +} +@-webkit-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.42857143em 0; + } + 100% { + background-position: 0 0; + } +} +@-moz-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.42857143em 0; + } + 100% { + background-position: 0 0; + } +} +@keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.42857143em 0; + } + 100% { + background-position: 0 0; + } } @@ -1540,7 +1571,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { line-height: 1.286; } .oo-ui-textInputWidget .oo-ui-pendingElement-pending { - background-color: transparent; + background-color: #eaecf0; } .oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-inputWidget-input { padding-left: 2.64285714em; @@ -2083,6 +2114,70 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { opacity: 1; } +.oo-ui-selectFileInputWidget { + width: 100%; + max-width: 50em; + margin-right: 0.5em; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button { + position: relative; + overflow: hidden; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + opacity: 0; + z-index: 1; + cursor: pointer; + padding-top: 100px; +} +.oo-ui-selectFileInputWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] { + display: none; +} +.oo-ui-selectFileInputWidget-info > .oo-ui-inputWidget-input { + pointer-events: none; +} +.oo-ui-selectFileInputWidget-label { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + text-overflow: ellipsis; +} +.oo-ui-selectFileInputWidget-clearButton { + position: absolute; + z-index: 2; +} +.oo-ui-selectFileInputWidget-empty .oo-ui-selectFileInputWidget-clearButton { + display: none; +} +.oo-ui-selectFileInputWidget-empty.oo-ui-widget-enabled .oo-ui-selectFileInputWidget-label { + cursor: default; +} +.oo-ui-selectFileInputWidget:last-child { + margin-right: 0; +} +.oo-ui-selectFileInputWidget-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + right: 2.85714286em; + padding-top: 0.57142857em; + padding-left: 0.57142857em; + padding-bottom: 0.57142857em; + white-space: nowrap; +} +.oo-ui-selectFileInputWidget.oo-ui-labelElement .oo-ui-selectFileInputWidget-label { + line-height: 1; +} + .oo-ui-defaultOverlay { position: absolute; top: 0; diff --git a/resources/lib/ooui/oojs-ui-core.js b/resources/lib/ooui/oojs-ui-core.js index 3ca6632b79..65f9558765 100644 --- a/resources/lib/ooui/oojs-ui-core.js +++ b/resources/lib/ooui/oojs-ui-core.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.1 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-20T23:07:02Z + * Date: 2019-03-21T15:54:37Z */ ( function ( OO ) { @@ -295,7 +295,7 @@ OO.ui.throttle = function ( func, wait ) { previous = 0, run = function () { timeout = null; - previous = OO.ui.now(); + previous = Date.now(); func.apply( context, args ); }; return function () { @@ -304,7 +304,7 @@ OO.ui.throttle = function ( func, wait ) { // period. If it's less, run the function immediately. If it's more, // set a timeout for the remaining time -- but don't replace an // existing timeout, since that'd indefinitely prolong the wait. - var remaining = wait - ( OO.ui.now() - previous ); + var remaining = wait - ( Date.now() - previous ); context = this; args = arguments; if ( remaining <= 0 ) { @@ -323,10 +323,12 @@ OO.ui.throttle = function ( func, wait ) { /** * A (possibly faster) way to get the current timestamp as an integer. * + * @deprecated Since 0.31.1; use `Date.now()` instead. * @return {number} Current timestamp, in milliseconds since the Unix epoch */ -OO.ui.now = Date.now || function () { - return new Date().getTime(); +OO.ui.now = function () { + OO.ui.warnDeprecation( 'OO.ui.now() is deprecated, use Date.now() instead' ); + return Date.now(); }; /** @@ -2118,7 +2120,7 @@ OO.ui.mixin.TabIndexedElement.prototype.getInputId = function () { OO.ui.mixin.TabIndexedElement.prototype.isLabelableNode = function ( $node ) { var labelableTags = [ 'button', 'meter', 'output', 'progress', 'select', 'textarea' ], - tagName = $node.prop( 'tagName' ).toLowerCase(); + tagName = ( $node.prop( 'tagName' ) || '' ).toLowerCase(); if ( tagName === 'input' && $node.attr( 'type' ) !== 'hidden' ) { return true; @@ -6420,6 +6422,7 @@ OO.ui.OptionWidget.prototype.getMatchText = function () { * Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See * the [OOUI documentation on MediaWiki] [2] for examples. * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options + * @cfg {boolean} [multiselect] Allow for multiple selections */ OO.ui.SelectWidget = function OoUiSelectWidget( config ) { // Configuration initialization @@ -6436,6 +6439,7 @@ OO.ui.SelectWidget = function OoUiSelectWidget( config ) { // Properties this.pressed = false; this.selecting = null; + this.multiselect = !!config.multiselect; this.onDocumentMouseUpHandler = this.onDocumentMouseUp.bind( this ); this.onDocumentMouseMoveHandler = this.onDocumentMouseMove.bind( this ); this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); @@ -6496,13 +6500,16 @@ OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupWidget ); * A `select` event is emitted when the selection is modified programmatically with the #selectItem * method. * - * @param {OO.ui.OptionWidget|null} item Selected item + * @param {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} items Currently selected items */ /** * @event choose + * * A `choose` event is emitted when an item is chosen with the #chooseItem method. + * * @param {OO.ui.OptionWidget} item Chosen item + * @param {boolean} selected Item is selected */ /** @@ -6699,12 +6706,13 @@ OO.ui.SelectWidget.prototype.onMouseLeave = function () { OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { var nextItem, handled = false, - currentItem = this.findHighlightedItem() || this.findSelectedItem(); + currentItem = this.findHighlightedItem(), + firstItem = this.getItems()[ 0 ]; if ( !this.isDisabled() && this.isVisible() ) { switch ( e.keyCode ) { case OO.ui.Keys.ENTER: - if ( currentItem && currentItem.constructor.static.highlightable ) { + if ( currentItem ) { // Was only highlighted, now let's select it. No-op if already selected. this.chooseItem( currentItem ); handled = true; @@ -6713,18 +6721,18 @@ OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { case OO.ui.Keys.UP: case OO.ui.Keys.LEFT: this.clearKeyPressBuffer(); - nextItem = this.findRelativeSelectableItem( currentItem, -1 ); + nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, -1 ) : firstItem; handled = true; break; case OO.ui.Keys.DOWN: case OO.ui.Keys.RIGHT: this.clearKeyPressBuffer(); - nextItem = this.findRelativeSelectableItem( currentItem, 1 ); + nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, 1 ) : firstItem; handled = true; break; case OO.ui.Keys.ESCAPE: case OO.ui.Keys.TAB: - if ( currentItem && currentItem.constructor.static.highlightable ) { + if ( currentItem ) { currentItem.setHighlighted( false ); } this.unbindDocumentKeyDownListener(); @@ -6944,20 +6952,36 @@ OO.ui.SelectWidget.prototype.findTargetItem = function ( e ) { return $option.data( 'oo-ui-optionWidget' ) || null; }; +/** + * Find all selected items, if there are any. If the widget allows for multiselect + * it will return an array of selected options. If the widget doesn't allow for + * multiselect, it will return the selected option or null if no item is selected. + * + * @return {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} If the widget is multiselect + * then return an array of selected items (or empty array), + * if the widget is not multiselect, return a single selected item, or `null` + * if no item is selected + */ +OO.ui.SelectWidget.prototype.findSelectedItems = function () { + var selected = this.items.filter( function ( item ) { + return item.isSelected(); + } ); + + return this.multiselect ? + selected : + selected[ 0 ] || null; +}; + /** * Find selected item. * - * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected + * @return {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} If the widget is multiselect + * then return an array of selected items (or empty array), + * if the widget is not multiselect, return a single selected item, or `null` + * if no item is selected */ OO.ui.SelectWidget.prototype.findSelectedItem = function () { - var i, len; - - for ( i = 0, len = this.items.length; i < len; i++ ) { - if ( this.items[ i ].isSelected() ) { - return this.items[ i ]; - } - } - return null; + return this.findSelectedItems(); }; /** @@ -7104,6 +7128,30 @@ OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) { return this.selectItem( itemFromData ); }; +/** + * Programmatically unselect an option by its reference. If the widget + * allows for multiple selections, there may be other items still selected; + * otherwise, no items will be selected. + * If no item is given, all selected items will be unselected. + * + * @param {OO.ui.OptionWidget} [item] Item to unselect + * @fires select + * @chainable + * @return {OO.ui.Widget} The widget, for chaining + */ +OO.ui.SelectWidget.prototype.unselectItem = function ( item ) { + if ( item ) { + item.setSelected( false ); + } else { + this.items.forEach( function ( item ) { + item.setSelected( false ); + } ); + } + + this.emit( 'select', this.findSelectedItems() ); + return this; +}; + /** * Programmatically select an option by its reference. If the `item` parameter is omitted, * all options will be deselected. @@ -7117,14 +7165,20 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) { var i, len, selected, changed = false; - for ( i = 0, len = this.items.length; i < len; i++ ) { - selected = this.items[ i ] === item; - if ( this.items[ i ].isSelected() !== selected ) { - this.items[ i ].setSelected( selected ); - changed = true; + if ( this.multiselect && item ) { + // Select the item directly + item.setSelected( true ); + } else { + for ( i = 0, len = this.items.length; i < len; i++ ) { + selected = this.items[ i ] === item; + if ( this.items[ i ].isSelected() !== selected ) { + this.items[ i ].setSelected( selected ); + changed = true; + } } } if ( changed ) { + // TODO: When should a non-highlightable element be selected? if ( item && !item.constructor.static.highlightable ) { if ( item ) { this.$focusOwner.attr( 'aria-activedescendant', item.getElementId() ); @@ -7132,7 +7186,7 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) { this.$focusOwner.removeAttr( 'aria-activedescendant' ); } } - this.emit( 'select', item ); + this.emit( 'select', this.findSelectedItems() ); } return this; @@ -7185,8 +7239,13 @@ OO.ui.SelectWidget.prototype.pressItem = function ( item ) { */ OO.ui.SelectWidget.prototype.chooseItem = function ( item ) { if ( item ) { - this.selectItem( item ); - this.emit( 'choose', item ); + if ( this.multiselect && item.isSelected() ) { + this.unselectItem( item ); + } else { + this.selectItem( item ); + } + + this.emit( 'choose', item, item.isSelected() ); } return this; @@ -7655,7 +7714,7 @@ OO.ui.MenuSelectWidget.prototype.onDocumentKeyDown = function ( e ) { break; case OO.ui.Keys.ESCAPE: case OO.ui.Keys.TAB: - if ( currentItem ) { + if ( currentItem && !this.multiselect ) { currentItem.setHighlighted( false ); } this.toggle( false ); @@ -7712,10 +7771,6 @@ OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () { section.toggle( showAll || !sectionEmpty ); } - if ( anyVisible && this.items.length && !exactMatch ) { - this.scrollItemIntoView( this.items[ 0 ] ); - } - if ( !anyVisible ) { this.highlightItem( null ); } @@ -7872,7 +7927,7 @@ OO.ui.MenuSelectWidget.prototype.clearItems = function () { * @inheritdoc */ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { - var change, originalHeight, flippedHeight; + var change, originalHeight, flippedHeight, selectedItem; visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length; change = visible !== this.isVisible(); @@ -7937,9 +7992,13 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { this.$focusOwner.attr( 'aria-expanded', 'true' ); - if ( this.findSelectedItem() ) { - this.$focusOwner.attr( 'aria-activedescendant', this.findSelectedItem().getElementId() ); - this.findSelectedItem().scrollElementIntoView( { duration: 0 } ); + selectedItem = this.findSelectedItem(); + if ( !this.multiselect && selectedItem ) { + // TODO: Verify if this is even needed; This is already done on highlight changes + // in SelectWidget#highlightItem, so we should just need to highlight the item we need to + // highlight here and not bother with attr or checking selections. + this.$focusOwner.attr( 'aria-activedescendant', selectedItem.getElementId() ); + selectedItem.scrollElementIntoView( { duration: 0 } ); } // Auto-hide @@ -7962,6 +8021,13 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { return this; }; +/** + * Scroll to the top of the menu + */ +OO.ui.MenuSelectWidget.prototype.scrollToTop = function () { + this.$element.scrollTop( 0 ); +}; + /** * DropdownWidgets are not menus themselves, rather they contain a menu of options created with * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that @@ -10397,7 +10463,7 @@ OO.ui.CheckboxMultiselectInputWidget.prototype.focus = function () { * // A TextInputWidget. * var textInput = new OO.ui.TextInputWidget( { * value: 'Text input' - * } ) + * } ); * $( document.body ).append( textInput.$element ); * * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs @@ -11117,6 +11183,7 @@ OO.ui.SearchInputWidget = function OoUiSearchInputWidget( config ) { this.connect( this, { change: 'onChange' } ); + this.$indicator.on( 'click', this.onIndicatorClick.bind( this ) ); // Initialization this.updateSearchIndicator(); @@ -11140,9 +11207,12 @@ OO.ui.SearchInputWidget.prototype.getSaneType = function () { }; /** - * @inheritdoc + * Handle click events on the indicator + * + * @param {jQuery.Event} e Click event + * @return {boolean} */ -OO.ui.SearchInputWidget.prototype.onIndicatorMouseDown = function ( e ) { +OO.ui.SearchInputWidget.prototype.onIndicatorClick = function ( e ) { if ( e.which === OO.ui.MouseButtons.LEFT ) { // Clear the text field this.setValue( '' ); @@ -11205,8 +11275,8 @@ OO.ui.SearchInputWidget.prototype.setReadOnly = function ( state ) { * // A MultilineTextInputWidget. * var multilineTextInput = new OO.ui.MultilineTextInputWidget( { * value: 'Text input on multiple lines' - * } ) - * $( 'body' ).append( multilineTextInput.$element ); + * } ); + * $( document.body ).append( multilineTextInput.$element ); * * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#MultilineTextInputWidget * @@ -12360,21 +12430,21 @@ OO.ui.FieldsetLayout.static.tagName = 'fieldset'; * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs * * @example - * // Example of a form layout that wraps a fieldset layout + * // Example of a form layout that wraps a fieldset layout. * var input1 = new OO.ui.TextInputWidget( { - * placeholder: 'Username' - * } ); - * var input2 = new OO.ui.TextInputWidget( { - * placeholder: 'Password', - * type: 'password' - * } ); - * var submit = new OO.ui.ButtonInputWidget( { - * label: 'Submit' - * } ); + * placeholder: 'Username' + * } ), + * input2 = new OO.ui.TextInputWidget( { + * placeholder: 'Password', + * type: 'password' + * } ), + * submit = new OO.ui.ButtonInputWidget( { + * label: 'Submit' + * } ), + * fieldset = new OO.ui.FieldsetLayout( { + * label: 'A form layout' + * } ); * - * var fieldset = new OO.ui.FieldsetLayout( { - * label: 'A form layout' - * } ); * fieldset.addItems( [ * new OO.ui.FieldLayout( input1, { * label: 'Username', @@ -12565,7 +12635,7 @@ OO.ui.PanelLayout.prototype.focus = function () { * Note that inline elements, such as OO.ui.ButtonWidgets, do not need this wrapper. * * @example - * // HorizontalLayout with a text input and a label + * // HorizontalLayout with a text input and a label. * var layout = new OO.ui.HorizontalLayout( { * items: [ * new OO.ui.LabelWidget( { label: 'Label' } ), @@ -12978,6 +13048,254 @@ OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { return this; }; +/** + * SelectFileInputWidgets allow for selecting files, using . These + * widgets can be configured with {@link OO.ui.mixin.IconElement icons}, {@link + * OO.ui.mixin.IndicatorElement indicators} and {@link OO.ui.mixin.TitledElement titles}. + * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples. + * + * SelectFileInputWidgets must be used in HTML forms, as getValue only returns the filename. + * + * @example + * // A file select input widget. + * var selectFile = new OO.ui.SelectFileInputWidget(); + * $( document.body ).append( selectFile.$element ); + * + * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets + * + * @class + * @extends OO.ui.InputWidget + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types. + * @cfg {string} [placeholder] Text to display when no file is selected. + * @cfg {Object} [button] Config to pass to select file button. + * @cfg {string} [icon] Icon to show next to file info + */ +OO.ui.SelectFileInputWidget = function OoUiSelectFileInputWidget( config ) { + config = config || {}; + + // Construct buttons before parent method is called (calling setDisabled) + this.selectButton = new OO.ui.ButtonWidget( $.extend( { + $element: $( '