From: James D. Forrester Date: Sat, 31 Jan 2015 01:18:26 +0000 (-0800) Subject: Update OOjs UI to v0.6.4 X-Git-Tag: 1.31.0-rc.0~12536 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22suivi_revisions%22%2C%22id_auteur=%24connecte%22%29%20.%20%22?a=commitdiff_plain;h=fc38b9b13af38cb10ceb0141277e8e46a27fbe14;p=lhc%2Fweb%2Fwiklou.git Update OOjs UI to v0.6.4 Release notes: https://git.wikimedia.org/blob/oojs%2Fui.git/v0.6.4/History.md Change-Id: I75746e6f702e2108e5282f9a37827a1a533ab57a --- diff --git a/composer.json b/composer.json index 1e75e6c3ac..2b7a40a514 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "cssjanus/cssjanus": "1.1.1", "leafo/lessphp": "0.5.0", - "oojs/oojs-ui": "0.6.3", + "oojs/oojs-ui": "0.6.4", "php": ">=5.3.3", "psr/log": "1.0.0", "wikimedia/cdb": "1.0.1", diff --git a/resources/lib/oojs-ui/i18n/bn.json b/resources/lib/oojs-ui/i18n/bn.json index 2696144446..c7df76857a 100644 --- a/resources/lib/oojs-ui/i18n/bn.json +++ b/resources/lib/oojs-ui/i18n/bn.json @@ -6,13 +6,20 @@ "Jayantanth", "Nasir8891", "Runab", - "Sayak Sarkar" + "Sayak Sarkar", + "Aftabuzzaman", + "RYasmeen (WMF)" ] }, "ooui-outline-control-move-down": "আইটেম নিচে স্থানান্তর", "ooui-outline-control-move-up": "আইটেম উপরে স্থানান্তর", "ooui-outline-control-remove": "আইটেম সরান", "ooui-toolbar-more": "আরও", + "ooui-toolgroup-expand": "আরও", + "ooui-toolgroup-collapse": "কম দেখাও", "ooui-dialog-message-accept": "ঠিক আছে", - "ooui-dialog-message-reject": "বাতিল" + "ooui-dialog-message-reject": "বাতিল", + "ooui-dialog-process-dismiss": "বাতিল করুন", + "ooui-dialog-process-retry": "আবার চেষ্টা করুন", + "ooui-dialog-process-continue": "অগ্রসর হোন" } diff --git a/resources/lib/oojs-ui/i18n/eu.json b/resources/lib/oojs-ui/i18n/eu.json index 62973d3bf3..e947582d14 100644 --- a/resources/lib/oojs-ui/i18n/eu.json +++ b/resources/lib/oojs-ui/i18n/eu.json @@ -3,10 +3,18 @@ "authors": [ "An13sa", "Unai Fdz. de Betoño", - "Xabier Armendaritz" + "Xabier Armendaritz", + "Subi" ] }, "ooui-outline-control-move-down": "Mugitu itema beherantz", "ooui-outline-control-move-up": "Mugitu itema gorantz", - "ooui-toolbar-more": "Gehiago" + "ooui-toolbar-more": "Gehiago", + "ooui-toolgroup-expand": "Gehiago", + "ooui-toolgroup-collapse": "Gutxiago", + "ooui-dialog-message-accept": "Ados", + "ooui-dialog-message-reject": "Utzi", + "ooui-dialog-process-error": "Zerbaitek huts egin du", + "ooui-dialog-process-retry": "Saiatu berriro", + "ooui-dialog-process-continue": "Jarraitu" } diff --git a/resources/lib/oojs-ui/oojs-ui-mediawiki.css b/resources/lib/oojs-ui/oojs-ui-mediawiki.css index 764f2eb47d..dea35fdc09 100644 --- a/resources/lib/oojs-ui/oojs-ui-mediawiki.css +++ b/resources/lib/oojs-ui/oojs-ui-mediawiki.css @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.6.3 + * OOjs UI v0.6.4 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-01-16T00:05:16Z + * Date: 2015-01-31T01:16:05Z */ .oo-ui-progressBarWidget-slide-frames from { margin-left: -40%; @@ -134,10 +134,6 @@ .oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { opacity: 0.2; } -.oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #ffffff; - background: #eeeeee; -} .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover, .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus { outline: none; @@ -191,6 +187,9 @@ color: #a7170f; box-shadow: none; } +.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button { + color: #eeeeee; +} .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { margin: 0.1em 0; padding: 0.2em 0.8em; @@ -220,7 +219,7 @@ .oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button { color: #ffffff; background: #eeeeee; - border: #eeeeee; + border: 1px solid #eeeeee; } .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button { color: #757575; @@ -488,6 +487,12 @@ .oo-ui-fieldLayout.oo-ui-fieldLayout-align-top.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label { padding: 0.5em 0; } +.oo-ui-fieldLayout > .oo-ui-popupButtonWidget { + margin-right: 0; +} +.oo-ui-fieldLayout > .oo-ui-popupButtonWidget:last-child { + margin-right: 0; +} .oo-ui-fieldLayout > .oo-ui-popupButtonWidget > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { margin-top: 0.25em; } @@ -509,6 +514,7 @@ } .oo-ui-actionFieldLayout-button { width: 1%; + white-space: nowrap; } .oo-ui-fieldsetLayout { position: relative; @@ -525,6 +531,16 @@ .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label { display: inline-block; } +.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help { + float: right; +} +.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help > .oo-ui-popupWidget > .oo-ui-popupWidget-popup { + z-index: 1; +} +.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help .oo-ui-fieldsetLayout-help-content { + padding: 0.5em 0.75em; + line-height: 1.5em; +} .oo-ui-fieldsetLayout + .oo-ui-fieldsetLayout { margin-top: 2em; } @@ -551,6 +567,48 @@ right: 0; bottom: 0; } +.oo-ui-menuLayout { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.oo-ui-menuLayout-menu, +.oo-ui-menuLayout-content { + position: absolute; + -webkit-transition: all ease-in-out 200ms; + -moz-transition: all ease-in-out 200ms; + -ms-transition: all ease-in-out 200ms; + -o-transition: all ease-in-out 200ms; + transition: all ease-in-out 200ms; +} +.oo-ui-menuLayout-content { + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.oo-ui-menuLayout-top .oo-ui-menuLayout-menu { + left: 0; + top: 0; + right: 0; +} +.oo-ui-menuLayout-after .oo-ui-menuLayout-menu { + top: 0; + right: 0; + bottom: 0; +} +.oo-ui-menuLayout-bottom .oo-ui-menuLayout-menu { + right: 0; + bottom: 0; + left: 0; +} +.oo-ui-menuLayout-before .oo-ui-menuLayout-menu { + bottom: 0; + left: 0; + top: 0; +} .oo-ui-panelLayout { position: relative; } @@ -824,6 +882,7 @@ padding: 0.25em 0 0.25em 0; border: 1px solid #aaaaaa; border-radius: 0.2em; + border-top-left-radius: 0; box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2), 0 0.1em 0 0 rgba(0, 0, 0, 0.1); } .oo-ui-menuToolGroup.oo-ui-widget-enabled:hover { @@ -943,7 +1002,9 @@ line-height: 1.5em; } .oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected, -.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed { +.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed, +.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted, +.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected { background-color: #d0d0d0; } .oo-ui-optionWidget.oo-ui-widget-disabled { @@ -975,6 +1036,14 @@ display: inline-block; white-space: nowrap; border-radius: 2px; + margin-right: 0.5em; +} +.oo-ui-buttonSelectWidget:last-child { + margin-right: 0; +} +.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button { + background: #999999; + color: #ffffff; } .oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button { border-radius: 0; @@ -989,9 +1058,6 @@ border-bottom-right-radius: 2px; border-top-right-radius: 2px; } -.oo-ui-radioSelectWidget { - padding: 0.75em 0 0.5em 0; -} .oo-ui-buttonOptionWidget { display: inline-block; padding: 0; @@ -1021,7 +1087,7 @@ } .oo-ui-radioOptionWidget { cursor: default; - padding: 0; + padding: 0.25em 0; background-color: transparent; } .oo-ui-radioOptionWidget .oo-ui-radioInputWidget, @@ -1034,9 +1100,6 @@ .oo-ui-radioOptionWidget.oo-ui-optionWidget-highlighted { background-color: transparent; } -.oo-ui-radioOptionWidget > .oo-ui-labelElement-label { - padding: 0 0.5em; -} .oo-ui-labelWidget { display: inline-block; } @@ -1067,11 +1130,29 @@ .oo-ui-buttonWidget { display: inline-block; vertical-align: middle; + margin-right: 0.5em; +} +.oo-ui-buttonWidget:last-child { + margin-right: 0; } .oo-ui-buttonGroupWidget { display: inline-block; white-space: nowrap; border-radius: 2px; + margin-right: 0.5em; +} +.oo-ui-buttonGroupWidget:last-child { + margin-right: 0; +} +.oo-ui-buttonGroupWidget .oo-ui-buttonWidget { + margin-right: 0; +} +.oo-ui-buttonGroupWidget .oo-ui-buttonWidget:last-child { + margin-right: 0; +} +.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed.oo-ui-buttonElement-active .oo-ui-buttonElement-button { + background: #999999; + color: #ffffff; } .oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button { border-radius: 0; @@ -1104,6 +1185,7 @@ width: 4em; border-radius: 1em; border: 1px #dddddd solid; + margin-right: 0.5em; } .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled { cursor: default; @@ -1130,6 +1212,9 @@ .oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow { display: none; } +.oo-ui-toggleSwitchWidget:last-child { + margin-right: 0; +} .oo-ui-toggleSwitchWidget-grip { top: 0.25em; left: 0.25em; @@ -1315,9 +1400,15 @@ .oo-ui-popupButtonWidget.oo-ui-buttonElement-framed > .oo-ui-popupWidget { left: 1.75em; } +.oo-ui-inputWidget { + margin-right: 0.5em; +} +.oo-ui-inputWidget:last-child { + margin-right: 0; +} .oo-ui-checkboxInputWidget { position: relative; - line-height: 2em; + line-height: 1.6em; white-space: nowrap; } .oo-ui-checkboxInputWidget * { @@ -1327,13 +1418,13 @@ .oo-ui-checkboxInputWidget input[type="checkbox"] { opacity: 0; margin: 0; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; max-width: none; } .oo-ui-checkboxInputWidget input[type="checkbox"] + span { cursor: pointer; - margin: 0 0.4em; + margin: 0 0.2em; } .oo-ui-checkboxInputWidget input[type="checkbox"] + span::before { -webkit-transition: background-size 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); @@ -1348,12 +1439,11 @@ position: absolute; left: 0; border-radius: 2px; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; background-color: white; border: 1px solid #777777; background-image: /* @embed */ url(themes/mediawiki/images/icons/check-constructive.svg); - background-size: 2em, 2em; background-repeat: no-repeat; background-position: center center; background-origin: border-box; @@ -1381,9 +1471,46 @@ .oo-ui-checkboxInputWidget input[type="checkbox"]:disabled:checked + span::before { background-image: /* @embed */ url(themes/mediawiki/images/icons/check-invert.svg); } +.oo-ui-dropdownInputWidget { + position: relative; + vertical-align: middle; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + max-width: 50em; +} +.oo-ui-dropdownInputWidget select { + display: inline-block; + width: 100%; + resize: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.oo-ui-dropdownInputWidget select { + height: 2.5em; + padding: 0.5em; + font-size: 1em; + font-family: sans-serif; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border: solid 1px #cccccc; +} +.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover, +.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus { + border-color: #aaaaaa; + outline: none; +} +.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select { + color: #cccccc; + border-color: #dddddd; + background-color: #f3f3f3; +} .oo-ui-radioInputWidget { position: relative; - line-height: 2em; + line-height: 1.6em; white-space: nowrap; } .oo-ui-radioInputWidget * { @@ -1393,13 +1520,13 @@ .oo-ui-radioInputWidget input[type="radio"] { opacity: 0; margin: 0; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; max-width: none; } .oo-ui-radioInputWidget input[type="radio"] + span { cursor: pointer; - margin: 0 0.4em; + margin: 0 0.2em; } .oo-ui-radioInputWidget input[type="radio"] + span::before { -webkit-transition: background-size 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); @@ -1414,8 +1541,8 @@ position: absolute; left: 0; border-radius: 100%; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; background: white; border: 1px solid #777777; background-image: /* @embed */ url(themes/mediawiki/images/icons/circle-constructive.svg); @@ -1483,6 +1610,16 @@ .oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator { right: 0; } +.oo-ui-textInputWidget > .oo-ui-labelElement-label { + position: absolute; + top: 0; +} +.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label { + right: 0; +} +.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label { + left: 0; +} .oo-ui-textInputWidget input, .oo-ui-textInputWidget textarea { padding: 0.5em 0.5em 0.5em 0.9em; @@ -1539,7 +1676,7 @@ } .oo-ui-textInputWidget.oo-ui-iconElement input, .oo-ui-textInputWidget.oo-ui-iconElement textarea { - padding-left: 2.4em; + padding-left: 2.75em; } .oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon { left: 0.4em; @@ -1556,12 +1693,23 @@ height: 100%; background-position: left center; } +.oo-ui-textInputWidget > .oo-ui-labelElement-label { + padding: 0.4em; + line-height: 1.5em; + color: #888888; +} +.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label { + padding-right: 2.4em; +} +.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label { + padding-left: 2.9em; +} .oo-ui-menuSelectWidget { position: absolute; background: #ffffff; margin-top: -1px; border: solid 1px #aaaaaa; - border-radius: 0 0 0.1em 0.1em; + border-radius: 0 0 0.2em 0.2em; padding-bottom: 0.25em; box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2), 0 0.1em 0 0 rgba(0, 0, 0, 0.2); } @@ -1574,6 +1722,7 @@ } .oo-ui-menuOptionWidget { position: relative; + padding: 0.5em 1em; } .oo-ui-menuOptionWidget .oo-ui-iconElement-icon { display: none; @@ -1585,10 +1734,15 @@ display: block; } .oo-ui-menuOptionWidget.oo-ui-optionWidget-selected { - background-color: transparent; + background-color: #999999; + color: #ffffff; +} +.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon { + display: none; } .oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted { background-color: #eeeeee; + color: black; } .oo-ui-menuSectionOptionWidget { cursor: default; @@ -1601,6 +1755,7 @@ margin: 0.25em 0; width: 100%; max-width: 50em; + margin-right: 0.5em; } .oo-ui-dropdownWidget-handle { width: 100%; @@ -1628,6 +1783,9 @@ .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle { cursor: default; } +.oo-ui-dropdownWidget:last-child { + margin-right: 0; +} .oo-ui-dropdownWidget-handle { height: 2.5em; border: solid 1px #cccccc; @@ -1766,11 +1924,15 @@ position: relative; width: 100%; max-width: 50em; + margin-right: 0.5em; } .oo-ui-comboBoxWidget > .oo-ui-menuSelectWidget { z-index: 1; width: 100%; } +.oo-ui-comboBoxWidget:last-child { + margin-right: 0; +} .oo-ui-comboBoxWidget .oo-ui-textInputWidget input, .oo-ui-comboBoxWidget .oo-ui-textInputWidget textarea { height: 2.35em; @@ -1787,9 +1949,6 @@ -moz-box-sizing: border-box; box-sizing: border-box; } -.oo-ui-searchWidget { - border: solid 1px #cccccc; -} .oo-ui-searchWidget-query { position: absolute; top: 0; @@ -2215,7 +2374,7 @@ .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-load { opacity: 1; } -.oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-setup > .oo-ui-window-frame { +.oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-ready > .oo-ui-window-frame { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); diff --git a/resources/lib/oojs-ui/oojs-ui-mediawiki.js b/resources/lib/oojs-ui/oojs-ui-mediawiki.js index a0a5a32fe6..75ffcc18a6 100644 --- a/resources/lib/oojs-ui/oojs-ui-mediawiki.js +++ b/resources/lib/oojs-ui/oojs-ui-mediawiki.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.6.3 + * OOjs UI v0.6.4 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-01-16T00:05:04Z + * Date: 2015-01-31T01:15:57Z */ /** * @class @@ -51,7 +51,7 @@ OO.ui.MediaWikiTheme.prototype.getElementClasses = function ( element ) { } for ( variant in variants ) { - classes[variants[variant] ? 'on' : 'off'].push( 'oo-ui-image-' + variant ); + classes[ variants[ variant ] ? 'on' : 'off' ].push( 'oo-ui-image-' + variant ); } return classes; diff --git a/resources/lib/oojs-ui/oojs-ui-mediawiki.svg.css b/resources/lib/oojs-ui/oojs-ui-mediawiki.svg.css index 474304b3a8..f4e7371aec 100644 --- a/resources/lib/oojs-ui/oojs-ui-mediawiki.svg.css +++ b/resources/lib/oojs-ui/oojs-ui-mediawiki.svg.css @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.6.3 + * OOjs UI v0.6.4 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-01-16T00:05:16Z + * Date: 2015-01-31T01:16:05Z */ .oo-ui-progressBarWidget-slide-frames from { margin-left: -40%; @@ -134,10 +134,6 @@ .oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { opacity: 0.2; } -.oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #ffffff; - background: #eeeeee; -} .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover, .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus { outline: none; @@ -191,6 +187,9 @@ color: #a7170f; box-shadow: none; } +.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button { + color: #eeeeee; +} .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { margin: 0.1em 0; padding: 0.2em 0.8em; @@ -220,7 +219,7 @@ .oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button { color: #ffffff; background: #eeeeee; - border: #eeeeee; + border: 1px solid #eeeeee; } .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button { color: #757575; @@ -488,6 +487,12 @@ .oo-ui-fieldLayout.oo-ui-fieldLayout-align-top.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label { padding: 0.5em 0; } +.oo-ui-fieldLayout > .oo-ui-popupButtonWidget { + margin-right: 0; +} +.oo-ui-fieldLayout > .oo-ui-popupButtonWidget:last-child { + margin-right: 0; +} .oo-ui-fieldLayout > .oo-ui-popupButtonWidget > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { margin-top: 0.25em; } @@ -509,6 +514,7 @@ } .oo-ui-actionFieldLayout-button { width: 1%; + white-space: nowrap; } .oo-ui-fieldsetLayout { position: relative; @@ -525,6 +531,16 @@ .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label { display: inline-block; } +.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help { + float: right; +} +.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help > .oo-ui-popupWidget > .oo-ui-popupWidget-popup { + z-index: 1; +} +.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help .oo-ui-fieldsetLayout-help-content { + padding: 0.5em 0.75em; + line-height: 1.5em; +} .oo-ui-fieldsetLayout + .oo-ui-fieldsetLayout { margin-top: 2em; } @@ -551,6 +567,48 @@ right: 0; bottom: 0; } +.oo-ui-menuLayout { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.oo-ui-menuLayout-menu, +.oo-ui-menuLayout-content { + position: absolute; + -webkit-transition: all ease-in-out 200ms; + -moz-transition: all ease-in-out 200ms; + -ms-transition: all ease-in-out 200ms; + -o-transition: all ease-in-out 200ms; + transition: all ease-in-out 200ms; +} +.oo-ui-menuLayout-content { + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.oo-ui-menuLayout-top .oo-ui-menuLayout-menu { + left: 0; + top: 0; + right: 0; +} +.oo-ui-menuLayout-after .oo-ui-menuLayout-menu { + top: 0; + right: 0; + bottom: 0; +} +.oo-ui-menuLayout-bottom .oo-ui-menuLayout-menu { + right: 0; + bottom: 0; + left: 0; +} +.oo-ui-menuLayout-before .oo-ui-menuLayout-menu { + bottom: 0; + left: 0; + top: 0; +} .oo-ui-panelLayout { position: relative; } @@ -824,6 +882,7 @@ padding: 0.25em 0 0.25em 0; border: 1px solid #aaaaaa; border-radius: 0.2em; + border-top-left-radius: 0; box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2), 0 0.1em 0 0 rgba(0, 0, 0, 0.1); } .oo-ui-menuToolGroup.oo-ui-widget-enabled:hover { @@ -943,7 +1002,9 @@ line-height: 1.5em; } .oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected, -.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed { +.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed, +.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted, +.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected { background-color: #d0d0d0; } .oo-ui-optionWidget.oo-ui-widget-disabled { @@ -975,6 +1036,14 @@ display: inline-block; white-space: nowrap; border-radius: 2px; + margin-right: 0.5em; +} +.oo-ui-buttonSelectWidget:last-child { + margin-right: 0; +} +.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button { + background: #999999; + color: #ffffff; } .oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button { border-radius: 0; @@ -989,9 +1058,6 @@ border-bottom-right-radius: 2px; border-top-right-radius: 2px; } -.oo-ui-radioSelectWidget { - padding: 0.75em 0 0.5em 0; -} .oo-ui-buttonOptionWidget { display: inline-block; padding: 0; @@ -1021,7 +1087,7 @@ } .oo-ui-radioOptionWidget { cursor: default; - padding: 0; + padding: 0.25em 0; background-color: transparent; } .oo-ui-radioOptionWidget .oo-ui-radioInputWidget, @@ -1034,9 +1100,6 @@ .oo-ui-radioOptionWidget.oo-ui-optionWidget-highlighted { background-color: transparent; } -.oo-ui-radioOptionWidget > .oo-ui-labelElement-label { - padding: 0 0.5em; -} .oo-ui-labelWidget { display: inline-block; } @@ -1067,11 +1130,29 @@ .oo-ui-buttonWidget { display: inline-block; vertical-align: middle; + margin-right: 0.5em; +} +.oo-ui-buttonWidget:last-child { + margin-right: 0; } .oo-ui-buttonGroupWidget { display: inline-block; white-space: nowrap; border-radius: 2px; + margin-right: 0.5em; +} +.oo-ui-buttonGroupWidget:last-child { + margin-right: 0; +} +.oo-ui-buttonGroupWidget .oo-ui-buttonWidget { + margin-right: 0; +} +.oo-ui-buttonGroupWidget .oo-ui-buttonWidget:last-child { + margin-right: 0; +} +.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed.oo-ui-buttonElement-active .oo-ui-buttonElement-button { + background: #999999; + color: #ffffff; } .oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button { border-radius: 0; @@ -1104,6 +1185,7 @@ width: 4em; border-radius: 1em; border: 1px #dddddd solid; + margin-right: 0.5em; } .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled { cursor: default; @@ -1130,6 +1212,9 @@ .oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow { display: none; } +.oo-ui-toggleSwitchWidget:last-child { + margin-right: 0; +} .oo-ui-toggleSwitchWidget-grip { top: 0.25em; left: 0.25em; @@ -1315,9 +1400,15 @@ .oo-ui-popupButtonWidget.oo-ui-buttonElement-framed > .oo-ui-popupWidget { left: 1.75em; } +.oo-ui-inputWidget { + margin-right: 0.5em; +} +.oo-ui-inputWidget:last-child { + margin-right: 0; +} .oo-ui-checkboxInputWidget { position: relative; - line-height: 2em; + line-height: 1.6em; white-space: nowrap; } .oo-ui-checkboxInputWidget * { @@ -1327,13 +1418,13 @@ .oo-ui-checkboxInputWidget input[type="checkbox"] { opacity: 0; margin: 0; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; max-width: none; } .oo-ui-checkboxInputWidget input[type="checkbox"] + span { cursor: pointer; - margin: 0 0.4em; + margin: 0 0.2em; } .oo-ui-checkboxInputWidget input[type="checkbox"] + span::before { -webkit-transition: background-size 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); @@ -1348,12 +1439,11 @@ position: absolute; left: 0; border-radius: 2px; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; background-color: white; border: 1px solid #777777; background-image: /* @embed */ url(themes/mediawiki/images/icons/check-constructive.svg); - background-size: 2em, 2em; background-repeat: no-repeat; background-position: center center; background-origin: border-box; @@ -1381,9 +1471,46 @@ .oo-ui-checkboxInputWidget input[type="checkbox"]:disabled:checked + span::before { background-image: /* @embed */ url(themes/mediawiki/images/icons/check-invert.svg); } +.oo-ui-dropdownInputWidget { + position: relative; + vertical-align: middle; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + max-width: 50em; +} +.oo-ui-dropdownInputWidget select { + display: inline-block; + width: 100%; + resize: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.oo-ui-dropdownInputWidget select { + height: 2.5em; + padding: 0.5em; + font-size: 1em; + font-family: sans-serif; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border: solid 1px #cccccc; +} +.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover, +.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus { + border-color: #aaaaaa; + outline: none; +} +.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select { + color: #cccccc; + border-color: #dddddd; + background-color: #f3f3f3; +} .oo-ui-radioInputWidget { position: relative; - line-height: 2em; + line-height: 1.6em; white-space: nowrap; } .oo-ui-radioInputWidget * { @@ -1393,13 +1520,13 @@ .oo-ui-radioInputWidget input[type="radio"] { opacity: 0; margin: 0; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; max-width: none; } .oo-ui-radioInputWidget input[type="radio"] + span { cursor: pointer; - margin: 0 0.4em; + margin: 0 0.2em; } .oo-ui-radioInputWidget input[type="radio"] + span::before { -webkit-transition: background-size 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); @@ -1414,8 +1541,8 @@ position: absolute; left: 0; border-radius: 100%; - width: 2em; - height: 2em; + width: 1.6em; + height: 1.6em; background: white; border: 1px solid #777777; background-image: /* @embed */ url(themes/mediawiki/images/icons/circle-constructive.svg); @@ -1483,6 +1610,16 @@ .oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator { right: 0; } +.oo-ui-textInputWidget > .oo-ui-labelElement-label { + position: absolute; + top: 0; +} +.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label { + right: 0; +} +.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label { + left: 0; +} .oo-ui-textInputWidget input, .oo-ui-textInputWidget textarea { padding: 0.5em 0.5em 0.5em 0.9em; @@ -1539,7 +1676,7 @@ } .oo-ui-textInputWidget.oo-ui-iconElement input, .oo-ui-textInputWidget.oo-ui-iconElement textarea { - padding-left: 2.4em; + padding-left: 2.75em; } .oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon { left: 0.4em; @@ -1556,12 +1693,23 @@ height: 100%; background-position: left center; } +.oo-ui-textInputWidget > .oo-ui-labelElement-label { + padding: 0.4em; + line-height: 1.5em; + color: #888888; +} +.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label { + padding-right: 2.4em; +} +.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label { + padding-left: 2.9em; +} .oo-ui-menuSelectWidget { position: absolute; background: #ffffff; margin-top: -1px; border: solid 1px #aaaaaa; - border-radius: 0 0 0.1em 0.1em; + border-radius: 0 0 0.2em 0.2em; padding-bottom: 0.25em; box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2), 0 0.1em 0 0 rgba(0, 0, 0, 0.2); } @@ -1574,6 +1722,7 @@ } .oo-ui-menuOptionWidget { position: relative; + padding: 0.5em 1em; } .oo-ui-menuOptionWidget .oo-ui-iconElement-icon { display: none; @@ -1585,10 +1734,15 @@ display: block; } .oo-ui-menuOptionWidget.oo-ui-optionWidget-selected { - background-color: transparent; + background-color: #999999; + color: #ffffff; +} +.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon { + display: none; } .oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted { background-color: #eeeeee; + color: black; } .oo-ui-menuSectionOptionWidget { cursor: default; @@ -1601,6 +1755,7 @@ margin: 0.25em 0; width: 100%; max-width: 50em; + margin-right: 0.5em; } .oo-ui-dropdownWidget-handle { width: 100%; @@ -1628,6 +1783,9 @@ .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle { cursor: default; } +.oo-ui-dropdownWidget:last-child { + margin-right: 0; +} .oo-ui-dropdownWidget-handle { height: 2.5em; border: solid 1px #cccccc; @@ -1766,11 +1924,15 @@ position: relative; width: 100%; max-width: 50em; + margin-right: 0.5em; } .oo-ui-comboBoxWidget > .oo-ui-menuSelectWidget { z-index: 1; width: 100%; } +.oo-ui-comboBoxWidget:last-child { + margin-right: 0; +} .oo-ui-comboBoxWidget .oo-ui-textInputWidget input, .oo-ui-comboBoxWidget .oo-ui-textInputWidget textarea { height: 2.35em; @@ -1787,9 +1949,6 @@ -moz-box-sizing: border-box; box-sizing: border-box; } -.oo-ui-searchWidget { - border: solid 1px #cccccc; -} .oo-ui-searchWidget-query { position: absolute; top: 0; @@ -2215,7 +2374,7 @@ .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-load { opacity: 1; } -.oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-setup > .oo-ui-window-frame { +.oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-ready > .oo-ui-window-frame { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); diff --git a/resources/lib/oojs-ui/oojs-ui.js b/resources/lib/oojs-ui/oojs-ui.js index e0c98d03e7..f013b06fb4 100644 --- a/resources/lib/oojs-ui/oojs-ui.js +++ b/resources/lib/oojs-ui/oojs-ui.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.6.3 + * OOjs UI v0.6.4 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-01-16T00:05:04Z + * Date: 2015-01-31T01:15:57Z */ ( function ( OO ) { @@ -70,24 +70,24 @@ OO.ui.getLocalValue = function ( obj, lang, fallback ) { var i, len, langs; // Requested language - if ( obj[lang] ) { - return obj[lang]; + if ( obj[ lang ] ) { + return obj[ lang ]; } // Known user language langs = OO.ui.getUserLanguages(); for ( i = 0, len = langs.length; i < len; i++ ) { - lang = langs[i]; - if ( obj[lang] ) { - return obj[lang]; + lang = langs[ i ]; + if ( obj[ lang ] ) { + return obj[ lang ]; } } // Fallback language - if ( obj[fallback] ) { - return obj[fallback]; + if ( obj[ fallback ] ) { + return obj[ fallback ]; } // First existing language for ( lang in obj ) { - return obj[lang]; + return obj[ lang ]; } return undefined; @@ -110,7 +110,7 @@ OO.ui.contains = function ( containers, contained, matchContainers ) { containers = [ containers ]; } for ( i = containers.length - 1; i >= 0; i-- ) { - if ( ( matchContainers && contained === containers[i] ) || $.contains( containers[i], contained ) ) { + if ( ( matchContainers && contained === containers[ i ] ) || $.contains( containers[ i ], contained ) ) { return true; } } @@ -171,13 +171,13 @@ OO.ui.contains = function ( containers, contained, matchContainers ) { * @return {string} Translated message with parameters substituted */ OO.ui.msg = function ( key ) { - var message = messages[key], + var message = messages[ key ], params = Array.prototype.slice.call( arguments, 1 ); if ( typeof message === 'string' ) { // Perform $1 substitution message = message.replace( /\$(\d+)/g, function ( unused, n ) { var i = parseInt( n, 10 ); - return params[i - 1] !== undefined ? params[i - 1] : '$' + n; + return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; } ); } else { // Return placeholder if message not found @@ -404,7 +404,7 @@ OO.ui.ActionSet.prototype.isSpecial = function ( action ) { var flag; for ( flag in this.special ) { - if ( action === this.special[flag] ) { + if ( action === this.special[ flag ] ) { return true; } } @@ -432,13 +432,13 @@ OO.ui.ActionSet.prototype.get = function ( filters ) { // Collect category candidates matches = []; for ( category in this.categorized ) { - list = filters[category]; + list = filters[ category ]; if ( list ) { if ( !Array.isArray( list ) ) { list = [ list ]; } for ( i = 0, len = list.length; i < len; i++ ) { - actions = this.categorized[category][list[i]]; + actions = this.categorized[ category ][ list[ i ] ]; if ( Array.isArray( actions ) ) { matches.push.apply( matches, actions ); } @@ -447,7 +447,7 @@ OO.ui.ActionSet.prototype.get = function ( filters ) { } // Remove by boolean filters for ( i = 0, len = matches.length; i < len; i++ ) { - match = matches[i]; + match = matches[ i ]; if ( ( filters.visible !== undefined && match.isVisible() !== filters.visible ) || ( filters.disabled !== undefined && match.isDisabled() !== filters.disabled ) @@ -459,7 +459,7 @@ OO.ui.ActionSet.prototype.get = function ( filters ) { } // Remove duplicates for ( i = 0, len = matches.length; i < len; i++ ) { - match = matches[i]; + match = matches[ i ]; index = matches.lastIndexOf( match ); while ( index !== i ) { matches.splice( index, 1 ); @@ -513,7 +513,7 @@ OO.ui.ActionSet.prototype.setMode = function ( mode ) { this.changing = true; for ( i = 0, len = this.list.length; i < len; i++ ) { - action = this.list[i]; + action = this.list[ i ]; action.toggle( action.hasMode( mode ) ); } @@ -537,10 +537,10 @@ OO.ui.ActionSet.prototype.setAbilities = function ( actions ) { var i, len, action, item; for ( i = 0, len = this.list.length; i < len; i++ ) { - item = this.list[i]; + item = this.list[ i ]; action = item.getAction(); - if ( actions[action] !== undefined ) { - item.setDisabled( !actions[action] ); + if ( actions[ action ] !== undefined ) { + item.setDisabled( !actions[ action ] ); } } @@ -583,7 +583,7 @@ OO.ui.ActionSet.prototype.add = function ( actions ) { this.changing = true; for ( i = 0, len = actions.length; i < len; i++ ) { - action = actions[i]; + action = actions[ i ]; action.connect( this, { click: [ 'emit', 'click', action ], resize: [ 'emit', 'resize', action ], @@ -612,7 +612,7 @@ OO.ui.ActionSet.prototype.remove = function ( actions ) { this.changing = true; for ( i = 0, len = actions.length; i < len; i++ ) { - action = actions[i]; + action = actions[ i ]; index = this.list.indexOf( action ); if ( index !== -1 ) { action.disconnect( this ); @@ -640,7 +640,7 @@ OO.ui.ActionSet.prototype.clear = function () { this.changing = true; for ( i = 0, len = this.list.length; i < len; i++ ) { - action = this.list[i]; + action = this.list[ i ]; action.disconnect( this ); } @@ -672,31 +672,31 @@ OO.ui.ActionSet.prototype.organize = function () { this.special = {}; this.others = []; for ( i = 0, iLen = this.list.length; i < iLen; i++ ) { - action = this.list[i]; + action = this.list[ i ]; if ( action.isVisible() ) { // Populate categories for ( category in this.categories ) { - if ( !this.categorized[category] ) { - this.categorized[category] = {}; + if ( !this.categorized[ category ] ) { + this.categorized[ category ] = {}; } - list = action[this.categories[category]](); + list = action[ this.categories[ category ] ](); if ( !Array.isArray( list ) ) { list = [ list ]; } for ( j = 0, jLen = list.length; j < jLen; j++ ) { - item = list[j]; - if ( !this.categorized[category][item] ) { - this.categorized[category][item] = []; + item = list[ j ]; + if ( !this.categorized[ category ][ item ] ) { + this.categorized[ category ][ item ] = []; } - this.categorized[category][item].push( action ); + this.categorized[ category ][ item ].push( action ); } } // Populate special/others special = false; for ( j = 0, jLen = specialFlags.length; j < jLen; j++ ) { - flag = specialFlags[j]; - if ( !this.special[flag] && action.hasFlag( flag ) ) { - this.special[flag] = action; + flag = specialFlags[ j ]; + if ( !this.special[ flag ] && action.hasFlag( flag ) ) { + this.special[ flag ] = action; special = true; break; } @@ -805,7 +805,7 @@ OO.ui.Element.static.getJQuery = function ( context, $iframe ) { */ OO.ui.Element.static.getDocument = function ( obj ) { // jQuery - selections created "offscreen" won't have a context, so .context isn't reliable - return ( obj[0] && obj[0].ownerDocument ) || + return ( obj[ 0 ] && obj[ 0 ].ownerDocument ) || // Empty jQuery selections might have a context obj.context || // HTMLElement @@ -840,7 +840,7 @@ OO.ui.Element.static.getDir = function ( obj ) { var isDoc, isWin; if ( obj instanceof jQuery ) { - obj = obj[0]; + obj = obj[ 0 ]; } isDoc = obj.nodeType === 9; isWin = obj.document !== undefined; @@ -880,8 +880,8 @@ OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) { // Get iframe element frames = from.parent.document.getElementsByTagName( 'iframe' ); for ( i = 0, len = frames.length; i < len; i++ ) { - if ( frames[i].contentWindow === from ) { - frame = frames[i]; + if ( frames[ i ].contentWindow === from ) { + frame = frames[ i ]; break; } } @@ -1057,14 +1057,14 @@ OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) } while ( $parent.length ) { - if ( $parent[0] === this.getRootScrollableElement( el ) ) { - return $parent[0]; + if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) { + return $parent[ 0 ]; } i = props.length; while ( i-- ) { - val = $parent.css( props[i] ); + val = $parent.css( props[ i ] ); if ( val === 'auto' || val === 'scroll' ) { - return $parent[0]; + return $parent[ 0 ]; } } $parent = $parent.parent(); @@ -1143,6 +1143,33 @@ OO.ui.Element.static.scrollIntoView = function ( el, config ) { } }; +/** + * Force the browser to reconsider whether it really needs to render scrollbars inside the element + * and reserve space for them, because it probably doesn't. + * + * Workaround primarily for , but also + * similar bugs in other browsers. "Just" forcing a reflow is not sufficient in all cases, we need + * to first actually detach (or hide, but detaching is simpler) all children, *then* force a reflow, + * and then reattach (or show) them back. + * + * @static + * @param {HTMLElement} el Element to reconsider the scrollbars on + */ +OO.ui.Element.static.reconsiderScrollbars = function ( el ) { + var i, len, nodes = []; + // Detach all children + while ( el.firstChild ) { + nodes.push( el.firstChild ); + el.removeChild( el.firstChild ); + } + // Force reflow + void el.offsetHeight; + // Reattach all children + for ( i = 0, len = nodes.length; i < len; i++ ) { + el.appendChild( nodes[ i ] ); + } +}; + /* Methods */ /** @@ -1177,7 +1204,7 @@ OO.ui.Element.prototype.supports = function ( methods ) { methods = $.isArray( methods ) ? methods : [ methods ]; for ( i = 0, len = methods.length; i < len; i++ ) { - if ( $.isFunction( this[methods[i]] ) ) { + if ( $.isFunction( this[ methods[ i ] ] ) ) { support++; } } @@ -1223,7 +1250,7 @@ OO.ui.Element.prototype.getTagName = function () { * @return {boolean} The element is attached to the DOM */ OO.ui.Element.prototype.isElementAttached = function () { - return $.contains( this.getElementDocument(), this.$element[0] ); + return $.contains( this.getElementDocument(), this.$element[ 0 ] ); }; /** @@ -1250,7 +1277,7 @@ OO.ui.Element.prototype.getElementWindow = function () { * Get closest scrollable container. */ OO.ui.Element.prototype.getClosestScrollableElementContainer = function () { - return OO.ui.Element.static.getClosestScrollableContainer( this.$element[0] ); + return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] ); }; /** @@ -1279,7 +1306,7 @@ OO.ui.Element.prototype.setElementGroup = function ( group ) { * @param {Object} [config] Configuration options */ OO.ui.Element.prototype.scrollElementIntoView = function ( config ) { - return OO.ui.Element.static.scrollIntoView( this.$element[0], config ); + return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config ); }; /** @@ -1397,6 +1424,7 @@ OO.ui.Widget.prototype.setDisabled = function ( disabled ) { if ( isDisabled !== this.wasDisabled ) { this.$element.toggleClass( 'oo-ui-widget-disabled', isDisabled ); this.$element.toggleClass( 'oo-ui-widget-enabled', !isDisabled ); + this.$element.attr( 'aria-disabled', isDisabled.toString() ); this.emit( 'disable', isDisabled ); this.updateThemeClasses(); } @@ -1556,7 +1584,7 @@ OO.ui.Window.static.transplantStyles = function ( parentDoc, frameDoc, timeout ) deferred = $.Deferred(); for ( i = 0, numSheets = parentDoc.styleSheets.length; i < numSheets; i++ ) { - styleNode = parentDoc.styleSheets[i].ownerNode; + styleNode = parentDoc.styleSheets[ i ].ownerNode; if ( styleNode.disabled ) { continue; } @@ -1568,7 +1596,7 @@ OO.ui.Window.static.transplantStyles = function ( parentDoc, frameDoc, timeout ) // Internal stylesheet; just copy the text // For IE10 we need to fall back to .cssText, BUT that's undefined in // other browsers, so fall back to '' rather than 'undefined' - styleText = styleNode.textContent || parentDoc.styleSheets[i].cssText || ''; + styleText = styleNode.textContent || parentDoc.styleSheets[ i ].cssText || ''; } // Create a node with a unique ID that we're going to monitor to see when the CSS @@ -1646,7 +1674,7 @@ OO.ui.Window.static.transplantStyles = function ( parentDoc, frameDoc, timeout ) */ OO.ui.Window.prototype.onMouseDown = function ( e ) { // Prevent clicking on the click-block from stealing focus - if ( e.target === this.$element[0] ) { + if ( e.target === this.$element[ 0 ] ) { return false; } }; @@ -1749,7 +1777,7 @@ OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) { // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements. // Disable transitions first, otherwise we'll get values from when the window was animating. var oldTransition, - styleObj = this.$frame[0].style; + styleObj = this.$frame[ 0 ].style; oldTransition = styleObj.transition || styleObj.OTransition || styleObj.MsTransition || styleObj.MozTransition || styleObj.WebkitTransition; styleObj.transition = styleObj.OTransition = styleObj.MsTransition = @@ -1769,8 +1797,8 @@ OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) { OO.ui.Window.prototype.getContentHeight = function () { var bodyHeight, win = this, - bodyStyleObj = this.$body[0].style, - frameStyleObj = this.$frame[0].style; + bodyStyleObj = this.$body[ 0 ].style, + frameStyleObj = this.$frame[ 0 ].style; // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements. // Disable transitions first, otherwise we'll get values from when the window was animating. @@ -1802,7 +1830,7 @@ OO.ui.Window.prototype.getContentHeight = function () { * @return {number} Height of content */ OO.ui.Window.prototype.getBodyHeight = function () { - return this.$body[0].scrollHeight; + return this.$body[ 0 ].scrollHeight; }; /** @@ -1960,6 +1988,16 @@ OO.ui.Window.prototype.setManager = function ( manager ) { */ OO.ui.Window.prototype.setSize = function ( size ) { this.size = size; + this.updateSize(); + return this; +}; + +/** + * Update the window size. + * + * @chainable + */ +OO.ui.Window.prototype.updateSize = function () { this.manager.updateWindowSize( this ); return this; }; @@ -1981,7 +2019,7 @@ OO.ui.Window.prototype.setSize = function ( size ) { OO.ui.Window.prototype.setDimensions = function ( dim ) { var height, win = this, - styleObj = this.$frame[0].style; + styleObj = this.$frame[ 0 ].style; // Calculate the height we need to set using the correct width if ( dim.height === undefined ) { @@ -2137,7 +2175,7 @@ OO.ui.Window.prototype.hold = function ( data ) { // Blur the focused element if ( $focus.length ) { - $focus[0].blur(); + $focus[ 0 ].blur(); } // Force redraw by asking the browser to measure the elements' widths @@ -2253,7 +2291,7 @@ OO.ui.Window.prototype.load = function () { this.$document = this.$( doc ); // Initialization - this.constructor.static.transplantStyles( this.getElementDocument(), this.$document[0] ) + this.constructor.static.transplantStyles( this.getElementDocument(), this.$document[ 0 ] ) .always( function () { // Initialize isolated windows win.initialize(); @@ -2473,7 +2511,7 @@ OO.ui.Dialog.prototype.getSetupProcess = function ( data ) { ); for ( i = 0, len = actions.length; i < len; i++ ) { items.push( - new OO.ui.ActionWidget( $.extend( { $: this.$ }, actions[i] ) ) + new OO.ui.ActionWidget( $.extend( { $: this.$ }, actions[ i ] ) ) ); } this.actions.add( items ); @@ -2533,7 +2571,7 @@ OO.ui.Dialog.prototype.detachActions = function () { // Detach all actions that may have been previously attached for ( i = 0, len = this.attachedActions.length; i < len; i++ ) { - this.attachedActions[i].$element.detach(); + this.attachedActions[ i ].$element.detach(); } this.attachedActions = []; }; @@ -2812,7 +2850,7 @@ OO.ui.WindowManager.prototype.hasWindow = function ( win ) { var name; for ( name in this.windows ) { - if ( this.windows[name] === win ) { + if ( this.windows[ name ] === win ) { return true; } } @@ -2876,7 +2914,7 @@ OO.ui.WindowManager.prototype.getTeardownDelay = function () { */ OO.ui.WindowManager.prototype.getWindow = function ( name ) { var deferred = $.Deferred(), - win = this.windows[name]; + win = this.windows[ name ]; if ( !( win instanceof OO.ui.Window ) ) { if ( this.factory ) { @@ -3003,7 +3041,7 @@ OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) { // Argument handling if ( typeof win === 'string' ) { - win = this.windows[win]; + win = this.windows[ win ]; } else if ( !this.hasWindow( win ) ) { win = null; } @@ -3076,11 +3114,11 @@ OO.ui.WindowManager.prototype.addWindows = function ( windows ) { // Convert to map of windows by looking up symbolic names from static configuration list = {}; for ( i = 0, len = windows.length; i < len; i++ ) { - name = windows[i].constructor.static.name; + name = windows[ i ].constructor.static.name; if ( typeof name !== 'string' ) { throw new Error( 'Cannot add window' ); } - list[name] = windows[i]; + list[ name ] = windows[ i ]; } } else if ( $.isPlainObject( windows ) ) { list = windows; @@ -3088,8 +3126,8 @@ OO.ui.WindowManager.prototype.addWindows = function ( windows ) { // Add windows for ( name in list ) { - win = list[name]; - this.windows[name] = win; + win = list[ name ]; + this.windows[ name ] = win; this.$element.append( win.$element ); } }; @@ -3108,13 +3146,13 @@ OO.ui.WindowManager.prototype.removeWindows = function ( names ) { manager = this, promises = [], cleanup = function ( name, win ) { - delete manager.windows[name]; + delete manager.windows[ name ]; win.$element.detach(); }; for ( i = 0, len = names.length; i < len; i++ ) { - name = names[i]; - win = this.windows[name]; + name = names[ i ]; + win = this.windows[ name ]; if ( !win ) { throw new Error( 'Cannot remove window' ); } @@ -3153,16 +3191,16 @@ OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) { sizes = this.constructor.static.sizes, size = win.getSize(); - if ( !sizes[size] ) { + if ( !sizes[ size ] ) { size = this.constructor.static.defaultSize; } - if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[size].width ) { + if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) { size = 'full'; } this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', size === 'full' ); this.$element.toggleClass( 'oo-ui-windowManager-floating', size !== 'full' ); - win.setDimensions( sizes[size] ); + win.setDimensions( sizes[ size ] ); this.emit( 'resize', win ); @@ -3388,7 +3426,7 @@ OO.ui.Process.prototype.execute = function () { // Use rejected promise for error return $.Deferred().reject( [ result ] ).promise(); } - if ( $.isArray( result ) && result.length && result[0] instanceof OO.ui.Error ) { + if ( $.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) { // Use rejected promise for list of errors return $.Deferred().reject( result ).promise(); } @@ -3404,9 +3442,9 @@ OO.ui.Process.prototype.execute = function () { if ( this.steps.length ) { // Generate a chain reaction of promises - promise = proceed( this.steps[0] )(); + promise = proceed( this.steps[ 0 ] )(); for ( i = 1, len = this.steps.length; i < len; i++ ) { - promise = promise.then( proceed( this.steps[i] ) ); + promise = promise.then( proceed( this.steps[ i ] ) ); } } else { promise = $.Deferred().resolve().promise(); @@ -3516,8 +3554,8 @@ OO.ui.ToolFactory.prototype.getTools = function ( include, exclude, promote, dem // Auto for ( i = 0, len = included.length; i < len; i++ ) { - if ( !used[included[i]] ) { - auto.push( included[i] ); + if ( !used[ included[ i ] ] ) { + auto.push( included[ i ] ); } } @@ -3545,22 +3583,22 @@ OO.ui.ToolFactory.prototype.extract = function ( collection, used ) { if ( collection === '*' ) { for ( name in this.registry ) { - tool = this.registry[name]; + tool = this.registry[ name ]; if ( // Only add tools by group name when auto-add is enabled tool.static.autoAddToCatchall && // Exclude already used tools - ( !used || !used[name] ) + ( !used || !used[ name ] ) ) { names.push( name ); if ( used ) { - used[name] = true; + used[ name ] = true; } } } } else if ( $.isArray( collection ) ) { for ( i = 0, len = collection.length; i < len; i++ ) { - item = collection[i]; + item = collection[ i ]; // Allow plain strings as shorthand for named tools if ( typeof item === 'string' ) { item = { name: item }; @@ -3568,26 +3606,26 @@ OO.ui.ToolFactory.prototype.extract = function ( collection, used ) { if ( OO.isPlainObject( item ) ) { if ( item.group ) { for ( name in this.registry ) { - tool = this.registry[name]; + tool = this.registry[ name ]; if ( // Include tools with matching group tool.static.group === item.group && // Only add tools by group name when auto-add is enabled tool.static.autoAddToGroup && // Exclude already used tools - ( !used || !used[name] ) + ( !used || !used[ name ] ) ) { names.push( name ); if ( used ) { - used[name] = true; + used[ name ] = true; } } } // Include tools with matching name and exclude already used tools - } else if ( item.name && ( !used || !used[item.name] ) ) { + } else if ( item.name && ( !used || !used[ item.name ] ) ) { names.push( item.name ); if ( used ) { - used[item.name] = true; + used[ item.name ] = true; } } } @@ -3612,7 +3650,7 @@ OO.ui.ToolGroupFactory = function OoUiToolGroupFactory() { // Register default toolgroups for ( i = 0, l = defaultClasses.length; i < l; i++ ) { - this.register( defaultClasses[i] ); + this.register( defaultClasses[ i ] ); } }; @@ -3684,6 +3722,87 @@ OO.ui.Theme.prototype.updateElementClasses = function ( element ) { .addClass( classes.on.join( ' ' ) ); }; +/** + * Element supporting "sequential focus navigation" using the 'tabindex' attribute. + * + * @abstract + * @class + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {jQuery} [$tabIndexed] tabIndexed node, assigned to #$tabIndexed, omit to use #$element + * @cfg {number|Function} [tabIndex=0] Tab index value. Use 0 to use default ordering, use -1 to + * prevent tab focusing. (default: 0) + */ +OO.ui.TabIndexedElement = function OoUiTabIndexedElement( config ) { + // Configuration initialization + config = config || {}; + + // Properties + this.$tabIndexed = null; + this.tabIndex = null; + + // Initialization + this.setTabIndex( config.tabIndex || 0 ); + this.setTabIndexedElement( config.$tabIndexed || this.$element ); +}; + +/* Setup */ + +OO.initClass( OO.ui.TabIndexedElement ); + +/* Methods */ + +/** + * Set the element with 'tabindex' attribute. + * + * If an element is already set, it will be cleaned up before setting up the new element. + * + * @param {jQuery} $tabIndexed Element to set tab index on + */ +OO.ui.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIndexed ) { + if ( this.$tabIndexed ) { + this.$tabIndexed.removeAttr( 'tabindex' ); + } + + this.$tabIndexed = $tabIndexed; + if ( this.tabIndex !== null ) { + this.$tabIndexed.attr( 'tabindex', this.tabIndex ); + } +}; + +/** + * Set tab index value. + * + * @param {number|null} tabIndex Tab index value or null for no tabIndex + * @chainable + */ +OO.ui.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) { + tabIndex = typeof tabIndex === 'number' && tabIndex >= 0 ? tabIndex : null; + + if ( this.tabIndex !== tabIndex ) { + if ( this.$tabIndexed ) { + if ( tabIndex !== null ) { + this.$tabIndexed.attr( 'tabindex', tabIndex ); + } else { + this.$tabIndexed.removeAttr( 'tabindex' ); + } + } + this.tabIndex = tabIndex; + } + + return this; +}; + +/** + * Get tab index value. + * + * @return {number} Tab index value + */ +OO.ui.TabIndexedElement.prototype.getTabIndex = function () { + return this.tabIndex; +}; + /** * Element with a button. * @@ -3697,8 +3816,6 @@ OO.ui.Theme.prototype.updateElementClasses = function ( element ) { * @param {Object} [config] Configuration options * @cfg {jQuery} [$button] Button node, assigned to #$button, omit to use a generated `` * @cfg {boolean} [framed=true] Render button with a frame - * @cfg {number} [tabIndex=0] Button's tab index. Use 0 to use default ordering, use -1 to prevent - * tab focusing. * @cfg {string} [accessKey] Button's access key */ OO.ui.ButtonElement = function OoUiButtonElement( config ) { @@ -3706,9 +3823,8 @@ OO.ui.ButtonElement = function OoUiButtonElement( config ) { config = config || {}; // Properties - this.$button = null; + this.$button = config.$button || this.$( '' ); this.framed = null; - this.tabIndex = null; this.accessKey = null; this.active = false; this.onMouseUpHandler = this.onMouseUp.bind( this ); @@ -3717,9 +3833,8 @@ OO.ui.ButtonElement = function OoUiButtonElement( config ) { // Initialization this.$element.addClass( 'oo-ui-buttonElement' ); this.toggleFramed( config.framed === undefined || config.framed ); - this.setTabIndex( config.tabIndex || 0 ); this.setAccessKey( config.accessKey ); - this.setButtonElement( config.$button || this.$( '' ) ); + this.setButtonElement( this.$button ); }; /* Setup */ @@ -3750,13 +3865,13 @@ OO.ui.ButtonElement.prototype.setButtonElement = function ( $button ) { if ( this.$button ) { this.$button .removeClass( 'oo-ui-buttonElement-button' ) - .removeAttr( 'role accesskey tabindex' ) + .removeAttr( 'role accesskey' ) .off( 'mousedown', this.onMouseDownHandler ); } this.$button = $button .addClass( 'oo-ui-buttonElement-button' ) - .attr( { role: 'button', accesskey: this.accessKey, tabindex: this.tabIndex } ) + .attr( { role: 'button', accesskey: this.accessKey } ) .on( 'mousedown', this.onMouseDownHandler ); }; @@ -3769,12 +3884,7 @@ OO.ui.ButtonElement.prototype.onMouseDown = function ( e ) { if ( this.isDisabled() || e.which !== 1 ) { return false; } - // Remove the tab-index while the button is down to prevent the button from stealing focus - this.$button.removeAttr( 'tabindex' ); this.$element.addClass( 'oo-ui-buttonElement-pressed' ); - // Run the mouseup handler no matter where the mouse is when the button is let go, so we can - // reliably reapply the tabindex and remove the pressed class - this.getElementDocument().addEventListener( 'mouseup', this.onMouseUpHandler, true ); // Prevent change of focus unless specifically configured otherwise if ( this.constructor.static.cancelButtonMouseDownEvents ) { return false; @@ -3790,11 +3900,7 @@ OO.ui.ButtonElement.prototype.onMouseUp = function ( e ) { if ( this.isDisabled() || e.which !== 1 ) { return false; } - // Restore the tab-index after the button is up to restore the button's accessibility - this.$button.attr( 'tabindex', this.tabIndex ); this.$element.removeClass( 'oo-ui-buttonElement-pressed' ); - // Stop listening for mouseup, since we only needed this once - this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true ); }; /** @@ -3825,29 +3931,6 @@ OO.ui.ButtonElement.prototype.toggleFramed = function ( framed ) { return this; }; -/** - * Set tab index. - * - * @param {number|null} tabIndex Button's tab index, use null to remove - * @chainable - */ -OO.ui.ButtonElement.prototype.setTabIndex = function ( tabIndex ) { - tabIndex = typeof tabIndex === 'number' && tabIndex >= 0 ? tabIndex : null; - - if ( this.tabIndex !== tabIndex ) { - if ( this.$button ) { - if ( tabIndex !== null ) { - this.$button.attr( 'tabindex', tabIndex ); - } else { - this.$button.removeAttr( 'tabindex' ); - } - } - this.tabIndex = tabIndex; - } - - return this; -}; - /** * Set access key. * @@ -3919,7 +4002,7 @@ OO.ui.GroupElement.prototype.setGroupElement = function ( $group ) { this.$group = $group; for ( i = 0, len = this.items.length; i < len; i++ ) { - this.$group.append( this.items[i].$element ); + this.$group.append( this.items[ i ].$element ); } }; @@ -3954,7 +4037,7 @@ OO.ui.GroupElement.prototype.getItemFromData = function ( data ) { hash = OO.getHash( data ); for ( i = 0, len = this.items.length; i < len; i++ ) { - item = this.items[i]; + item = this.items[ i ]; if ( hash === OO.getHash( item.getData() ) ) { return item; } @@ -3977,7 +4060,7 @@ OO.ui.GroupElement.prototype.getItemsFromData = function ( data ) { items = []; for ( i = 0, len = this.items.length; i < len; i++ ) { - item = this.items[i]; + item = this.items[ i ]; if ( hash === OO.getHash( item.getData() ) ) { items.push( item ); } @@ -4001,7 +4084,7 @@ OO.ui.GroupElement.prototype.aggregate = function ( events ) { var i, len, item, add, remove, itemEvent, groupEvent; for ( itemEvent in events ) { - groupEvent = events[itemEvent]; + groupEvent = events[ itemEvent ]; // Remove existing aggregated event if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { @@ -4011,27 +4094,27 @@ OO.ui.GroupElement.prototype.aggregate = function ( events ) { } // Remove event aggregation from existing items for ( i = 0, len = this.items.length; i < len; i++ ) { - item = this.items[i]; + item = this.items[ i ]; if ( item.connect && item.disconnect ) { remove = {}; - remove[itemEvent] = [ 'emit', groupEvent, item ]; + remove[ itemEvent ] = [ 'emit', groupEvent, item ]; item.disconnect( this, remove ); } } // Prevent future items from aggregating event - delete this.aggregateItemEvents[itemEvent]; + delete this.aggregateItemEvents[ itemEvent ]; } // Add new aggregate event if ( groupEvent ) { // Make future items aggregate event - this.aggregateItemEvents[itemEvent] = groupEvent; + this.aggregateItemEvents[ itemEvent ] = groupEvent; // Add event aggregation to existing items for ( i = 0, len = this.items.length; i < len; i++ ) { - item = this.items[i]; + item = this.items[ i ]; if ( item.connect && item.disconnect ) { add = {}; - add[itemEvent] = [ 'emit', groupEvent, item ]; + add[ itemEvent ] = [ 'emit', groupEvent, item ]; item.connect( this, add ); } } @@ -4053,7 +4136,7 @@ OO.ui.GroupElement.prototype.addItems = function ( items, index ) { itemElements = []; for ( i = 0, len = items.length; i < len; i++ ) { - item = items[i]; + item = items[ i ]; // Check if item exists then remove it first, effectively "moving" it currentIndex = $.inArray( item, this.items ); @@ -4068,7 +4151,7 @@ OO.ui.GroupElement.prototype.addItems = function ( items, index ) { if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) { events = {}; for ( event in this.aggregateItemEvents ) { - events[event] = [ 'emit', this.aggregateItemEvents[event], item ]; + events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ]; } item.connect( this, events ); } @@ -4083,7 +4166,7 @@ OO.ui.GroupElement.prototype.addItems = function ( items, index ) { this.$group.prepend( itemElements ); this.items.unshift.apply( this.items, items ); } else { - this.items[index].$element.before( itemElements ); + this.items[ index ].$element.before( itemElements ); this.items.splice.apply( this.items, [ index, 0 ].concat( items ) ); } @@ -4103,7 +4186,7 @@ OO.ui.GroupElement.prototype.removeItems = function ( items ) { // Remove specific items for ( i = 0, len = items.length; i < len; i++ ) { - item = items[i]; + item = items[ i ]; index = $.inArray( item, this.items ); if ( index !== -1 ) { if ( @@ -4112,7 +4195,7 @@ OO.ui.GroupElement.prototype.removeItems = function ( items ) { ) { remove = {}; if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { - remove[itemEvent] = [ 'emit', this.aggregateItemEvents[itemEvent], item ]; + remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ]; } item.disconnect( this, remove ); } @@ -4137,14 +4220,14 @@ OO.ui.GroupElement.prototype.clearItems = function () { // Remove all items for ( i = 0, len = this.items.length; i < len; i++ ) { - item = this.items[i]; + item = this.items[ i ]; if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) { remove = {}; if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { - remove[itemEvent] = [ 'emit', this.aggregateItemEvents[itemEvent], item ]; + remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ]; } item.disconnect( this, remove ); } @@ -4347,7 +4430,7 @@ OO.ui.DraggableGroupElement.prototype.onItemDragStart = function ( item ) { // Map the index of each object for ( i = 0, len = this.items.length; i < len; i++ ) { - this.items[i].setIndex( i ); + this.items[ i ].setIndex( i ); } if ( this.orientation === 'horizontal' ) { @@ -4420,9 +4503,9 @@ OO.ui.DraggableGroupElement.prototype.onDragOver = function ( e ) { // Get the OptionWidget item we are dragging over dragOverObj = this.getElementDocument().elementFromPoint( clientX, clientY ); $optionWidget = $( dragOverObj ).closest( '.oo-ui-draggableElement' ); - if ( $optionWidget[0] ) { + if ( $optionWidget[ 0 ] ) { itemOffset = $optionWidget.offset(); - itemBoundingRect = $optionWidget[0].getBoundingClientRect(); + itemBoundingRect = $optionWidget[ 0 ].getBoundingClientRect(); itemPosition = $optionWidget.position(); itemIndex = $optionWidget.data( 'index' ); } @@ -4871,6 +4954,13 @@ OO.ui.LabelElement = function OoUiLabelElement( config ) { OO.initClass( OO.ui.LabelElement ); +/* Events */ + +/** + * @event labelChange + * @param {string} value + */ + /* Static Properties */ /** @@ -4915,15 +5005,16 @@ OO.ui.LabelElement.prototype.setLabel = function ( label ) { label = typeof label === 'function' ? OO.ui.resolveMsg( label ) : label; label = ( typeof label === 'string' && label.length ) || label instanceof jQuery ? label : null; + this.$element.toggleClass( 'oo-ui-labelElement', !!label ); + if ( this.label !== label ) { if ( this.$label ) { this.setLabelContent( label ); } this.label = label; + this.emit( 'labelChange' ); } - this.$element.toggleClass( 'oo-ui-labelElement', !!this.label ); - return this; }; @@ -5209,7 +5300,7 @@ OO.ui.LookupElement.prototype.getLookupMenuItems = function () { this.abortLookupRequest(); if ( Object.prototype.hasOwnProperty.call( this.lookupCache, value ) ) { - deferred.resolve( this.getLookupMenuOptionsFromData( this.lookupCache[value] ) ); + deferred.resolve( this.getLookupMenuOptionsFromData( this.lookupCache[ value ] ) ); } else { this.pushPending(); this.lookupQuery = value; @@ -5230,8 +5321,8 @@ OO.ui.LookupElement.prototype.getLookupMenuItems = function () { if ( ourRequest === widget.lookupRequest ) { widget.lookupQuery = null; widget.lookupRequest = null; - widget.lookupCache[value] = widget.getLookupCacheDataFromResponse( data ); - deferred.resolve( widget.getLookupMenuOptionsFromData( widget.lookupCache[value] ) ); + widget.lookupCache[ value ] = widget.getLookupCacheDataFromResponse( data ); + deferred.resolve( widget.getLookupMenuOptionsFromData( widget.lookupCache[ value ] ) ); } } ) .fail( function () { @@ -5424,8 +5515,8 @@ OO.ui.FlaggedElement.prototype.clearFlags = function () { for ( flag in this.flags ) { className = classPrefix + flag; - changes[flag] = false; - delete this.flags[flag]; + changes[ flag ] = false; + delete this.flags[ flag ]; remove.push( className ); } @@ -5457,36 +5548,36 @@ OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) { if ( typeof flags === 'string' ) { className = classPrefix + flags; // Set - if ( !this.flags[flags] ) { - this.flags[flags] = true; + if ( !this.flags[ flags ] ) { + this.flags[ flags ] = true; add.push( className ); } } else if ( $.isArray( flags ) ) { for ( i = 0, len = flags.length; i < len; i++ ) { - flag = flags[i]; + flag = flags[ i ]; className = classPrefix + flag; // Set - if ( !this.flags[flag] ) { - changes[flag] = true; - this.flags[flag] = true; + if ( !this.flags[ flag ] ) { + changes[ flag ] = true; + this.flags[ flag ] = true; add.push( className ); } } } else if ( OO.isPlainObject( flags ) ) { for ( flag in flags ) { className = classPrefix + flag; - if ( flags[flag] ) { + if ( flags[ flag ] ) { // Set - if ( !this.flags[flag] ) { - changes[flag] = true; - this.flags[flag] = true; + if ( !this.flags[ flag ] ) { + changes[ flag ] = true; + this.flags[ flag ] = true; add.push( className ); } } else { // Remove - if ( this.flags[flag] ) { - changes[flag] = false; - delete this.flags[flag]; + if ( this.flags[ flag ] ) { + changes[ flag ] = false; + delete this.flags[ flag ]; remove.push( className ); } } @@ -5646,9 +5737,8 @@ OO.ui.ClippableElement = function OoUiClippableElement( config ) { OO.ui.ClippableElement.prototype.setClippableElement = function ( $clippable ) { if ( this.$clippable ) { this.$clippable.removeClass( 'oo-ui-clippableElement-clippable' ); - this.$clippable.css( { width: '', height: '' } ); - this.$clippable.width(); // Force reflow for https://code.google.com/p/chromium/issues/detail?id=387290 - this.$clippable.css( { overflowX: '', overflowY: '' } ); + this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } ); + OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); } this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' ); @@ -5681,9 +5771,8 @@ OO.ui.ClippableElement.prototype.toggleClipping = function ( clipping ) { // Initial clip after visible this.clip(); } else { - this.$clippable.css( { width: '', height: '' } ); - this.$clippable.width(); // Force reflow for https://code.google.com/p/chromium/issues/detail?id=387290 - this.$clippable.css( { overflowX: '', overflowY: '' } ); + this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } ); + OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); this.$clippableContainer = null; this.$clippableScroller.off( 'scroll', this.onClippableContainerScrollHandler ); @@ -5789,16 +5878,17 @@ OO.ui.ClippableElement.prototype.clip = function () { if ( clipWidth ) { this.$clippable.css( { overflowX: 'scroll', width: desiredWidth } ); } else { - this.$clippable.css( 'width', this.idealWidth || '' ); - this.$clippable.width(); // Force reflow for https://code.google.com/p/chromium/issues/detail?id=387290 - this.$clippable.css( 'overflowX', '' ); + this.$clippable.css( { width: this.idealWidth || '', overflowX: '' } ); } if ( clipHeight ) { this.$clippable.css( { overflowY: 'scroll', height: desiredHeight } ); } else { - this.$clippable.css( 'height', this.idealHeight || '' ); - this.$clippable.height(); // Force reflow for https://code.google.com/p/chromium/issues/detail?id=387290 - this.$clippable.css( 'overflowY', '' ); + this.$clippable.css( { height: this.idealHeight || '', overflowY: '' } ); + } + + // If we stopped clipping in at least one of the dimensions + if ( !clipWidth || !clipHeight ) { + OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); } this.clippedHorizontally = clipWidth; @@ -6156,7 +6246,7 @@ OO.ui.Toolbar.prototype.getToolGroupFactory = function () { OO.ui.Toolbar.prototype.onPointerDown = function ( e ) { var $closestWidgetToEvent = this.$( e.target ).closest( '.oo-ui-widget' ), $closestWidgetToToolbar = this.$element.closest( '.oo-ui-widget' ); - if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[0] === $closestWidgetToToolbar[0] ) { + if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[ 0 ] === $closestWidgetToToolbar[ 0 ] ) { return false; } }; @@ -6194,7 +6284,7 @@ OO.ui.Toolbar.prototype.setup = function ( groups ) { // Build out new groups for ( i = 0, len = groups.length; i < len; i++ ) { - group = groups[i]; + group = groups[ i ]; if ( group.include === '*' ) { // Apply defaults to catch-all groups if ( group.type === undefined ) { @@ -6222,7 +6312,7 @@ OO.ui.Toolbar.prototype.reset = function () { this.groups = []; this.tools = {}; for ( i = 0, len = this.items.length; i < len; i++ ) { - this.items[i].destroy(); + this.items[ i ].destroy(); } this.clearItems(); }; @@ -6244,7 +6334,7 @@ OO.ui.Toolbar.prototype.destroy = function () { * @return {boolean} Tool is available */ OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) { - return !this.tools[name]; + return !this.tools[ name ]; }; /** @@ -6253,7 +6343,7 @@ OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) { * @param {OO.ui.Tool} tool Tool to reserve */ OO.ui.Toolbar.prototype.reserveTool = function ( tool ) { - this.tools[tool.getName()] = tool; + this.tools[ tool.getName() ] = tool; }; /** @@ -6262,7 +6352,7 @@ OO.ui.Toolbar.prototype.reserveTool = function ( tool ) { * @param {OO.ui.Tool} tool Tool to release */ OO.ui.Toolbar.prototype.releaseTool = function ( tool ) { - delete this.tools[tool.getName()]; + delete this.tools[ tool.getName() ]; }; /** @@ -6396,7 +6486,7 @@ OO.ui.ToolGroup.prototype.updateDisabled = function () { if ( this.constructor.static.autoDisable ) { for ( i = this.items.length - 1; i >= 0; i-- ) { - item = this.items[i]; + item = this.items[ i ]; if ( !item.isDisabled() ) { allDisabled = false; break; @@ -6540,31 +6630,31 @@ OO.ui.ToolGroup.prototype.populate = function () { // Build a list of needed tools for ( i = 0, len = list.length; i < len; i++ ) { - name = list[i]; + name = list[ i ]; if ( // Tool exists toolFactory.lookup( name ) && // Tool is available or is already in this group - ( this.toolbar.isToolAvailable( name ) || this.tools[name] ) + ( this.toolbar.isToolAvailable( name ) || this.tools[ name ] ) ) { - tool = this.tools[name]; + tool = this.tools[ name ]; if ( !tool ) { // Auto-initialize tools on first use - this.tools[name] = tool = toolFactory.create( name, this ); + this.tools[ name ] = tool = toolFactory.create( name, this ); tool.updateTitle(); } this.toolbar.reserveTool( tool ); add.push( tool ); - names[name] = true; + names[ name ] = true; } } // Remove tools that are no longer needed for ( name in this.tools ) { - if ( !names[name] ) { - this.tools[name].destroy(); - this.toolbar.releaseTool( this.tools[name] ); - remove.push( this.tools[name] ); - delete this.tools[name]; + if ( !names[ name ] ) { + this.tools[ name ].destroy(); + this.toolbar.releaseTool( this.tools[ name ] ); + remove.push( this.tools[ name ] ); + delete this.tools[ name ]; } } if ( remove.length ) { @@ -6591,9 +6681,9 @@ OO.ui.ToolGroup.prototype.destroy = function () { this.clearItems(); this.toolbar.getToolFactory().disconnect( this ); for ( name in this.tools ) { - this.toolbar.releaseTool( this.tools[name] ); - this.tools[name].disconnect( this ).destroy(); - delete this.tools[name]; + this.toolbar.releaseTool( this.tools[ name ] ); + this.tools[ name ].disconnect( this ).destroy(); + delete this.tools[ name ]; } this.$element.remove(); }; @@ -6764,16 +6854,13 @@ OO.ui.MessageDialog.prototype.getBodyHeight = function () { var bodyHeight, oldOverflow, $scrollable = this.container.$element; - oldOverflow = $scrollable[0].style.overflow; - $scrollable[0].style.overflow = 'hidden'; + oldOverflow = $scrollable[ 0 ].style.overflow; + $scrollable[ 0 ].style.overflow = 'hidden'; - // Force… ugh… something to happen - $scrollable.contents().hide(); - $scrollable.height(); - $scrollable.contents().show(); + OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] ); bodyHeight = this.text.$element.outerHeight( true ); - $scrollable[0].style.overflow = oldOverflow; + $scrollable[ 0 ].style.overflow = oldOverflow; return bodyHeight; }; @@ -6788,15 +6875,12 @@ OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) { // Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced. // Need to do it after transition completes (250ms), add 50ms just in case. setTimeout( function () { - var oldOverflow = $scrollable[0].style.overflow; - $scrollable[0].style.overflow = 'hidden'; + var oldOverflow = $scrollable[ 0 ].style.overflow; + $scrollable[ 0 ].style.overflow = 'hidden'; - // Force… ugh… something to happen - $scrollable.contents().hide(); - $scrollable.height(); - $scrollable.contents().show(); + OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] ); - $scrollable[0].style.overflow = oldOverflow; + $scrollable[ 0 ].style.overflow = oldOverflow; }, 300 ); return this; @@ -6848,7 +6932,7 @@ OO.ui.MessageDialog.prototype.attachActions = function () { } if ( others.length ) { for ( i = 0, len = others.length; i < len; i++ ) { - other = others[i]; + other = others[ i ]; this.$actions.append( other.$element ); other.toggleFramed( false ); } @@ -6861,7 +6945,7 @@ OO.ui.MessageDialog.prototype.attachActions = function () { if ( !this.isOpening() ) { // If the dialog is currently opening, this will be called automatically soon. // This also calls #fitActions. - this.manager.updateWindowSize( this ); + this.updateSize(); } }; @@ -6878,7 +6962,7 @@ OO.ui.MessageDialog.prototype.fitActions = function () { // Detect clipping this.toggleVerticalActionLayout( false ); for ( i = 0, len = actions.length; i < len; i++ ) { - action = actions[i]; + action = actions[ i ]; if ( action.$element.innerWidth() < action.$label.outerWidth( true ) ) { this.toggleVerticalActionLayout( true ); break; @@ -6888,7 +6972,7 @@ OO.ui.MessageDialog.prototype.fitActions = function () { if ( this.verticalActionLayout !== previous ) { this.$body.css( 'bottom', this.$foot.outerHeight( true ) ); // We changed the layout, window height might need to be updated. - this.manager.updateWindowSize( this ); + this.updateSize(); } }; @@ -7027,7 +7111,7 @@ OO.ui.ProcessDialog.prototype.attachActions = function () { } if ( others.length ) { for ( i = 0, len = others.length; i < len; i++ ) { - other = others[i]; + other = others[ i ]; this.$otherActions.append( other.$element ); other.toggleFramed( true ); } @@ -7076,16 +7160,16 @@ OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) { warning = false; for ( i = 0, len = errors.length; i < len; i++ ) { - if ( !errors[i].isRecoverable() ) { + if ( !errors[ i ].isRecoverable() ) { recoverable = false; } - if ( errors[i].isWarning() ) { + if ( errors[ i ].isWarning() ) { warning = true; } $item = this.$( '
' ) .addClass( 'oo-ui-processDialog-error' ) - .append( errors[i].getMessage() ); - items.push( $item[0] ); + .append( errors[ i ].getMessage() ); + items.push( $item[ 0 ] ); } this.$errorItems = this.$( items ); if ( recoverable ) { @@ -7221,7 +7305,7 @@ OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) { $target = $( e.target ).closest( '.oo-ui-pageLayout' ); for ( name in this.pages ) { // Check for page match, exclude current page to find only page changes - if ( this.pages[name].$element[0] === $target[0] && name !== this.currentPageName ) { + if ( this.pages[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentPageName ) { this.setPage( name ); break; } @@ -7255,15 +7339,15 @@ OO.ui.BookletLayout.prototype.focus = function () { if ( !page && this.outlined ) { this.selectFirstSelectablePage(); page = this.stackLayout.getCurrentItem(); - if ( !page ) { - return; - } + } + if ( !page ) { + return; } // Only change the focus if is not already in the current page if ( !page.$element.find( ':focus' ).length ) { $input = page.$element.find( ':input:first' ); if ( $input.length ) { - $input[0].focus(); + $input[ 0 ].focus(); } } }; @@ -7334,8 +7418,8 @@ OO.ui.BookletLayout.prototype.getClosestPage = function ( page ) { index = $.inArray( page, pages ); if ( index !== -1 ) { - next = pages[index + 1]; - prev = pages[index - 1]; + next = pages[ index + 1 ]; + prev = pages[ index - 1 ]; // Prefer adjacent pages at the same level if ( this.outlined ) { level = this.outlineSelectWidget.getItemFromData( page.getName() ).getLevel(); @@ -7381,7 +7465,7 @@ OO.ui.BookletLayout.prototype.getOutlineControls = function () { * @return {OO.ui.PageLayout|undefined} Page, if found */ OO.ui.BookletLayout.prototype.getPage = function ( name ) { - return this.pages[name]; + return this.pages[ name ]; }; /** @@ -7412,16 +7496,16 @@ OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) { // Remove pages with same names for ( i = 0, len = pages.length; i < len; i++ ) { - page = pages[i]; + page = pages[ i ]; name = page.getName(); if ( Object.prototype.hasOwnProperty.call( this.pages, name ) ) { // Correct the insertion index - currentIndex = $.inArray( this.pages[name], stackLayoutPages ); + currentIndex = $.inArray( this.pages[ name ], stackLayoutPages ); if ( currentIndex !== -1 && currentIndex + 1 < index ) { index--; } - remove.push( this.pages[name] ); + remove.push( this.pages[ name ] ); } } if ( remove.length ) { @@ -7430,9 +7514,9 @@ OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) { // Add new pages for ( i = 0, len = pages.length; i < len; i++ ) { - page = pages[i]; + page = pages[ i ]; name = page.getName(); - this.pages[page.getName()] = page; + this.pages[ page.getName() ] = page; if ( this.outlined ) { item = new OO.ui.OutlineOptionWidget( { $: this.$, data: name } ); page.setOutlineItem( item ); @@ -7461,9 +7545,9 @@ OO.ui.BookletLayout.prototype.removePages = function ( pages ) { items = []; for ( i = 0, len = pages.length; i < len; i++ ) { - page = pages[i]; + page = pages[ i ]; name = page.getName(); - delete this.pages[name]; + delete this.pages[ name ]; if ( this.outlined ) { items.push( this.outlineSelectWidget.getItemFromData( name ) ); page.setOutlineItem( null ); @@ -7494,7 +7578,7 @@ OO.ui.BookletLayout.prototype.clearPages = function () { if ( this.outlined ) { this.outlineSelectWidget.clearItems(); for ( i = 0, len = pages.length; i < len; i++ ) { - pages[i].setOutlineItem( null ); + pages[ i ].setOutlineItem( null ); } } this.stackLayout.clearItems(); @@ -7513,7 +7597,7 @@ OO.ui.BookletLayout.prototype.clearPages = function () { OO.ui.BookletLayout.prototype.setPage = function ( name ) { var selectedItem, $focused, - page = this.pages[name]; + page = this.pages[ name ]; if ( name !== this.currentPageName ) { if ( this.outlined ) { @@ -7523,15 +7607,15 @@ OO.ui.BookletLayout.prototype.setPage = function ( name ) { } } if ( page ) { - if ( this.currentPageName && this.pages[this.currentPageName] ) { - this.pages[this.currentPageName].setActive( false ); + if ( this.currentPageName && this.pages[ this.currentPageName ] ) { + this.pages[ this.currentPageName ].setActive( false ); // Blur anything focused if the next page doesn't have anything focusable - this // is not needed if the next page has something focusable because once it is focused // this blur happens automatically if ( this.autoFocus && !page.$element.find( ':input' ).length ) { - $focused = this.pages[this.currentPageName].$element.find( ':focus' ); + $focused = this.pages[ this.currentPageName ].$element.find( ':focus' ); if ( $focused.length ) { - $focused[0].blur(); + $focused[ 0 ].blur(); } } } @@ -7776,10 +7860,28 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) { OO.ui.LabelElement.call( this, config ); OO.ui.GroupElement.call( this, config ); + if ( config.help ) { + this.popupButtonWidget = new OO.ui.PopupButtonWidget( { + $: this.$, + classes: [ 'oo-ui-fieldsetLayout-help' ], + framed: false, + icon: 'info' + } ); + + this.popupButtonWidget.getPopup().$body.append( + this.$( '
' ) + .text( config.help ) + .addClass( 'oo-ui-fieldsetLayout-help-content' ) + ); + this.$help = this.popupButtonWidget.$element; + } else { + this.$help = this.$( [] ); + } + // Initialization this.$element .addClass( 'oo-ui-fieldsetLayout' ) - .prepend( this.$icon, this.$label, this.$group ); + .prepend( this.$help, this.$icon, this.$label, this.$group ); if ( $.isArray( config.items ) ) { this.addItems( config.items ); } @@ -7880,8 +7982,8 @@ OO.ui.GridLayout = function OoUiGridLayout( panels, config ) { // Initialization this.$element.addClass( 'oo-ui-gridLayout' ); for ( i = 0, len = panels.length; i < len; i++ ) { - this.panels.push( panels[i] ); - this.$element.append( panels[i].$element ); + this.panels.push( panels[ i ] ); + this.$element.append( panels[ i ].$element ); } if ( config.widths || config.heights ) { this.layout( config.widths || [ 1 ], config.heights || [ 1 ] ); @@ -7930,19 +8032,19 @@ OO.ui.GridLayout.prototype.layout = function ( widths, heights ) { // Sum up denominators for ( x = 0; x < cols; x++ ) { - xd += widths[x]; + xd += widths[ x ]; } for ( y = 0; y < rows; y++ ) { - yd += heights[y]; + yd += heights[ y ]; } // Store factors this.widths = []; this.heights = []; for ( x = 0; x < cols; x++ ) { - this.widths[x] = widths[x] / xd; + this.widths[ x ] = widths[ x ] / xd; } for ( y = 0; y < rows; y++ ) { - this.heights[y] = heights[y] / yd; + this.heights[ y ] = heights[ y ] / yd; } // Synchronize view this.update(); @@ -7963,10 +8065,10 @@ OO.ui.GridLayout.prototype.update = function () { rows = this.heights.length; for ( y = 0; y < rows; y++ ) { - height = this.heights[y]; + height = this.heights[ y ]; for ( x = 0; x < cols; x++ ) { - width = this.widths[x]; - panel = this.panels[i]; + width = this.widths[ x ]; + panel = this.panels[ i ]; dimensions = { width: ( width * 100 ) + '%', height: ( height * 100 ) + '%', @@ -8008,6 +8110,220 @@ OO.ui.GridLayout.prototype.getPanel = function ( x, y ) { return this.panels[ ( x * this.widths.length ) + y ]; }; +/** + * Layout with a content and menu area. + * + * The menu area can be positioned at the top, after, bottom or before. The content area will fill + * all remaining space. + * + * @class + * @extends OO.ui.Layout + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {number|string} [menuSize='18em'] Size of menu in pixels or any CSS unit + * @cfg {boolean} [showMenu=true] Show menu + * @cfg {string} [position='before'] Position of menu, either `top`, `after`, `bottom` or `before` + * @cfg {boolean} [collapse] Collapse the menu out of view + */ +OO.ui.MenuLayout = function OoUiMenuLayout( config ) { + var positions = this.constructor.static.menuPositions; + + // Configuration initialization + config = config || {}; + + // Parent constructor + OO.ui.MenuLayout.super.call( this, config ); + + // Properties + this.showMenu = config.showMenu !== false; + this.menuSize = config.menuSize || '18em'; + this.menuPosition = positions[ config.menuPosition ] || positions.before; + + /** + * Menu DOM node + * + * @property {jQuery} + */ + this.$menu = this.$( '
' ); + /** + * Content DOM node + * + * @property {jQuery} + */ + this.$content = this.$( '
' ); + + // Events + this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) ); + + // Initialization + this.toggleMenu( this.showMenu ); + this.$menu + .addClass( 'oo-ui-menuLayout-menu' ) + .css( this.menuPosition.sizeProperty, this.menuSize ); + this.$content.addClass( 'oo-ui-menuLayout-content' ); + this.$element + .addClass( 'oo-ui-menuLayout ' + this.menuPosition.className ) + .append( this.$content, this.$menu ); +}; + +/* Setup */ + +OO.inheritClass( OO.ui.MenuLayout, OO.ui.Layout ); + +/* Static Properties */ + +OO.ui.MenuLayout.static.menuPositions = { + top: { + sizeProperty: 'height', + positionProperty: 'top', + className: 'oo-ui-menuLayout-top' + }, + after: { + sizeProperty: 'width', + positionProperty: 'right', + rtlPositionProperty: 'left', + className: 'oo-ui-menuLayout-after' + }, + bottom: { + sizeProperty: 'height', + positionProperty: 'bottom', + className: 'oo-ui-menuLayout-bottom' + }, + before: { + sizeProperty: 'width', + positionProperty: 'left', + rtlPositionProperty: 'right', + className: 'oo-ui-menuLayout-before' + } +}; + +/* Methods */ + +/** + * Handle DOM attachment events + */ +OO.ui.MenuLayout.prototype.onElementAttach = function () { + // getPositionProperty won't know about directionality until the layout is attached + if ( this.showMenu ) { + this.$content.css( this.getPositionProperty(), this.menuSize ); + } +}; + +/** + * Toggle menu. + * + * @param {boolean} showMenu Show menu, omit to toggle + * @chainable + */ +OO.ui.MenuLayout.prototype.toggleMenu = function ( showMenu ) { + showMenu = showMenu === undefined ? !this.showMenu : !!showMenu; + + if ( this.showMenu !== showMenu ) { + this.showMenu = showMenu; + this.updateSizes(); + } + + return this; +}; + +/** + * Check if menu is visible + * + * @return {boolean} Menu is visible + */ +OO.ui.MenuLayout.prototype.isMenuVisible = function () { + return this.showMenu; +}; + +/** + * Set menu size. + * + * @param {number|string} size Size of menu in pixels or any CSS unit + * @chainable + */ +OO.ui.MenuLayout.prototype.setMenuSize = function ( size ) { + this.menuSize = size; + this.updateSizes(); + + return this; +}; + +/** + * Update menu and content CSS based on current menu size and visibility + */ +OO.ui.MenuLayout.prototype.updateSizes = function () { + if ( this.showMenu ) { + this.$menu + .css( this.menuPosition.sizeProperty, this.menuSize ) + .css( 'overflow', '' ); + this.$content.css( this.getPositionProperty(), this.menuSize ); + } else { + this.$menu + .css( this.menuPosition.sizeProperty, 0 ) + .css( 'overflow', 'hidden' ); + this.$content.css( this.getPositionProperty(), 0 ); + } +}; + +/** + * Get menu size. + * + * @return {number|string} Menu size + */ +OO.ui.MenuLayout.prototype.getMenuSize = function () { + return this.menuSize; +}; + +/** + * Set menu position. + * + * @param {string} position Position of menu, either `top`, `after`, `bottom` or `before` + * @throws {Error} If position value is not supported + * @chainable + */ +OO.ui.MenuLayout.prototype.setMenuPosition = function ( position ) { + var positionProperty, positions = this.constructor.static.menuPositions; + + if ( !positions[ position ] ) { + throw new Error( 'Cannot set position; unsupported position value: ' + position ); + } + + positionProperty = this.getPositionProperty(); + this.$menu.css( this.menuPosition.sizeProperty, '' ); + this.$content.css( positionProperty, '' ); + this.$element.removeClass( this.menuPosition.className ); + + this.menuPosition = positions[ position ]; + + this.updateSizes(); + this.$element.addClass( this.menuPosition.className ); + + return this; +}; + +/** + * Get menu position. + * + * @return {string} Menu position + */ +OO.ui.MenuLayout.prototype.getMenuPosition = function () { + return this.menuPosition; +}; + +/** + * Get the menu position property. + * + * @return {string} Menu position CSS property + */ +OO.ui.MenuLayout.prototype.getPositionProperty = function () { + if ( this.menuPosition.rtlPositionProperty && this.$element.css( 'direction' ) === 'rtl' ) { + return this.menuPosition.rtlPositionProperty; + } else { + return this.menuPosition.positionProperty; + } +}; + /** * Layout that expands to cover the entire area of its parent, with optional scrolling and padding. * @@ -8251,7 +8567,7 @@ OO.ui.StackLayout.prototype.addItems = function ( items, index ) { OO.ui.GroupElement.prototype.addItems.call( this, items, index ); if ( !this.currentItem && items.length ) { - this.setItem( items[0] ); + this.setItem( items[ 0 ] ); } return this; @@ -8272,7 +8588,7 @@ OO.ui.StackLayout.prototype.removeItems = function ( items ) { if ( $.inArray( this.currentItem, items ) !== -1 ) { if ( this.items.length ) { - this.setItem( this.items[0] ); + this.setItem( this.items[ 0 ] ); } else { this.unsetCurrentItem(); } @@ -8314,7 +8630,7 @@ OO.ui.StackLayout.prototype.setItem = function ( item ) { if ( item !== this.currentItem ) { if ( !this.continuous ) { for ( i = 0, len = this.items.length; i < len; i++ ) { - this.items[i].$element.css( 'display', '' ); + this.items[ i ].$element.css( 'display', '' ); } } if ( $.inArray( item, this.items ) !== -1 ) { @@ -8457,7 +8773,7 @@ OO.ui.PopupToolGroup.prototype.setDisabled = function () { */ OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) { // Only deactivate when clicking outside the dropdown element - if ( this.$( e.target ).closest( '.oo-ui-popupToolGroup' )[0] !== this.$element[0] ) { + if ( this.$( e.target ).closest( '.oo-ui-popupToolGroup' )[ 0 ] !== this.$element[ 0 ] ) { this.setActive( false ); } }; @@ -8595,8 +8911,8 @@ OO.ui.ListToolGroup.prototype.populate = function () { this.collapsibleTools = []; for ( i = 0, len = allowCollapse.length; i < len; i++ ) { - if ( this.tools[ allowCollapse[i] ] !== undefined ) { - this.collapsibleTools.push( this.tools[ allowCollapse[i] ] ); + if ( this.tools[ allowCollapse[ i ] ] !== undefined ) { + this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] ); } } @@ -8662,7 +8978,7 @@ OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () { .setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) ); for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) { - this.collapsibleTools[i].toggle( this.expanded ); + this.collapsibleTools[ i ].toggle( this.expanded ); } }; @@ -8713,8 +9029,8 @@ OO.ui.MenuToolGroup.prototype.onUpdateState = function () { labelTexts = []; for ( name in this.tools ) { - if ( this.tools[name].isActive() ) { - labelTexts.push( this.tools[name].getTitle() ); + if ( this.tools[ name ].isActive() ) { + labelTexts.push( this.tools[ name ].getTitle() ); } } @@ -8816,7 +9132,7 @@ OO.ui.GroupWidget.prototype.setDisabled = function ( disabled ) { // During construction, #setDisabled is called before the OO.ui.GroupElement constructor if ( this.items ) { for ( i = 0, len = this.items.length; i < len; i++ ) { - this.items[i].updateDisabled(); + this.items[ i ].updateDisabled(); } } @@ -9097,7 +9413,7 @@ OO.ui.LookupInputWidget.prototype.getLookupMenuItems = function () { this.abortLookupRequest(); if ( Object.prototype.hasOwnProperty.call( this.lookupCache, value ) ) { - deferred.resolve( this.getLookupMenuItemsFromData( this.lookupCache[value] ) ); + deferred.resolve( this.getLookupMenuItemsFromData( this.lookupCache[ value ] ) ); } else { this.lookupInput.pushPending(); this.lookupQuery = value; @@ -9118,8 +9434,8 @@ OO.ui.LookupInputWidget.prototype.getLookupMenuItems = function () { if ( ourRequest === widget.lookupRequest ) { widget.lookupQuery = null; widget.lookupRequest = null; - widget.lookupCache[value] = widget.getLookupCacheItemFromData( data ); - deferred.resolve( widget.getLookupMenuItemsFromData( widget.lookupCache[value] ) ); + widget.lookupCache[ value ] = widget.getLookupCacheItemFromData( data ); + deferred.resolve( widget.getLookupMenuItemsFromData( widget.lookupCache[ value ] ) ); } } ) .fail( function () { @@ -9283,15 +9599,15 @@ OO.ui.OutlineControlsWidget.prototype.onOutlineChange = function () { i = -1; len = items.length; while ( ++i < len ) { - if ( items[i].isMovable() ) { - firstMovable = items[i]; + if ( items[ i ].isMovable() ) { + firstMovable = items[ i ]; break; } } i = len; while ( i-- ) { - if ( items[i].isMovable() ) { - lastMovable = items[i]; + if ( items[ i ].isMovable() ) { + lastMovable = items[ i ]; break; } } @@ -9355,6 +9671,7 @@ OO.ui.ToggleWidget.prototype.setValue = function ( value ) { this.emit( 'change', value ); this.$element.toggleClass( 'oo-ui-toggleWidget-on', value ); this.$element.toggleClass( 'oo-ui-toggleWidget-off', !value ); + this.$element.attr( 'aria-checked', value.toString() ); } return this; }; @@ -9405,6 +9722,7 @@ OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.GroupElement ); * @mixins OO.ui.LabelElement * @mixins OO.ui.TitledElement * @mixins OO.ui.FlaggedElement + * @mixins OO.ui.TabIndexedElement * * @constructor * @param {Object} [config] Configuration options @@ -9425,6 +9743,7 @@ OO.ui.ButtonWidget = function OoUiButtonWidget( config ) { OO.ui.LabelElement.call( this, config ); OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) ); OO.ui.FlaggedElement.call( this, config ); + OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) ); // Properties this.href = null; @@ -9455,6 +9774,7 @@ OO.mixinClass( OO.ui.ButtonWidget, OO.ui.IndicatorElement ); OO.mixinClass( OO.ui.ButtonWidget, OO.ui.LabelElement ); OO.mixinClass( OO.ui.ButtonWidget, OO.ui.TitledElement ); OO.mixinClass( OO.ui.ButtonWidget, OO.ui.FlaggedElement ); +OO.mixinClass( OO.ui.ButtonWidget, OO.ui.TabIndexedElement ); /* Events */ @@ -9480,6 +9800,31 @@ OO.ui.ButtonWidget.prototype.onClick = function () { return false; }; +/** + * @inheritdoc + */ +OO.ui.ButtonWidget.prototype.onMouseDown = function ( e ) { + // Remove the tab-index while the button is down to prevent the button from stealing focus + this.$button.removeAttr( 'tabindex' ); + // Run the mouseup handler no matter where the mouse is when the button is let go, so we can + // reliably reapply the tabindex and remove the pressed class + this.getElementDocument().addEventListener( 'mouseup', this.onMouseUpHandler, true ); + + return OO.ui.ButtonElement.prototype.onMouseDown.call( this, e ); +}; + +/** + * @inheritdoc + */ +OO.ui.ButtonWidget.prototype.onMouseUp = function ( e ) { + // Restore the tab-index after the button is up to restore the button's accessibility + this.$button.attr( 'tabindex', this.tabIndex ); + // Stop listening for mouseup, since we only needed this once + this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true ); + + return OO.ui.ButtonElement.prototype.onMouseUp.call( this, e ); +}; + /** * Handles keypress events. * @@ -9730,6 +10075,7 @@ OO.ui.PopupButtonWidget = function OoUiPopupButtonWidget( config ) { // Initialization this.$element .addClass( 'oo-ui-popupButtonWidget' ) + .attr( 'aria-haspopup', 'true' ) .append( this.popup.$element ); }; @@ -9747,7 +10093,7 @@ OO.mixinClass( OO.ui.PopupButtonWidget, OO.ui.PopupElement ); */ OO.ui.PopupButtonWidget.prototype.onClick = function ( e ) { // Skip clicks within the popup - if ( $.contains( this.popup.$element[0], e.target ) ) { + if ( $.contains( this.popup.$element[ 0 ], e.target ) ) { return; } @@ -9809,6 +10155,7 @@ OO.ui.ToggleButtonWidget.prototype.onClick = function () { OO.ui.ToggleButtonWidget.prototype.setValue = function ( value ) { value = !!value; if ( value !== this.value ) { + this.$button.attr( 'aria-pressed', value.toString() ); this.setActive( value ); } @@ -9915,7 +10262,7 @@ OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) { */ OO.ui.DropdownWidget.prototype.onClick = function ( e ) { // Skip clicks within the menu - if ( $.contains( this.menu.$element[0], e.target ) ) { + if ( $.contains( this.menu.$element[ 0 ], e.target ) ) { return; } @@ -10012,6 +10359,7 @@ OO.ui.IndicatorWidget.static.tagName = 'span'; * @class * @extends OO.ui.Widget * @mixins OO.ui.FlaggedElement + * @mixins OO.ui.TabIndexedElement * * @constructor * @param {Object} [config] Configuration options @@ -10026,14 +10374,15 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) { // Parent constructor OO.ui.InputWidget.super.call( this, config ); - // Mixin constructors - OO.ui.FlaggedElement.call( this, config ); - // Properties this.$input = this.getInputElement( config ); this.value = ''; this.inputFilter = config.inputFilter; + // Mixin constructors + OO.ui.FlaggedElement.call( this, config ); + OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) ); + // Events this.$input.on( 'keydown mouseup cut paste change input select', this.onEdit.bind( this ) ); @@ -10049,6 +10398,7 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) { OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget ); OO.mixinClass( OO.ui.InputWidget, OO.ui.FlaggedElement ); +OO.mixinClass( OO.ui.InputWidget, OO.ui.TabIndexedElement ); /* Events */ @@ -10062,8 +10412,11 @@ OO.mixinClass( OO.ui.InputWidget, OO.ui.FlaggedElement ); /** * Get input element. * + * Subclasses of OO.ui.InputWidget use the `config` parameter to produce different elements in + * different circumstances. The element must have a `value` property (like form elements). + * * @private - * @param {Object} [config] Configuration options + * @param {Object} config Configuration options * @return {jQuery} Input element */ OO.ui.InputWidget.prototype.getInputElement = function () { @@ -10091,6 +10444,12 @@ OO.ui.InputWidget.prototype.onEdit = function () { * @return {string} Input value */ OO.ui.InputWidget.prototype.getValue = function () { + // Resynchronize our internal data with DOM data. Other scripts executing on the page can modify + // it, and we won't know unless they're kind enough to trigger a 'change' event. + var value = this.$input.val(); + if ( this.value !== value ) { + this.setValue( value ); + } return this.value; }; @@ -10100,13 +10459,7 @@ OO.ui.InputWidget.prototype.getValue = function () { * @param {boolean} isRTL */ OO.ui.InputWidget.prototype.setRTL = function ( isRTL ) { - if ( isRTL ) { - this.$input.removeClass( 'oo-ui-ltr' ); - this.$input.addClass( 'oo-ui-rtl' ); - } else { - this.$input.removeClass( 'oo-ui-rtl' ); - this.$input.addClass( 'oo-ui-ltr' ); - } + this.$input.prop( 'dir', isRTL ? 'rtl' : 'ltr' ); }; /** @@ -10157,7 +10510,7 @@ OO.ui.InputWidget.prototype.simulateLabelClick = function () { if ( this.$input.is( ':checkbox,:radio' ) ) { this.$input.click(); } else if ( this.$input.is( ':input' ) ) { - this.$input[0].focus(); + this.$input[ 0 ].focus(); } } }; @@ -10179,7 +10532,7 @@ OO.ui.InputWidget.prototype.setDisabled = function ( state ) { * @chainable */ OO.ui.InputWidget.prototype.focus = function () { - this.$input[0].focus(); + this.$input[ 0 ].focus(); return this; }; @@ -10189,7 +10542,7 @@ OO.ui.InputWidget.prototype.focus = function () { * @chainable */ OO.ui.InputWidget.prototype.blur = function () { - this.$input[0].blur(); + this.$input[ 0 ].blur(); return this; }; @@ -10263,18 +10616,11 @@ OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.FlaggedElement ); /* Methods */ /** - * Get input element. - * + * @inheritdoc * @private - * @param {Object} [config] Configuration options - * @return {jQuery} Input element */ OO.ui.ButtonInputWidget.prototype.getInputElement = function ( config ) { - // Configuration initialization - config = config || {}; - var html = '<' + ( config.useInputTag ? 'input' : 'button' ) + ' type="' + config.type + '">'; - return this.$( html ); }; @@ -10373,10 +10719,8 @@ OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget ); /* Methods */ /** - * Get input element. - * + * @inheritdoc * @private - * @return {jQuery} Input element */ OO.ui.CheckboxInputWidget.prototype.getInputElement = function () { return this.$( '' ); @@ -10417,9 +10761,141 @@ OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) { * @return {boolean} Checkbox is selected */ OO.ui.CheckboxInputWidget.prototype.isSelected = function () { + // Resynchronize our internal data with DOM data. Other scripts executing on the page can modify + // it, and we won't know unless they're kind enough to trigger a 'change' event. + var selected = this.$input.prop( 'checked' ); + if ( this.selected !== selected ) { + this.setSelected( selected ); + } return this.selected; }; +/** + * A OO.ui.DropdownWidget synchronized with a `` for form submission. Intended to + * be used within a OO.ui.FormLayout. + * + * @class + * @extends OO.ui.InputWidget + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }` + */ +OO.ui.DropdownInputWidget = function OoUiDropdownInputWidget( config ) { + // Configuration initialization + config = config || {}; + + // Properties (must be done before parent constructor which calls #setDisabled) + this.dropdownWidget = new OO.ui.DropdownWidget( { + $: this.$ + } ); + + // Parent constructor + OO.ui.DropdownInputWidget.super.call( this, config ); + + // Events + this.dropdownWidget.getMenu().connect( this, { select: 'onMenuSelect' } ); + + // Initialization + this.setOptions( config.options || [] ); + this.$element + .addClass( 'oo-ui-dropdownInputWidget' ) + .append( this.dropdownWidget.$element ); +}; + +/* Setup */ + +OO.inheritClass( OO.ui.DropdownInputWidget, OO.ui.InputWidget ); + +/* Methods */ + +/** + * @inheritdoc + * @private + */ +OO.ui.DropdownInputWidget.prototype.getInputElement = function () { + return this.$( '' ); +}; + +/** + * Handles menu select events. + * + * @param {OO.ui.MenuOptionWidget} item Selected menu item + */ +OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) { + this.setValue( item.getData() ); +}; + +/** + * @inheritdoc + */ +OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) { + var item = this.dropdownWidget.getMenu().getItemFromData( value ); + if ( item ) { + this.dropdownWidget.getMenu().selectItem( item ); + } + OO.ui.DropdownInputWidget.super.prototype.setValue.call( this, value ); + return this; +}; + +/** + * @inheritdoc + */ +OO.ui.DropdownInputWidget.prototype.setDisabled = function ( state ) { + this.dropdownWidget.setDisabled( state ); + OO.ui.DropdownInputWidget.super.prototype.setDisabled.call( this, state ); + return this; +}; + +/** + * Set the options available for this input. + * + * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` + * @chainable + */ +OO.ui.DropdownInputWidget.prototype.setOptions = function ( options ) { + var value = this.getValue(); + + // Rebuild the dropdown menu + this.dropdownWidget.getMenu() + .clearItems() + .addItems( options.map( function ( opt ) { + return new OO.ui.MenuOptionWidget( { + data: opt.data, + label: opt.label !== undefined ? opt.label : opt.data + } ); + } ) ); + + // Restore the previous value, or reset to something sensible + if ( this.dropdownWidget.getMenu().getItemFromData( value ) ) { + // Previous value is still available, ensure consistency with the dropdown + this.setValue( value ); + } else { + // No longer valid, reset + if ( options.length ) { + this.setValue( options[ 0 ].data ); + } + } + + return this; +}; + +/** + * @inheritdoc + */ +OO.ui.DropdownInputWidget.prototype.focus = function () { + this.dropdownWidget.getMenu().toggle( true ); + return this; +}; + +/** + * @inheritdoc + */ +OO.ui.DropdownInputWidget.prototype.blur = function () { + this.dropdownWidget.getMenu().toggle( false ); + return this; +}; + /** * Radio input widget. * @@ -10449,10 +10925,8 @@ OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget ); /* Methods */ /** - * Get input element. - * + * @inheritdoc * @private - * @return {jQuery} Input element */ OO.ui.RadioInputWidget.prototype.getInputElement = function () { return this.$( '' ); @@ -10502,15 +10976,21 @@ OO.ui.RadioInputWidget.prototype.isSelected = function () { * @cfg {boolean} [autofocus=false] Ask the browser to focus this widget, using the 'autofocus' HTML * attribute * @cfg {boolean} [readOnly=false] Prevent changes + * @cfg {number} [maxLength] Maximum allowed number of characters to input * @cfg {boolean} [multiline=false] Allow multiple lines of text * @cfg {boolean} [autosize=false] Automatically resize to fit content * @cfg {boolean} [maxRows=10] Maximum number of rows to make visible when autosizing - * @cfg {RegExp|string} [validate] Regular expression (or symbolic name referencing + * @cfg {string} [labelPosition='after'] Label position, 'before' or 'after' + * @cfg {RegExp|string} [validate] Regular expression to validate against (or symbolic name referencing * one, see #static-validationPatterns) */ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { // Configuration initialization - config = $.extend( { readOnly: false }, config ); + config = $.extend( { + type: 'text', + labelPosition: 'after', + maxRows: 10 + }, config ); // Parent constructor OO.ui.TextInputWidget.super.call( this, config ); @@ -10519,13 +10999,15 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { OO.ui.IconElement.call( this, config ); OO.ui.IndicatorElement.call( this, config ); OO.ui.PendingElement.call( this, config ); + OO.ui.LabelElement.call( this, config ); // Properties this.readOnly = false; this.multiline = !!config.multiline; this.autosize = !!config.autosize; - this.maxRows = config.maxRows !== undefined ? config.maxRows : 10; + this.maxRows = config.maxRows; this.validate = null; + this.attached = false; // Clone for resizing if ( this.autosize ) { @@ -10536,6 +11018,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { } this.setValidation( config.validate ); + this.setPosition( config.labelPosition ); // Events this.$input.on( { @@ -10543,21 +11026,25 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { blur: this.setValidityFlag.bind( this ) } ); this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) ); + this.$element.on( 'DOMNodeRemovedFromDocument', this.onElementDetach.bind( this ) ); this.$icon.on( 'mousedown', this.onIconMouseDown.bind( this ) ); this.$indicator.on( 'mousedown', this.onIndicatorMouseDown.bind( this ) ); + this.on( 'labelChange', this.updatePosition.bind( this ) ); // Initialization this.$element .addClass( 'oo-ui-textInputWidget' ) - .append( this.$icon, this.$indicator ); - this.setReadOnly( config.readOnly ); + .append( this.$icon, this.$indicator, this.$label ); + this.setReadOnly( !!config.readOnly ); if ( config.placeholder ) { this.$input.attr( 'placeholder', config.placeholder ); } + if ( config.maxLength ) { + this.$input.attr( 'maxlength', config.maxLength ); + } if ( config.autofocus ) { this.$input.attr( 'autofocus', 'autofocus' ); } - this.$element.attr( 'role', 'textbox' ); }; /* Setup */ @@ -10566,6 +11053,7 @@ OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget ); OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IconElement ); OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IndicatorElement ); OO.mixinClass( OO.ui.TextInputWidget, OO.ui.PendingElement ); +OO.mixinClass( OO.ui.TextInputWidget, OO.ui.LabelElement ); /* Static properties */ @@ -10606,7 +11094,7 @@ OO.ui.TextInputWidget.static.validationPatterns = { */ OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) { if ( e.which === 1 ) { - this.$input[0].focus(); + this.$input[ 0 ].focus(); this.emit( 'icon' ); return false; } @@ -10620,7 +11108,7 @@ OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) { */ OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) { if ( e.which === 1 ) { - this.$input[0].focus(); + this.$input[ 0 ].focus(); this.emit( 'indicator' ); return false; } @@ -10644,7 +11132,20 @@ OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) { * @param {jQuery.Event} e Element attach event */ OO.ui.TextInputWidget.prototype.onElementAttach = function () { + this.attached = true; + // If we reattached elsewhere, the valCache is now invalid + this.valCache = null; this.adjustSize(); + this.positionLabel(); +}; + +/** + * Handle element detach events. + * + * @param {jQuery.Event} e Element detach event + */ +OO.ui.TextInputWidget.prototype.onElementDetach = function () { + this.attached = false; }; /** @@ -10702,18 +11203,18 @@ OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) { OO.ui.TextInputWidget.prototype.adjustSize = function () { var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight; - if ( this.multiline && this.autosize && this.$input.val() !== this.valCache ) { + if ( this.multiline && this.autosize && this.attached && this.$input.val() !== this.valCache ) { this.$clone .val( this.$input.val() ) .attr( 'rows', '' ) // Set inline height property to 0 to measure scroll height .css( 'height', 0 ); - this.$clone[0].style.display = 'block'; + this.$clone[ 0 ].style.display = 'block'; this.valCache = this.$input.val(); - scrollHeight = this.$clone[0].scrollHeight; + scrollHeight = this.$clone[ 0 ].scrollHeight; // Remove inline height property to measure natural heights this.$clone.css( 'height', '' ); @@ -10729,10 +11230,10 @@ OO.ui.TextInputWidget.prototype.adjustSize = function () { // Difference between reported innerHeight and scrollHeight with no scrollbars present // Equals 1 on Blink-based browsers and 0 everywhere else - measurementError = maxInnerHeight - this.$clone[0].scrollHeight; + measurementError = maxInnerHeight - this.$clone[ 0 ].scrollHeight; idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError ); - this.$clone[0].style.display = 'none'; + this.$clone[ 0 ].style.display = 'none'; // Only apply inline height when expansion beyond natural height is needed if ( idealHeight > innerHeight ) { @@ -10746,19 +11247,11 @@ OO.ui.TextInputWidget.prototype.adjustSize = function () { }; /** - * Get input element. - * + * @inheritdoc * @private - * @param {Object} [config] Configuration options - * @return {jQuery} Input element */ OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) { - // Configuration initialization - config = config || {}; - - var type = config.type || 'text'; - - return config.multiline ? this.$( '